ASP.NET Web API Controller 是怎樣建成的
文章目錄
這篇筆記主要在解釋 ASP.NET Web API Controller 是怎麼建成的。摘自《.NET 相依性注入》第五章(其實先前也摘過一些,只是這篇比較完整)。
先看ASP.NET Web API 訊息管線:
Handlers)、以及控制器(Controller)。圖中的紅色實心箭頭代表 HTTP 請求訊息,虛線箭
頭代表 HTTP 回應訊息。訊息處理流程如下:
以上便是 Web API HTTP 訊息管線的大致處理流程。其中的建立 controller 這項工作還有一些細節,接著說明。
Controller 是怎樣建成的?
剛才只說明了 Web API HTTP 訊息管線的大致處理流程,而欲注入相依物件至 controller 類
別的建構函式,或從中動些手腳來改變預設行為,必得了解 Web API 框架建立 controller 的
內部過程。本節將進一步說明其中的複雜環節,其中會反覆提及多個抽象介面,第一次閱
讀時可能略感吃力,並難免心生疑惑,但等到實際寫過、跑過一遍後面的範例程式,再回
頭來看這一節的說明,整個拼圖應該就會漸漸明朗了。
剛才提到,HttpControllerDispatcher 會建立目標 controller 物件,亦即先前 ASP.NET Web
API 管線架構圖中標示「(A) 建立 controller」的步驟。此步驟其實包含兩件工作:
首先,「解析目標 controller」的工作主要是從應用程式的 DLL 組件中尋找所有可用的
controller 類別,再從中選擇一個與當前 HTTP request 匹配的。其處理邏輯如下圖所示:
說明:
從確定目標 controller 型別之後,到建立完成 controller 執行個體的過程中,還有經過一些核
心標準介面所提供的擴充點。底下再用一張 UML 活動圖搭配 Web API 原始碼的方式來解構其內部處理過程。
說明如下(與上圖中的數字編號對應):
(1) HttpControllerDispatcher 透過 IHttpControllerSelector 物件的 SelectController 方法來取得目標 controller 型別資訊,這型別資訊是包在一個 HttpControllerDescriptor 物件裡。
(2) HttpControllerDispatcher 接著呼叫 HttpControllerDescriptor 物件的 CreateController 方法,而該方法又會去呼叫 ServicesContainer 物件的 GetHttpControllerActivator 方法來取得 IHttpControllerActivator 物件。以下程式片段摘自 Web API 原始碼,涵蓋了此步驟至下一步驟的部分邏輯:
(3) 取得 IHttpControllerActivator 物件之後,便接著呼叫它的 Create 方法,而此方法會呼叫自己的 GetInstanceOrActivator 方法,以便取得 controller 執行個體。以下程式片段摘自 DefaultHttpControllerActivator 類別的原始碼,我把錯誤處理以及快取機制的部分拿掉,並加上了中文註解:
(4) IHttpControllerActivator 物件的 GetInstanceOrActivator 方法會呼叫 HttpRequestMessage 的擴充方法 GetDependencyScope 來取得與當前 request 關聯的 IDependencyScope 物件(其實就是個 Service Locator),並利用它的 GetService 方法來取得 controller 物件。若 GetService 方法並未傳回 controller 物件,而是傳回 null(代表無法解析服務型別),則退而求其次,改用型別反射(reflection)機制來建立 controller 物件。一樣搭配原始碼來看:
其中的 request.GetDependencyScope() 就是對應到剛才說的「呼叫 HttpRequestMessage 的擴充方法 GetDependencyScope 來取得與當前 request 關聯的 IDependencyScope 物件。」而這裡實際取得的 IDependencyScope 物件會是 Web API 框架提供的預設實作:EmptyResolver。從類別名稱可知,這類別其實啥事也沒做——它的 GetService 方法一律傳回 null。因此,在預設情況下,Web API 框架會一律使用型別反射(reflection)機制來建立 controller 物件,而這也就是為什麼我們的 controller 類別一定要有預設建構函式(default constructor)的緣故。
工商時間:購買電子書請看這裡。另外,近日有優惠活動,點此連結可以折扣價格結帳,8/17 以前有效)
先看ASP.NET Web API 訊息管線:
註:為了避免圖片太大以至於超過版面,上圖中的「HTTP 訊息處理常式」區塊省略了 HttpRoutingDispatcher 處理路由分派的部分。「控制器」區塊則省略了篩選條件(filter)的處理細節。微軟網站有提供一份比較完整的 Web API 訊息處理流程圖,網址是 http://www.microsoft.com/zh-tw/download/details.aspx?id=36476。此訊息管線架構圖分為三層,由上至下,分別是裝載(Hosting)、訊息處理常式(Message
Handlers)、以及控制器(Controller)。圖中的紅色實心箭頭代表 HTTP 請求訊息,虛線箭
頭代表 HTTP 回應訊息。訊息處理流程如下:
- 當用戶端對伺服器發出的 HTTP 請求開始進入 ASP.NET Web API 框架時,該 HTTP 請求訊息會被包裝成 HttpRequestMessage 物件,並且進入圖中最頂端「裝載」方塊的 HttpServer(web 裝載)或 HttpSelfHostServer(自我裝載)。接著該訊息便流入管線的下一個階段,直到整個訊息流程處理完畢,會得到一個代表 HTTP 回應訊息的HttpResponseMessage 物件,並將此物件的訊息內容傳回用戶端。
- HttpRequestMessage 物件進入「訊息處理常式」管線。在此階段,HTTP 訊息行經數個訊息處理常式(message handlers),並且在返回 HTTP 回應訊息時以相反的順序執行。
- 在各個訊息處理常式之後,HTTP 請求訊息接著會傳遞給 HttpControllerDispatcher,並且由這個物件來建立 Web API controller,然後將 HTTP 請求傳遞給 controller 物件(圖中標示「(A) 建立 controller」的步驟)。
- Controller 會先決定目標動作方法(即圖中標示「(B) 選擇 action」的步驟),然後呼叫它。動作方法將負責產生回應內容,之後便依前述管線流程的反方向沿路返回。
以上便是 Web API HTTP 訊息管線的大致處理流程。其中的建立 controller 這項工作還有一些細節,接著說明。
Controller 是怎樣建成的?
剛才只說明了 Web API HTTP 訊息管線的大致處理流程,而欲注入相依物件至 controller 類
別的建構函式,或從中動些手腳來改變預設行為,必得了解 Web API 框架建立 controller 的
內部過程。本節將進一步說明其中的複雜環節,其中會反覆提及多個抽象介面,第一次閱
讀時可能略感吃力,並難免心生疑惑,但等到實際寫過、跑過一遍後面的範例程式,再回
頭來看這一節的說明,整個拼圖應該就會漸漸明朗了。
剛才提到,HttpControllerDispatcher 會建立目標 controller 物件,亦即先前 ASP.NET Web
API 管線架構圖中標示「(A) 建立 controller」的步驟。此步驟其實包含兩件工作:
- 解析目標 controller。亦即決定該使用哪一個 controller 類別。
- 建立目標 controller 類別的執行個體,並將 HTTP 請求(HttpRequestMessage 物件)傳遞給它,以便由 controller 進行後續處理。
首先,「解析目標 controller」的工作主要是從應用程式的 DLL 組件中尋找所有可用的
controller 類別,再從中選擇一個與當前 HTTP request 匹配的。其處理邏輯如下圖所示:
說明:
- 圖中下方的 IAssembliesResolver 物件的 GetAssemblies 方法將提供應用程式的組件清單,並由 IHttpControllerTypeResolver 物件的 GetControllerTypes 方法取得可用的controller 類別清單。
- IHttpControllerSelector 負責決定要選擇哪一個 controller 類別,然後返回一個包含其型別資訊的 HttpControllerDescriptor 物件給 HttpControllerDispatcher。
從確定目標 controller 型別之後,到建立完成 controller 執行個體的過程中,還有經過一些核
心標準介面所提供的擴充點。底下再用一張 UML 活動圖搭配 Web API 原始碼的方式來解構其內部處理過程。
說明如下(與上圖中的數字編號對應):
(1) HttpControllerDispatcher 透過 IHttpControllerSelector 物件的 SelectController 方法來取得目標 controller 型別資訊,這型別資訊是包在一個 HttpControllerDescriptor 物件裡。
(2) HttpControllerDispatcher 接著呼叫 HttpControllerDescriptor 物件的 CreateController 方法,而該方法又會去呼叫 ServicesContainer 物件的 GetHttpControllerActivator 方法來取得 IHttpControllerActivator 物件。以下程式片段摘自 Web API 原始碼,涵蓋了此步驟至下一步驟的部分邏輯:
// HttpControllerDescriptor 類別的 CreateController 方法。
public virtual IHttpController CreateController(HttpRequestMessage request)
{
IHttpControllerActivator activator = Configuration.Services.GetHttpControllerActivator();
IHttpController instance = activator.Create(request, this, ControllerType);
return instance;
}
(3) 取得 IHttpControllerActivator 物件之後,便接著呼叫它的 Create 方法,而此方法會呼叫自己的 GetInstanceOrActivator 方法,以便取得 controller 執行個體。以下程式片段摘自 DefaultHttpControllerActivator 類別的原始碼,我把錯誤處理以及快取機制的部分拿掉,並加上了中文註解:
// DefaultHttpControllerActivator 類別的 Create 方法(重點摘錄)
public IHttpController Create(HttpRequestMessage request,
HttpControllerDescriptor controllerDescriptor, Type controllerType)
{
Funcactivator;
IHttpController controller =
GetInstanceOrActivator(request, controllerType, out activator);
if (controller != null)
{
// 註冊至 Web API 框架的 dependency resolver
// 已經建立此 controller 型別的執行個體。
return controller; // 那就直接使用此物件。
}
// 目標 controller 物件尚未建立
return activator(); // 那就用 GetInstanceOrActivator 方法傳回的委派來建立物件
}
(4) IHttpControllerActivator 物件的 GetInstanceOrActivator 方法會呼叫 HttpRequestMessage 的擴充方法 GetDependencyScope 來取得與當前 request 關聯的 IDependencyScope 物件(其實就是個 Service Locator),並利用它的 GetService 方法來取得 controller 物件。若 GetService 方法並未傳回 controller 物件,而是傳回 null(代表無法解析服務型別),則退而求其次,改用型別反射(reflection)機制來建立 controller 物件。一樣搭配原始碼來看:
// 摘自 DefaultHttpControllerActivator.cs
private static IHttpController GetInstanceOrActivator(HttpRequestMessage request, Type controllerType, out Func> activator)
{
// 若 dependency scope 有傳回 controller 物件,便使用它。
IHttpController instance = (IHttpController)request.GetDependencyScope().GetService(controllerType);
if (instance != null)
{
activator = null;
return instance;
}
// 否則,建立一個委派來創建此型别的執行個體。
activator = TypeActivator.Create(controllerType);
return null;
}
其中的 request.GetDependencyScope() 就是對應到剛才說的「呼叫 HttpRequestMessage 的擴充方法 GetDependencyScope 來取得與當前 request 關聯的 IDependencyScope 物件。」而這裡實際取得的 IDependencyScope 物件會是 Web API 框架提供的預設實作:EmptyResolver。從類別名稱可知,這類別其實啥事也沒做——它的 GetService 方法一律傳回 null。因此,在預設情況下,Web API 框架會一律使用型別反射(reflection)機制來建立 controller 物件,而這也就是為什麼我們的 controller 類別一定要有預設建構函式(default constructor)的緣故。
工商時間:購買電子書請看這裡。另外,近日有優惠活動,點此連結可以折扣價格結帳,8/17 以前有效)