1. 程式人生 > >Asp.Net底層解析(四)——應用程式生命週期與HttpModule

Asp.Net底層解析(四)——應用程式生命週期與HttpModule

    前言:一般ASP.NET開發者對頁面生命週期(PageLife Cycle)是比較熟悉的,在開發ASP.NET應用程式中經常需要從頁面週期的角度去思考問題。實際上在頁面生命週期的背後,還存在著一個不太為人所熟知的更廣義的週期——應用程式生命週期(Application Life Cycle)。本篇文章將詳細對其進行說明(IIS6.0)。

    一,應用程式生命週期綜述

    所謂應用程式生命週期指的是:從客戶端向Web伺服器發出資源請求開始,到Web伺服器反饋結果返回給客戶端的整個在伺服器端執行的過程。

    ASP.NET是Web 伺服器下的 ISAPI 擴充套件,Web 伺服器接收到請求時,會對所請求的檔案的副檔名進行檢查,確定應由哪個 ISAPI 擴充套件處理該請求,然後將該請求傳遞給合適的 ISAPI 擴充套件。ASP.NET處理已對映到其上的副檔名,如.aspx、.ascx、.ashx和.asmx。因此,aspx頁面的請求是首先提交給IIS,然後由IIS通過副檔名查詢ISAPI最終交給ASP.NET(aspnet_isapi.dll程序)進行處理,再經過一系列規範化的步驟最終生成對應的html編碼返回給瀏覽器。綜上可知,ASP.NET的頁面生命週期是應用程式生命週期的一部分,而且僅僅當請求檔案為aspx頁面時才能觸發該頁面的生命週期。

    下面分階段描述ASP.NET 應用程式生命週期(這裡假設請求資原始檔型別為aspx頁面,下面的內容大部分是從bitfan前輩的部落格文章和MSDN中複製而來):

    1,  使用者請求aspx頁面:
        A, 當此請求到達Web伺服器時,由HTTP.SYS(Windows進行http協議資訊通訊的核心元件)負責接收,根據ISAPI擴充套件設定,將其傳遞給此ASP.NET應用程式所對應的應用程式池,在此應用程式池中執行的工作者程序負責處理請求。
       B, 工作者程序接收到這個請求之後,裝載專用於處理ASP.NET頁面的一個ISAPI擴充套件“aspnet_isapi.dll”,並將HTTP請求傳給它。
       C, 工作者程序載入完aspnet_isapi.dll後,由aspnet_isapi.dll負責載入ASP.NET應用程式的執行環境――CLR
       D, 工作者程序工作於非託管環境(指Windows作業系統本身)之中,而.NET中的物件則工作於託管環境(指CLR)之中,aspnet_isapi.dll起到了一個溝通兩者的橋樑作用,將收到的HTTP請求(由非託管環境傳來)轉發給相應.NET物件(處於託管環境中)處理。
   2,  ASP.NET 應用程式(通常意義上的網站)接收第一個請求:
       A, 說明:同一個ASP.NET應用程式只在第一次請求時才會執行該階段。
       B, 載入

CLR之後,由System.Web.Hosting.ApplicationManager類負責建立一個應用程式域。
       C, 每個ASP.NET應用程式都運行於自己的應用程式域中,且對應著一個ApplicationManager類的例項物件,該物件負責對該應用程式進行管理,包括:啟用和初始化 ASP.NET 應用程式、管理應用程式生存期和在應用程式中註冊的物件的生存期、公開宿主環境使用的物件以處理 ASP.NET 應用程式請求、提供任意給定時刻運行於宿主程序中的應用程式的列表。
       D, 接著在應用程式域中建立一個System.Web.Hosting.HostingEnvironment例項物件,該物件提供對有關應用程式的資訊(如儲存該應用程式在伺服器中的目錄等等資訊)的訪問。
       E,關係圖表示為:

   3,  為每個請求建立 ASP.NET 核心物件:
        A, 當應用程式域建立完成之後,一個ISAPIRuntime物件被建立,並自動呼叫它的ProcessRequest()方法。在此方法中,ISAPIRuntime物件根據傳入的HTTP請求建立一個HttpWorkerRequest物件,此物件以面向物件的方式包裝了HTTP請求的各種資訊(這就是說,原始的HTTP請求資訊被封裝為HttpWorkerRequest物件)
       B, 然後,呼叫ISAPIRuntime物件的StartProcessing()方法啟動整個HTTP請求處理過程(此即“HTTP管線:HTTP Pipeline”),在這個處理過程的開端,一個HttpRuntime型別的物件被建立,前面建立好的HttpWorkerRequest物件作為方法引數被傳送給此HttpRuntime物件的ProcessRequest()方法。
       C, HttpRuntime類的ProcessRequest()方法根據HttpWorkerRequest物件中所提供的HTTP請求資訊,建立了一個HttpContext物件。HttpContext 類包含特定於當前應用程式請求的物件,如HttpRequest 和 HttpResponse 物件。
       D, HttpRequest 物件包含有關當前請求的資訊,包括 Cookie 和瀏覽器資訊。HttpResponse 物件包含傳送到客戶端的響應,包括所有呈現的輸出和 Cookie。
       E,  在整個HTTP處理過程中,HttpContext物件都是可以訪問的,也就是說這個階段之後的應用程式生命週期內所有使用的HttpContext實際上都是這個HttpContext物件的引用,如aspx頁面後臺常用的Context屬性。
   4,  分配一個HttpApplication 物件用於處理請求:
       A, HttpRuntime類的ProcessRequest()方法除了建立HttpContext物件之外,還完成了另一個很重要的工作——向HttpApplicationFactory類的一個例項申請分配一個HttpApplication物件用於管理整個HTTP請求處理管線中的各種事件。
       B, HttpApplicationFactory物件負責管理一個HttpApplication物件池,當有HTTP請求到來時,如果池中還有可用的 HttpApplication物件,就直接分配此物件用於處理HTTP請求,否則,建立一個新的HttpApplication物件。
       C, 如果ASP.NET應用程式無 Global.asax 檔案,使用原始的System.Web.HttpApplication類,如果ASP.NET應用程式中有使用在Global.asax中定義的繼承於System.Web.HttpApplication的類,作為建立HttpApplication物件的類。
    5,  HttpApplication物件啟動HTTP管線:
       A, HttpApplication物件負責裝配出整個“HTTP請求處理管線(HTTP Pipeline)”,可以將HttpContext看成是訂單(HttpRequest)與產品(HttpResponse)的打包,而將HTTP請求處理管線看做是產品生成流程。在這個流程中,各個模組將根據訂單(HttpRequest)提供的資訊,將特定的內容新增到產品(HttpResponse)中。
       B, HttpContext物件經過“生產流水線”的不同部分時,HttpApplication物件會先後激發出一連串的事件。一些特定的元件——HTTP模組(HTTP Module)可以響應這些事件,在此事件響應程式碼中可以對HttpContext物件進行“加工和處理”。從這個意義上說,HTTP模組可以看成是“生產流水線”中的工人。
       C, HTTP模組物件是在HttpApplication物件的InitModules()方法中被建立的,一般在HTTP模組物件的Init方法內對HttpApplication物件所激發的特定事件進行繫結,在這些事件中,各個HTTP模組可以對HttpContext物件進行操作,主要是對HttpResponse進行新增內容,也能夠直接將本次請求終止(這裡需要注意的是,這裡的終止並不會完全將HttpResponse返回空,而是跳過HttpApplication中的一些處理步驟,直接引發EndRequest事件)。
       D, ASP.NET提供了一些預定義的HTTP模組響應特定的事件,開發者也可以編寫自己的HTTP模組並將其插入到“HTTP請求處理管線”中(可通過Web.Config配置)。
       E,  在HttpApplication物件觸發PreRequestHandlerExecute事件之後,會根據請求呼叫合適的 IHttpHandler(或由IHttpHandlerFactory確定目標IHttpHandler)類的 ProcessRequest 方法,處理過程臨時交給已配置的HttpHandler。這裡只考慮aspx頁面請求的話,那麼本階段將會呼叫aspx頁面對應後臺的繼承於Page的類的ProcessRequest方法,從而觸發頁面生命週期開始執行。呼叫執行完成目標頁面的生命週期之後,得到頁面週期內呈現(Render)的html編碼將加入到HttpContext物件的HttpResponse中。
       F,  呼叫請求頁面的ProcessRequest 方法之後將會觸發HttpApplication的PostRequestHandlerExecute事件,處理過程又交還給HttpApplication,直到觸發PreSendRequestHenaders及PreSendRequestConternt。這時HttpApplication的任務算是完成了,HttpContext內的HttpResponse產品就是最終版了。

       G, HttpContext物件帶著最後的處理結果來到了“HTTP請求處理管線”的未端,其資訊被取出來,再次以aspnet_isapi.dll為橋樑傳送給工作者程序。工作者程序再將HTTP請求的處理結果轉給HTTP.SYS,由它負責將結果返回給瀏覽器。

    下面用流程圖來直觀地展示應用程式週期模型:


    以生產個性化麵包對上述過程打個簡單的比喻(僅參考,部分細節可能不符):

    1,  IIS就是整個麵包店,所有參與的成員都是在IIS的環境下進行的。
    2,  HTTP.SYS是前臺收費員,負責收集客戶對產品的需求資訊,包括請求資源的型別、地址等。
    3,  ISAPI負責聯絡各個麵包房,有些負責生產ASP.NET麵包,有些負責htm、shtml麵包,還有其他生成各種型別麵包的麵包房。
    4,  工作者程序是大堂小二,負責前臺收費員與麵包房內溝通,還負責將麵包房生成出來的麵包交給前臺收費員。
    5,  ASP.NET 應用程式是其中一種麵包房,它可以生產aspx、asmx等型別的麵包,由它根據前臺收費員(HTTP.SYS)接到的訂單進行生產。ASP.NET 麵包房可以有多個,它們之間是獨立運營的,如果其中某個ASP.NET麵包房從來沒有過客戶訂單,就沒有配備相應的麵點師傅了;只有當第一個訂單來了,麵包房才開始招師傅來幹活了。
    6,  ApplicationManager物件是ASP.NET麵包房的總負責人,他有對這個麵包房的總控制權,包括什麼時候開工,什麼時候乾脆關閉得了。
    7,  由ApplicationManager建立的應用程式域就是麵包房內部的工作場所,所有的人員及設定都在裡面。
    8,  HostingEnvironment物件是對這個麵包房的說明書,各個裝置在哪,怎麼用等等資訊,當然包括衛生合格證明啦,要不然就是違法生產了。
    9,  ISAPIRuntime物件及HttpRuntime物件就看做麵包房負責人的命令吧,ISAPIRuntime是初步動員令,而HttpRuntime更是正式命令。
    10, HttpWorkerRequest物件是負責人在釋出ISAPIRuntime命令的同時根據前臺收費員提供的訂單資訊而組織的使麵包房內部更容易理解的簡要需求說明書。
    11, HttpContext物件則是負責人在釋出HttpRuntime正式命令時,覺得HttpWorkerRequest還不太規範,因此將其修改為HttpContext物件。HttpContext是打包好的,裡面有根據HttpWorkerRequest修改後的更詳細的麵包需求說明書HttpRequest,還有一個包裝盒HttpResponse,裡面是“基本”是空的,要求在剩下的步驟裡將這個包裝盒按照說明書的要求製作出準確的麵包。
    12, HttpApplicationFactory是所有面包房的人力資源經理,由他分配由哪個師傅來接手面包房負責人的HttpContext打包。如果店裡有手藝的師傅不夠了,就再招一個;如果有師傅有閒餘時間了,那得了,您上吧。
    13, HttpApplication物件就是人力資源經理分配的師傅了,這裡稱為“主師傅”,他相當於一個小工頭。拿到HttpContext打包之後就開始在麵包房了吼嗓子了:“我開始工作了!有誰要在HttpResponse中新增內容的趕緊了,過了就不候著了啊!”、“我要載入初始化Session了,之後Session就可以隨便用了!”、“雛形師傅,該你大顯身手了!”等等。主師傅的每次吼嗓子都代表著HttpApplication的每個事件,所有主師傅是最累最辛苦的角色了,因為他要吼二十幾個嗓子,而且嗓門要大,好讓HTTP模組物件們都能聽見。
    14, HTTP模組物件的是裝點師,裝點師有多個,各個師傅的作用都不同;每個裝點師在HttpApplication主師傅每次吼嗓子的時候都可以往HttpResponse中新增內容,撒點糖、加點裝飾品什麼的。裝點師有一個很大的權利,他可以要求主師傅直接跳過一些步驟,直接吼“我的任務馬上就要結束了!”。這裡要注意的是,每次主師傅吼嗓子,裝點師往HttpResponse加東西的順序是固定的,誰來店裡來早(就是在Web.config中的配置順序),誰是前輩,誰就先操作;這一嗓子吼完之後,所有想要往HttpResponse加東西的的裝點師都操作過之後,主師傅才會吼下一嗓子;各個裝點師可以在主師傅吼完一嗓子之後不理他,等到他吼另一個嗓子的時候再行動,也就是說裝點師傅的行動完全是自主的。
    15, IHttpHandler物件是特殊的一類師傅,是專業人才,這裡稱為雛形師傅(而IHttpHandlerFactory則是這些專業人才的介紹人什麼的)。每個IHttpHandler物件只負責一種麵包,他會按照自己的理解將這個麵包做出來,然後放在主師傅的HttpResponse中。
    16, 這裡有個流程需要特別注意,在吼完“雛形師傅,該你大顯身手了!”之後,主師傅會將HttpContext打包臨時交給IHttpHandler物件,等IHttpHandler物件的工作完成之後再交還給主師傅,之後主師傅會吼“雛形師傅的工作完成了!”。
後續流程:等到主師傅吼完“麵包已經完成了,我要把麵包放到麵包房的產品架上了!”,這時主師傅的工作已經結束了,這時大堂小二會到麵包房的產品架去麵包交給前臺收費員,前臺拿到麵包後交個客戶。這樣,一次麵包的定製過程就完成了。
    17,  後續流程:等到主師傅吼完“麵包已經完成了,我要把麵包放到麵包房的產品架上了!”,這時主師傅的工作已經結束了,這時大堂小二會到麵包房的產品架去麵包交給前臺收費員,前臺拿到麵包後交個客戶。這樣,一次麵包的定製過程就完成了。

    下面的表格列舉了HttpApplication在啟動HTTP管線階段依次觸發的事件

BeginRequest

在 ASP.NET 響應請求時作為 HTTP 執行管線鏈中的第一個事件發生

AuthenticateRequest

當安全模組已建立使用者標識時發生。

PostAuthenticateRequest

當安全模組已建立使用者標識時發生。

AuthorizeRequest

當安全模組已驗證使用者授權時發生。

PostAuthorizeRequest

在當前請求的使用者已獲授權時發生。

ResolveRequestCache

在 ASP.NET 完成授權事件以使快取模組從快取中為請求提供服務後發生,從而繞過事件處理程式(例如某個頁或 XML Web services)的執行。

PostResolveRequestCache

在 ASP.NET 跳過當前事件處理程式的執行並允許快取模組滿足來自快取的請求時發生。

PostMapRequestHandler

在 ASP.NET 已將當前請求對映到相應的事件處理程式時發生。

AcquireRequestState

當 ASP.NET 獲取與當前請求關聯的當前狀態(如會話狀態)時發生。

PostAcquireRequestState

在已獲得與當前請求關聯的請求狀態(例如會話狀態)時發生。

PreRequestHandlerExecute

恰好在 ASP.NET 開始執行事件處理程式(例如,某頁或某個 XML Web services)前發生。

PostRequestHandlerExecute

在 ASP.NET 事件處理程式(例如,某頁或某個 XML Web service)執行完畢時發生。

ReleaseRequestState

在 ASP.NET 執行完所有請求事件處理程式後發生。 該事件將使狀態模組儲存當前狀態資料。

PostReleaseRequestState

在 ASP.NET 已完成所有請求事件處理程式的執行並且請求狀態資料已儲存時發生。

UpdateRequestCache

當 ASP.NET 執行完事件處理程式以使快取模組儲存將用於從快取為後續請求提供服務的響應時發生。

PostUpdateRequestCache

在 ASP.NET 完成快取模組的更新並存儲了用於從快取中為後續請求提供服務的響應後,發生此事件。

EndRequest

在 ASP.NET 響應請求時作為 HTTP 執行管線鏈中的最後一個事件發生。

PreSendRequestHeaders

恰好在 ASP.NET 向客戶端傳送 HTTP 標頭之前發生。

PreSendRequestContent

恰好在 ASP.NET 向客戶端傳送內容之前發生。

二,HttpModule程式設計

HTTP模組(HttpModule)在應用程式生命週期中的作用在於HttpApplication物件啟動HTTP管線階段,這時所有的HttpModule都可以進行以下操作:

1,  在HttpApplication的各個事件中往HttpResponse新增內容。
2,  當HttpApplication執行出現異常時,執行異常處理機制。
3,  直接終止本次請求,這裡需要注意的是,這裡的終止並不會完全將HttpResponse返回空,而是跳過HttpApplication中的一些處理步驟,直接引發EndRequest事件,實際上是終結本次HTTP管線。

    利用操作1,我們可以很自由地“影響”最終生成的請求結果,包括實現很猥瑣的功能,比如在頁面正文之前或之後新增小廣告等等。操作2功能很明確,當出現異常時,可以收集異常資訊記錄錯誤日誌等等。操作3也很好理解,比如今天某個頁面我不想對外公開了,直接新增一個HttpModule,如果有訪問該頁面請求,則直接終止掉就行了;明天又要恢復公開了,那好,把之前那個HttpModule配置給刪除就行了。

    下面給出一下實現操作1的簡單示例,新建一個Web應用程式,新增一個類庫,命名為HttpModules,並且為Web應用程式新增對該類庫的引用,解決方案截圖如下:


    HttpModules類庫新增對System.Web程式集的引用,該類庫用於編寫各個測試用的HttpModule。這裡先編寫FullEvents.cs,用於測試Application的所有事件,該HttpModule會在Application的每個事件中都往HttpResponse新增文字(每次新增的文字都會記錄本次Application的事件),FullEvents.cs程式碼如下:

using System;
using System.Web;
namespace HttpModules
{
    public class FullEvents : IHttpModule
    {
        public void Dispose()
        { }  //這裡沒有什麼要釋放的資源,所以這裡為空
        public void Init(HttpApplication application)
        {
            application.BeginRequest += (sender, e) => { application.Context.Response.Write("Application_BeginRequest<br/>"); };
            application.AuthenticateRequest += (sender, e) => { application.Context.Response.Write("Application_AuthenticateRequest<br/>"); };
            application.PostAuthenticateRequest += (sender, e) => { application.Context.Response.Write("Application_PostAuthenticateRequest<br/>"); };
            application.AuthorizeRequest += (sender, e) => { application.Context.Response.Write("Application_AuthorizeRequest<br/>"); };
            application.PostAuthorizeRequest += (sender, e) => { application.Context.Response.Write("Application_PostAuthorizeRequest<br/>"); };
            application.ResolveRequestCache += (sender, e) => { application.Context.Response.Write("Application_ResolveRequestCache<br/>"); };
            application.PostResolveRequestCache += (sender, e) => { application.Context.Response.Write("Application_PostResolveRequestCache<br/>"); };
            application.PostMapRequestHandler += (sender, e) => { application.Context.Response.Write("Application_PostMapRequestHandler<br/>"); };
            application.AcquireRequestState += (sender, e) => { application.Context.Response.Write("Application_AcquireRequestState<br/>"); };
            application.PostAcquireRequestState += (sender, e) => { application.Context.Response.Write("Application_PostAcquireRequestState<br/>"); };
            application.PreRequestHandlerExecute += (sender, e) => { application.Context.Response.Write("Application_PreRequestHandlerExecute<br/>"); };
            //這裡正在執行頁面生命週期
            application.PostRequestHandlerExecute += (sender, e) => { application.Context.Response.Write("Application_PostRequestHandlerExecute<br/>"); };
            application.ReleaseRequestState += (sender, e) => { application.Context.Response.Write("Application_ReleaseRequestState<br/>"); };
            application.PostReleaseRequestState += (sender, e) => { application.Context.Response.Write("Application_PostReleaseRequestState<br/>"); };
            application.UpdateRequestCache += (sender, e) => { application.Context.Response.Write("Application_UpdateRequestCache<br/>"); };
            application.PostUpdateRequestCache += (sender, e) => { application.Context.Response.Write("Application_PostUpdateRequestCache <br/>"); };
            application.EndRequest += (sender, e) => { application.Context.Response.Write("Application_EndRequest<br/>"); };
            application.PreSendRequestHeaders += (sender, e) => { application.Context.Response.Write("Application_PreSendRequestHeaders<br/>"); };
            application.PreSendRequestContent += (sender, e) => { application.Context.Response.Write("Application_PreSendRequestContent<br/>"); };
        }
    }
}

    要想在Web應用程式中使用該HttpModule就必須先對其進行註冊,可以在Web.config中註冊,程式碼如下:

<system.web>
    <compilation debug="true" targetFramework="4.0" />
  <httpModules>
    <!--只有測試完整Application事件時才使用下面的配置,否則請註釋下面的配置-->
    <add name="FullEvents" type="HttpModules.FullEvents,HttpModules"/>
  </httpModules>
</system.web>

    使用頁面AspxForFullEvents.aspx用於本次測試,該頁面的前臺程式碼為(僅含Form內標籤):

<form id="form1" runat="server">
這是頁面前臺文字<br/><asp:Label ID="Label1" runat="server" Text="Label"></asp:Label>
</form>

    後臺程式碼如下:

protected void Page_Load(object sender, EventArgs e)
{
    Label1.Text = "這是後臺伺服器控制元件生成文字";
}
    執行該頁面,得到結果如下:

    下面給出一個實現操作2的簡單示例,沿用測試1的解決方案,在類庫HttpModules中新增檔案ErrorHandler.cs用於在HttpModule中進行錯誤處理,ErrorHandler.cs程式碼如下:

using System;
using System.Web;
namespace HttpModules
{
    public class ErrorHandler : IHttpModule
    {
        public void Dispose()
        { }  //這裡沒有什麼要釋放的資源,所以這裡為空
        public void Init(HttpApplication application)
        {
            application.Error += new EventHandler(application_Error);
            application.BeginRequest += new EventHandler(application_BeginRequest);
        }
        private void application_BeginRequest(object sender, EventArgs e)
        {
            throw new Exception("這裡故意丟擲一個異常,用於測試!"); //Application在執行時可能會出現異常,但是這是小概率事件,所以這裡主動丟擲異常以便進行測試
        }
        private void application_Error(object sender, EventArgs e)
        {
            HttpApplication application = (HttpApplication)sender;
            //這裡可以設定斷點進行監視
            Exception LastError = application.Context.Server.GetLastError();    //這裡獲取異常資訊
            //這裡利用LastError,新增任意程式碼進行錯誤處理,一般操作是記錄錯誤日誌,以便後續查錯修正
        }
    }
}

    在Web應用程式中新增AspxForErrorHandler.aspx空頁面,且將Web.config中的配置內容修改為:

<httpModules>
  <!--只有測試完整Application事件時才使用下面的配置,否則請註釋下面的配置-->
  <!--<add name="FullEvents" type="HttpModules.FullEvents,HttpModules"/>-->
  <!--只有測試錯誤處理時才使用下面的配置,否則請註釋下面的配置-->
  <add name="ErrorHandler" type="HttpModules.ErrorHandler,HttpModules"/>
</httpModules>

    執行AspxForErrorHandler.aspx頁面(實際上執行AspxForFullEvents.aspx也是一樣的,因為HttpModule是針對每次請求都執行一遍的,不管這次請求的具體頁面是什麼),因為在Application的BeginRequest中主動丟擲一個異常,所以會觸發Application的Error事件,執行application_Error方法;後通過ExceptionLastError = application.Context.Server.GetLastError()可獲取出錯異常的Exception的例項,接下來就可以利用這個LastError進行錯誤日誌記錄了,當然你也可以做其他任何你需要的操作。


    下面給出一個實現操作3的簡單示例,同樣沿用測試1的解決方案,在類庫HttpModules中新增檔案ErrorHandler.cs用於在HttpModule中進行錯誤處理,ErrorHandler.cs程式碼如下:

using System;
using System.Web;
namespace HttpModules
{
    public class CompleteRequest : IHttpModule
    {
        public void Dispose() { }
        public void Init(HttpApplication application)
        {
            application.BeginRequest += new EventHandler(Application_BeginRequest);
            //下面兩個是從剩下的近20個事件中隨機選擇的兩個,可以發現這兩個事件都不會觸發,因為在BeginRequest中呼叫了CompleteRequest方法,導致直接跳到EndRequest事件。
            application.PostResolveRequestCache += (sender, e) => { application.Context.Response.Write("Application_PostResolveRequestCache<br/>"); };
            application.PostMapRequestHandler += (sender, e) => { application.Context.Response.Write("Application_PostMapRequestHandler<br/>"); };
            //EndRequest事件會被執行,PreSendRequestHeaders與PreSendRequestContent因為在EndRequest之後,因此也會被執行
            application.EndRequest += (sender, e) => { application.Context.Response.Write("Application_EndRequest<br/>"); };
            application.PreSendRequestHeaders += (sender, e) => { application.Context.Response.Write("Application_PreSendRequestHeaders<br/>"); };
            application.PreSendRequestContent += (sender, e) => { application.Context.Response.Write("Application_PreSendRequestContent<br/>"); };
        }
        public void Application_BeginRequest(object sender, EventArgs e)
        {
            HttpApplication application = sender as HttpApplication;
            application.CompleteRequest();
            application.Context.Response.Write("請求被終止<br/>");
        }
    }
}

    在Web應用程式中新增AspxForCompleteRequest.aspx空頁面(就算不是空頁面也不會顯示頁面內容,因為已經HttpModule中終止請求,不會再去執行頁面生命週期),且將Web.config中的配置內容修改為:

<httpModules>
  <!--只有測試“完整Application事件”時才使用下面的配置,否則請註釋下面的配置-->
  <!--<add name="FullEvents" type="HttpModules.FullEvents,HttpModules"/>-->
  <!--只有測試“錯誤處理”時才使用下面的配置,否則請註釋下面的配置-->
  <!--<add name="ErrorHandler" type="HttpModules.ErrorHandler,HttpModules"/>-->
  <!--只有測試“直接終止請求”時才使用下面的配置,否則請註釋下面的配置-->
  <add name="CompleteRequest" type="HttpModules.CompleteRequest,HttpModules"/>
</httpModules>

    執行AspxForCompleteRequest.aspx得到結果如下:


    可以看到,Application在BeginRequest中呼叫了CompleteRequest方法,終止了本次請求,跳過PostResolveRequestCache等等中間事件(包括執行目標頁面的生命週期)而直接跳到EndRequest事件。

    可以選擇在任意HttpModule的EndRequest之前的任意事件中呼叫CompleteRequest方法,這裡要注意的是:如果HttpModuleA在BeginRequest中呼叫了CompleteRequest方法,那麼HttpModuleB、HttpModuleC的BeginRequest事件都不會執行,而直接轉到依次執行三個HttpModule(A、B、C)的EndRequest事件。


    本篇部落格的測試程式碼下載地址:點選開啟連結