1. 程式人生 > >MVC之前的那點事兒系列(8):UrlRouting的理解

MVC之前的那點事兒系列(8):UrlRouting的理解

文章內容

根據對Http Runtime和Http Pipeline的分析,我們知道一個ASP.NET應用程式可以有多個HttpModuel,但是隻能有一個HttpHandler,並且通過這個HttpHandler的BeginProcessRequest(或ProcessRequest)來處理並返回請求,前面的章節將到了再MapHttpHandler這個週期將會根據請求的URL來查詢對應的HttpHandler,那麼它是如何查詢的呢?

一起我們在做自定義HttpHandler的時候,需要執行URL以及副檔名匹配規則,然後查詢HttpHandler的時候就是根據相應的規則來查詢哪個HttpHandler可以使用。另一方面我們本系列教材講的MVC就是通過註冊路由(Route)來匹配到對應的Controller和Action上的,例如Global.asax裡的程式碼:

routes.MapRoute(
    "Default",
    "{controller}/{action}/{id}",
    new { controller = "Home", action = "Index", id = UrlParameter.Optional }

但是在匹配這個之前,MVC首先要接管請求才能處理,也就是說我們要有對應MVC的HttpHandler(後面知道它的名字叫MvcHandler)被MapRequestHandler週期的處理引擎查詢到並且應用上才行,然後後面才能由 Controller/Action執行。另外一方面,由於該URL地址沒有副檔名,所以無法進入ASP.NET的RunTime,MVC2的實現方式是:註冊萬用字元(*.*)對映到aspnet_ISPAI.dll,然後通過一個自定義的UrlRoutingModuel來匹配Route規則,再繼續處理,但是MVC3的時候,匹配Route規則的處理機制整合到ASP.NET4.0裡了,也就是今天我們這篇文章所要講的主角(UrlRoutingModule)的處理機制。

先來看UrlRoutingModule的原始碼,無容置疑地這個類是繼承於IHttpModule,首先看一下Init方法的程式碼:

protected virtual void Init(HttpApplication application) {

    ////////////////////////////////////////////////////////////////// 
    // Check if this module has been already addded
    if (application.Context.Items[_contextKey] != null) { 
        return; // already added to the pipeline 
    }
    application.Context.Items[_contextKey] 
= _contextKey; // Ideally we would use the MapRequestHandler event. However, MapRequestHandler is not available // in II6 or IIS7 ISAPI Mode. Instead, we use PostResolveRequestCache, which is the event immediately // before MapRequestHandler. This allows use to use one common codepath for all versions of IIS. application.PostResolveRequestCache += OnApplicationPostResolveRequestCache; }

該程式碼在PostResolveRequestCache週期事件上添加了我們需要執行的方法,用於URL匹配規則的設定,但是為什麼要在這個週期點上新增事件呢?看了註釋,再結合我們前面對Pipeline的瞭解,釋然了,要像動態註冊自己的HttpHandler,那就需要在MapRequestHandler之前進行註冊自己的規則(因為這個週期點就是做這個事情的),但由於IIS6不支援這個事件,所以為了能讓IIS6也能執行MVC3,所以我們需要在這個週期之前的PostResolveRequestCache的事件點上去註冊我們的規則,也許如果IIS6被微軟廢棄以後,就會將這個事件新增到真正的開始點MapRequestHandler上哦。

我們繼續來看註冊該事件的OnApplicationPostResolveRequestCache方法的程式碼:

public virtual void PostResolveRequestCache(HttpContextBase context) { 
    // Match the incoming URL against the route table
    RouteData routeData = RouteCollection.GetRouteData(context);

    // Do nothing if no route found 
    if (routeData == null) {
        return; 
    } 

    // If a route was found, get an IHttpHandler from the route's RouteHandler 
    IRouteHandler routeHandler = routeData.RouteHandler;
    if (routeHandler == null) {
        throw new InvalidOperationException(
            String.Format( 
                CultureInfo.CurrentUICulture,
                SR.GetString(SR.UrlRoutingModule_NoRouteHandler))); 
    } 

    // This is a special IRouteHandler that tells the routing module to stop processing 
    // routes and to let the fallback handler handle the request.
    if (routeHandler is StopRoutingHandler) {
        return;
    } 

    RequestContext requestContext = new RequestContext(context, routeData); 
 
    // Dev10 766875    Adding RouteData to HttpContext
    context.Request.RequestContext = requestContext; 

    IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);
    if (httpHandler == null) {
        throw new InvalidOperationException( 
            String.Format(
                CultureInfo.CurrentUICulture, 
                SR.GetString(SR.UrlRoutingModule_NoHttpHandler), 
                routeHandler.GetType()));
    } 

    if (httpHandler is UrlAuthFailureHandler) {
        if (FormsAuthenticationModule.FormsAuthRequired) {
            UrlAuthorizationModule.ReportUrlAuthorizationFailure(HttpContext.Current, this); 
            return;
        } 
        else { 
            throw new HttpException(401, SR.GetString(SR.Assess_Denied_Description3));
        } 
    }

    // Remap IIS7 to our handler
    context.RemapHandler(httpHandler); 
}

我已經加粗了4行重要的程式碼,第一行是通過傳遞HttpContext引數,從RouteCollection找到對應的靜態屬性RouteData( GetRouteData方法裡會先判斷真實檔案是否存在,如果不存在才去找RouteData),第二行然後從RouteData的屬性RouteHandler獲取一個IRouteHandler的例項,第三行是從該例項裡獲取對應的IHttpHandler例項,第4行是呼叫HttpContext的RemapHandler方法重新map新的handler(這行程式碼的註釋雖然說是remap IIS7,其實IIS6也是用了,只不過判斷該方法裡對IIS7整合模式多了一點特殊處理而已),然後可以通過HttpContext. RemapHandlerInstance屬性來得到這個例項。

關於Route/RouteData/RouteCollection/IRouteHandler的作用主要就是定義URL匹配到指定的IHttpHandler,然後註冊進去,具體實現我們稍後再講,現在先看一下Http Pipeline裡是如何找到這個IHttpHandler例項的,由於IIS6和IIS7整合模式是差不多的,前面的文章我們提到了都是最終呼叫到IHttpHandlerFactory的例項,然後從中獲取IHttpHandler,所以我們這裡只分析IIS6和IIS7經典模式的實現。

先來看BuildSteps裡查詢HttpHandler的方法MapHandlerExecutionStep的程式碼,只有幾行程式碼,最重要的是:

context.Handler = _application.MapHttpHandler(
    context,
    request.RequestType,
    request.FilePathObject, 
    request.PhysicalPathInternal,
    false /*useAppConfig*/); 

MapHttpHandler就是我們要查詢Handler的方法了,來仔細看看程式碼:

internal IHttpHandler MapHttpHandler(HttpContext context, String requestType, VirtualPath path, String pathTranslated, bool useAppConfig) { 
    // Don't use remap handler when HttpServerUtility.Execute called
    IHttpHandler handler = (context.ServerExecuteDepth == 0) ? context.RemapHandlerInstance : null;

    using (new ApplicationImpersonationContext()) { 
        // Use remap handler if possible
        if (handler != null){ 
            return handler; 
        }
 
        // Map new handler
        HttpHandlerAction mapping = GetHandlerMapping(context, requestType, path, useAppConfig);

        // If a page developer has removed the default mappings with <httpHandlers><clear> 
        // without replacing them then we need to give a more descriptive error than
        // a null parameter exception. 
        if (mapping == null) { 
            PerfCounters.IncrementCounter(AppPerfCounter.REQUESTS_NOT_FOUND);
            PerfCounters.IncrementCounter(AppPerfCounter.REQUESTS_FAILED); 
            throw new HttpException(SR.GetString(SR.Http_handler_not_found_for_request_type, requestType));
        }

        // Get factory from the mapping 
        IHttpHandlerFactory factory = GetFactory(mapping);
 
 
        // Get factory from the mapping
        try { 
            // Check if it supports the more efficient GetHandler call that can avoid
            // a VirtualPath object creation.
            IHttpHandlerFactory2 factory2 = factory as IHttpHandlerFactory2;
 
            if (factory2 != null) {
                handler = factory2.GetHandler(context, requestType, path, pathTranslated); 
            } 
            else {
                handler = factory.GetHandler(context, requestType, path.VirtualPathString, pathTranslated); 
            }
        }
        catch (FileNotFoundException e) {
            if (HttpRuntime.HasPathDiscoveryPermission(pathTranslated)) 
                throw new HttpException(404, null, e);
            else 
                throw new HttpException(404, null); 
        }
        catch (DirectoryNotFoundException e) { 
            if (HttpRuntime.HasPathDiscoveryPermission(pathTranslated))
                throw new HttpException(404, null, e);
            else
                throw new HttpException(404, null); 
        }
        catch (PathTooLongException e) { 
            if (HttpRuntime.HasPathDiscoveryPermission(pathTranslated)) 
                throw new HttpException(414, null, e);
            else 
                throw new HttpException(414, null);
        }

        // Remember for recycling 
        if (_handlerRecycleList == null)
            _handlerRecycleList = new ArrayList(); 
        _handlerRecycleList.Add(new HandlerWithFactory(handler, factory)); 
    }
 
    return handler;
}

從程式碼可以看出,首先如果當前頁面使用了HttpServerUtility.Execute進行頁面內跳轉,就不使用我們通過路由設定的HttpHandler(也就是HttpContent.RemapHandlerInstance屬性),如果沒有跳轉,就使用,並且優先順序是第一的,只有當不設定任何基於Route的HttpHandler,才走剩餘的匹配規則(也就是之前ASP.NET預設的按照副檔名類匹配的,這部分和我們關係不大就不做詳細分析了)。

好了,知道了UrlRouteModuel的大概機制,我們再回頭看看如何通過Route/RouteData/RouteCollection/IRouteHandler這幾個類來實現動態註冊Route規則的,先來看Route的程式碼:

[TypeForwardedFrom("System.Web.Routing, Version=3.5.0.0, Culture=Neutral, PublicKeyToken=31bf3856ad364e35")]
public class Route : RouteBase
{    
    public Route(string url, IRouteHandler routeHandler)
    {
        Url = url;
        RouteHandler = routeHandler;
    }
     
    public Route(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, IRouteHandler routeHandler) { 
            Url = url;
            Defaults = defaults; 
            Constraints = constraints; 
            RouteHandler = routeHandler;
        }

    //省略部分程式碼
    public override RouteData GetRouteData(HttpContextBase httpContext)
    {
        // Parse incoming URL (we trim off the first two chars since they're always "~/")
        string requestPath = httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring(2) + httpContext.Request.PathInfo;

        RouteValueDictionary values = _parsedRoute.Match(requestPath, Defaults);

        if (values == null)
        {
            // If we got back a null value set, that means the URL did not match
            return null;
        }

        RouteData routeData = new RouteData(this, RouteHandler);

                // Validate the values
        if (!ProcessConstraints(httpContext, values, RouteDirection.IncomingRequest)) { 
            return null; 
        }
 
        // Copy the matched values
        foreach (var value in values) {
            routeData.Values.Add(value.Key, value.Value);
        } 

        // Copy the DataTokens from the Route to the RouteData 
        if (DataTokens != null) { 
            foreach (var prop in DataTokens) {
                routeData.DataTokens[prop.Key] = prop.Value; 
            }
        }
        return routeData;
    }       
    }

Route程式碼提供了一系列的建構函式過載(我們這裡只列出了兩個),建構函式主要是傳入URL和對應的IRouteHandler例項以及約束規則(比如正則等),然後提供了一個最重要的GetRouteData方法,用於將Route自身和IRouteHandler組裝成RouteData,然後返回(中途也會驗證相應的約束條件,比如是否符合某個正則表示式),RouteData類本身沒有什麼邏輯,只是暴露了Route和RouteHandler屬性。

我們再來看RouteCollection,該類儲存了所有的Route規則(即URL和對應的IRouteHandler),通過靜態屬性RouteTable.Routes來獲取RouteCollection例項,通過UrlRoutingModule裡暴露的RouteCollection屬性我們可以驗證這一點:

public RouteCollection RouteCollection {
    get { 
        if (_routeCollection == null) { 
            _routeCollection = RouteTable.Routes;
        } 
        return _routeCollection;
    }
    set {
        _routeCollection = value; 
    }
} 

還有一個需要注意的,RouteHandler繼承的IRouteHandler的程式碼:

public interface IRouteHandler
{
     IHttpHandler GetHttpHandler(RequestContext requestContext);
}

該程式碼只提供了一個GetHttpHandler方法,所有實現這個介面的類需要實現這個方法,MVCHandler就是這麼實現的(下一章節我們再細看)。

至此,我們應該有一個清晰的認識了,我們通過全域性靜態屬性集合(RouteTable.Routes)去新增各種各樣的Route(但應該在HttpModule初始化週期之前),然後通過UrlRoutingModule負責註冊Route以及對應的IRouteHandler例項(IRouteHandler例項可以通過GetHttpHandler獲取IHttpHandler),最終實現根據不同URL來接管不同的HttpHandler。

MVC正是利用HttpApplication建立的週期(Application_Start方法)來添加了我們所需要的Route規則,當然在新增規則的時候帶上了MVCHandler這個重要的HttpHandler,

程式碼如下:

protected void Application_Start()
{
    RegisterRoutes(RouteTable.Routes);
}

public static void RegisterRoutes(RouteCollection routes)
{
    routes.MapRoute(
        "Default",
        "{controller}/{action}/{id}",
        new { controller = "Home", action = "Index", id = UrlParameter.Optional }
                );
}

MapRoute方法是一個擴充套件方法,通過該擴充套件方法註冊Route是個不錯的方法,下一章節,我們講講解MVC是如何註冊自己的MVCRouteHandler例項以及如何實現MVCHandler的呼叫的。

參考資料:

同步與推薦

MVC之前的那點事兒系列文章,包括了原創,翻譯,轉載等各型別的文章,如果對你有用,請推薦支援一把,給大叔寫作的動力。

相關推薦

MVC之前事兒系列8UrlRouting理解

文章內容 根據對Http Runtime和Http Pipeline的分析,我們知道一個ASP.NET應用程式可以有多個HttpModuel,但是隻能有一個HttpHandler,並且通過這個HttpHandler的BeginProcessRequest(或ProcessRequest)來處理並返回請求,前

MVC之前事兒系列7WebActivator的實現原理詳解

文章內容 上篇文章,我們分析如何動態註冊HttpModule的實現,本篇我們來分析一下通過上篇程式碼原理實現的WebActivator類庫,WebActivator提供了3種功能,允許我們分別在HttpApplication初始化之前,之後以及ShutDown的時候分別執行指定的程式碼,示例如下: [

MVC之前事兒系列9MVC如何在Pipeline中接管請求的?

文章內容 上個章節我們講到了,可以在HttpModules初始化之前動態新增Route的方式來自定義自己的HttpHandler,最終接管請求的,那MVC是這麼實現的麼?本章節我們就來分析一下相關的MVC原始碼來驗證一下我們的這個問題。 先建立一個MVC3的Web Application,選擇預設的模

MVC之前事兒系列3HttpRuntime詳解分析

文章內容 話說,經過各種各樣複雜的我們不知道的內部處理,非託管程式碼正式開始呼叫ISPAIRuntime的ProcessRequest方法了(ISPAIRuntime繼承了IISPAIRuntime介面,該介面可以和COM進行互動,並且暴露了ProcessRequest介面方法)。至於為什麼要呼叫這個方法,

MVC之前事兒系列4Http Pipeline詳細分析

文章內容 繼續上一章節的內容,通過HttpApplicationFactory的GetApplicationInstance靜態方法獲取例項,然後執行該例項的BeginProcessRequest方法進行執行餘下的Http Pipeline 操作,程式碼如下: // Get application i

MVC之前事兒系列5Http Pipeline詳細分析

文章內容 接上面的章節,我們這篇要講解的是Pipeline是執行的各種事件,我們知道,在自定義的HttpModule的Init方法裡,我們可以新增自己的事件,比如如下程式碼: public class Test : IHttpModule { public void Init(HttpAp

MVC之前事兒系列2HttpRuntime詳解分析

文章內容 從上章文章都知道,asp.net是執行在HttpRuntime裡的,但是從CLR如何進入HttpRuntime的,可能大家都不太清晰。本章節就是通過深入分析.Net4的原始碼來展示其中的重要步驟。請先看下圖:   首先,CLR在初始化載入的時候,會載入一個非常重要的類AppManagerApp

MVC之前事兒系列6動態註冊HttpModule

文章內容 通過前面的章節,我們知道HttpApplication在初始化的時候會初始化所有配置檔案裡註冊的HttpModules,那麼有一個疑問,能否初始化之前動態載入HttpModule,而不是隻從Web.config裡讀取? 答案是肯定的, ASP.NET MVC3釋出的時候提供了一個Microsof

MVC之前事兒系列10MVC為什麼不再需要註冊萬用字元*.*了?

文章內容 很多教程裡都提到了,在部署MVC程式的時候要配置萬用字元對映(或者是*.mvc)到aspnet_ISPAI.dll上,在.NET4.0之前確實應該這麼多,但是.NET4.0之後已經不要再費事了,因為它預設就支援了。 你可以會問,沒有對映配置,請求這麼可能會走到aspnet_ISPAI.dll

MVC之前事兒系列1:進入CLR

MVC之前的那點事兒系列,是筆者在2012年初閱讀MVC3原始碼的時候整理的,主要講述的是從HTTP請求道進入MVCHandler之前的內容,包括了原創,翻譯,轉載,整理等各型別文章,當然也參考了部落格園多位大牛的文章,對此表示感謝,這次有時間貼出來,希望對大家有用。 主要內容 本文講解的是:伺服器接受H

arcgis jsapi介面入門系列8滑鼠在地圖畫面

初始化,每個map執行一次 PS:畫點也差不多,都是用SketchViewModel,因此本demo沒有專門寫畫點的 drawPolygonInit: function () { //畫幾何物件初始化 //新建一個圖形圖

python快速學習系列8異常處理

-異常通常出現的處理方式 ·條件語句:if/else ·異常處理:try/except/else/finally 1.python中的異常和相關語法 ·exception:python內建的異常類 ·raise:丟擲異常 ·try:嘗試執行以下語句 ·except:在try語句之後,捕獲

解讀ASP.NET 5 & MVC6系列8Session與Caching

在之前的版本中,Session存在於System.Web中,新版ASP.NET 5中由於不在依賴於System.Web.dll庫了,所以相應的,Session也就成了ASP.NET 5中一個可配置的模組(middleware)了。 配置啟用Session ASP.NET 5中的Session模組存在於Micr

爬蟲入門系列快速理解HTTP協議

爬蟲入門系列目錄: 4月份給自己挖一個爬蟲系列的坑,主要涉及HTTP 協議、正則表示式、爬蟲框架 Scrapy、訊息佇列、資料庫等內容。 爬蟲的基本原理是模擬瀏覽器進行 HTTP 請求,理解 HTTP 協議是寫爬蟲的必備基礎,招聘網站的爬蟲崗位也赫然寫著熟練掌握HTTP協議規範,寫

MVC之前事兒 ---- 系列文章

需要 cnblogs post omx pip 實現原理 內容 activator div MVC之前的那點事兒系列,是筆者在2012年初閱讀MVC3源碼的時候整理的,主要講述的是從HTTP請求道進入MVCHandler之前的內容,包括了原創,翻譯,轉載,整理等各類型文

HD-ACM算法專攻系列8——排序

star == std str clu alt img mes 技術 題目描述: 源碼: #include"iostream" #include"string" using namespace std; void Order(int *p, int n) {

設計模式, mvc 模型視圖控制器模式8

== 器) urn indexer 分層 myself all 模型 log MVC 模式代表 Model-View-Controller(模型-視圖-控制器) 模式。這種模式用於應用程序的分層開發。 Model(模型) - 模型代表一個存取數據的對象或 JAVA P

NMI,FIQ 與arm構架事-1

微信公眾號 mindshare思享   NMI是Non Maskable Interrupt的縮寫,它是一種不能mask的硬體中斷,主要用於當一些不能恢復的硬體錯誤發生時傳送訊號給CPU。 NMI通常是用在當一般的中斷被軟體(比如OS)mask的情況下需要響應一

NMI, FIQ 與arm構架事-2

微信公眾號 mindshare思享   以下內容會談到: 1.   NMI的使用場景。 2.   在arm上怎麼模擬NMI a.   通過GIC的中斷優先順序模擬 b

arcgis jsapi介面入門系列5幾何線面基本操作

點 point: function () { //通過wkt生成點 //wkt,代表點的座標 let wkt = "POINT(113.566806 22.22445)";