ASP.NET路由系統(tǒng)實現(xiàn)原理:HttpHandler的動態(tài)映射
我們知道一個請求最終通過一個具體的HttpHandler進(jìn)行處理,而我們熟悉的用于表示一個Web頁面的Page對象就是一個HttpHandler,被用于處理基于某個.aspx文件的請求。我們可以通過HttpHandler的動態(tài)映射來實現(xiàn)請求地址與物理文件路徑之間的分離。實際上ASP.NET路由系統(tǒng)就是采用了這樣的實現(xiàn)原理。如下圖所示,ASP.NET路由系統(tǒng)通過一個注冊到當(dāng)前應(yīng)用的自定義HttpModule對所有的請求進(jìn)行攔截,并通過對請求的分析為之動態(tài)匹配一個用于處理它的HttpHandler。HttpHandler對請求進(jìn)行處理后將相應(yīng)的結(jié)果寫入HTTP回復(fù)以實現(xiàn)對請求的相應(yīng)。
目錄
一、UrlRoutingModule
一、UrlRoutingModule
二、PageRouteHandler V.S. MvcRouteHandler
三、ASP.NET路由系統(tǒng)擴展
實例演示:通過自定義Route對ASP.NET路由系統(tǒng)進(jìn)行擴展
上圖所示的作為請求攔截器的HttpModule類型為UrlRoutingModule。如下面的代碼片斷所示,UrlRoutingModule對請求的攔截是通過注冊表示當(dāng)前應(yīng)用的HttpApplication的PostResolveRequestCache事件實現(xiàn)的。
1: public class UrlRoutingModule : IHttpModule
2: {
3: //其他成員
4: public RouteCollection RouteCollection { get; set; }
5: public void Init(HttpApplication context)
6: {
7: context.PostResolveRequestCache += new EventHandler(this.OnApplicationPostResolveRequestCache);
8:
9: }
10: private void OnApplicationPostResolveRequestCache(object sender, EventArgs e);
11: }
UrlRoutingModule具有一個類型為RouteCollection的RouteCollection屬性,在默認(rèn)的情況下引用這通過RouteTable的靜態(tài)屬性Routes表示的全局路由表。針對請求的HttpHandler的動態(tài)映射就實現(xiàn)在OnApplicationPostResolveRequestCache方法中,具體的實現(xiàn)邏輯非常簡單:通過HttpApplication獲得但前的HTTP上下文,并將其作為參數(shù)調(diào)用RouteCollection的GetRouteData方法得到一個RouteData對象。
通過RouteData的RouteHandler屬性可以得到一個實現(xiàn)了IRouteHandler的路由處理器對象,而調(diào)用后者的GetHttpHandler方法直接可以獲取對應(yīng)的HttpHandler對象,而我們需要映射到當(dāng)前請求的就是這么一個 HttpHandler。下面的代碼片斷基本上體現(xiàn)了定義在UrlRoutingModule的OnApplicationPostResolveRequestCache方法中的動態(tài)HttpHandler映射邏輯。
1: public class UrlRoutingModule : IHttpModule
2: {
3: //其他成員
4: private void OnApplicationPostResolveRequestCache(object sender, EventArgs e)
5: {
6: HttpContext context = ((HttpApplication)sender).Context;
7: HttpContextBase contextWrapper = new HttpContextWrapper(context);
8: RouteData routeData = this.RouteCollection.GetRouteData(contextWrapper);
9: RequestContext requestContext = new RequestContext(contextWrapper, routeData);
10: IHttpHandler handler = routeData.RouteHandler.GetHttpHandler(requestContext);
11: context.RemapHandler(handler);
12: }
13: }
二、 PageRouteHandler V.S. MvcRouteHandler
通過前面的介紹我們知道對于調(diào)用RouteCollection的GetRouteData獲得的RouteData對象,其RouteHandler來源于匹配的Route對象。對于通過調(diào)用RouteCollection的MapPageRoute方法注冊的Route來說,它的RouteHandler是一個類型為PageRouteHandler對象。
由于調(diào)用MapPageRoute方法的目的在于實現(xiàn)請求地址與某個.aspx頁面文件之間的映射,所以我們最終還是要創(chuàng)建的Page對象還處理相應(yīng)的請求,所以PageRouteHandler的GetHttpHandler方法最終返回的就是針對映射頁面文件路徑的Page對象。此外,MapPageRoute方法中還可以控制是否對物理文件地址實施授權(quán),而授權(quán)在返回Page對象之前進(jìn)行。
定義在PageRouteHandler中的HttpHandler獲取邏輯基本上體現(xiàn)在如下的代碼片斷中,兩個屬性VirtualPath和CheckPhysicalUrlAccess表示頁面文件的地址和是否需要對物理文件地址實施URL授權(quán),它們在構(gòu)造函數(shù)中被初始化,而最終來源于調(diào)用RouteCollection的MapPageRoute方法傳入的參數(shù)。
1: public class PageRouteHandler : IRouteHandler
2: {
3: public bool CheckPhysicalUrlAccess { get; private set; }
4: public string VirtualPath { get; private set; }
5: public PageRouteHandler(string virtualPath, bool checkPhysicalUrlAccess)
6: {
7: this.VirtualPath = virtualPath;
8: this.CheckPhysicalUrlAccess = checkPhysicalUrlAccess;
9: }
10: public IHttpHandler GetHttpHandler(RequestContext requestContext)
11: {
12: if (this.CheckPhysicalUrlAccess)
13: {
14: //Check Physical Url Access
15: }
16: return (IHttpHandler)BuildManager.CreateInstanceFromVirtualPath(this.VirtualPath, typeof(Page))
17: }
18: }
ASP.NET MVC的Route對象是通過調(diào)用RouteCollection的擴展方法MapRoute方法進(jìn)行注冊的,它對應(yīng)的RouteHandler是一個類型為MvcRouteHandler的對象。如下面的代碼片斷所示,MvcRouteHandler用于獲取處理當(dāng)前請求的HttpHandler是一個MvcHandler對象。MvcHandler實現(xiàn)對Controller的激活、Action方法的執(zhí)行以及對請求的相應(yīng),毫不夸張地說,整個MVC框架實現(xiàn)在MvcHandler之中。
1: public class MvcRouteHandler : IRouteHandler
2: {
3: //其他成員
4: public IHttpHandler GetHttpHandler(RequestContext requestContext)
5: {
6: return new MvcHandler(requestContext)
7: }
8: }
三、 ASP.NET路由系統(tǒng)擴展
到此為止我們已經(jīng)對ASP.NET的路由系統(tǒng)的實現(xiàn)進(jìn)行了詳細(xì)介紹,總的來說,整個路由系統(tǒng)是通過對HttpHandler的動態(tài)注冊的方式來實現(xiàn)的。具體來說,UrlRoutingModule通過對代表Web應(yīng)用的HttpApplication的PostResolveRequestCache事件的注冊實現(xiàn)了對請求的攔截。對于被攔截的請求,UrlRoutingModule利用注冊的路由表對其進(jìn)行匹配和解析,進(jìn)而得到一個包含所有路由信息的RouteData對象。最終借助該對象的RouteHandler創(chuàng)建出相應(yīng)的HttpHandler映射到當(dāng)前請求。從可擴展性的角度來講,我們可以通過如下三種方式來實現(xiàn)我們需要的路由方式。
- 通過集成抽象類RouteBase創(chuàng)建自定義Route定制路由邏輯。
- 通過實現(xiàn)接口IRouteHandler創(chuàng)建自定義RouteHandler定制HttpHandler提供機制。
- 通過實現(xiàn)IHttpHandler創(chuàng)建自定義HttpHandler來對請求處理。
實例演示:通過自定義Route對ASP.NET路由系統(tǒng)進(jìn)行擴展
定義在ASP.NET路由系統(tǒng)中默認(rèn)的路由類型Route建立了定義成文本模板的URL模式與某個物理文件之間的映射,如果我們對WCF REST有一定的了解,應(yīng)該知道其中也有類似的實現(xiàn)。具體來說,WCF REST借助于System.UriTemplate這個對象實現(xiàn)了同樣定義成某個文本模板的URI模式與目標(biāo)操作之間的映射。篇幅所限,我們不能對WCF REST的UriTemplate作詳細(xì)的介紹,有興趣的讀者可以參考《UriTemplate、UriTemplateTable與WebHttpDispatchOperationSelector》。[源代碼從這里下載]
我們創(chuàng)建一個新的ASP.NET Web應(yīng)用,并且添加針對程序集System.ServiceModel.dll的引用(UriTemplate定義在該程序集中),然后創(chuàng)建如下一個針對UriTemplate的路由類型UriTemplateRoute。
1: public class UriTemplateRoute:RouteBase
2: {
3: public UriTemplate UriTemplate { get; private set; }
4: public IRouteHandler RouteHandler { get; private set; }
5: public RouteValueDictionary DataTokens { get; private set; }
6:
7: public UriTemplateRoute(string template, string physicalPath, object dataTokens = null)
8: {
9: this.UriTemplate = new UriTemplate(template);
10: this.RouteHandler = new PageRouteHandler(physicalPath);
11: if (null != dataTokens)
12: {
13: this.DataTokens = new RouteValueDictionary(dataTokens);
14: }
15: else
16: {
17: this.DataTokens = new RouteValueDictionary();
18: }
19: }
20: public override RouteData GetRouteData(HttpContextBase httpContext)
21: {
22: Uri uri = httpContext.Request.Url;
23: Uri baseAddress = new Uri(string.Format("{0}://{1}", uri.Scheme, uri.Authority));
24: UriTemplateMatch match = this.UriTemplate.Match(baseAddress, uri);
25: if (null == match)
26: {
27: return null;
28: }
29: RouteData routeData = new RouteData();
30: routeData.RouteHandler = this.RouteHandler;
31: routeData.Route = this;
32: foreach (string name in match.BoundVariables.Keys)
33: {
34: routeData.Values.Add(name,match.BoundVariables[name]);
35: }
36: foreach (var token in this.DataTokens)
37: {
38: routeData.DataTokens.Add(token.Key, token.Value);
39: }
40: return routeData;
41: }
42: public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
43: {
44: Uri uri = requestContext.HttpContext.Request.Url;
45: Uri baseAddress = new Uri(string.Format("{0}://{1}", uri.Scheme, uri.Authority));
46: Dictionary<string, string> variables = new Dictionary<string, string>();
47: foreach(var item in values)
48: {
49: variables.Add(item.Key, item.Value.ToString());
50: }
51:
52: //確定段變量是否被提供
53: foreach (var name in this.UriTemplate.PathSegmentVariableNames)
54: {
55: if(!this.UriTemplate.Defaults.Keys.Any(key=> string.Compare(name,key,true) == 0) &&
56: !values.Keys.Any(key=> string.Compare(name,key,true) == 0))
57: {
58: return null;
59: }
60: }
61: //確定查詢變量是否被提供
62: foreach (var name in this.UriTemplate.QueryValueVariableNames)
63: {
64: if(!this.UriTemplate.Defaults.Keys.Any(key=> string.Compare(name,key,true) == 0) &&
65: !values.Keys.Any(key=> string.Compare(name,key,true) == 0))
66: {
67: return null;
68: }
69: }
70:
71: Uri virtualPath = this.UriTemplate.BindByName(baseAddress, variables);
72: string strVirtualPath = virtualPath.ToString().ToLower().Replace(baseAddress.ToString().ToLower(),"");
73: VirtualPathData virtualPathData = new VirtualPathData(this, strVirtualPath);
74: foreach (var token in this.DataTokens)
75: {
76: virtualPathData.DataTokens.Add(token.Key, token.Value);
77: }
78: return virtualPathData;
79: }
80: }
如上面的代碼片斷所示,UriTemplateRoute具有UriTemplate、DataTokens和RouteHandler三個只讀屬性,前兩個通過構(gòu)造函數(shù)的參數(shù)進(jìn)行初始化,后者則是在構(gòu)造函數(shù)中創(chuàng)建的PageRouteHandler對象。
用于對入棧請求進(jìn)行匹配判斷的GetRouteData方法中,我們解析出基于應(yīng)用的基地址并量連同請求地址作為參數(shù)調(diào)用UriTemplate的Match方法,如果返回的UriTemplateMatch對象不為Null,則意味著URL模板的模式與請求地址匹配。在匹配的情況下我們創(chuàng)建并返回相應(yīng)的RouteData對象,否則直接返回Null。
在用于生成出棧URL的GetVirtualPath方法中,我們通過定義在URL模板中的模板(包括變量名包含在屬性PathSegmentVariableNames的路徑段變量和包含在QueryValueVariableNames屬性的查詢變量)是否在提供的RouteValueDictionary字段或者默認(rèn)變量列表(通過屬性Defaults表示)從判斷URL模板是否與提供的變量列表匹配。在匹配的情況下通過調(diào)用UriTemplate的BindByName方法得到一個完整的Uri。由于該方法返回的是相對路徑,所以我們需要將應(yīng)用基地址剔除并最終創(chuàng)建并返回一個VirtualPathData對象。如果不匹配,則直接返回Null。
在創(chuàng)建的Global.asax文件中我們采用如下的代碼對我們自定義的UriTemplateRoute進(jìn)行注冊,選用的場景還是我們上面采用的天氣預(yù)報的例子。我個人具有基于UriTemplate的URI模板比針對Route的URL模板更好用,其中一點就是它在定義默認(rèn)值方法更為直接。如下面的代碼片斷所示,我們直接將默認(rèn)值定義在模板中(("{areacode=010}/{days=2})。
1: public class Global : System.Web.HttpApplication
2: {
3: protected void Application_Start(object sender, EventArgs e)
4: {
5: UriTemplateRoute route = new UriTemplateRoute("{areacode=010}/{days=2}",
6: "~/Weather.aspx", new { defualtCity = "BeiJing", defaultDays = 2});
7: RouteTable.Routes.Add("default", route);
8: }
9: }
在注冊的路由對應(yīng)的目標(biāo)頁面Weather.aspx的后臺代碼中,我們定義了如下一個GenerateUrl根據(jù)指定的區(qū)號(areacode)和預(yù)報天數(shù)(days)創(chuàng)建一個Url,而Url的生成直接通過調(diào)用RouteTable的Routes屬性的GetVirtualPathData方法完成。生成的URL連同當(dāng)前頁面的RouteData的屬性通過如下所示的HTML輸出來。
1: <body>
2: <form id="form1" runat="server">
3: <div>
4: <table>
5: <tr>
6: <td>Router:</td>
7: <td><%=RouteData.Route != null? RouteData.Route.GetType().FullName:"" %></td>
8: </tr>
9: <tr>
10: <td>RouteHandler:</td>
11: <td><%=RouteData.RouteHandler != null? RouteData.RouteHandler.GetType().FullName:"" %></td>
12: </tr>
13: <tr>
14: <td>Values:</td>
15: <td>
16: <ul>
17: <%foreach (var variable in RouteData.Values)
18: {%>
19: <li><%=variable.Key%>=<%=variable.Value%></li>
20: <% }%>
21: </ul>
22: </td>
23: </tr>
24: <tr>
25: <td>DataTokens:</td>
26: <td>
27: <ul>
28: <%foreach (var variable in RouteData.DataTokens)
29: {%>
30: <li><%=variable.Key%>=<%=variable.Value%></li>
31: <% }%>
32: </ul>
33: </td>
34: </tr>
35: <tr>
36: <td>Generated Url:</td>
37: <td>
38: <%=GenerateUrl("0512",3)%>
39: </td>
40: </tr>
41: </table>
42: </div>
43: </form>
44: </body>
由于注冊的URL模板所包含的段均由具有默認(rèn)值的變量構(gòu)成,所以當(dāng)我們請求根地址時,會自動路由到Weather.aspx。下圖是我們在瀏覽器訪問應(yīng)用根目錄的截圖,上面顯示了我們注冊的UriTemplateRoute生成的RouteData的信息和生成URL(/0512/3)。


我們知道一個請求最終通過一個具體的HttpHandler進(jìn)行處理,而我們熟悉的用于表示一個Web頁面的Page對象就是一個HttpHandler,被用于處理基于某個.aspx文件的請求。我們可以通過HttpHandler的動態(tài)映射來實現(xiàn)請求地址與物理文件路徑之間的分離。實際上ASP.NET路由系統(tǒng)就是采用了這樣的實現(xiàn)原理。


浙公網(wǎng)安備 33010602011771號