1. 程式人生 > >MVC之前的那點事兒系列(9):MVC如何在Pipeline中接管請求的?

MVC之前的那點事兒系列(9):MVC如何在Pipeline中接管請求的?

文章內容

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

先建立一個MVC3的Web Application,選擇預設的模板以便建立以後就預設包含HomeController和AccountController。我們知道MVC要先接管請求才能通過這些Controller來處理,那我們先去Global.asax.cs檔案裡看程式碼(定義接管請求要在初始化HttpModule之前,所以只能到這裡來找程式碼(或者是利用WebActivator之類的特性來動態新增),Global.asax.cs檔案裡程式碼很少,但是有我們需要的東西,首先在Application_Start的方法裡發現一行程式碼:

RegisterRoutes(RouteTable.Routes);

這行程式碼,看呼叫的方法名稱RegisterRoutes是註冊Route的意思,但是為什麼引數卻是全域性的RouteTable.Routes集合呢?找到RegisterRoutes方法來看看具體的內容:

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.MapRoute(
        "Default
", // Route name "{controller}/{action}/{id}", // URL with parameters new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults ); }

該方法有2行程式碼,第一行是忽略一個Route(我們先不看這個),第二行是使用MapRoute方法註冊一個新的Route,預設是對映到Home Controller的Index Action上,我們可能想到了,RouteCollection(也就是剛才傳入的RouteTable.Routes)的MapRoute方法就是提供我們所說的接管請求的入口,但是如何把MVC自己的HttpHandler傳進去的呢?我們Go to一下這個MapRoute方法(需要安裝ReShaper來查詢MVC的原始碼),調整到了MVC的RouteCollectionExtensions類,發現MapRoute並不是RouteCollection自帶的方法,而是在MVC原始碼裡提供的一個擴充套件方法,程式碼如下:

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;
}

該程式碼的主要作用是new一個新的Route,然後將該Route新增到我們剛才提到的靜態集合RouteTable.Routes裡,以便後期查詢Handler的時候使用,OK,這一步符合我們前面章節的分析。

接下來看,Route是如何new出來的,程式碼裡的引數傳入的分別是我們知道的url,以及一個MVCRouteHandler的例項,這一步也符合我們前面的分析,那我們來看一下MVCRouteHandler的GetHttpHandler方法是如何實現的獲取MVCHandler的:

    public class MvcRouteHandler : IRouteHandler { 
        private IControllerFactory _controllerFactory;
 
        public MvcRouteHandler() { 
        }
 
        public MvcRouteHandler(IControllerFactory controllerFactory) {
            _controllerFactory = controllerFactory;
        }
 
        protected virtual IHttpHandler GetHttpHandler(RequestContext requestContext) {
            requestContext.HttpContext.SetSessionStateBehavior(GetSessionStateBehavior(requestContext)); 
            return new MvcHandler(requestContext); 
        }
 
        protected virtual SessionStateBehavior GetSessionStateBehavior(RequestContext requestContext) {
            string controllerName = (string)requestContext.RouteData.Values["controller"];
            IControllerFactory controllerFactory = _controllerFactory ?? ControllerBuilder.Current.GetControllerFactory();
            return controllerFactory.GetControllerSessionBehavior(requestContext, controllerName); 
        }
 
        #region IRouteHandler Members 
        IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext) {
            return GetHttpHandler(requestContext); 
        }
        #endregion
    }

看以上的粗體程式碼,MvcRouteHandler在實現了IRouteHandler的GetHttpHandler,該方法呼叫了MvcRouteHandler自身定義的GetHttpHandler虛方法,而在這個虛方法裡我們看到了一個非常重要而又期待已久的程式碼——返回MvcHandler的例項,大概看一下MvcHandler這個類,得到它就是我們所猜想的:繼承於IHttpHandler介面的一個類,並且也繼承了 IHttpAsyncHandler介面,我們先不管MvcHandler內部是如何實現的,但我們前面幾章節的全部分析終於得到了驗證,也就說在這裡得到了Mvc的專用處理Handler,然後呼叫它的BeginProcessRequest方法進入Mvc自身的Pipeline進行處理了。

至此,我們終於弄明白了Mvc在整個ASP.NET Runtime是如何接管請求的了,也應該大概清楚整個ASP.NET Runtime的執行機制了,至於MvcHandler的實現方式,我們會在後面的很多章節逐一給大家分析每行程式碼,今天我們還有一個小任務,那就是:看完了Mvc的實現機制,我們能否自己來寫一個自定義的HttpHandler通過Route動態註冊進去,來實現我們自己的自定義擴充套件,我們來嘗試著做一下吧。

第一步:建立HttpHandler類

    public class TomHandler : IHttpHandler
    {
        public TomHandler(RequestContext requestContext)
        {
            // do nothing
        }

        public virtual void ProcessRequest(HttpContext context)
        {
            string url = context.Request.Url.AbsoluteUri;
            context.Response.Write("當前地址為:" + url);
            context.Response.End();
            // 這裡我們什麼都不做,只輸出URL地址
        }

        public virtual bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }

第二步:建立RouteHandler類

public class TomRouteHandler : IRouteHandler
{
    IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext)
    {
        return new TomHandler(requestContext);
    }
}

在GetHttpHandler實現裡,返回我們定義的TomHandler例項。

第三步:註冊我們的Route和RouteHandler

protected void Application_Start(object sender, EventArgs e)
{
    Route route = new Route("tom/{other}", new TomRouteHandler());
    RouteTable.Routes.Add(route);
}

我們設定成,只要訪問tom資料夾下的任意檔案或者子目錄,都提示該URL。下面是我的測試結果:

訪問:Http//localhost/tom/

結果:沒有提示我們預想的結果(原因是不符合我們的規則)

訪問:Http//localhost/tom/123/

結果:輸出正常(說明TomHandler已經接管了該請求)

訪問:Http//localhost/tom/123.aspx?id=123

結果:輸出正常(也說明TomHandler已經接管了該請求)

在建立真實的tom資料夾,然後在裡面建立一個 index.html檔案(內容為123),然後訪問Http//localhost/tom/index.html,規則符合我們的Route定義,但輸出結果卻不是我們預期的結果,而是123,怎麼回事?還記得上一章節裡談到的RouteCollection的GetRouteData方法麼?該方法是先判斷URL對應的檔案是否真實存在,如果存在就直接輸出,如果不存在就再來找RouteData的資料,這就解釋了上面的index.html路徑為什麼不是我們期望結果的原因了吧?。

注:如果你建立一個index.aspx檔案,並在index.aspx.cs檔案裡寫Response.Write程式碼輸出123的話,該檔案也會按照aspx頁面的正常週期來執行(也就是說輸出123字串),如果你在<system.web>裡的httpHandlers節點用remove命令把*.aspx的匹配設定去掉,那結果就只會輸出index.aspx這個檔案裡的字串了(包括裡面內嵌的任何C#程式碼)。

最後總結了,到這裡,我們已經知道了MvcHandler是如何接管請求的了,而且自己也做了一個簡單的例子來驗證這套機制。重新回顧一下前面的這麼多篇文章,我們應該大概對ASP.NET RunTime, Pipeline以及ASP.NET MVC切入點應該有個整體的瞭解了。

同步與推薦

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

相關推薦

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

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

MVC之前事兒系列8UrlRouting的理解

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

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

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

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介面入門系列9可以同時顯示多個的地圖popup

jsapi有提供popup功能,但缺點很多,例如地圖上只能同時顯示一個popup,popup內容有限制等 本文提供另一個方法,原理不用jsapi,在地圖外用一個普通的div放在地圖上面,再監聽地圖的滑鼠移動等時間控制這div跟著地圖聯動 本文程式碼可能存在跟框架的css等繫結,不一定能直接執

python快速學習系列9上下文管理器

上下文管理器context manager -為什麼要學context manager? ·類似於decorator,TensorFlow裡面出現了不少context manager ·Pythonic的程式碼複用工具,適用於所有有始必有終模式的程式碼複用 ·減少錯誤,降低編寫程式碼的認知資

解讀ASP.NET 5 & MVC6系列9日誌框架

框架介紹 在之前的.NET中,微軟還沒有提供過像樣的日誌框架,目前能用的一些框架比如Log4Net、NLog、CommonLogging使用起來多多少少都有些費勁,和java的SLF4J根本無法相比。但在新版的ASP.NET5中,可謂是牛氣沖天,微軟提供的Microsoft.Framework.Logging

ASP.NET AJAX入門系列9在母版頁使用UpdatePanel

{    switch (((Control)sender).ID)    {        case"IncrementButton":            this.Offset =this.Offset +1;            break;        case"DecrementButton

Quartz.Net系列十三DateBuilder的API詳解

1.DateOf、ToDayAt、TomorrowAt DateOf:指定年月日時分秒 public static DateTimeOffset DateOf(int hour, int minute, int second) { ValidateSe

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

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

HD-ACM算法專攻系列9——大菲波數

主函數 nbsp system.in -a index scanner can str () 題目描述: 源碼: 運用Java大數求解。 import java.math.BigInteger; import java.util.*; public cla

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)";