【轉載】ASP.Net請求處理機制初步探索之旅 - Part 3 管道

開篇:上一篇咱們瞭解了一個ASP.Net頁面請求的核心處理入口,它經歷了三個重要的入口,分別是:ISAPIRuntime.ProcessRequest()、HttpRuntime.ProcessRequest()以及HttpApplication.Init()。其中,在HttpApplication的Init()方法中觸發了請求處理管道事件的執行,本篇咱們就來看看所謂的請求處理管道。javascript

(1)Part 1:前奏html

(2)Part 2:核心java

(3)Part 3:管道程序員

(4)Part 4:WebForm頁面生命週期web

(5)Part 5:MVC頁面聲命週期瀏覽器

1、所謂「請求處理管道」

  HttpApplication對象是ASP.NET中處理請求的重要對象,可是,這種類型的對象實例不是由程序員來建立的,而是由ASP.NET幫助咱們建立的。爲了便於擴展處理工做,HttpApplication採用處理管道的方法進行處理,將處理的過程分爲多個步驟,每一個步驟經過事件的形式暴露給程序員,這些事件按照固定的處理順序依次觸發,程序員經過編寫事件處理方法就能夠自定義每個請求的擴展處理過程。緩存

①傳說中的19個事件

   對於HttpApplication來講,到ASP.NET 4.0版本,提供了19個重要的標準事件,以下圖所示:服務器

  在整個請求處理管道中,HttpContext上下文被依次傳輸到各個處理事件中,由不一樣的處理單元(HttpModule、HttpHandler、Page等)進行處理。從這裏能夠看出,ASP.NET請求處理管道就像是一個大型的AOP框架。session

②HttpModule與HttpHandler

  在進一步深刻了解以前,讓咱們先來了解一下什麼是HttpModuleHttpHandlers。他們幫助咱們在ASP.NET頁面處理過程的先後注入自定義的邏輯處理。他們之間主要的差異在於:app

  • 若是你想要注入的邏輯是基於像'.aspx','.html'這樣的擴展文件,那麼你可使用HttpHandler。換句話說,HttpHandler是一個基於處理器的擴展。
  • HttpHandler總結:在ASP.NET WebForm中,不管是通常處理程序仍是WebPage都實現了IHttpHandler接口,而ASP.NET MVC中也有MvcHandler實現了IHttpHandler接口;
  • 若是你想要在ASP.NET管道事件中注入邏輯,那麼你可使用HttpModule。也能夠說,HttpModule是一個基於處理器的事件。
  • HttpModule總結:剛剛咱們說到ASP.NET請求處理管道就像是一個大型的AOP框架,所以咱們能夠藉助HttpModule自定義地註冊或移除一些事件邏輯,以完成咱們想要的效果。ASP.NET默認實現了針對WebForm和MVC的HttpModule,像ASP.NET MVC中默認使用的是UrlRoutingModule。具體實現方式是:經過改寫Global文件或自定義一個實現IHttpModule接口的類並在Web.config中進行註冊。
  • 複製代碼
    <?xml version="1.0"?>
    <configuration>
        <system.web>
            <httpModules>
                <add name="myHttpModule" type="FirstModule"/>
            </httpModules>
        </system.web>
    </configuration>
    複製代碼
    複製代碼
        public class FirstModule : IHttpModule
        {
            public void Dispose()
            {
                throw new NotImplementedException();
            }
    
            public void Init(HttpApplication context)
            {
                context.BeginRequest += new EventHandler(context_BeginRequest);
            }
    
            void context_BeginRequest(object sender, EventArgs e)
            {
                HttpApplication application = sender as HttpApplication;
                application.Context.Response.Write("第三方過濾器:哇哈哈!");
            }
        }
    複製代碼

③19個事件中咱們能夠作些什麼?

  一個十分有價值的問題就是在什麼事件中咱們又能夠作些什麼?下表就展現了這個問題的答案:

Section Event Description
HttpModule BeginRequest 此事件標誌着一個新的請求,它保證在每一個請求中都會被觸發。
HttpModule AuthenticateRequest 此事件標誌ASP.NET運行時準備驗證用戶。任何身份驗證代碼均可以在此注入。
HttpModule AuthorizeRequest 此事件標誌ASP.NET運行時準備受權用戶。任何受權代碼均可以在此注入。
HttpModule ResolveRequest 在ASP.NET中咱們一般使用OutputCache指令作緩存。在這個事件中,ASP.NET運行時肯定是否可以從緩存中加載頁面,而不是從頭開始生成。任何緩存的具體活動能夠被注入這裏。
HttpModule AcquireRequestState 此事件標誌着ASP.NET運行時準備得到Session會話變量。能夠對Session變量作任何你想要作的處理。
HttpModule PreRequestHandlerExecute 剛好在ASP.NET 開始執行事件處理程序前發生。能夠預處理你想作的事。
HttpHandler ProcessRequest HttpHandler邏輯被執行。在這個部分咱們將爲每一個頁面擴展寫須要的邏輯。
Page Init 此事件發生在ASP.NET頁面且能夠用來: 
一、動態地建立控件,若是你必定要在運行時建立控件; 
二、任何初始化設置 
三、母版頁及其設置 
在這部分中咱們沒有得到viewstate、postedvalues及已經初始化的控件。
Page Load 在這部分ASP.NET控件徹底被加載且在這裏你能夠寫UI操做邏輯或任何其餘邏輯。NOTE:這個事件也是咱們最多見且最經常使用的一個事件。
Page Validate 若是在頁面上你有驗證器,你一樣想在這裏作一下檢查。
Page Render 是時候將輸出發送到瀏覽器。若是你想對最終的HTML作些修改,你能夠在這裏輸入你的HTML邏輯。
Page Unload 頁面對象從內存中卸載。
HttpModule PostRequestHandlerExecute 能夠注入任何你想要的邏輯,在處理程序執行以後。
HttpModule ReleaseRequestState 若是你想要保存對某些狀態變量的更改,例如:Session變量的值。
HttpModule UpdateRequestCache 在結束以前,你是否想要更新你的緩存。
HttpModule EndRequest 這是將輸出發送到客戶端瀏覽器以前的最後一個階段。

④自定義處理邏輯

  咱們能夠經過一個示例程序代碼來展現以上介紹的那些事件是怎樣被最終觸發的。在這個示例中,咱們已經建立了一個HttpModule和HttpHandler,而且也在全部的事件中經過添加自定義邏輯代碼展現了一個簡單的響應。

  下面是HttpModule類,它跟蹤了全部的事件並將其添加到了一個全局的集合中。

public class clsHttpModule : IHttpModule
{
    ...... 
    void OnUpdateRequestCache(object sender, EventArgs a)
    {
        objArrayList.Add("httpModule:OnUpdateRequestCache");
    }
    void OnReleaseRequestState(object sender, EventArgs a)
    {
        objArrayList.Add("httpModule:OnReleaseRequestState");
    }
    void OnPostRequestHandlerExecute(object sender, EventArgs a)
    {     objArrayList.Add("httpModule:OnPostRequestHandlerExecute");
    }
    void OnPreRequestHandlerExecute(object sender, EventArgs a)
    {  objArrayList.Add("httpModule:OnPreRequestHandlerExecute");
    }
    void OnAcquireRequestState(object sender, EventArgs a)
    {
        objArrayList.Add("httpModule:OnAcquireRequestState");
    }
    void OnResolveRequestCache(object sender, EventArgs a)
    {
        objArrayList.Add("httpModule:OnResolveRequestCache");
    }
    void OnAuthorization(object sender, EventArgs a)
    {
        objArrayList.Add("httpModule:OnAuthorization");
    }
    void OnAuthentication(object sender, EventArgs a)
    {
        objArrayList.Add("httpModule:AuthenticateRequest");
    }
    void OnBeginrequest(object sender, EventArgs a)
    {
        objArrayList.Add("httpModule:BeginRequest");
    }
    void OnEndRequest(object sender, EventArgs a)
    {
        objArrayList.Add("httpModule:EndRequest");
        objArrayList.Add("<hr>");
        foreach (string str in objArrayList)
        {
            httpApp.Context.Response.Write(str + "<br>") ;
        }
    } 
}            
View Code

  下面是HttpHandler類的一個代碼片斷,它跟蹤了ProcessRequest事件。

public class clsHttpHandler : IHttpHandler
{
    public void ProcessRequest(HttpContext context)
    {             clsHttpModule.objArrayList.Add("HttpHandler:ProcessRequest");
        context.Response.Redirect("Default.aspx");
    }
}
View Code

   同上,咱們也能夠跟蹤來自ASP.NET Page頁面的全部事件。

public partial class _Default : System.Web.UI.Page 
{
    protected void Page_init(object sender, EventArgs e)
    {
        clsHttpModule.objArrayList.Add("Page:Init");
    }

    protected void Page_Load(object sender, EventArgs e)
    {
        clsHttpModule.objArrayList.Add("Page:Load");
    }

    public override void Validate() 
    {
        clsHttpModule.objArrayList.Add("Page:Validate");
    }

    protected void Button1_Click(object sender, EventArgs e)
    {
        clsHttpModule.objArrayList.Add("Page:Event");
    }

    protected override void Render(HtmlTextWriter output) 
    {
        clsHttpModule.objArrayList.Add("Page:Render");
        base.Render(output);
    }

    protected void Page_Unload(object sender, EventArgs e)
    {
        clsHttpModule.objArrayList.Add("Page:UnLoad");
    }
}    
View Code

  下圖則顯示了上面咱們所討論的全部事件的執行順序:

2、WebForm經歷的管道事件概覽

  在ASP.NET WebForm應用中,其在請求處理管道中主要經歷了三個重要階段:

①在第八個事件中建立Page類對象並轉換爲IHttpHandler接口

  從上面的介紹中能夠看到,第八個事件是:PostMapRequestHandler。在這個事件中,對於訪問不一樣的資源類型,ASP.NET具備不一樣的HttpHandler對其進程處理。對於每一個請求,ASP.NET會經過擴展名選擇匹配相應的HttpHandler類型,成功匹配後,該實現被觸發。所以,若是請求的擴展名是.aspx,便會生成Page類對象,而Page類對象是實現了IHttpHandler接口的。

②在第九個到第十事件之間根據SessionId獲取Session

sessionId

  從上面的介紹中能夠看到,第九到第十個事件是:AcquireRequestStatePostAcquireRequestState。這期間首先會接收到瀏覽器發過來的SessionId,而後先會將IHttpHandler接口嘗試轉換爲IRequiresSessionState接口,若是轉換成功,ASP.NET會根據這個SessionId到服務器的Session池中去查找所對應的Session對象,並將這個Session對象賦值到HttpContext對象的Session屬性。若是嘗試轉換爲IRequiresSessionState接口不成功,則不加載Session。

sessionstate

③在第十一個事件與第十二個事件之間執行頁面生命週期

  從上面的介紹中能夠看到,第十一和第十二個事件是:PreRequestHandlerExecutePostRequestHandlerExecute。在這兩個事件之間,ASP.NET最終經過請求資源類型相對應的HttpHandler實現對請求的處理,其實現方式是調用在第八個事件建立的頁面對象的ProcessRequest方法。

  在FrameworkInitialize()這個方法內部就開始打造WebForm的頁面控件樹,在其中調用了ProcessRequestMain方法,在這個方法裏面就執行了整個ASP.NET WebFom頁面生命週期。至於WebForm頁面生命週期的細節,咱們在本系列後續的Part 4再來細細研究。

當咱們直接使用*.ashx頁面的時候,它的ProcessRequest()方法就直接調用了一個FrameworkInitialize(),並最終生成響應報文,發送回客戶端。

當咱們在使用 *.aspx頁面的時候,它繼承自Page類,而Page類實現了IHttpHandler接口,而後了調用Page類的ProcessRequest()方法,其中會構建頁面控件樹,而後一個一個地去呈現。

3、ASP.NET MVC經歷的管道事件概覽

  在ASP.NET MVC中,最核心的當屬「路由系統」,而路由系統的核心則源於一個強大的System.Web.Routing.dll組件。

  在這個System.Web.Routing.dll中,有一個最重要的類叫作UrlRoutingModule,它是一個實現了IHttpModule接口的類,在請求處理管道中專門針對ASP.NET MVC請求進行處理。首先,咱們要了解一下UrlRoutingModule是如何起做用的。

  (1)IIS網站的配置能夠分爲兩個塊:全局 Web.config 和本站 Web.config。Asp.Net Routing屬於全局性的,因此它配置在全局Web.Config 中,咱們能夠在以下路徑中找到:「$\Windows\Microsoft.NET\Framework\版本號\Config\Web.config「

複製代碼
 <?xml version="1.0" encoding="utf-8"?>
 <!-- the root web configuration file -->
 <configuration>
     <system.web>
         <httpModules>
             <add name="UrlRoutingModule-4.0" type="System.Web.Routing.UrlRoutingModule" />
         </httpModules>
    </system.web>
 </configuration>
複製代碼

  (2)經過在全局Web.Config中註冊 System.Web.Routing.UrlRoutingModule,IIS請求處理管道接到請求後,就會加載 UrlRoutingModule類型的Init()方法。其源碼入下:

public class UrlRoutingModule : IHttpModule
{
    // Fields
    private static readonly object _contextKey = new object();
    private static readonly object _requestDataKey = new object();
    private RouteCollection _routeCollection;
 
    // Methods
    protected virtual void Dispose()
    {
    }
 
    protected virtual void Init(HttpApplication application)
    {
        if (application.Context.Items[_contextKey] == null)
        {
            application.Context.Items[_contextKey] = _contextKey;
            // 這裏爲UrlRoutingModule 註冊了一個PostResolveRequestCache 事件處理方法:OnApplicationPostResolveRequestCache().
            application.PostResolveRequestCache += new EventHandler(this.OnApplicationPostResolveRequestCache);
        }
    }
 
    private void OnApplicationPostResolveRequestCache(object sender, EventArgs e)
    {
        HttpContextBase context = new HttpContextWrapper(((HttpApplication) sender).Context);
        this.PostResolveRequestCache(context);
    }
 
    [Obsolete("This method is obsolete. Override the Init method to use the PostMapRequestHandler event.")]
    public virtual void PostMapRequestHandler(HttpContextBase context)
    {
    }
 
    public virtual void PostResolveRequestCache(HttpContextBase context)
    {
        RouteData routeData = this.RouteCollection.GetRouteData(context);
        if (routeData != null)
        {
            IRouteHandler routeHandler = routeData.RouteHandler;
            if (routeHandler == null)
            {
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, SR.GetString("UrlRoutingModule_NoRouteHandler"), new object[0]));
            }
            if (!(routeHandler is StopRoutingHandler))
            {
                RequestContext requestContext = new RequestContext(context, routeData);
                context.Request.RequestContext = requestContext;
                IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);
                if (httpHandler == null)
                {
                    throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, SR.GetString("UrlRoutingModule_NoHttpHandler"), new object[] { routeHandler.GetType() }));
                }
                if (httpHandler is UrlAuthFailureHandler)
                {
                    if (!FormsAuthenticationModule.FormsAuthRequired)
                    {
                        throw new HttpException(0x191, SR.GetString("Assess_Denied_Description3"));
                    }
                    UrlAuthorizationModule.ReportUrlAuthorizationFailure(HttpContext.Current, this);
                }
                else
                {
                    context.RemapHandler(httpHandler);
                }
            }
        }
    }
 
    void IHttpModule.Dispose()
    {
        this.Dispose();
    }
 
    void IHttpModule.Init(HttpApplication application)
    {
        this.Init(application);
    }
 
    // Properties
    public RouteCollection RouteCollection
    {
        get
        {
            if (this._routeCollection == null)
            {
                this._routeCollection = RouteTable.Routes;
            }
            return this._routeCollection;
        }
        set
        {
            this._routeCollection = value;
        }
    }
}
View Code

  從源碼中能夠看出,在UrlRoutingModule中爲請求處理管道中的第七個事件PostResolveRequestCache註冊了一個事件處理方法:OnApplicationPostResolveRequestCache。從這裏能夠看出:ASP.NET MVC的入口在UrlRoutingModule,即訂閱了HttpApplication的第7個管道事件PostResolveRequestCahce。換句話說,是在HtttpApplication的第7個管道事件處對請求進行了攔截。

  如今咱們將ASP.NET MVC的請求處理分爲兩個重要階段來看看:

①在第七個事件中建立實現了IHttpHandler接口的MvcHandler

  當請求到達UrlRoutingModule的時候,UrlRoutingModule取出請求中的Controller、Action等RouteData信息,與路由表中的全部規則進行匹配,若匹配,把請求交給IRouteHandler,即MVCRouteHandler。咱們能夠看下UrlRoutingModule的源碼來看看,如下是幾句核心的代碼:

複製代碼
public virtual void PostResolveRequestCache(HttpContextBase context)
{
    // 經過RouteCollection的靜態方法GetRouteData獲取到封裝路由信息的RouteData實例
    RouteData routeData = this.RouteCollection.GetRouteData(context);
    if (routeData != null)
    {
        // 再從RouteData中獲取MVCRouteHandler
        IRouteHandler routeHandler = routeData.RouteHandler;
        ......
        if (!(routeHandler is StopRoutingHandler))
        {
            ......
            // 調用 IRouteHandler.GetHttpHandler(),獲取的IHttpHandler 類型實例,它是由 IRouteHandler.GetHttpHandler獲取的,這個得去MVC的源碼裏看
            IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);
            ......
            // 合適條件下,把以前將獲取的IHttpHandler 類型實例 映射到IIS HTTP處理管道中
            context.RemapHandler(httpHandler);
        }
    }
}
複製代碼

  MVCRouteHandler的做用是用來生成實現IHttpHandler接口的MvcHandler

複製代碼
namespace System.Web.Routing
{  
    public interface IRouteHandler
    {       
        IHttpHandler GetHttpHandler(RequestContext requestContext);
    }
}
複製代碼

  那麼,MvcRouteHandler從何而來呢?衆所周知,ASP.NET MVC項目啓動是從Global中的Application_Start()方法開始的,那就去看看它:

複製代碼
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            ......
            //這裏要註冊路由了
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
        }
    }

    public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
            // 玄機就在這了,這個MapRoute位於System.Web.Mvc.RouteCollectionExtensions
            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
            );
        }
    }    
複製代碼

   因而,咱們再去看看Route類的這個MapRoute()方法的源碼:

複製代碼
        public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces) 
        {
            ......
       // 終於等到你,還好我沒放棄。 Route route
= new Route(url, new MvcRouteHandler()) { Defaults = new RouteValueDictionary(defaults), Constraints = new RouteValueDictionary(constraints), DataTokens = new RouteValueDictionary() }; ...... return route; }
複製代碼

  從上面的源碼能夠得知爲何能夠從RouteData中拿到MvcRouteHadnler?由於當咱們在HttpApplication的第一個管道事件,使用MapRoute()方法註冊路由的時候,已經經過Route類的構造函數把MvcRouteHandler注入到路由中了。

  剛剛咱們知道MvcRouteHandler是用來生成實現IHttpHandler接口的MvcHandler,那麼咱們繼續從UrlRoutingModule的源碼能夠看到,經過HttpHandler的GetHttpHandler()方法獲取到了實現了IHttpHandler接口的MvcHandler

            // 調用 IRouteHandler.GetHttpHandler(),獲取的IHttpHandler 類型實例,它是由 IRouteHandler.GetHttpHandler獲取的,這個得去MVC的源碼裏看
            IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);
            ......
            // 合適條件下,把以前將獲取的IHttpHandler 類型實例 映射到IIS HTTP處理管道中
            context.RemapHandler(httpHandler);

  因而,咱們進入ASP.NET MVC的源碼看看MvcHandlerd的實現,這裏我看的是MVC 4.0的源碼:

        protected virtual IHttpHandler GetHttpHandler(RequestContext requestContext)
        {
          requestContext.HttpContext.SetSessionStateBehavior(GetSessionStateBehavior(requestContext));
            return new MvcHandler(requestContext);
        }

  能夠看出,在這裏建立了MvcHandler實例。換句話說,MvcRouteHandler把請求交給了MvcHandler去作請求處理管道中後續事件的處理操做了。

②在第十一個事件與第十二個事件之間調用MvcHandler的ProcessRequest()方法

  (1)在WebForm中,此階段會調用Page類對象的ProcessRequest()方法。在ASP.NET MVC中,會調用MvcHandler的ProcessRequest()方法,此方法會激活具體請求的Controller類對象,觸發Action方法,返回ActionResult實例。

  (2)若是ActionResult是非ViewResult,好比JsonResult, ContentResult,這些內容將直接被輸送到Response響應流中,顯示給客戶端;若是是ViewResult,就會進入下一個渲染視圖環節。

  (3)在渲染視圖環節,ViewEngine找到須要被渲染的視圖,View被加載成WebViewPage<TModel>類型,並渲染生成Html,最終返回Html。

TIP:有關此ProcessRequest()處理環節的詳細內容,請等待本系列Part 5中的介紹。

參考資料

致謝:本文參閱了大量園友的文章,也直接使用了大量園友製做的圖,在此對如下各位園友表示感謝。

(1)Darren Ji,《ASP.NET MVC請求處理管道聲明週期的19個關鍵環節》:http://www.cnblogs.com/darrenji/p/3795661.html

(2)木宛城主,《ASP.NET那點鮮爲人知的事兒》:http://www.cnblogs.com/OceanEyes/archive/2012/08/13/aspnetEssential-1.html

(3)Tony He,《ASP.NET請求處理機制》:http://www.cnblogs.com/cilence/archive/2012/05/28/2520712.html

(4)兩會的博客,《IIS是怎樣處理ASP.NET請求的》:http://www.cnblogs.com/hkncd/archive/2012/03/23/2413917.html

(5)wjn2000,《ASP.NET請求處理過程(IIS6)》:http://www.cnblogs.com/wjn2010/archive/2011/04/21/2024341.html

(6)農村出來的大學生,《ASP.NET網頁請求處理全過程(反編譯)》:http://www.cnblogs.com/poorpan/archive/2011/09/25/2190308.html

(7)碧血軒,《ASP.NET頁面生命週期》,http://www.cnblogs.com/xhwy/archive/2012/05/20/2510178.html

(8)吳秦,《ASP.NET 應用程序與頁面生命週期(意譯)》,http://www.cnblogs.com/skynet/archive/2010/04/29/1724020.html

(9)我本身,《【翻譯】ASP.NET應用程序和頁面聲明週期》:http://www.cnblogs.com/edisonchou/p/3958305.html

(10)Shivprasad koirala,《ASP.NET Application and Page Life Cycle》:http://www.codeproject.com/Articles/73728/ASP-NET-Application-and-Page-Life-Cycle

(11)學而不思則罔,《ASP.NET Routing與MVC之一:請求如何到達MVC》:http://www.cnblogs.com/acejason/p/3869731.html

(12)初心不可忘,《綜述:ASP.NET MVC請求處理管道》:http://www.cnblogs.com/luguobin/archive/2013/03/15/2962458.html

 

相關文章
相關標籤/搜索