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事件。


    本篇博客的测试代码下载地址:点击打开链接
--------------------- 
作者:NewClass 
来源:CSDN 
原文:https://blog.csdn.net/mlcactus/article/details/8564372 
版权声明:本文为博主原创文章,转载请附上博文链接!

发布了8 篇原创文章 · 获赞 64 · 访问量 60万+

猜你喜欢

转载自blog.csdn.net/lishimin1012/article/details/84591340