閱讀881 返回首頁    go 阿裏雲 go 技術社區[雲棲]


ASP.NET Web API路由係統:Web Host下的URL路由

ASP.NET Web API提供了一個獨立於執行環境的抽象化的HTTP請求處理管道,而ASP.NET Web API自身的路由係統也不依賴於ASP.NET路由係統,所以它可以采用不同的寄宿方式運行於不同的應用程序中。如果采用Web Host的方式將定義Web API寄宿於一個Web應用之中,其實最終的URL路由還是通過ASP.NET本身的路由係統完成的,那麼兩個路由係統之間是如何銜接在一起的呢?。[本文已經同步到《How ASP.NET Web API Works?》]

目錄
一、HostedHttpRoute
二、HttpWebRoute
三、HostedHttpRouteCollection
四、HttpControllerRouteHandler
五、HttpControllerHandler

前文我們談到包括路由注冊在內的對整個ASP.NET Web API管道的配置是通過HttpConfiguration來完成的。對於Web Host這種寄宿方式,這麼一個HttpConfiguration可以通過靜態類型GlobalConfiguration來獲取。如下麵的代碼片斷所示,GlobalConfiguration具有一個靜態隻讀屬性Configuration,它返回的正式我們用於配置的全局HttpConfiguration對象。

   1: public static class GlobalConfiguration
   2: {
   3:     //其他成員
   4:     private static Lazy<HttpConfiguration> _configuration = new Lazy<HttpConfiguration>(delegate {
   5:         HttpConfiguration config = new HttpConfiguration(new HostedHttpRouteCollection(RouteTable.Routes));
   6:         //其他操作
   7:         return config;
   8:     });
   9:     
  10:     public static HttpConfiguration Configuration
  11:     {
  12:         get
  13:         {
  14:             return _configuration.Value;
  15:         }
  16:     }
  17: }

如上麵的代碼片斷所示,GlobalConfiguration的Configuration屬性采用了延遲加載的模式來設計的。從對字段_configuration的初始化代表我們可以看到:返回的HttpConfiguration包含的路由表的真實類型並不是我們上麵介紹的HttpRouteCollection,而是一個叫做HostedHttpRouteCollection的類型。HostedHttpRouteCollection是一個定義在System.Web.Http.WebHost.Routing命名空間下的內部類型,它直接繼承自HttpRouteCollection。包含在HostedHttpRouteCollection之中的Route的類型也不是HttpRoute,而是HostedHttpRoute。

一、HostedHttpRoute

與HostedHttpRouteCollection一樣,HostedHttpRoute也是System.Web.Http.WebHost.Routing命名空間下的一個內部類型,它直接實現了接口IHttpRoute(而不是繼承自HttpRoute)。HostedHttpRoute可以看成是對一個Route對象的封裝,這個被封裝的Route對象對應著隻讀屬性OriginalRoute。實現在HostedHttpRoute之中的核心路由功能基本上是通過這個Route對象完成的,所以我們才說Web Host下的ASP.NET Web API的URL路由最終還是利用ASP.NET自身的路由係統實現的。

   1: internal class HostedHttpRoute : IHttpRoute
   2: {
   3:     public HostedHttpRoute(string uriTemplate, 
   4:     IDictionary<string, object> defaults, 
   5:     IDictionary<string, object> constraints, 
   6:     IDictionary<string, object> dataTokens, HttpMessageHandler handler);
   7:  
   8:     public IHttpRouteData GetRouteData(string rootVirtualPath, HttpRequestMessage request);
   9:     public IHttpVirtualPathData GetVirtualPath(HttpRequestMessage request, IDictionary<string, object> values);
  10:  
  11:     public IDictionary<string, object>    Constraints { get; }
  12:     public IDictionary<string, object>    DataTokens { get; }
  13:     public IDictionary<string, object>    Defaults { get; }
  14:     public HttpMessageHandler             Handler { get; }
  15:     internal Route                        OriginalRoute { get; }
  16:     public string                         RouteTemplate { get; }
  17: }

在正常情況下,當我們調用HostedHttpRoute的GetRouteData或者GetVirtualPath方法的時候,當前HTTP上下文對象已經被方式表示當前HTTP請求的HttpRequestMessage的屬性字典中,對應的Key為“MS_HttpContext”。HostedHttpRoute可以直接這個Key從通過HttpRequestMessage的Properties屬性表示的屬性字典中獲取當前HTTP上下文。

對於GetRouteData方法來說,它會將此HTTP上下文作為參數調用通過屬性OriginalRoute屬性表示的Route對象的GetRouteData方法。如果返回一個具體的RouteData對象,它會被轉換成一個具有如下定義的HostedHttpRouteData對象並返回。如果調用Route的GetRouteData方法返回Null,最終的返回結果自然為Null。對於返回的HostedHttpRouteData對象來說,其Route屬性自然是對自身的引用,RouteData的Values屬性直接作為HostedHttpRouteData對象的同名屬性,而OriginalRouteData屬性直接就是對該RouteData對象的引用。

   1: internal class HostedHttpRouteData : IHttpRouteData
   2: {
   3:     public HostedHttpRouteData(RouteData routeData);
   4:  
   5:     public IHttpRoute                      Route { get; }
   6:     public IDictionary<string, object>     Values { get; }
   7:     internal RouteData                     OriginalRouteData { get; }
   8: }

對於GetVirtualPath方法來說,邏輯稍微複雜一些。除了得到當前HTTP上下文之外,HostedHttpRoute還會通過調用擴展方法GetRouteData方法獲取附加在HttpRequestMessage對象上的HttpRouteData對象。在這種情況下,得到的HttpRouteData實際上就是一個HostedHttpRouteData對象,所以它可以通過其OriginalRouteData屬性得到原始的RouteData。隨後HostedHttpRoute根據得到HTTP上下文和RouteData創建一個RequestContext對象,並將其作為參數調用Route對象的GetVirtualPath方法,傳輸的參數除了該RequestContext對象之外還有一個根據values參數創建的RouteValueDictionary對象。如果該方法調用返回一個具體的VirtualPathData對象,HostedHttpRoute會將其轉換成一個具有如下定義的HostedHttpVirtualPathData對象,該對象的用於返回生成URL的VirtualPath屬性自然對應於VirtualPathData的VirtualPath屬性。倘若方法返回Null,那麼最終返回的自然就是Null。

   1: internal class HostedHttpVirtualPathData : IHttpVirtualPathData
   2: {   
   3:     public HostedHttpVirtualPathData(VirtualPathData virtualPath, IHttpRoute httpRoute);
   4:    
   5:     public IHttpRoute     Route { get; private set; }
   6:     public string         VirtualPath { get; set; }
   7: }

上麵介紹的關於HostedHttpRoute的兩個方法GetRouteData和GetVirtualPath的邏輯基本上可以通過如下的代碼片斷來體現(真實的代碼於此稍有不同)。

   1: internal class HostedHttpRoute : IHttpRoute
   2: {
   3:     //其他成員
   4:     public IHttpRouteData GetRouteData(string rootVirtualPath, HttpRequestMessage request)
   5:     {      
   6:         HttpContextBase httpContextBase;
   7:         if (!request.Properties.TryGetValue<HttpContextBase>("MS_HttpContext", out httpContextBase))
   8:         {
   9:             return null;
  10:         }    
  11:         RouteData routeData = this.OriginalRoute.GetRouteData(httpContextBase);
  12:         if (routeData != null)
  13:         {
  14:             return new HostedHttpRouteData(routeData);
  15:         }
  16:         return null;
  17:     }
  18:  
  19:     public IHttpVirtualPathData GetVirtualPath(HttpRequestMessage request, IDictionary<string, object> values)
  20:     {       
  21:         HttpContextBase httpContextBase = request.GetHttpContext();
  22:         if (httpContextBase != null)
  23:         {
  24:             HostedHttpRouteData routeData = request.GetRouteData() as HostedHttpRouteData;
  25:             if (routeData != null)
  26:             {
  27:                 RequestContext requestContext = new RequestContext(httpContextBase, routeData.OriginalRouteData);
  28:                 VirtualPathData virtualPathData = this.OriginalRoute.GetVirtualPath(requestContext, new RouteValueDictionary(values));
  29:                 if (virtualPathData != null)
  30:                 {
  31:                     return new HostedHttpVirtualPathData(virtualPathData, routeData.Route);
  32:                 }
  33:             }
  34:         }
  35:         return null;
  36:     }
  37: }

 

二、HttpWebRoute

HostedHttpRoute的隻讀屬性OriginalRoute在構造函數中初始化,其真實類型並非Route,而是它具有如下定義的子類HttpWebRoute。HttpWebRoute返回的是創建它的HttpRoute對象,在此情況下自然就是創建它的HostedHttpRoute對象。對於重寫的GetRouteData和GetVirtualPath,如果HttpRoute屬性類型為HostedHttpRoute(在此情況下此條件永遠成立),它們會直接調用基類Route的同名方法。除此之外,HttpWebRoute還重寫了用於檢驗約束的ProcessConstraint方法,在該方法中如果表示約束的constraint參數是一個HttpRouteConstraint對象(在此情況下此條件永遠成立),它會根據HTTP上下文創建一個HttpRequestMessage對象,並將其作為參數傳入HttpRouteConstraint對象的Match方法進行約束檢驗。

   1: internal class HttpWebRoute : Route
   2: {   
   3:     public HttpWebRoute(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, RouteValueDictionary dataTokens, IRouteHandler routeHandler, IHttpRoute httpRoute);
   4:    
   5:     public override RouteData GetRouteData(HttpContextBase httpContext);   
   6:     public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values);
   7:  
   8:     protected override bool ProcessConstraint(HttpContextBase httpContext, object constraint, string parameterName, RouteValueDictionary values, RouteDirection routeDirection);
   9:  
  10:     public IHttpRoute HttpRoute { get; }
  11: }

到此為止,我們基本上可以清楚地了解到ASP.NET Web API路由係統在Web Host情況下是如何利用ASP.NET自身的路有係統實現URL路由的:ASP.NET Web API路由係統中的HostedHttpRoute對象通過創建ASP.NET路由係統的HttpWebRoute進行基於URL模板的路由解析,但是針對約束的檢驗依然是利用ASP.NET Web API路由係統中的HttpRouteConstraint來完成的

三、HostedHttpRouteCollection

上麵我們對ASP.NET Web API在Web Host下采用的路由類型HostedHttpRoute作了詳細介紹,對於通過靜態類型GlobalConfiguration的Configuration屬性獲取到的用於配置請求處理管道的HttpConfiguration對象,我們也指出通過其Routes屬性返回的路由表類型是HostedHttpRouteCollection,但是依然沒有回答:調用該對象的擴展方法MapHttpRoute進行路由影射時對應的HostedHttpRoute對象是如何創建並添加的

在上麵介紹HttpRouteCollection的擴展方法的時候提到過:該方法調用HttpRouteCollection的CreateRoute方法最終實現對HttpRoute的創建。HostedHttpRouteCollection就是通過重現該方法來創建HostedHttpRoute的,如下所示的代碼片斷體現了該方法的實現邏輯。

   1: internal class HostedHttpRouteCollection : HttpRouteCollection
   2: {
   3:     //其他成員
   4:     public override IHttpRoute CreateRoute(string uriTemplate, IDictionary<string, object> defaults, IDictionary<string, object> constraints, IDictionary<string, object> dataTokens, HttpMessageHandler handler)
   5:     {
   6:         return new HostedHttpRoute(uriTemplate, defaults, constraints, dataTokens, handler);
   7:     }
   8: }

既然ASP.NET Web API在Web Host模式下依然是借助ASP.NET自身的路由係統實現URL路由,那麼意味著當我們針對ASP.NET Web API進行路由映射的時候必須在ASP.NET路由係統的全局路由表中添加對一個繼承自抽象類RouteBase的Route對象(而不是實現了接口IHttpRoute的HttpRoute對象)。說得更加具體一點,當我們針對一個HostedHttpRouteCollection對象調用其擴展方法MapHttpRoute方法的時候,創建的HostedHttpRoute對象必須被轉換成一個Route對象。

通過上麵的介紹,HostedHttpRoute對象實際上是對一個HttpWebRoute對象的封裝,對應其OriginalRoute屬性,最終被添加到ASP.NET全局路由表的就是這麼一個HttpWebRoute對象。如下麵的代碼片斷所示,HostedHttpRouteCollection具有一個RouteCollection類型的字段_routeCollection。通過前麵GlobalConfiguration的定義我們知道,默認使用的HostedHttpRouteCollection是根據通過RouteTable的靜態屬性Routes表示的ASP.NET路由表創建的。

   1: internal class HostedHttpRouteCollection : HttpRouteCollection
   2: {
   3:     //其他成員
   4:     private readonly RouteCollection _routeCollection;
   5:  
   6:     public HostedHttpRouteCollection(RouteCollection routeCollection)
   7:     {    
   8:         this._routeCollection = routeCollection;
   9:     }
  10:  
  11:     public override void Add(string name, IHttpRoute route)
  12:     {
  13:         this._routeCollection.Add(name, 
route.ToRoute()
);
  14:     }
  15: }

HostedHttpRouteCollection重寫了Add方法,它會將添加的HttpRoute對象轉換成Route對象並添加到ASP.NET的全局路由表中。如果添加的HttpRoute是一個HostedHttpRoute對象,被“轉換”後的Route對象就是通過其OriginalRoute屬性表示的HttpWebRoute對象。

通過前麵針對ASP.NET路由實現原理的介紹,我們知道整個路由係統的核心是一個叫做UrlRoutingModule的HttpModule,它通過注冊HttpApplication的PostResolveRequestCache事件的注冊實現了請求的攔截,並動態映射一個HttpHandler來實現對請求的處理和響應。通過UrlRoutingModule映射的HttpHandler來源於與當前請求匹配的Route對象。說得更加具體一點,Route利用通過其RouteHandler提供對應的HttpHandler用於處理與之匹配的請求。Web Host模式下的ASP.NET Web API使用的Route類型為HttpWebRoute,它的RouteHandler是一個類型為System.Web.Http.WebHost.HttpControllerRouteHandler的對象,而後者提供的HttpHandler類型為System.Web.Http.WebHost.HttpControllerHandler。

四、HttpControllerRouteHandler

通過上麵的介紹我們知道ASP.NET Web API在Web Host下真正使用的Route是一個類型為HttpWebRoute的對象,而該對象被ASP.NET Web API路由係統下一個類型為HostedHttpRoute的對象封裝,那麼HttpWebRoute最終用於處理與之匹配的請求的HttpHandler是什麼呢?

   1: internal class HostedHttpRoute : IHttpRoute
   2: {
   3:     //其他成員
   4:     public HostedHttpRoute(string uriTemplate, IDictionary<string, object> defaults,     IDictionary<string, object> constraints, IDictionary<string, object> dataTokens, HttpMessageHandler handler)
   5:     {
   6:         //其他操作
   7:         this.OriginalRoute = new HttpWebRoute(uriTemplate, routeDefaults, 
   8:         routeConstraints, routeDataTokens, 
   9:         HttpControllerRouteHandler.Instance, this);   
  10:     }
  11: }
  12:  
  13: public class HttpControllerRouteHandler : IRouteHandler
  14: {   
  15:     protected HttpControllerRouteHandler();
  16:     protected virtual IHttpHandler GetHttpHandler(RequestContext requestContext);
  17:     IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext);
  18:  
  19:     public static HttpControllerRouteHandler Instance { get; }
  20: }

HostedHttpRoute通過隻讀屬性OriginalRoute引用被其封裝的HttpWebRoute對象。如上麵的代碼片斷所示,當HostedHttpRoute在對該屬性進行初始化的時候為其指定的RouteHandler對象通過HttpControllerRouteHandler的靜態屬性Instance提供,實際上它以單例的模式(通過“延遲加載”的方式實現)提供一個HttpControllerRouteHandler對象。

五、HttpControllerHandler

RouteHandler之於Route的最終目的在於提供一個具體的HttpHandler來處理與之匹配的請求,HttpWebRoute的RouteHandler是一個HttpControllerRouteHandler對象,而後者具體提供一個怎樣的HttpHandler呢?

   1: public class HttpControllerRouteHandler : IRouteHandler
   2: {
   3:     //其他成員   
   4:     protected virtual IHttpHandler GetHttpHandler(RequestContext requestContext)
   5:     {
   6:         return new HttpControllerHandler(requestContext.RouteData);
   7:     }
   8:  
   9:     IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext)
  10:     {
  11:         return this.GetHttpHandler(requestContext);
  12:     }
  13: }
  14:  
  15: public class HttpControllerHandler : HttpTaskAsyncHandler
  16: {
  17:     //省略成員
  18: }

如上麵的代碼片斷所示,HttpControllerRouteHandler實現的GetHttpHandler方法返回的是一個類型為HttpControllerHandler對象。HttpControllerHandler直接繼承自HttpTaskAsyncHandler,所以它是一個異步模式的HttpHandler。ASP.NET Web API提供一個管道來處理請求和響應回複,毫不誇張地說:整個消息處理管道就是通過HttpControllerHandler這個HttpHandler創建的

當我們將定義的Web API已Web Host模式部署在某個Web應用中並進行相應的路由影射,這些注冊的HttpRoute(HostedHttpRoute)最終轉換成ASP.NET全局路由表中的Route(HttpWebRoute)。ASP.NET路由係統對每個抵達的請求進行攔截,如果當前請求與路由表中的某個Route匹配,相應的路由數據被解析出來並保存在RequestContext中。

隨後,ASP.NET路由係統的實現者UrlRoutingModule從匹配的Route中獲取RouteHandler,這是一個HttpControllerRouteHandler對象,後者提供的HttpHandler(一個HttpControllerHandler對象)被UrlRoutingModule映射到當前請求。HttpHandler一旦成功映射,HttpControllerHandler將最終接管當前請求,而它會構造整個消息處理管道來處理這個請求並對請求予以響應。至於ASP.NET Web API的消息處理管道以及HttpControllerHandler對它的創建,我們會在後續的文章中進行詳細介紹。


作者:蔣金楠
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

最後更新:2017-10-25 15:34:16

  上一篇:go  ASP.NET Web API路由係統:路由係統的幾個核心類型
  下一篇:go  Razor Engine,實現代碼生成器的又一件利器