《ASP.NET 本质论》HttpApplication的处理管道

时间:2022-05-14 04:13:44

        HttpApplication对象是ASP.NET中处理请求的重要对象,但是,这种类型的对象实例不是由程序员来创建的,而是由ASP.NET帮助我们创建的。为了便于扩展处理工作,HttpApplication采用处理管道的方法进行处理,将处理的过程分为多个步骤,每个步骤通过事件的形式暴露给程序域,这些事件按照固定的处理顺序依次触发,程序员通过编写事件处理方法就可以自定义每一个请求的扩展处理过程。
      对于HttpApplication来说,到ASP.NET 4.0版本,提供了19个标准事件,如下表:
可参见MSDN:http://msdn.microsoft.com/zh-cn/library/system.web.httpapplication.aspx

序号

名称

说明

1

BeginRequest

ASP.NET开始处理的第一个时间,表示处理的开始

2

AuthenticateRequest

验证请求,一般用来取得请求的用户信息

3

PostAuthenticateRequest

已经获取请求的用户信息

4

AuthorizeRequest

授权,一般用来检查用户的请求是否获得权限

5

PostAuthorizeRequest

用户请求已经得到授权

6

ResolveRequestCache

获取以前处理缓存的处理结果,如果以前缓存过,那么不必再进行请求的处理工作,直接返回缓存结果

7

PostResolveRequestCache

已经完成缓存的存取工作(在 PostResolveRequestCache 事件之后和 PostMapRequestHandler 事件之前,会创建一个事件处理程序(一个对应于请求 URL 的页)。 如果服务器在集成模式下运行 IIS 7.0 并且 .NET Framework 至少为 3.0 版本,则会引发 MapRequestHandler 事件。 如果服务器在经典模式下运行 IIS 7.0 或者运行的是较早版本的 IIS,则无法处理此事件。)

8

PostMapRequestHandler

已经根据用户的请求,创建了处理请求的处理器对象

9

AcquireRequestState

取得请求的状态,一般用于Session

10

PostAcquireRequestState

已经取得了Session

11

PreRequestHandlerExecute

准备执行处理程序(执行事件处理程序。)

12

PostRequestHandlerExecute

已经执行了处理程序

13

ReleaseRequestState

释放请求的状态

14

PostReleaseRequestState

已经释放了请求的状态(在引发 PostReleaseRequestState 事件之后,现有的所有响应筛选器都将对输出进行筛选。)

15

UpdateRequestCache

更新缓存

16

PostUpdateRequestCache

已经更新了缓存

17

LogRequest.

请求的日志操作(仅在 IIS 7.0 处于集成模式并且 .NET Framework 至少为 3.0 版本的情况下才支持此事件。)

18

PostLogRequest

已经完成了请求的日志操作(仅在 IIS 7.0 处于集成模式并且 .NET Framework 至少为 3.0 版本的情况下才支持此事件。)

19

EndRequest

本次请求处理完成


处理过程简单介绍
        在ASP.NET中,ASP.NET服务器对于每一次请求的的处理过程是相同的,都要经过这个HttpApplication的处理管道。管道内部的处理过程是固定的,在服务器处理请求的各个阶段,伴随着处理的进行,依次触发对应的事件,以便于程序员在处理的各个阶段完成自定义的处理工作。
       首先触发的事件是BeginRequest,这个事件标志着ASP.NET服务器处理工作的开始,也是程序员在ASP.NET中针对请求所能够处理的第一个事件。
      开始处理请求后,第一个重要的工作就是确定请求用户的身份以实现安全机制,这个工作通过AuthenticateRequest和PostAuthenticateRequest两个事件提供检查当前请求用户身份的机会。显然,AuthenticateRequest表示开始检查用户的身份,而PostAuthentiacteRequest则表示用户身份已经检查完成,检查后的用户可以通过HttpContext的User属性获取到。这个属性的类型为System.Security.Principal.IPrincipal,IPrincipal又有一个名为Identity,类型为System.Security.Pricipan.IIdentity的属性,IIdentity有一个类型为bool型的属性IsAuthenticated,表示当前请求的用户是否已经被验证,或者说确定了用户是否是匿名用户,IsAuthenticated如果为false,那么,表示是一个匿名用户,如果为true,那么通过IIdentity类型为string的Name属性,这就是当前请求的用户名。
       当ASP.NET获取用户的身份之后,根据当前请求的用户身份,开始请求权限的检查工作。当第4个事件AuthorizeRequest触发的时候,表示开始进行用户的权限检查,而第5个事件PostAuthorizeRequest则标志着已经完成了用户权限的检查工作。如果用户没有通过安全检查,一般情况下,将跳过剩下的事件,直接触发EndRequest事件结束请求的处理过程。
      当用户取得了请求的权限,那么服务器开始准备用最快的方式来使用户得到回应的结果。ResolveRequestcache事件标志着到从前缓存的结果中进行检查,看看是否可以直接从以前缓存的结果中直接获取处理的结果,PostResolveRequestCache表示缓存检查的结束。
      当不能从缓存中获取结果的时候,必须通过一次处理来计算出当前请求的结果。在ASP.NET中,用于处理请求以得到结果的对象成为处理程序Handler,在ASP.NET中提供了许多的处理程序,程序员也可以自定义处理程序。为了处理这个请求,ASP.NET必须按照匹配规则找到一个处理当前请求的处理程序,PostMapRequestHandler事件表示ASP.NET已经获取了这个处理程序,HttpContext的Handler属性就表示这个处理程序对系那个。从上面的分析可以看到,HttpContext的Handler属性到这里才有实际意义。
      得到了处理程序之后,还不能马上开始进行处理,这是由于处理请求还需要许多与这个请求有关的数据,比如说,这个用户在上一次向服务器发请求的时候,在服务器保存了一些这个用户特有的数据。从ASP时代开始,Session这个概念就处在Web开发中,提供基于会话的状态管理,由于HTTP协议的无状态性,状态管理的问题是Web开发的一个核心问题。
      为了获取这个用户在以前保存的专属数据,AcquireRequestState事件给程序员提供了一个切入点,PostAcquireRequestState事件则表示已经完成了用户数据的获取工作,可以在处理中使用了。
     万事俱备,只欠东风,PreRequestHandlerExecute事件用来通知程序员,处理程序就要开始进行处理工作了,如果在用户的状态已经获取之后,还需要在处理程序之前进行的工作,那么,就在这个事件中处理吧。
       在PreRequestHandlerExecute事件之后,ASP.NET服务器将通过执行处理程序完成请求的处理工作。这个处理程序可能是一个Web窗体,也可能是一个Web服务。这个工作在第11个事件和第12个事件之间完成。
     处理程序完成之,服务器开始进行扫尾工作,PostRequestHandlerExecute事件通知程序员,ASP.NET服务器的处理程序已经完成。
     在处理完成之后,由于在处理程序在,用户可能修改了用户特定的专属数据,那么,修改之后的用户状态数据可能需要进行序列化或者进行保存处理。ReleaseRequestState事件通知程序员需要释放这些状态数据,PostReleaseRequestState则表示已经释放完成。
      在处理完成之后,如果希望将这次处理的结果缓存起来,以便于在后继的请求中可以直接使用这个结果,UpdateRequestCache事件提供了处理的机会,PostUpdateRequestCache则表示缓存已经更新完成。
      在ASP.NET 4.0 中,新增加了两个完成处理的日志工作,LogRequest表示将这次请求记入日志中,PostLogRequest表示完成了日志工作。
     在前述的事件中,请求不一定要经过所有的事件,比如说,用户没有经过授权的检查,那么将跳过后面的事件,但是,EndRequest事件是所有请求都要经过的最后一个HttpApplication处理管道的事件,也是程序员处理的ASP.NET处理请求中的最后一个机会。这个事件之后,处理的结果将被回应到浏览器,完成ASP.NET服务器的处理工作。


处理HttpApplication的事件
       HttpApplication提供了基于事件的扩展机制,运行程序员借助于处理管道中的事件进行处理过程扩展。由于HttpApplication对象是由ASP.NET基础架构来创建和维护的,那么,如何才能获取这个对象引用,以便于注册HttpApplication对象的事件处理,就是程序员首先要解决的问题。在ASP.NET中,提高了两种方式来解决这个问题:IHttpModule方式和 global.asax方式。这两种方式的核心都是IHttpModule接口。

通过IHttpModule创建HttpApplication的事件处理程序
       在ASP.NET中,定义在System.Web命名空间下的IHttpModule接口专门用来定义HttpApplication对象的事件处理。
      实现IHttpModule接口的类成为HttpModule。IHtpModule接口的定义如下,其中仅仅包含两个成员:

public interface IHttpModule
{

void Dispose()
vodi Init(HttpApplication context)

}


创建和注册自定义 HTTP 模块 MSDN:http://msdn.microsoft.com/zh-cn/library/ms227673.aspx
     
      其中,Dispose方法用于回收Module所使用的非托管资源,如果没有的话,直接返回即可。
      最重要的是Init方法,可以看到,这个方法接受一个HttpApplication类型的参数,在ASP.NET中,每当创建一个HttpApplication对象实例,将遍历注册的HttpModule类型,通过反射,依次创建每个注册HttpModule类型的实例对象,并将这个HttpApplication实例通过Init方法传递给各个HttpModule,这样HttpModule就可以在第一时间完成针对HttpApplication对象的事件注册了。
       例如,希望写一个处理PostAuthentiacteRequest事件的HttpModule,那么,可以如下完成事件的注册:
       
 public void Init(HttpApplication context)
{
context.PostAuthorizeRequest += new EventHandler(context_PostAuthorizeRequest);
}



注册 HttpModule
       在APS.NET中,实现IHttpModule接口只是实现HttpModule的第一步,在ASP.NET中所使用的HttpModule还必须在网站配置文件中进行注册才能真正生效,并在ASP.NET中使用。
      在.NET中,网站的配置文件分为三个级别,首先在.NET的系统文件夹中,有针对本服务器所有.NET程序的配置文件,配置文件所在的文件夹位于如下位置:
                     操作系统文件夹\Microsoft.NET\Framework\ASP.NET版本\Config
      在这个文件夹中,有两个重要的配置文件:machine.config   和  web.config。machine.config配置文件中保存有针对此服务器所有.NET程序的基本配置参数。web.config配置文件中保存有针对此服务器所有Web应用程序的基本配置参数。在我们开发的网站项目中的web.config中所做的配置是专门针对这个网站应用程序的配置文件,在网站应用程序中起作用的配置参数来自这三个配置文件的整合。
      在ASP.NET的网站配置文件web.config中,system.web配置元素的子元素httpModules用来配置网站所使用的HttpModule;httpModules的子元素add用来增加一个新的HttpModule;clear将清除前面注册的所有HttpModule。
     add元素有两个必选的属性name 和type,简介如下:
      name表示这个HttpModule在程序中的名字,在网站应用程序中,可以通过这个名字来找到HttpModule对象的引用。HttpApplication的Modules属性表示这个对象所管理的所有HttpModule对象,通过这个name作为索引器,可以找到对应的HttpModule对象。
     type表示HttpModule对象的类型名,ASP.NET网站可以使用这个类型名,通过反射来动态创建HttpModule对象。类型哦写法就是反射中要求的类型名称写法,如果这个类型定义在网站中,那么,就是一个博阿含命名空间哦类的全名,否则的话,在全名的后面,使用逗号(,)分隔,还需要跟上类型所在程序集的名称,这个程序集的名称不需要包含.dll扩展名。

      例如,自定义的HttpModule类位于程序集OnlineUserModule中,类的全名为Samples.AspNet.CS.CustomHTTPModule,将这个自定义的HttpModule注册到网站中,那么配置文件中的定义如下所示:
以下设置适用于 IIS 7.0 经典模式以及较早的 IIS 版本。

<configuration>
<system.web>
<httpModules>
<add type="Samples.AspNet.CS.CustomHTTPModule,OnlineUserModule"
name="CustomHttpModule" />
</httpModules>
</system.web>
</configuration>

以下设置适用于 IIS 7.0 集成模式。

<configuration>
<system.webServer>
<modules>
<add type="Samples.AspNet.CS.CustomHTTPModule,OnlineUserModule"
name="CustomHttpModule" />
</modules>
</system.webServer>
</configuration>


对于IIS 7.0,可以为MapRequestHandler,LogRequest和PostLogRequest事件添加处理程序。只有在IIS 7.0集成模式下运行并且与 .NET Frameword 3.0或更高版本一起运行的应用程序,才可以支持这些事件。
       托管代码模块也可以在IIS 7.0配置存储区(ApplicationHost.config文件)的modules元素中注册。在ApplicationHost.config文件中注册的模块具有全局范围,因为它们为所有由IIS 7.0承载的应用程序而注册。同样,在ApplicationHost.config文件的globalModules元素中定义的本机代码模块也具有全局范围。如果Web应用程序不需要全局模块,则可以将其禁用。


不使用配置文件注册HttpModule
        在APS.NET  4.0 中,还可以不通过修改配置文件来完成Module的注册。从。NET3.5开始,新提供的PreApplicationStartMethodAttribute特征可以应用在程序集上,使得自定义的网站初始化代码可以在Web应用程序的Application_Start初始化环节之前就执行。这个步骤设置在动态编译和执行Application_Start之前。对于每个程序集,可以定义依次。特征的定义如下。

[AttributeUsageAttribute(AttributeTargets.Assembly, AllowMultiple = false)]
public sealed class PreApplicationStartMethodAttribute : Attribute
{
public Type Type{get;}
public string MethorName{get;}
......
}

        Type用来指定定义了初始化方法的类型,MethodName用来指定将要执行的初始化的方法。
        这样,可以不再配置文件中固定配置HttpModule,而是定义一个方法,这个方法可以返回需要动态注册的HttpModule,将这个方法以委托的形式登记在一个集合中。在网站启动之后,每当HttpApplicationFactory创建一个HttpAppplication对象,完成正常注册的HttpModule创建及初始化之后,再来创建我们动态注册的这些HttpModule。
       对于HttpApplication来说,其Init方法将在网站正常注册的HttpModule创建及注册之后被调用,用来完成自定义的HttpApplication初始化。我们可以在这个时间点动态注册HttpModule。在ASP.NET网站中,Global.asax文件用来生成一个HttpApplication的派生类,这个类用来创建网站中使用的HttpApplication对象,我们可以重写这个派生类的Init方法,完成动态注册的HttpModule创建和注册工作。

Global.asax
private List<IHttpModule> dynamicModules;

public override void Init()
{
base.Init();

this.dynamicModules
= DynamicHttpModuleManager.GetModules();
foreach (IHttpModule module in this.dynamicModules)
{
module.Init(this);
}
}
在网站初始化之前,将需要注册的Module类型记录在 一个集合中。

public delegate IHttpModule CreateDynamicHttpModule();

public static class DynamicHttpModuleManager
{
public static List<CreateDynamicHttpModule> _createModuleHandlerList =
new List<CreateDynamicHttpModule>();

public static void RegisterDynamicModule(
CreateDynamicHttpModule handler)
{
_createModuleHandlerList.Add(handler);
}

public static List<IHttpModule> GetModules()
{
List<IHttpModule> list = new List<IHttpModule>();
foreach (CreateDynamicHttpModule handler in _createModuleHandlerList)
{
IHttpModule module = handler();
list.Add(module);
}
return list;
}
}

在Module中增加一个用户注册的方法Register,注意,必须是public 和static

public class Class1:IHttpModule
{

public static void Register()
{
DynamicHttpModuleManager.RegisterDynamicModule
(
()=>new Class1()
);
}

最后,在项目的AssemblyInfo.cs文件中增加PreApplicationStartMethod特征完成动态注册。
[assembly: PreApplicationStartMethod(typeof(WebApplication1.Class1),"Register")]

        在ASP.NEt中,已经与定义了许多HttpModule,甚至已经在服务器的网站配置文件中进行了注册,在系统文件夹 系统目录\Microsoft.NET\Framework\v4.0.30319\Config中,web.config文件中已经注册了14个HttpModule。
 <httpModules>
<add name="OutputCache" type="System.Web.Caching.OutputCacheModule" />
<add name="Session" type="System.Web.SessionState.SessionStateModule" />
<add name="WindowsAuthentication" type="System.Web.Security.WindowsAuthenticationModule" />
<add name="FormsAuthentication" type="System.Web.Security.FormsAuthenticationModule" />
<add name="PassportAuthentication" type="System.Web.Security.PassportAuthenticationModule" />
<add name="RoleManager" type="System.Web.Security.RoleManagerModule" />
<add name="UrlAuthorization" type="System.Web.Security.UrlAuthorizationModule" />
<add name="FileAuthorization" type="System.Web.Security.FileAuthorizationModule" />
<add name="AnonymousIdentification" type="System.Web.Security.AnonymousIdentificationModule" />
<add name="Profile" type="System.Web.Profile.ProfileModule" />
<add name="ErrorHandlerModule" type="System.Web.Mobile.ErrorHandlerModule, System.Web.Mobile, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<add name="ServiceModel" type="System.ServiceModel.Activation.HttpModule, System.ServiceModel.Activation, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add name="UrlRoutingModule-4.0" type="System.Web.Routing.UrlRoutingModule" />
<add name="ScriptModule-4.0" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
</httpModules>

主要的HttpModule的解释如下:

OutputCacheModule完成ASP.NET的输出缓存管理工作。
    OutputCacheModule的配置参数通过system.web配置元素的caching子元素的outputCache元素进行定义。当启用输出缓存之后,OutputCacheModule将注 册      HttpApplication的ResolverRequestcache和UpdateRequestcache两个事件完成输出缓存管理。

SesionStateModule完成Sesion管理工作。
     这个Module的配置参数通过配置文件中的system.web配置元素的sessionState子元素进行配置。当启用Session状态管理之后,SessionStateModule将注册HttpApplication的AcquireRequestState、ReleaseRequestState、EndRequest三个事件完成Session状态的管理工作。

ProfileModule在.NET2.0之后,提供个性化数据管理。
    这是一个自定义的类似于Session的回环状态管理,但是,个性化数据的读取和保存可以由程序员完全控制,并且提供了强类型的数据访问方式。这个Module的配置参数在system.web的子元素profile中进行说明。当启用了个性化数据管理之后,Module将注册HttpApplication的AcquireRequestState和EndRequest事件处理。

AnonymousIdentifiactionModule提供匿名用户标识。
    是否启用匿名用户标识在配置文件的system.web配置元素的子元素anonymodusIdentification中定义,还可以配置匿名标识的管理方式。由于在AuthenticateRequest事件中将验证用户,获取用户名,所以,这个Module注册了PostAuthenticateRequest的事件处理,当用户没有经过验证的时候,为用户分配一个唯一的匿名标识。

WindowsAuthenticationModule、FormsAuthentiactionModule和PassportAuthenticationModule用来完成用户验证工作。
    它们通过配置文件中的system.web的子元素authentication子元素定义,mode属性用来指定网站当前使用的验证方式,也就是哪一个Module将被用来完成验证工作。在启用验证的情况下,FormsAuthenticationModule和PassprotauthenticationModule将注册HttpApplication的AuthenticateRequest和EndRqeust事件进行用户的验证处理。WindowsAuthenticaitonModule将注册AuthenticateRequest的事件处理。

RoleManagerModule、UrlAuthorizationModule、FileAuthorizetionModue用来完成用户的授权管理。
     授权管理的配置参数来自system.web的authorization子元素。UrlAuthorizationModule和FileAuthorizationModule注册了HttpApplication的AuthorizeRequest事件处理,用来检查Url和文件的访问授权。RoleManagerModule在Url和文件访问授权检查通过之后,通过用户的标识和角色来完成用户的授权检查,RoleManagerModule注册了HttpApplication的PostAuthenticateRequest和EndRequest事件处理。

HttpModule的事件

    每个HttpModule也可以触发自定义的事件,但是,处理这些HttpModule事件更加麻烦一些,因为这些HttpModule对象实例也不是我们自己创建的。
    一般来说,可以通过HttpApplication的Modules属性获取特定的HttpModule,这个属性的定义如下
public HttpModuleCollection Modules { get; }

    可以使用定义HttpModule时候哦name作为索引器来获取对应的HttpModule。例如获取前面定义的HttpModule对象的引用,可以如下进行:

application,Modules["CustomHttpModule"];

然后,就可以定义这个HttpModule的事件处理了。
不过这样比较麻烦,更简单的方式是在global.asax中进行事件处理。

global.asax中HttpApplication事件的自动注册

      在global.asax中,针对HttpApplication的事件处理,可以通过定义特殊命令的方法来实现。首先,这些方法必须符合System.EventHandler,因为所有的HttpApplication管道事件都是用这个委托定义。第二,方法的作用于必须是public。第三,方法的命名格式必须如下:Application_注册的事件名称。按照这种命名方法定义在global.asax中的方法将被自动注册到对应事件中。
     例如,希望在global.asax中注册PostAuthentiacteRequest事件处理,按么global.asax中应该定义一个如下的方法:

void Appliaction_PostAuthenticateRequest(object sender, EventArgs e)
{
//....
}