Asp.net Mvc 請求是如何到達 MvcHandler的——UrlRoutingModule、MvcRouteHandler分析,並造個輪子


前言

  本文假定讀者對 HttpModule 、HttpHandler和IIS的處理流程有一定的了解,如果為了解可以參考以下鏈接。文中大部分代碼通過Reflector反編譯  System.Web.dll 得到,.net 版本為4.0 

IIS 5.0 和 6.0 的 ASP.NET 應用程序生命周期概述

IIS 7.0 的 ASP.NET 應用程序生命周期概述

HTTP 處理程序和 HTTP 模塊概述

   Asp.net MVC 程序雖然開發的模式不同,但是其本質上還是 Asp.net。其利用了HttpModule 和 HttpHandler 做了擴展,可以參考博客園里的大牛——Artech 相關系列文章。

本文主要關注UrlRoutingModule 、MvcRouteHandler 兩個類的源代碼,進而分析客戶的請求是如何到達MvcHandler 的。

Asp.net MVc 程序啟動流程 需要關注的行為

  • 1、Application啟動時先通過RouteTable把URL映射到Handler
  • 2、通過UrlRouting Module 這個HttpModule 攔截用戶請求。

我們知道,HttpModule 是注冊在 Web.config 中的,可是當你打開Asp.net MVc 程序的Web .Config 時 卻沒有發現該配置節,原因是:"它已經默認的寫在全局的中"。應此 你可以在 “$\Windows\Microsoft.NET\Framework\版本號\Config\Web.config“ 中找到 " <add name="UrlRoutingModule-4.0" type="System.Web.Routing.UrlRoutingModule" />”

UrlRoutingModule 源碼


UrlRoutingModule 位於 System.web.dll 文件中,利用Reflector 可以查看到其源碼:

UrlRoutingModuel
 1 [TypeForwardedFrom("System.Web.Routing, Version=3.5.0.0, Culture=Neutral, PublicKeyToken=31bf3856ad364e35")]
 2 public class UrlRoutingModule : IHttpModule
 3 {
 4     // Fields
 5     private static readonly object _contextKey = new object();
 6     private static readonly object _requestDataKey = new object();
 7     private RouteCollection _routeCollection;
 8  
 9     // Methods
10     protected virtual void Dispose()
11     {
12     }
13  
14     protected virtual void Init(HttpApplication application)
15     {
16         if (application.Context.Items[_contextKey] == null)
17         {
18             application.Context.Items[_contextKey] = _contextKey;
19             application.PostResolveRequestCache += new EventHandler(this.OnApplicationPostResolveRequestCache);
20         }
21     }
22  
23     private void OnApplicationPostResolveRequestCache(object sender, EventArgs e)
24     {
25         HttpContextBase context = new HttpContextWrapper(((HttpApplication) sender).Context);
26         this.PostResolveRequestCache(context);
27     }
28  
29     [Obsolete("This method is obsolete. Override the Init method to use the PostMapRequestHandler event.")]
30     public virtual void PostMapRequestHandler(HttpContextBase context)
31     {
32     }
33  
34     public virtual void PostResolveRequestCache(HttpContextBase context)
35     {
36         RouteData routeData = this.RouteCollection.GetRouteData(context);
37         if (routeData != null)
38         {
39             IRouteHandler routeHandler = routeData.RouteHandler;
40             if (routeHandler == null)
41             {
42                 throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, SR.GetString("UrlRoutingModule_NoRouteHandler"), new object[0]));
43             }
44             if (!(routeHandler is StopRoutingHandler))
45             {
46                 RequestContext requestContext = new RequestContext(context, routeData);
47                 context.Request.RequestContext = requestContext;
48                 IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);
49                 if (httpHandler == null)
50                 {
51                     throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, SR.GetString("UrlRoutingModule_NoHttpHandler"), new object[] { routeHandler.GetType() }));
52                 }
53                 if (httpHandler is UrlAuthFailureHandler)
54                 {
55                     if (!FormsAuthenticationModule.FormsAuthRequired)
56                     {
57                         throw new HttpException(0x191, SR.GetString("Assess_Denied_Description3"));
58                     }
59                     UrlAuthorizationModule.ReportUrlAuthorizationFailure(HttpContext.Current, this);
60                 }
61                 else
62                 {
63                     context.RemapHandler(httpHandler);
64                 }
65             }
66         }
67     }
68  
69     void IHttpModule.Dispose()
70     {
71         this.Dispose();
72     }
73  
74     void IHttpModule.Init(HttpApplication application)
75     {
76         this.Init(application);
77     }
78  
79     // Properties
80     public RouteCollection RouteCollection
81     {
82         get
83         {
84             if (this._routeCollection == null)
85             {
86                 this._routeCollection = RouteTable.Routes;
87             }
88             return this._routeCollection;
89         }
90         set
91         {
92             this._routeCollection = value;
93         }
94     }
95 }

 

UrlHttpModule 實現了 IHttpModule 接口

  HTTP Module在應用程序發出請求時被調用的並進行特定的事件處理。 HTTP Module作為請求管道的一部分調用,它們能夠訪問請求過程中請求周期中的各種管線事件。
Http Module必須經過注冊才能從請求管道接收通知。 注冊 HTTP 模塊的最常用方法是在應用程序的 Web.config 文件中進行注冊。 在 IIS 7.0 中,統一的請求管道使您還可以通過其他方式注冊模塊,其中包括通過 IIS 管理器和 Appcmd.exe 命令行工具。
  當 ASP.NET 創建表示您的應用程序的 HttpApplication 類的實例時,將創建已注冊的任何模塊的實例。 在創建模塊時,將調用它的 Init 方法,並且模塊會自行初始化。在模塊的 Init 方法中,可以注冊各種應用程序事件的處理程序(如 BeginRequest 或 EndRequest)。

可以看到 UrlHttpModule 在 init 方法中注冊了PostResolveRequestCache 事件的處理程序。
關於Asp.net的生命周期事件可以參考:
http://msdn.microsoft.com/zh-cn/library/ms178472
http://msdn.microsoft.com/zh-cn/library/bb470252
http://msdn.microsoft.com/zh-cn/library/ms178473


PostResolveRequestCache

    該事件在完成緩存解析並投遞時觸發。

  在UrlRoutingModule中它主要是進行上下文的初始化,同時根據傳遞過來的路由信息獲取指定IHttpHandler (其實就是我們的MvcHandler類)
最后通過 context.RemapHandler() 代碼將HttpHandler 處理程序映射到 管線處理中。
在 PostResolveRequestCache 之前分別觸發的事件有:
引發 BeginRequest 事件。
引發 AuthenticateRequest 事件。
引發 PostAuthenticateRequest 事件。
引發 AuthorizeRequest 事件。
引發 PostAuthorizeRequest 事件。
引發 ResolveRequestCache 事件。

http://i.msdn.microsoft.com/dynimg/IC5405.png


核心邏輯代碼:

 1 //獲取路由信息
 2 RouteData routeData = this.RouteCollection.GetRouteData(context);
 3 IRouteHandler routeHandler = routeData.RouteHandler;
 4 //構建請求上下文
 5 RequestContext requestContext = new RequestContext(context, routeData);
 6 context.Request.RequestContext = requestContext;
 7 IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);
 8 //將MvcHandler 實例 映射到管線中(通常我們是利用web.config 進行配置的,但是MvcHandler 沒有默認無參構造函數,所以直接通過向其傳遞一個實例
 9 //進行映射)
10 context.RemapHandler(httpHandler);

如何獲取到MvcRouteHandler?

  MvcRouteHandler 實現了 IRouteHandler接口。 上文的 IRouteHandler routeHandler=routeData.RouteHandler; 在Asp.net MVc 程序中實際上獲取的是MvcRouteHandler實例。
RouteData 類中包含了 IRouteHandler實例的引用,它通過 RouteData 的構造函數:

public RouteData(RouteBase route, IRouteHandler routeHandler);

或者 屬性

public IRouteHandler RouteHandler { get; set; }

進行注入。

我們再往回搜索,RouteData實例是通過 RouteCollection.GetRouteData(Context) 方法獲取的。查看該方法的主要邏輯實現:

 using (this.GetReadLock())
        {
            foreach (RouteBase base2 in this)
            {
                RouteData routeData = base2.GetRouteData(httpContext);
                if (routeData != null)
                {
                    return routeData;
                }
            }
        }

可以看到通過 RouteBase 類的 GetRouteData(HttpContext)獲取了 RouteData實例,並且將第一個部位Null的值返回。 我們需要深入查看RouteBase GetRouteData方法。 RouteBase 是抽象類,其方法是在 Route上具體實現的。(這里又引出一個問題,程序是何時將 Route實例綁定到了 RouteBase上)。

Route類 

深入到Route 類中 發現其和 RouteData 一樣, IRouteHandler 也是通過 構造參數 或 屬性對 IRouteHandler 進行了注入。

public override RouteData GetRouteData(HttpContextBase httpContext)
{
    string virtualPath = httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring(2) + httpContext.Request.PathInfo;
    RouteValueDictionary values = this._parsedRoute.Match(virtualPath, this.Defaults);
    if (values == null)
    {
        return null;
    }
    RouteData data = new RouteData(this, this.RouteHandler);
    if (!this.ProcessConstraints(httpContext, values, RouteDirection.IncomingRequest))
    {
        return null;
    }
    foreach (KeyValuePair<string, object> pair in values)
    {
        data.Values.Add(pair.Key, pair.Value);
    }
    if (this.DataTokens != null)
    {
        foreach (KeyValuePair<string, object> pair2 in this.DataTokens)
        {
            data.DataTokens[pair2.Key] = pair2.Value;
        }
    }
    return data;
}

關於Asp.net Mvc 中的 MapRoute() 方法

  在Asp.net MVc 程序Global 文件的RegisterRoutes 方法里,RouteCollection 類使用的是MapRoute 方法添加的路由,該方法是一個擴展方法。它位於System.Web.Mvc 的RouteCollectionExtensions類中。

 public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces) {
            if (routes == null) {
                throw new ArgumentNullException("routes");
            }
            if (url == null) {
                throw new ArgumentNullException("url");
            }

            Route route = new Route(url, new MvcRouteHandler()) {
                Defaults = new RouteValueDictionary(defaults),
                Constraints = new RouteValueDictionary(constraints),
                DataTokens = new RouteValueDictionary()
            };

            if ((namespaces != null) && (namespaces.Length > 0)) {
                route.DataTokens["Namespaces"] = namespaces;
            }

            routes.Add(name, route);

            return route;
        }

 

查看上面的關鍵行:
Route route = new Route(url, new MvcRouteHandler()) {
Defaults = new RouteValueDictionary(defaults),
Constraints = new RouteValueDictionary(constraints),
DataTokens = new RouteValueDictionary()
};

 

可以清楚看到,當我們在Asp.net MVc 程序里使用MapRoute()添加路由時,會生成一個 Route 類的實例,並且該實例在生成時會被注入 MvcRouteHandler 實例。 最后被添加到 RouteCollection的集合里(Route 和 MvcRouteHandler 都是以多態形式存在於 RouteCollection中的),這樣就建立了一個關系映射表,只有請求的上下文通過該上下文的驗證,就可以返回對應MvcRouteHandler實例。

  • MvcRouteHandler 實現了 IRouteHandler接口
  • Route 繼承了 RouteBase 抽象類。
  • RouteCollection 中維護着一個 RouteBase 集合。

如何根據路由信息獲取MvcHandler

  在Asp.net Mvc 程序 啟動時,會觸發 Appliction_start 方法,該方法調用了 RouteTable,生成一個全局RouteCollection(單件模式),並將其作為參數傳遞到RegisterRoutes 方法中。

registerRoutes 方法里通過MapRoute 向 RouteCollection添加路由。 添加的路由是包含MvcRouteHandler實例的Route。


當用戶的請求到達IIS后,由於Asp.net MVC 注冊了一個HttpModule(UrlRoutingModule) ,應此會觸發對應的管線事件處理方法。


1、根據上下文獲取RouteData方法, 其內部 實現是:從RouteCollection中遍歷RouteBase(實際為Route)實例,並調用RouteBase的GetRouteData() 方法,如果上下文與路由匹配
該方法就會構造一個 RouteData實例,並將 this.IRouteHandler的實例 注入(在Application映射路由時,每個Route實例都包含一個MvcRouteHandler實例的引用)。


2、從 RouteData中 獲取IRouteHandler 實例,即 MvcRouteHandler


3、構建上下文,創建RequestContext 該類僅包含 HttpContextBase 和 RouteData 。調用MvcHandler的構造函數需要傳遞該參數(可以知道請求的Controller、action名等信息),MvcHandler 實現了 IHttpHandler,注意它與MvcHttpHandler 是不相同的:

MvcHandler . 此處理程序負責啟動用於 MVC 應用程序的 ASP.NET 管道。 它從 MVC 控制器工廠接收 Controller 實例;此控制器處理請求的進一步處理。 注意,即使 MvcHandler 實現 IHttpHandler,也不能將其映射為處理程序(例如,.mvc 文件擴展名),因為該類不支持無參數構造函數。 (它唯一的構造函數需要一個 RequestContext 對象。)

因此 在 UrlRoutenModule 中 是 通過 HttpContext.RemapHttp(HttpHandler) 。 直接將 一個實例 映射到處理程序上 。(不需要通過系統對其實例化)。

MvcHttpHandler . 此處理程序用於在不通過路由模塊的情況下幫助直接處理程序映射。 如果您希望一個文件的擴展名(如 .mvc)直接映射到一個 MVC 處理程序,這很有用。 在內部,MvcHttpHandler 執行 ASP.NET 路由通常執行的相同任務(通過 MvcRouteHandler 和 MvcHandler)。 但是,它將這些任務作為處理程序而不是模塊來執行。 UrlRoutingModule 為所有請求啟用時,通常不使用此處理程序。



4、調用IRouteHandler的 GetHttpHandler 方法 獲取 IHttpHandler實例。(即調用了MvcRouteHandler實例的GetHttpHandler 方法,生成了一個 MvcHandler 實例)


5、向當前上下文注冊 IHttpHandler 實例,進入 Controller 處理。

 

 經過以上步驟,我們就大致了解到了Asp.net Mvc程序啟動后,用戶的請求是如何到達HttpHandler的。


RouteTable 類

這個類很簡單只包含一個靜態的RouteCollection 屬性,是一個單件類。

RouteTable

在Asp.net MVc 中application_start 方法里 調用了RouteTable來獲取唯一的RouteCollection實例,

所以在UrlRouteModuel中可以通過RouteTable.Routes獲取所配置的路由集合。

RouteCollection類

  是一個集合類,內部維護着一個RouteBase 以路由名作為Key的字典集合, 所以我們可以給路由命名。主要的屬性和方法有:

RouteData GetRouteData(HttpContextBase httpContext)

  該方法遍歷集合內部的RouteBase實體,並返回第一個非Null的RouteData ,具體的RouteData實例,是由所遍歷的RouteBase 通過調用 RouteBase.GetRouteData(HttpContext)方法獲取的 。

返回的RouteData 中包含一個 IRouteHandler 對象,該接口的只有一個方法,GetHttpHandler,用於獲取IHttpHandler 對象。

VirtualPathData GetVirtualPath(...)

  該方法具有多個重載,當您使用 ASP.NET 路由框架生成 URL 時,GetVirtualPath 方法將返回 VirtualPathData 類的一個實例。 VirtualPathData 類包含與所提供的上下文匹配路由的相關信息。

Route MapPageRoute(...)

  該方法用於向路由集合中添加路由,提供此方法是為了方便編碼, 它等效於調用 Add 方法。其內部實現的主要代碼為:

Route item = new Route(routeUrl, defaults, constraints, dataTokens, new PageRouteHandler(physicalFile, checkPhysicalUrlAccess));

注冊使用 PageRouteHandler 類創建的 Route 對象。

  在Asp.net Mvc 方法中,並沒有利用該方法向集合中添加路由而是通過了了 MapRoute()方法 這是一個擴展方法,定義在了 RouteCollectionExtensions 類中。

static Route MapRoute(this RouteCollection routes, ......)

  這是一個擴展方法,並且具有多個重載,目的是方便編碼,用於向RouteCollection中 添加路由。其內部主要是 生成了一個 以 MvcRouteHandler 為 路由處理的Route 並將其加入到集合中。

Route route = new Route(url, new MvcRouteHandler()) {
                Defaults = new RouteValueDictionary(defaults),
                Constraints = new RouteValueDictionary(constraints),
                DataTokens = new RouteValueDictionary()
            };

IRouteHandler 接口

  接口的定義很簡單,只有一個 GetHttpHandler 方法用於返回 一個 IHttpHandler 對象。在 Route 對象,及 RouteData對象中 都包含該對象的引用。該接口定義了一種協議, 指定了 Route 即路由應該有哪一個處理對象進行處理。 它是 Route 到 Handler的 重要橋梁。

RouteBase 類

   這是一個抽象類,RouteBase 類用於定義應用程序中的路由。 在定義路由時,通常使用 Route 類,Route 類是從 RouteBase 類派生的。 但是,如果要提供與 Route 類所提供的功能不同的功能,則可以創建一個從 RouteBase 派生的類,並實現所需的屬性和方法。

主要方法有:GetRouteData 在派生類中重寫時,會返回有關請求的路由信息。
GetVirtualPath 在派生類中重寫時,會檢查路由是否與指定值匹配,如果匹配,則生成一個 URL,然后檢索有關該路由的信息。

Route 類

  可以通過 Route 類指定 ASP.NET 應用程序中路由的處理方式。 可以為要映射的每個 URL 模式創建一個 Route 對象,該類可處理與該模式相對應的請求。 當應用程序收到請求時,ASP.NET 路由會循環訪問 Routes 集合中的路由,以查找與該 URL 模式匹配的第一個路由。

  可將 Url 屬性設置為 URL 模式。 該 URL 模式包含某些分段,這些分段位於 HTTP 請求中應用程序名稱之后。 例如,在 URL http://www.contoso.com/products/show/beverages 中,該模式應用於 products/show/beverages。 包含三個分段的模式(如 {controller}/{action}/{id})與 URL http://www.contoso.com/products/show/beverages 匹配。 每個分段由 / 字符分隔。 如果分段位於大括號({ 和 })內,則表明該分段是一個 URL 參數。 ASP.NET 路由會檢索請求中的值並將其分配給 URL 參數。 在上面的示例中,URL 參數 action 被賦予值 show。 如果該分段不在大括號內,則該值被視為文本值。將 Defaults 屬性設置為 RouteValueDictionary 對象,該對象包含當 URL 中缺少某個參數時所使用的值,或者包含用於設置 URL 中未參數化的其他值的值。 將 Constraints 屬性設置為 RouteValueDictionary 對象,該對象包含的值為正則表達式或 IRouteConstraint 對象。 這些值用於確定參數值是否有效。

  

public override RouteData GetRouteData(HttpContextBase httpContext)
{
    string virtualPath = httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring(2) + httpContext.Request.PathInfo;
    RouteValueDictionary values = this._parsedRoute.Match(virtualPath, this.Defaults);
    if (values == null)
    {
        return null;
    }
    RouteData data = new RouteData(this, this.RouteHandler);
    if (!this.ProcessConstraints(httpContext, values, RouteDirection.IncomingRequest))
    {
        return null;
    }
    foreach (KeyValuePair<string, object> pair in values)
    {
        data.Values.Add(pair.Key, pair.Value);
    }
    if (this.DataTokens != null)
    {
        foreach (KeyValuePair<string, object> pair2 in this.DataTokens)
        {
            data.DataTokens[pair2.Key] = pair2.Value;
        }
    }
    return data;
}

該方法重寫了父類的方法,返回一個RouteData 作。方法一開始 首先通過 ParsedRoute 類的 Match 方法進行路由匹配,匹配成功后則生成一個RouteData對象實例。

ParsedRoute 類
該類是一個內部類,用於匹配、綁定URL,大家可以參考:

 

 public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
    BoundUrl url = this._parsedRoute.Bind(requestContext.RouteData.Values, values, this.Defaults, this.Constraints);
    if (url == null)
    {
        return null;
    }
    if (!this.ProcessConstraints(requestContext.HttpContext, url.Values, RouteDirection.UrlGeneration))
    {
        return null;
    }
    VirtualPathData data = new VirtualPathData(this, url.Url);
    if (this.DataTokens != null)
    {
        foreach (KeyValuePair<string, object> pair in this.DataTokens)
        {
            data.DataTokens[pair.Key] = pair.Value;
        }
    }
    return data;
}

  該方法返回與路由相關聯的URL信息。其內部是通過ParsedRoute 類的Bind 把RouteData 綁定到一個BoundUrl 對象中。 如果有約束在則進行驗證,最后返回一個 VirtualPathData 對象。

 

protected virtual bool ProcessConstraint(HttpContextBase httpContext, object constraint, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
    object obj2;
    IRouteConstraint constraint2 = constraint as IRouteConstraint;
    if (constraint2 != null)
    {
        return constraint2.Match(httpContext, this, parameterName, values, routeDirection);
    }
    string str = constraint as string;
    if (str == null)
    {
        throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, SR.GetString("Route_ValidationMustBeStringOrCustomConstraint"), new object[] { parameterName, this.Url }));
    }
    values.TryGetValue(parameterName, out obj2);
    string input = Convert.ToString(obj2, CultureInfo.InvariantCulture);
    string pattern = "^(" + str + ")$";
    return Regex.IsMatch(input, pattern, RegexOptions.CultureInvariant | RegexOptions.Compiled | RegexOptions.IgnoreCase);
}

  這是一個路由約束的處理方法,可以看到如果傳遞的 object constraint 是一個 IRouteConstraint 類型 則直接調用 其Match 方法。

如果正則表達式字符串,利用正則表達式進行驗證,否則拋出異常。 


 

造一個簡陋的輪子

  不要再造輪子是軟件設計的一個准則,但是在學習研究時,造一個簡單的輪子能更好的幫我們了解其原理。

創建一個HttpHandler 類,讓Asp.net程序通過路由,運行我們指定的HttpHandler,同時在Handler中能夠獲取路由信息。

1、創建一個類庫項目,在項目里創建一個WheelHandler類並實現IHttpHandler接口,這個類只有一個構造函數構造函數需要傳遞一個 RequestContext

 public class WheelHandler : IHttpHandler
    {
        public WheelHandler(RequestContext requestContext)
        {

            this.RequestContext = requestContext;
        }

        #region IHttpHandler Members

        public bool IsReusable
        {
            get { return true; }
        }

        public void ProcessRequest(HttpContext context)
        {
            context.Response.Write(String.Format("this is a Wheel for {0}Controller and {1}action "
                , this.RequestContext.RouteData.Values["Controller"]
                , this.RequestContext.RouteData.Values["Action"]));
            context.Response.End();
        }

        #endregion

        public RequestContext RequestContext { get; private set; }
    }

 WheelHandler沒有默認的無參構造函數,所以不能直接在Web.config 中注冊。ProcessRequest對象很簡單,就是輸出傳入的路由信息。

我們需要定義一個IRouteHandler對象,當路由被捕獲時,返回一個WheelHandler,然后將其映射到Http處理中。

2.定義WheelRouteHandler

 public class WheelRouteHandler : IRouteHandler
    {


        public IHttpHandler GetHttpHandler(RequestContext requestContext)
        {
            return new WheelHandler(requestContext);
        }

        IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext)
        {
            return this.GetHttpHandler(requestContext);
        }
    }

3、映射路由和WheelRouteHandler。

  我們可以自定義一個派生自RouteBase 的類,進行特定的路由處理,但是本例是一個簡單的"輪子",因此繼續使用Route類,只需要在生成Route時 向其注入 WheelRouteHandler即可。

因此我們需要修改添加路由的方式,這里模仿MVc 利用擴展方法。當然你也可以不用擴展方法,在添加路由是直接使用Add方法,記得注入WheelRouteHandler便行。

public static Route MapWheelRoute(this RouteCollection routes, string name, string url, object defaults)
        {
            return MapWheelRoute(routes, name, url, defaults, null, null);
        }

        public static Route MapWheelRoute(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces)
        {
            if (routes == null)
            {
                throw new ArgumentNullException("routes");
            }
            if (url == null)
            {
                throw new ArgumentNullException("url");
            }
            // 在這里注冊 Route 與 WheelRouteHandler的映射關系
            Route route = new Route(url, new WheelRouteHandler())
            {
                Defaults = new RouteValueDictionary(defaults),
                Constraints = new RouteValueDictionary(constraints),
                DataTokens = new RouteValueDictionary()
            };

            if ((namespaces != null) && (namespaces.Length > 0))
            {
                route.DataTokens["Namespaces"] = namespaces;
            }

            routes.Add(name, route);

            return route;
        }

 

MapWheelRoute 方法里 調用了Route構造函數,並傳入一個WheelRouteHandler對象。

4、創建HttpModule 對象

  自定義的HttpModule的責任是,構建上下文,創建HttpHandler對象並將它映射到Http處理程序里去。

 public void Init(HttpApplication context)
        {
            context.PostResolveRequestCache += new EventHandler(context_PostResolveRequestCache);
        }

        void context_PostResolveRequestCache(object sender, EventArgs e)
        {
            HttpContextBase context = new HttpContextWrapper(((HttpApplication)sender).Context);
            this.PostResolveRequestCache(context);

        }

        private void PostResolveRequestCache(HttpContextBase context)
        {
            RouteData routeData = RouteTable.Routes.GetRouteData(context);

            if (routeData == null)
            {
                throw new InvalidOperationException();
            }

            IRouteHandler routeHandler = routeData.RouteHandler;
            if (routeHandler == null)
            {
                throw new InvalidOperationException();
            }

            RequestContext requestContext = new RequestContext(context, routeData);
            IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);
            if (httpHandler == null)
            {
                throw new InvalidOperationException("無法創建對應的HttpHandler對象");
            }
            context.RemapHandler(httpHandler);

        }

  這里我們也是模仿Mvc 捕獲的是PostResolveRequestCache事件進行處理,處理時首先將 包裝HttpContext對象為 HttpContextBase對象。然后通過GetRouteData 獲取RouteData對象,它包含有IRouteHandler對象。獲取到IRouteHandler對象后,需要構造 一個RequestContext 對象(該對象很簡單就是包含上下文和路由信息)因為 創建WheelHandler 需要該對象。

創建好IHttpHandler對象后,利用RemapHandler方法將其映射為處理程序。

5、注冊路由 和Module

  新建一個空的Asp.net 項目,移除里面所有的Aspx文件,事實上只要保留Global和 Web.Config文件即可。在Global 里注冊路由:

路由注冊
 1   void Application_Start(object sender, EventArgs e)
 2         {
 3             RegRoutes(RouteTable.Routes);
 4         }
 5 
 6         private void RegRoutes(RouteCollection routeCollection)
 7         {
 8             routeCollection.MapWheelRoute(null,
 9                "{controller}/{action}/{*id}",
10                new { controller = "Home", action = "index", id = 1 });
11         }

  完成最后我們還需要組成自定義的HttpModule

在Web.Config 中添加如下配置:

 <system.webServer>
    <modules runAllManagedModulesForAllRequests="true">
      <add name="WheelRouting" type="WheelRouting.WheelRoutingModule,WheelRouting"/>
    </modules>

  </system.webServer>

關於 HttpModule的介紹 可以參考:演練:創建和注冊自定義 HTTP 模塊

下面是運行結果:

Controller默認值為 Home  Action 默認值為 index

 

源碼:點擊下載

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM