首页 > 代码库 > 【译】ASP.NET应用程序和页面生命周期

【译】ASP.NET应用程序和页面生命周期

<style></style>

为何翻译此文

  一、此文是Code Project社区2010年4月ASP.NET板块的最佳文章,说明了此文的份量;

  二、锻炼自己的英文技术文章翻译能力,提高英文技术文档阅读能力;

  三、了解掌握ASP.NET页面生命周期是非常必要的,这有助于我们更加灵活的控制页面,以我们需要的方式编程开发;

关于原文作者

技术分享原文作者:Shivprasad koirala
原文地址:http://www.codeproject.com/Articles/73728/ASP-NET-Application-and-Page-Life-Cycle
他的介绍:微软MVP(ASP/ASP.NET),现在是印度一家小型在线教育公司的CEO。他非常积极地在制作在线培训视频,写技术书籍及做企业培训。

内容导读

  • 概述
  • 大体上的两步处理流程
  • ASP.NET环境的创建
  • 通过MHPM触发的事件处理请求
  • 在什么事件中我们可以做什么?
  • 一个简单的示例
  • 详解ASP.NET页面事件

一、概述

  在本文中,我们会试着了解用户在浏览器中发出一个Web请求 到 这个请求被响应并显示在浏览器中的过程中究竟会发生哪些不同的事件。首先,我们先试着了解一下ASP.NET请求的两个大体上的步凑,然后我们将关注点转移到从‘HttpHandler‘、‘HttpModule‘以及ASP.NET页面对象所触发的不同事件上。当我们进入这个事件之旅时,我们也会试着明白在请求处理的每个事件当中我们可以做什么业务逻辑处理操作。

二、大体上的两步处理流程

  大体上,ASP.NET请求的处理流程分为如下图所示的两个步凑。用户发送一个请求到IIS服务器时:

  (1)ASP.NET会创建一个能够处理请求的环境。换句话说,它会创建一个包含请求、响应以及上下文对象的应用程序对象来处理这个请求。

  (2)一旦ASP.NET环境被创建,用户请求就会通过由modules(管道)、handlers(处理程序)和page objects(页面对象)触发的一系列事件进行处理。简而言之,我们暂且将此步凑称为MHPM(Module、Handler、Page和Module Event)。

技术分享

 

  图1 ASP.NET的两个主要处理步凑

  在接下来的部分中,我们会深入地理解这两个主要的步凑。

三、ASP.NET环境的创建

  第一步:用户请求到达IIS后,IIS首先会检查哪一个ISAPI扩展能够处理这个请求,这会取决于文件的后缀名。例如:如果请求的是一个‘.aspx‘的页面,那么就会被传递到‘aspnet_isapi.dll‘来进行处理。

  第二步:如果这是该网站的首次请求,那么一个称为‘ApplicationManager‘的类会首先创建一个该网站可以运行的应用程序域(App Domain)。正如我们所知,应用程序域隔离部署在同一台IIS服务器上的两个不同的Web应用程序。因此,即使其中一个应用程序域出现了错误,也不会影响其他应用程序域的正常运作。

Note:下面的内容是我补充的,非原文内容。

.NET平台下,程序集并没有直接加载进 进程 中(传统的Win32程序是直接承载的)。.NET可执行程序承载在进程的一个逻辑分区中,术语称应用程序域(简称AppDomain)。应用程序域是.NET引入的一个新概念,它比进程所占用的资源要少,可以被看作是一个 轻量级的进程

在一个进程中可以包含多个应用程序域,一个应用程序域可以装载一个可执行程序(*.exe)或者多个程序集(*.dll)。这样可以使应用程序域之间实现深度隔离,所以:即使进程中的某个应用程序域出现错误,也不会影响其他应用程序域的正常运作。

更多关于AppDomain的介绍,请自行搜索,这里不再赘述。

  第三步:在新创建的应用程序域中,会创建ASP.NET的宿主环境,也就是HttpRuntime对象。一旦宿主环境被创建完成,ASP.NET最核心的对象如HttpContextHttpRequestHttpResponse对象都会被创建好。

  第四步:一旦所有核心的ASP.NET对象被创建好,HttpApplication对象就会随之被创建来服务这个请求。如果你的系统中存在一个global.asax文件,那么这个global.asax文件的对象也会被创建。但是,需要注意的是你的global.asax需要继承自HttpApplication类。

  注意:在一个ASP.NET页面第一次附加到网站,一个HttpApplication实例便随之产生。为了最大化得提高处理性能,HttpApplication的实例将会被复用以处理多个请求。

Note:下面的内容是我补充的,非原文内容。

Global.asax 文件(也称作 ASP.NET 应用程序文件)是可选文件,包含用于响应 ASP.NET 或 HttpModule 引发的应用程序级别事件的代码。(换句话说,我们可以自定义后面我们所要介绍的一些事件,因为请求处理流程会经历后面的10多个事件,我们可以写代码来自定义其中的一些事件,加一些我们想做的业务逻辑操作,比如:URL重写、身份验证、图片水印等等。)

如果不定义该文件,ASP.NET 页框架假设您未定义任何应用程序或会话事件处理程序。

  第五步:此时HttpApplication对象将会被分配给一系列的ASP.NET核心对象来处理请求的页面。

  第六步:这时,HttpApplication开始通过HTTP管道事件、处理程序(Handlers)和页面事件来处理请求了。也就是说:它会触发 MHPM 中的事件来处理请求。

  么么嗒,你也可以通过下图来详细地了解这几个步凑。

技术分享

  图2 ASP.NET环境的创建

  下图则形象地展示了在一个ASP.NET请求过程中的重要内部对象模型。最高层是ASP.NET运行时,它创建了一个应用程序域(AppDoamin),下层则创建了一个包含request、response以及context对象的HttpRuntime。

技术分享

 图3 ASP.NET请求过程中的内部对象模型

四、通过MHPM触发的事件处理请求

  一旦HttpApplication创建好,它就开始处理请求了。它经历了三个不同的部分:HttpModulePageHttpHandler。当它经过这些部分时,它将调用不同的事件,而这些事件的逻辑处理还可以由开发者来进行扩展和增加自定义处理。

  在进一步深入了解之前,让我们先来了解一下什么是HttpModuleHttpHandlers。他们帮助我们在ASP.NET页面处理过程的前后注入自定义的逻辑处理。他们之间主要的差别在于:

  • 如果你想要注入的逻辑是基于像‘.aspx‘,‘.html‘这样的扩展名,那么你可以使用HttpHandler。换句话说,HttpHandler是一个基于处理器的扩展。

技术分享

图4 HttpHandler

  • 如果你想要在ASP.NET管道事件中注入逻辑,那么你可以使用HttpModule。也可以说,HttpModule是一个基于处理器的事件。

技术分享

图5 HttpModule

  你可以从这里了解更多关于他们这对好基友之间的差别。

  下面是请求处理过程的逻辑流程,其中有4个重要的步凑,解释如下:

  第一步(M:HttpModule):客户端请求开始被处理。在ASP.NET引擎执行和创建HttpModule触发事件(在此过程中,你也可以注入自定义逻辑)之前,有6个事件你可以在页面对象创建之前来使用,它们分别是:BeginRequestAuthenticateRequestAuthorizeRequestResolveRequestCacheAcquireRequestState 以及 PreRequestHandlerExecute

  第二步(H:HttpHandler):一旦以上6个事件被触发后,ASP.NET引擎就将会调用 ProcessRequest 事件,即使你已经在项目中实现了 HttpHandler

  第三步(P:ASP.NET Page):一旦HttpHandler逻辑执行,ASP.NET页面对象就被创建了。而ASP.NET页面被创建,一系列的事件也会随之被触发,它们可以帮助我们自定义逻辑注入到这些事件里边。在此过程中,有6个重要事件给我们提供了占位符,以便我们在ASP.NET页面中写入逻辑,它们分别是:InitLoadValidateRender Unload。你可以通过记住单词SILVER来记忆这几个事件,S—Start(没有任何意义,仅仅是为了形成一个单词),I(Init)、L(Load)、V(Validate)、E(Event)、R(Render)。

  第四步(M:HttpModule):一旦页面对象执行结束并从内存中被卸载,HttpModule提供了提交返回页面的执行事件,同样,在这些事件中也可以被注入自定义的返回处理逻辑。这里有4个重要的提交处理事件:PostRequestHandlerExecuteReleaserequestStateUpdateRequestCache以及EndRequest

  下图形象地展示了上面的四个步凑。

技术分享

图6 MHPM过程

五、在什么事件中我们可以做什么?

  一个十分有价值的问题就是在什么事件中我们又可以做些什么?下表就展示了这个问题的答案:

SectionEventDescription
HttpModuleBeginRequest此事件标志着一个新的请求,它保证在每个请求中都会被触发。
HttpModuleAuthenticateRequest此事件标志ASP.NET运行时准备验证用户。任何身份验证代码都可以在此注入。
HttpModuleAuthorizeRequest此事件标志ASP.NET运行时准备授权用户。任何授权代码都可以在此注入。
HttpModuleResolveRequest在ASP.NET中我们通常使用OutputCache指令做缓存。在这个事件中,ASP.NET运行时确定是否能够从缓存中加载页面,而不是从头开始生成。任何缓存的具体活动可以被注入这里。
HttpModuleAcquireRequestState此事件标志着ASP.NET运行时准备获得Session会话变量。可以对Session变量做任何你想要做的处理。
HttpModulePreRequestHandlerExecute恰好在ASP.NET 开始执行事件处理程序前发生。可以预处理你想做的事。
HttpHandlerProcessRequestHttpHandler逻辑被执行。在这个部分我们将为每个页面扩展写需要的逻辑。
PageInit此事件发生在ASP.NET页面且可以用来: 
1、动态地创建控件,如果你一定要在运行时创建控件; 
2、任何初始化设置 
3、母版页及其设置 
在这部分中我们没有获得viewstate、postedvalues及已经初始化的控件。
PageLoad在这部分ASP.NET控件完全被加载且在这里你可以写UI操作逻辑或任何其他逻辑。NOTE:这个事件也是我们最常见且最常用的一个事件。
PageValidate如果在页面上你有验证器,你同样想在这里做一下检查。
PageRender是时候将输出发送到浏览器。如果你想对最终的HTML做些修改,你可以在这里输入你的HTML逻辑。
PageUnload页面对象从内存中卸载。
HttpModulePostRequestHandlerExecute可以注入任何你想要的逻辑,在处理程序执行之后。
HttpModuleReleaseRequestState如果你想要保存对某些状态变量的更改,例如:Session变量的值。
HttpModuleUpdateRequestCache在结束之前,你是否想要更新你的缓存。
HttpModuleEndRequest这是将输出发送到客户端浏览器之前的最后一个阶段。

六、一个简单的示例

  我们可以通过一个示例程序代码来展示以上介绍的那些事件是怎样被最终触发的。在这个示例中,我们已经创建了一个HttpModuleHttpHandler,并且也在所有的事件中通过添加自定义逻辑代码展示了一个简单的响应。

  下面是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

  下图则显示了上面我们所讨论的所有事件的执行顺序。

技术分享

图7 示例结果—事件的执行次序

七、详解ASP.NET页面事件

  在上面的部分中,我们已经了解了一个ASP.NET页面请求事件的整体流程。那么,在其中一个最重要的部分就是ASP.NET页面,但是我们并没有对其进行详细讨论。因此,我们在此深入地了解一下ASP.NET页面事件。

  每一个ASP.NET页都有2个部分:一个是在浏览器中进行显示的部分,它包含了HTML标签、viewstate形式的隐藏域 以及 在HTML input中的数据。当这个页面被提交到服务器时,这些HTML标签会被创建到ASP.NET控件,并且viewstate还会和表单数据绑定在一起。一旦你在后置代码中得到所有的服务器控件,你可以执行和写入你自己的逻辑并呈现给客户浏览器。

技术分享

图8 ASP.NET页的两个部分

  现在这些HTML控件会作为ASP.NET控件存活在服务器上,ASP.NET会触发一系列的事件,我们也可以在这些事件中注入自定义逻辑代码。根据你想要执行什么样的任务/逻辑,我们需要将逻辑合理地放入这些事件之中。

  注意:大部分的开发者直接使用Page_Load来干所有的事情,但这并不是一个好的思路。因此,无论是填充控件、设置ViewState还是应用主题等所有发生在页面加载中的所有事情。因此,如果我们能够在合适的事件中放入逻辑,那么毫无疑问我们代码将会干净很多。  

顺序事件名称控件初始化ViewState可用表单数据可用什么逻辑可以写在这里?
1InitNoNoNo注意:你可以通过使用ASP.NET请求对象访问表单数据等,但不是通过服务器控件。
动态地创建控件,如果你一定要在运行时创建;任何初始化设置;母版页及其设置。在这部分中我们没有获得viewstate、提交的数据值及已经初始化的控件。
2Load View StateNot guaranteedYesNot guaranteed你可以访问View State及任何同步逻辑,你希望viewstate被推到后台代码变量可以在这里完成。
3PostBackdataNot guaranteedYesYes你可以访问表单数据。任何逻辑,你希望表单数据被推到后台代码变量可以在这里完成。
4LoadYesYesYes在这里你可以放入任何你想操作控件的逻辑,如从数据库填充combox、对grid中的数据排序等。这个事件,我们可以访问所有控件、viewstate、他们发送过来的值。
5ValidateYesYesYes如果你的页面有验证器或者你想为你的页面执行验证,那就在这里做吧。
6EventYesYesYes

如果这是通过点击按钮或下拉列表的改变的一个回发,相关的事件将被触发。与事件相关的任何逻辑都可以在这里执行。

PS:这个事件想必很多使用WebForm的开发人员都很常用吧,是否记得那些Button1_Click(Object sender,EventArgs e)?

7Pre-renderYesYesYes如果你想对UI对象做最终的修改,如改变属性结构或属性值,在这些控件保存到ViewState之前。
8Save ViewStateYesYesYes一旦对服务器控件的所有修改完成,将会保存控件数据到View State中。
9RenderYesYesYes如果你想添加一些自定义HTML到输出,可以在这里完成。
10UnloadYesYesYes任何你想做的清理工作都可以在这里执行。

技术分享

图9 ASP.NET Page事件流程

一张图复习ASP.NET请求处理(自己补充,非原文内容)

技术分享

翻译中参考的资料 

(1)碧血轩,《ASP.NET页面生命周期》,http://www.cnblogs.com/xhwy/archive/2012/05/20/2510178.html

(2)吴秦,《ASP.NET 应用程序与页面生命周期(意译)》,http://www.cnblogs.com/skynet/archive/2010/04/29/1724020.html

(3)风尘浪子,《C#综合揭秘—细说进程、应用程序域与上下文之间的关系》,http://www.cnblogs.com/skynet/archive/2010/04/29/1724020.html

(4)菩提树下的杨过,《温故而知新:HttpApplication,HttpModule,HttpContext及Asp.Net页生命周期》,http://www.cnblogs.com/yjmyzz/archive/2010/03/28/1698968.html

(5)MSDN,《ASP.NET页面生命周期概述》,http://msdn.microsoft.com/zh-cn/library/ms178472.aspx

(6)皱华栋,《ASP.NET!=拖控件之2011版视频教程》,http://bbs.itcast.cn/thread-8439-1-1.html

【译】ASP.NET应用程序和页面生命周期