閱讀71 返回首頁    go 技術社區[雲棲]


ASP.NET MVC是如何運行的[2]: URL路由

在一個ASP.NET MVC應用來說,針對HTTP請求的處理和相應定義Controller類型的某個Action方法中,每個HTTP請求的目標對象不再像ASP .NET Web Form應用一樣是一個物理文件,而是某個Controller的某個Action。目標Controller和Action的名稱包含在HTTP請求中,而ASP.NET MVC的首要任務就是通過當前HTTP請求的解析得到正確的Controller和Action的名稱。這個過程是通過ASP.NET MVC的URL路由機製來實現的。

ASP.NET定義了一個全局的路由表,路由表中的每個路由對象對應著一個將Controller和Action名稱作為站位符的URL模板。對於每一個抵達的HTTP請求,ASP.NET MVC會遍曆路由表找到一個URL模板的模式與請求地址相匹配的路有對象,並最終解析出以Controller和Action名稱為核心的路由數據。在我們自定義的ASP.NET MVC框架中,路由數據通過具有如下定義的RouteData類型表示。

   1: public class RouteData
   2: {
   3:     public IDictionary<string, object> Values { get; private set; }
   4:     public IDictionary<string, object> DataTokens { get; private set; }
   5:     public IRouteHandler RouteHandler { get;  set; }
   6:     public RouteBase Route { get; set; }
   7:  
   8:     public RouteData()
   9:     {
  10:         this.Values = new Dictionary<string, object>();
  11:         this.DataTokens = new Dictionary<string, object>();
  12:         this.DataTokens.Add("namespaces", new List<string>());
  13:     }
  14:     public string Controller
  15:     {
  16:         get
  17:         {
  18:             object controllerName = string.Empty;
  19:             this.Values.TryGetValue("controller", out controllerName);
  20:             return controllerName.ToString();
  21:         }
  22:     }
  23:     public string ActionName
  24:     {
  25:         get
  26:         {
  27:             object actionName = string.Empty;
  28:             this.Values.TryGetValue("action", out actionName);
  29:             return actionName.ToString();
  30:         }
  31:     } 
  32:     public IEnumerable<string> Namespaces
  33:     {
  34:         get
  35:         {
  36:             return (IEnumerable<string>)this.DataTokens["namespaces"];
  37:         }
  38:     } 
  39: }

從上麵的代碼片斷所示,RouteData定義了兩個字典類型的屬性Values和DataTokens,前者代表直接從請求地址解析出來的變量,後者代表其他類型的變量。表示Controller和Action名稱的同名屬性直接從Values字典中提取,對應的Key分別為controller和action。屬性Namespaces表示輔助Controller類型的解析而設置的命名空間列表,該屬性值從DataTokens字典中提取,對應的Key為namespaces。

我們之前已經提到過ASP.NET MVC本質上是兩個自定義的ASP.NET組件來實現的,一個是自定義的HttpModule,另一個是自定義的HttpHandler,而後者從RouteData的RouteHandler屬性獲得。RouteData的RouteHandler屬性類型為IRouteHandler接口,如下麵的代碼片斷所示,該接口具有一個唯一的GetHttpHandler用於返回真正用於處理HTTP請求的HttpHandler對象。

   1: public interface IRouteHandler
   2: {
   3:     IHttpHandler GetHttpHandler(RequestContext requestContext);
   4: }

IRouteHandler接口的GetHttpHandler方法接受一個類型為RequestContext的參數。顧名思義,RequestContext表示當前(HTTP)請求的上下文,其核心就是對當前HttpContext和RouteData的封裝,這可以通過如下的代碼片斷看出來。

   1: public class RequestContext
   2: {
   3:     public virtual HttpContextBase HttpContext { get; set; }
   4:     public virtual RouteData RouteData { get; set; }
   5: }

RouteData具有一個類型為RouteBase的Route屬性,表示當前路由表中與當前請求匹配的路由對象。換句話說,當前的RouteData就是通過該路由對象針對當前HTTP請求進行解析獲得的。RouteBase是一個抽象類,如下麵的代碼片斷所示,它僅僅包含一個GetRouteData方法,該方法通過對以HttpContextBase對象表示的當前HTTP上下文進行解析從而獲取一個RouteData對象。

   1: public abstract class RouteBase
   2: {
   3:     public abstract RouteData GetRouteData(HttpContextBase httpContext);
   4: }

ASP.NET MVC提供的基於URL模板的路由機製是通過具有如下定義的Route類型實現的。Route是RouteBase的子類,字符串類型的Url屬性代表定義的URL模板 。在實現的GetRouteData方法中,通過HttpContextBase獲取相對請求地址,如果該地址與定義在模板中的URL模式相匹配則創建一個RouteData返回;否則返回Null。對於返回的RouteData對象,其Values屬性表示的字典包含直接通過地址解析出來的變量,而對於DataTokens字典和RouteHandler屬性,則直接取自Route對象的同名屬性。

   1: public class Route : RouteBase
   2: {
   3:     public IRouteHandler RouteHandler { get; set; }
   4:     public Route()
   5:     {
   6:         this.DataTokens = new Dictionary<string, object>();
   7:         this.RouteHandler = new MvcRouteHandler();
   8:     }
   9:     public override RouteData GetRouteData(HttpContextBase httpContext)
  10:     {
  11:         IDictionary<string, object> variables;
  12:         if (this.Match(httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring(2), out variables))
  13:         {
  14:             RouteData routeData = new RouteData();
  15:             foreach (var item in variables)
  16:             {
  17:                 routeData.Values.Add(item.Key, item.Value);
  18:             }
  19:             foreach (var item in DataTokens)
  20:             {
  21:                 routeData.DataTokens.Add(item.Key, item.Value);
  22:             }
  23:             routeData.RouteHandler = this.RouteHandler;
  24:             return routeData;
  25:         }
  26:         return null;
  27:     }
  28:     public string Url { get; set; }
  29:     public IDictionary<string, object> DataTokens { get; set; }
  30:     protected bool Match(string requestUrl, out IDictionary<string,object> variables)
  31:     {
  32:         variables = new Dictionary<string,object>();
  33:         string[] strArray1 = requestUrl.Split('/');
  34:         string[] strArray2 = this.Url.Split('/');
  35:         if (strArray1.Length != strArray2.Length)
  36:         {
  37:             return false;
  38:         }
  39:  
  40:         for (int i = 0; i < strArray2.Length; i++)
  41:         { 
  42:             if(strArray2[i].StartsWith("{") && strArray2[i].EndsWith("}"))
  43:             {
  44:                 variables.Add(strArray2[i].Trim("{}".ToCharArray()),strArray1[i]);
  45:             }
  46:         }
  47:         return true;
  48:     }
  49: }

由於同一個Web應用可以采用多種不同的URL模式,所以也需要注冊多個繼承自RouteBase的路由對象對它們進行解析,多個路由對象組成了一個路由表。在我們自定義ASP.NET MVC框架中,路由表通過類型RouteTable表示。如下麵的代碼片斷所示,RouteTable僅僅具有一個類型為RouteDictionary的Routes屬性表示針對真個Web應用的全局路由表。

   1: public class RouteTable
   2: {
   3:     public static RouteDictionary Routes { get; private set; }
   4:     static RouteTable()
   5:     {
   6:         Routes = new RouteDictionary();
   7:     }
   8: }

RouteDictionary表示一個具名的路由對象的列表,我們直接讓它繼承自Dictionary<string, RouteBase>類型,其中的Key表示注冊的路由對象的名稱。在GetRouteData方法中,我們遍曆集合找到與指定的HttpContextBase對象匹配的路由對象,並得到對應的RouteData。

   1: public class RouteDictionary: Dictionary<string, RouteBase>
   2: {
   3:     public RouteData GetRouteData(HttpContextBase httpContext)
   4:     {
   5:         foreach (var route in this.Values)
   6:         {
   7:             RouteData routeData = route.GetRouteData(httpContext);
   8:             if (null != routeData)
   9:             {
  10:                 return routeData;
  11:             }
  12:         }
  13:         return null;
  14:     }
  15: }

在Global.asax中我們創建了一個基於指定URL模板的Route對象,並將其添加到通過RouteTable的靜態隻讀屬性Routes表示的全局路由表中。

路由表的目的在於對當前的HTTP請求進行解析從而獲取一個以Controller和Action名稱為核心的路由數據,即上麵介紹的RouteData,而整個解析工作是通過一個類型為UrlRoutingModule的自定義HttpModule來完成的。如下麵的代碼片斷所示,在實現了接口IHttpModule的UrlRoutingModule類型的Init方法中,我們注冊了HttpApplicataion的PostResolveRequestCache事件。

   1: public class UrlRoutingModule: IHttpModule
   2: {
   3:     public void Dispose()
   4:     {}
   5:     public void Init(HttpApplication context)
   6:     {
   7:         context.PostResolveRequestCache += OnPostResolveRequestCache;
   8:     }
   9:     protected virtual void OnPostResolveRequestCache(object sender, EventArgs e)
  10:     {
  11:         HttpContextWrapper httpContext = new HttpContextWrapper(HttpContext.Current);
  12:         RouteData routeData = RouteTable.Routes.GetRouteData(httpContext);
  13:         if (null == routeData)
  14:         {
  15:             return;
  16:         }
  17:         RequestContext requestContext = new RequestContext { RouteData = routeData, HttpContext = httpContext };
  18:         IHttpHandler handler = routeData.RouteHandler.GetHttpHandler(requestContext);
  19:         httpContext.RemapHandler(handler); 
  20:     }
  21: }

當PostResolveRequestCache事件觸發之後,UrlRoutingModule通過RouteTable的靜態隻讀屬性Routes得到表示全局路由表的RouteDictionary對象,然後調用其GetRouteData方法並傳入用於封裝當前HttpContext的HttpContextWrapper對象(HttpContextWrapper是HttpContextBase的子類)並得到一個封裝了路由數據的RouteData對象。如果得到的RouteData不為空,根據該對象本身和和之前得到的HttpContextWrapper對象創建一個表示當前請求上下文的RequestContext對象,將其作為參數傳入RouteData的RouteHandler的GetHttpHandler方法得到一個HttpHandler對象。最後我們調用HttpContextWrapper對象的RemapHandler方法對得到HttpHandler進行映射,這意味著該HttpHandler將最終用於處理當前的HTTP請求。

ASP.NET MVC是如何運行的[1]: 建立在“偽”MVC框架上的Web應用
ASP.NET MVC是如何運行的[2]: URL路由
ASP.NET MVC是如何運行的[3]: Controller的激活
ASP.NET MVC是如何運行的[4]: Action的執行


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

最後更新:2017-10-26 12:04:40

  上一篇:go  公立醫院的醫療設備也要共享了
  下一篇:go  建設互聯網醫療的思考