71
技術社區[雲棲]
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