開篇:上一篇我們瞭解了一個ASP.Net頁面請求的核心處理入口,它經歷了三個重要的入口,分別是:ISAPIRuntime.ProcessRequest()、HttpRuntime.ProcessRequest()以及HttpApplication.Init()。其中,在HttpApplication ...
開篇:上一篇我們瞭解了一個ASP.Net頁面請求的核心處理入口,它經歷了三個重要的入口,分別是:ISAPIRuntime.ProcessRequest()、HttpRuntime.ProcessRequest()以及HttpApplication.Init()。其中,在HttpApplication的Init()方法中觸發了請求處理管道事件的執行,本篇我們就來看看所謂的請求處理管道。
(1)Part 1:前奏
(2)Part 2:核心
(3)Part 3:管道
(4)Part 4:WebForm頁面生命周期
(5)Part 5:MVC頁面聲命周期
一、所謂“請求處理管道”
HttpApplication對象是ASP.NET中處理請求的重要對象,但是,這種類型的對象實例不是由程式員來創建的,而是由ASP.NET幫助我們創建的。為了便於擴展處理工作,HttpApplication採用處理管道的方法進行處理,將處理的過程分為多個步驟,每個步驟通過事件的形式暴露給程式員,這些事件按照固定的處理順序依次觸發,程式員通過編寫事件處理方法就可以自定義每一個請求的擴展處理過程。
①傳說中的19個事件
對於HttpApplication來說,到ASP.NET 4.0版本,提供了19個重要的標準事件,如下圖所示:
在整個請求處理管道中,HttpContext上下文被依次傳輸到各個處理事件中,由不同的處理單元(HttpModule、HttpHandler、Page等)進行處理。從這裡可以看出,ASP.NET請求處理管道就像是一個大型的AOP框架。
②HttpModule與HttpHandler
在進一步深入瞭解之前,讓我們先來瞭解一下什麼是HttpModule和HttpHandlers。他們幫助我們在ASP.NET頁面處理過程的前後註入自定義的邏輯處理。他們之間主要的差別在於:
- 如果你想要註入的邏輯是基於像'.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頁面且可以用來: 1、動態地創建控制項,如果你一定要在運行時創建控制項; 2、任何初始化設置 3、母版頁及其設置 在這部分中我們沒有獲得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
下圖則顯示了上面我們所討論的所有事件的執行順序:
二、WebForm經歷的管道事件概覽
在ASP.NET WebForm應用中,其在請求處理管道中主要經歷了三個重要階段:
①在第八個事件中創建Page類對象並轉換為IHttpHandler介面
從上面的介紹中可以看到,第八個事件是:PostMapRequestHandler。在這個事件中,對於訪問不同的資源類型,ASP.NET具有不同的HttpHandler對其進程處理。對於每個請求,ASP.NET會通過擴展名選擇匹配相應的HttpHandler類型,成功匹配後,該實現被觸發。因此,如果請求的擴展名是.aspx,便會生成Page類對象,而Page類對象是實現了IHttpHandler介面的。
②在第九個到第十事件之間根據SessionId獲取Session
從上面的介紹中可以看到,第九到第十個事件是:AcquireRequestState,PostAcquireRequestState。這期間首先會接收到瀏覽器發過來的SessionId,然後先會將IHttpHandler介面嘗試轉換為IRequiresSessionState介面,如果轉換成功,ASP.NET會根據這個SessionId到伺服器的Session池中去查找所對應的Session對象,並將這個Session對象賦值到HttpContext對象的Session屬性。如果嘗試轉換為IRequiresSessionState介面不成功,則不載入Session。
③在第十一個事件與第十二個事件之間執行頁面生命周期
從上面的介紹中可以看到,第十一和第十二個事件是:PreRequestHandlerExecute,PostRequestHandlerExecute。在這兩個事件之間,ASP.NET最終通過請求資源類型相對應的HttpHandler實現對請求的處理,其實現方式是調用在第八個事件創建的頁面對象的ProcessRequest方法。
在FrameworkInitialize()這個方法內部就開始打造WebForm的頁面控制項樹,在其中調用了ProcessRequestMain方法,在這個方法裡面就執行了整個ASP.NET WebFom頁面生命周期。至於WebForm頁面生命周期的細節,我們在本系列後續的Part 4再來細細研究。
當我們直接使用*.ashx頁面的時候,它的ProcessRequest()方法就直接調用了一個FrameworkInitialize(),並最終生成響應報文,發送回客戶端。
當我們在使用*.aspx頁面的時候,它繼承自Page類,而Page類實現了IHttpHandler介面,然後了調用Page類的ProcessRequest()方法,其中會構建頁面控制項樹,然後一個一個地去呈現。
三、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
作者:周旭龍
出處:http://edisonchou.cnblogs.com/