雖然通過Visual Studio向導在ASP.NET Web API項目中創建的 Controller類型默認派生與抽象類型ApiController,但是ASP.NET Web API框架本身只要求它實現IHttpController接口即可,所以我們將其統稱為HttpController。既然HttpController指的是所有實現了IHttpController接口的類型,我們自然得先來了解一下這個接口的定義。如下面的代碼片斷所示,在IHttpController接口中僅僅定義了唯一的方法ExecuteAsync方法,它以異步的方式執行HttpController,并返回一個Task<HttpResponseMessage>對象。[本文已經同步到《How ASP.NET Web API Works?》]
1: public interface IHttpController
2: {
3: Task<HttpResponseMessage> ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken);
4: }
HttpController可以視為對ASP.NET Web API的消息處理管道的延續。通過“ASP.NET Web API標準的“管道式”設計”的介紹我們知道位于管道末端的是一個HttpRoutingDispatcher對象。當SendAsync方法被執行的時候,HttpRoutingDispatcher會利用隸屬于它的HttpControllerDispatcher來激活目標HttpController對象,隨后調用該對象的ExecuteAsync方法并將返回的Task<HttpResponseMessage>對象作為返回值。右圖揭示了包含激活的HttpController在內的消息處理管道的結構。
一、HttpControllerContext
與HttpMessageHandler的SendAsync方法有所不同,HttpController的ExecuteAsync方法并沒有一個表示請求的類型為HttpRequestMessage的參數,取而代之的是一個HttpControllerContext類型的參數。HttpControllerContext定義在命名空間“System.Web.Http.Controllers”下,表示執行HttpController的上下文。
如下面的代碼片斷所示,通過定義在HttpControllerContext中的屬性我們可以得到用于配置消息處理管道的HttpConfiguration對象和封裝路由數據的HttpRouteData對象,以及表示當前請求的HttpRequestMessage對象。這三個屬性可以在構建HttpControllerContext的時候直接通過構造函數的參數指定,我們也可以先創建一個空的HttpControllerContext對象之后直接對這些屬性賦值。
1: public class HttpControllerContext
2: {
3: public HttpControllerContext();
4: public HttpControllerContext(HttpConfiguration configuration, IHttpRouteData routeData, HttpRequestMessage request);
5:
6: public HttpConfiguration Configuration { get; set; }
7: public IHttpRouteData RouteData { get; set; }
8: public HttpRequestMessage Request { get; set; }
9:
10: public IHttpController Controller { get; set; }
11: public HttpControllerDescriptor ControllerDescriptor { get; set; }
12: }
一個HttpControllerContext對象表示執行HttpController的上下文,我們可以通過Controller屬性來獲取或者設置這個HttpController對象。除此之外,我們還可以利用另一個屬性ControllerDescriptor獲取或者設置用于描述HttpController的HttpControllerDescriptor對象(類型HttpControllerDescriptor定義在命名空間“System.Web.Http.Controllers”下)。
二、HttpControllerDescriptor
HttpControllerDescriptor封裝了某個HttpController類型的元數據,我們可以將它視為某個HttpController類型的描述對象。HttpControllerDescriptor具有根據元數據創建對應HttpController的能力,實際上ASP.NET Web API的HttpController激活系統就是根據HttpControllerDescriptor來創建目標HttpController的。
如下面的代碼片斷所示,我們可以通過HttpControllerDescriptor的屬性Configuration、ControllerName和ControllerType獲取當前的HttpConfiguration對象和被描述HttpController的名稱和類型。這三個屬性可以在構建HttpControllerDescriptor時通過構造函數的參數顯式指定,也可以先構建一個空的HttpControllerDescriptor對象,然后手工設置這些屬性。
1: public class HttpControllerDescriptor
2: {
3: public HttpControllerDescriptor();
4: public HttpControllerDescriptor(HttpConfiguration configuration, string controllerName, Type controllerType);
5:
6: public virtual IHttpController CreateController(HttpRequestMessage request);
7:
8: public virtual Collection<T> GetCustomAttributes<T>() where T: class;
9: public virtual Collection<T> GetCustomAttributes<T>(bool inherit) where T: class;
10: public virtual Collection<IFilter> GetFilters();
11:
12: public HttpConfiguration Configuration { get; set; }
13: public string ControllerName { get; set; }
14: public Type ControllerType { get; set; }
15:
16: public virtual ConcurrentDictionary<object, object> Properties { get; }
17: }
HttpControllerDescriptor具有創建HttpController的能力主要體現在其CreateController方法上,該方法完成了目標方法的激活。換句話說,目標HttpController的激活是通過調用描述它的HttpControllerDescriptor對象的CreateController方法完成的。本章的核心就在于剖析此方法的實現邏輯。
我們可以通過HttpControllerDescriptor的GetCustomAttributes<T>方法得到應用在被描述HttpController類型上指定類型的特性列表。調用另一個方法GetFilters可以獲取應用到目標HttpController類型上的所有Filter,Filter在ASP.NET Web API中是一個非常重要的概念,同時也是一種常見的擴展方式,我們會在本書第12章“過濾器”中對Filter進行單獨介紹。
HttpControllerDescriptor還具有一個字典類型的只讀屬性Properties,它使我們可以將任何一個對象附加到某個HttpControllerDescriptor上。我們在HttpRequestMessage和HttpConfiguration類型中已經看到過了類似的設計。
三、ApiController
我們現在來介紹一下我們創建HttpController類型默認繼承的基類ApiController。如下面的代碼片斷所示,除了實現接口IHttpController外,HttpController還采用標準的方式實現了另一個接口IDisposable。如果自定義HttpController需要實現一些資源回收的工作,可以將它們定義在重寫的(受保護的)虛方法Dispose中。
1: public abstract class ApiController : IHttpController, IDisposable
2: {
3: public virtual Task<HttpResponseMessage> ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken);
4: protected virtual void Initialize(HttpControllerContext controllerContext);
5:
6: public void Dispose();
7: protected virtual void Dispose(bool disposing);
8:
9: public HttpControllerContext ControllerContext { get; set; }
10: public HttpConfiguration Configuration { get; set; }
11: public HttpRequestMessage Request { get; set; }
12: public IHttpRouteData RouteData { get; set; }
13:
14: public ModelStateDictionary ModelState { get; }
15: public UrlHelper Url { get; set; }
16: public IPrincipal User { get; }
17: }
ApiController的三個屬性Configuration、Request和RouteData與此HttpControllerContext對象的同名屬性具有相同的引用。表示執行當前ApiController上下文的HttpControllerContext對象可以通過ControllerContext屬性獲取,這是一個可讀寫的屬性,意味著我們也可以通過設置該屬性為其指定相應的上下文。如果我們沒有對ControllerContext屬性進行顯式設置,該屬性會在第一次被獲取時被自動賦值。
ApiController的只讀屬性ModelState返回一個具有字典數據結構的ModelStateDictionary對象,包含其中的數據會被以“Model綁定”的形式綁定到目標Action方法的對應的參數。除此之外,此ModelStateDictionary還用于保存參數驗證失敗后的錯誤消息。另一個參數Url返回一個類型為UrlHelper的對象(UrlHelper定義在命名空間“System.Web.Http.Routing”下),我們利用它可以根據注冊的HttpRoute和提供的路由變量生成一個完整的URL。
ApiController的User屬性返回當前線程的Principal。相信讀者還會記得在本書第3章“消息處理管道”中介紹HttpServer時我們談到:如果當前線程的Principal為Null,作為消息處理管道“龍頭”的HttpServer會在SendAsync方法執行過程中創建一個空的GenericPrincipal對象作為當前線程的“匿名”Principal。所以對于匿名請求來說,這個User屬性會返回這個通過HttpServer設置的空GenericPrincipal對象。
從上面給出的代碼片斷我們還會看到ApiController包含一個受保護的Initialize方法,它會根據由指定HttpControllerContext提供的上下文信息對自身作相應的初始化。一旦Initialize方法被成功執行,當前ApiController對象將處于初始化狀態。此Initialize在默認情況下會在實現的ExecuteAsync方法中被自動調用。在默認情況下,ASP.NET Web API的HttpController激活系統總是創建一個新的HttpController來處理每一個請求。對于其類型繼承自ApiController的HttpController來說,如果在執行ExecuteAsync方法的時候發現當前的ApiController已經處于“初始化”的狀態,系統會直接拋出一個InvalidOperationException異常。
舉個簡單的例子,假設我們定義了如下一個繼承自ApiController的DemoController類型,并通如下的方式將原本為受保護的Initialize方法轉換成一個公有方法,以方便我們后續的調用。
1: public class DemoController : ApiController
2: {
3: public new void Initialize(HttpControllerContext controllerContext)
4: {
5: base.Initialize(controllerContext);
6: }
7: }
然后我們執行如下一段代碼,它的特別之處在于在調用DemoController對象的ExecuteAsync方法之前調用了Initialize方法對其作了初始化處理。
1: DemoController controller = new DemoController ();
2: HttpControllerContext controllerContext = new HttpControllerContext(new HttpConfiguration(), new HttpRouteData(new HttpRoute()), new HttpRequestMessage());
3: controller.ControllerContext = controllerContext;
4: controller.Initialize(controllerContext);
5: controller.ExecuteAsync(controllerContext, new CancellationToken(false));
當執行ApiController的ExecuteAsync方法的時候會拋出如右圖所示的InvalidOperation異常,并提示“Cannot reuse an 'ApiController' instance. 'ApiController' has to be constructed per incoming message. Check your custom 'IHttpControllerActivator' and make sure that it will not manufacture the same instance.”錯誤消息已經表明了ApiController是不能“重用”的,用于處理每一個請求的ApiController都應該是“全新”的。
文章列表