ASP.NET 应用程序生命周期概述
下表描述了 ASP.NET 应用程序生命周期的各个阶段。
阶段 | 说明 | ||||
---|---|---|---|---|---|
用户从 Web 服务器请求应用程序资源。 |
ASP.NET 应用程序的生命周期以浏览器向 Web 服务器(对于 ASP.NET 应用程序,通常为 IIS)发送请求为起点。ASP.NET 是 Web 服务器下的 ISAPI 扩展。Web 服务器接收到请求时,会对所请求的文件的文件扩展名进行检查,确定应由哪个 ISAPI 扩展处理该请求,然后将该请求传递给合适的 ISAPI 扩展。ASP.NET 处理已映射到其上的文件扩展名,如 .aspx、.ascx、.ashx 和 .asmx。
|
||||
ASP.NET 接收对应用程序的第一个请求。 |
当 ASP.NET 接收到对应用程序中任何资源的第一个请求时,名为 ApplicationManager 的类会创建一个应用程序域。应用程序域为全局变量提供应用程序隔离,并允许单独卸载每个应用程序。在应用程序域中,将为名为 HostingEnvironment 的类创建一个实例,该实例提供对有关应用程序的信息(如存储该应用程序的文件夹的名称)的访问。 下面的关系图说明了这种关系: 如果需要,ASP.NET 还可对应用程序中的顶级项进行编译,其中包括 App_Code 文件夹中的应用程序代码。有关更多信息,请参见本主题后面的“编译生命周期”。 |
||||
为每个请求创建 ASP.NET 核心对象。 |
创建了应用程序域并对 HostingEnvironment 对象进行了实例化之后,ASP.NET 将创建并初始化核心对象,如HttpContext、HttpRequest 和 HttpResponse。HttpContext 类包含特定于当前应用程序请求的对象,如 HttpRequest 和 HttpResponse对象。HttpRequest 对象包含有关当前请求的信息,包括 Cookie 和浏览器信息。HttpResponse 对象包含发送到客户端的响应,包括所有呈现的输出和 Cookie。 |
||||
将HttpApplication对象分配给请求 |
初始化所有核心应用程序对象之后,将通过创建 HttpApplication 类的实例启动应用程序。如果应用程序具有 Global.asax 文件,则 ASP.NET 会创建 Global.asax 类(从 HttpApplication 类派生)的一个实例,并使用该派生类表示应用程序。
创建 HttpApplication 的实例时,将同时创建所有已配置的模块。例如,如果将应用程序这样配置,ASP.NET 就会创建一个SessionStateModule 模块。创建了所有已配置的模块之后,将调用HttpApplication 类的 Init 方法。 下面的关系图说明了这种关系: |
||||
由HttpApplication管线处理请求。 |
在处理该请求时将由 HttpApplication 类执行以下事件。希望扩展 HttpApplication 类的开发人员尤其需要注意这些事件。
|
其中HttpApplication管线处理请求事件详解如下:
1、ValidatePathExecutionStep:负责对请求的路径进行安全检查,禁止非法路径访问。深入到该类的Execute方法我们发现,该类直接调用HttpContext.ValidatePath方法,而后一个方法在检测到非法路径时,直接抛出HttpException。
2、UrlMappingsExecutionStep:负责对Web.Config中设置的<urlMappings>配置节进行应用,将某一url映射为一个新的url地址。深入该类的Execute方法我们发现,该类在检测到需要url映射时,调用HttpContext.RewritePath方法实现地址重写。
3、引发BeginRequest事件:该步骤没啥好说的,就是通知开始处理请求了。然而,我在查阅有哪些Module订阅该事件时,发现该事件被UrlMappingsModule订阅,而该Module处理的事情跟UrlMappingsExecutionStep一模一样。我很纳闷,这不多此一举吗?
4、引发AuthenticateRequest、DefaultAuthenticateRequest、PostAuthenticateRequest事件:负责对用户的身份进行验证。说白了,就是确定当前请求者的身份,从代码层次上说,就是设置好HttpContext的User属性。
a) AuthenticateRequest事件根据web.config的配置,可被FormsAuthenticationModule、PassportAuthenticationModule、WindowsAuthenticationModule订阅,这3者分别对应处理不同的身份验证方式。
b) DefaultAuthenticateRequest事件是个internal的事件,可被DefaultAuthenticationModule订阅,该module在AuthenticateRequest事件中无法确定用户身份(即没有设置HttpContext.User属性)时,给出一个默认的用户身份,从而确保在管线的后续处理中,HttpContext.User总是非空的。然而,查看该Module的源代码后,发现,该Module在IIS7.0的集成管线模式下时,并不订阅DefaultAuthenticationRequest事件,而是订阅PostAuthenticateRequest事件。估计是在IIS7.0的集成管线模式下时,该Module必须在某一订阅PostAuthenticateRequest的Module之后执行,从而打的一个补丁吧。
c) PostAuthenticateRequest事件可被AnonymousIdentificationModule与RoleManagerModule订阅。AnonymousIdentificationModule管理应用程序的匿名标识符,可使应用程序进行匿名访问,并保持Session的连贯性。RoleManagerModule可确定用户的身份。
5、引发AuthorizeRequest、PostAuthorizeRequest事件:在经过第4个步骤确定用户身份后,接下来就是授权的问题了。即判断用户是否具有权限访问所请求的资源。在通过该步骤后,就确认用户具有对请求资源访问的权限。
a) AuthorizeRequest事件可被FileAuthorizationModule、UrlAuthorizationModule订阅,分别用于判断用户是否具有访问指定File或Url的权限。
b) PostAuthorizeRequest事件默认不被任何Module订阅。
6、引发ResolveRequestCache、PostResolveRequestCache事件:该步骤即是输出缓存发挥作用的时候。如果用户请求的资源可由输出缓存直接提供,则跳过后续步骤,从而起到加快处理速度,提高性能的作用。
a) ResolveRequestChche事件可被OutputCacheModule订阅,该Module提供输出缓存的功能。
b) PostResolveRequestCache事件默认不被任何Module订阅。
7、MapHandlerExecutionStep、引发PostMapRequestHandler事件:当无法由输出缓存进行处理时,则进入了正规的处理过程中。而正规处理的第一步 ,就是根据配置获取IHttpHandler的实例对象,从代码层次上说,就是设置好HttpContext.Handler属性。该Handler将具体根据上下文信息生成具体的Html等代码。
最最常见的Handler对象就是Page类及其各个子类,也就是我们编写的每一个aspx页面,都是一个具体的Handler,MapHandlerExecutionStep类将根据用户访问各个aspx页面的url从而确定具体的page子类。
另外,还有HttpForbiddenHandler,当用户访问App_Code、App_Data等目录中的资源时,MapHandlerExecutionStep就返回HttpForbiddenHandelr,该Handler将仅仅告诉用户该资源无法访问等信息,从而起到保护特定资源的作用。
还有TraceHandler,该Handler当且仅当用户访问根目录下的"trace.axd"资源时返回,从而实现Asp.net跟踪的功能。
用户可以注册更多的自定义Handler,已满足更多的特定的需求。
在IIS7.0的集成管线模式下,还将引发MapRequestHandler事件。
PostMapRequestHandler事件默认不被任何Module订阅。
8、引发AcquireRequestState、PostAcquireRequestState事件:负责获取用户的会话状态、个性化数据等信息。从代码层次上说,就是设置好HttpContext.Session与HttpContext.Profile属性。
a) AcquireRequestState事件可被SessionStateModule、ProfileModule订阅,这两个Module分别提供Session与Profile功能。
b) PostAcquireRequestState事件默认不被任何Module订阅。
9、引发PreRequestHandlerExecute事件、CallHandlerExecutionStep、PostRequestHandlerExecute事件:至此,一切准备就绪,该执行第7部中获取的Handler的ProcessRequest的时候了。如果Handler为Page类或其子类,则该方法将引发Page的生命周期。
a) PreRequestHandlerExecute与PostRequestHandlerExecute事件默认不被任何Module订阅。
b) CallHandlerExecutionStep最主要的工作就是调用Handler的PrecessRequest方法,该方法负责生成Html等代码,并将这些代码写入HttpContext的Response中。因此,该步骤是最重要的一个步骤,也是最需要开发人员去定制的步骤,事实上,我们开发Asp.net应用程序,绝大部分就是在定制这一步的步骤,我们写出的一个又一个的不同的aspx页面,实际上就是一个又一个不同的Handler,而这些一个又一个不同的Handler,就生成了一个又一个不同的html页面。经过此步骤之后,Html等代码就基本完成了(为什么说基本完成了?是因为后续还有一个步骤将可能改变html代码,请看第11个步骤),后续的步骤,主要是一些收尾的工作。
10、引发ReleaseRequestState、PostReleaseRequestState事件:对应于第8个步骤,该步骤负责将用户的会话状态等信息保存起来,以供下次处理用户请求时所用。
a) ReleaseRequestState事件可被SessionStateModule订阅,该Module在该步骤中保存可能被第9步骤所改变的会话状态。该事件并不被ProfileModule订阅,因为ProfileModule另外订阅EndRequest事件,在该事件中完成保存Profile数据的工作。
b) PostReleaseRequestState事件默认不被任何Module订阅。
11、CallFilterExecutionStep:该步骤将执行HttpContext.Response.FilterOutput方法,将对已经写入Response的Html代码进行最后的过滤。而所谓过滤,实际上也就是改变html的一个过程,比如将html全部改为小写或全部改为大写,就是某一种形式的过滤。当然,你也可以说要过滤掉前面100个字符等,虽然通常这没有什么意义。要设置过滤器,请参考Response.Filter属性。
12、引发UpdateRequestCache、PostUpdateRequestCache事件:对应于第6个步骤,该步骤将生成的Html代码保存到输出缓存中,以供下次处理用户请求时所用。
a) UpdateRequestCache事件可被OutputCacheModule订阅。
b) PostUpdateRequestCache事件默认不被任何Module订阅。
13、引发EndRequest事件:对应于第3个步骤,该步骤通知请求已经处理完毕。该步骤有个特别之处,就在于前面那些步骤不一定在每次处理请求时都发生,而该步骤却必然发生,因此,该步骤也就成了很多Module处理扫尾等工作的订阅事件。ProfileModule、FormsAuthenticationModule、PassportAuthenticationModule、RoleManagerModule、SessionStateModule都订阅该事件,而各个Module订阅该事件所做的工作却又各不相同。
a) ProfileModule订阅该事件是为了完成对Profile数据的保存。
b) FormsAuthenticationModule订阅该事件是为了处理当用户未登录时将用户重定向到指定的Login页面的工作。
c) PassportAuthenticationModule订阅该事件是为了处理当用户未登陆时将用户重定向到指定Passport地址的工作。
d) RoleManagerModule订阅该事件的作用我还不大清楚,等待高手的补充或我的继续探索中。
e) SessionStateModule订阅该事件的作用我也还不大清楚,等待高手的补充或我的继续探索中。
生命周期事件和 Global.asax 文件
在应用程序的生命周期期间,应用程序会引发可处理的事件并调用可重写的特定方法。若要处理应用程序事件或方法,可以在应用程序根目录中创建一个名为 Global.asax 的文件。
如果创建了 Global.asax 文件,ASP.NET 会将其编译为从 HttpApplication 类派生的类,然后使用该派生类表示应用程序。
HttpApplication 进程的一个实例每次只处理一个请求。由于在访问应用程序类中的非静态成员时不需要将其锁定,这样可以简化应用程序的事件处理过程。这样还可以将特定于请求的数据存储在应用程序类的非静态成员中。例如,可以在 Global.asax 文件中定义一个属性,然后为该属性赋一个特定于请求的值。
通过使用命名约定 Application_event(如 Application_BeginRequest),ASP.NET 可在 Global.asax 文件中将应用程序事件自动绑定到处理程序。这与将 ASP.NET 页方法自动绑定到事件(如页的 Page_Load 事件)的方法类似。有关详细信息,请参见 ASP.NET 页生命周期概述。
Application_Start 和 Application_End 方法是不表示 HttpApplication 事件的特殊方法。在应用程序域的生命周期期间,ASP.NET 仅调用这些方法一次,而不是对每个 HttpApplication 实例都调用一次。
下表列出在应用程序生命周期期间使用的一些事件和方法。实际远不止列出的这些事件,但这些事件是最常用的。
事件或方法 | 说明 |
---|---|
Application_Start |
请求 ASP.NET 应用程序中第一个资源(如页)时调用。在应用程序的生命周期期间仅调用一次 Application_Start 方法。可以使用此方法执行启动任务,如将数据加载到缓存中以及初始化静态值。 在应用程序启动期间应仅设置静态数据。由于实例数据仅可由创建的 HttpApplication 类的第一个实例使用,所以请勿设置任何实例数据。 |
Application_ event |
在应用程序生命周期中的适当时候引发,请参见本主题前面的应用程序生命周期表中列出的内容。 Application_Error 可在应用程序生命周期的任何阶段引发。 由于请求会短路,因此 Application_EndRequest 是唯一能保证每次请求时都会引发的事件。例如,如果有两个模块处理Application_BeginRequest 事件,第一个模块引发一个异常,则不会为第二个模块调用 Application_BeginRequest 事件。但是,会始终调用 Application_EndRequest 方法使应用程序清理资源。 |
HttpApplication.Init |
在创建了所有模块之后,对 HttpApplication 类的每个实例都调用一次。 |
在销毁应用程序实例之前调用。可使用此方法手动释放任何非托管资源。有关更多信息,请参见清理非托管资源。 |
|
Application_End |
在卸载应用程序之前对每个应用程序生命周期调用一次。 |