ASP.NET页面生存周期介绍
一旦完全确定 HTTP 页面处理程序类,ASP.NET 运行时就调用该处理程序的 ProcessRequest 方法以处理请求。通常情况下,无需更改此方法的实现方式,因为它是由 Page 类提供的。
此实现方法一开始就调用 FrameworkInitialize 方法,以此建立页面的控件树。此方法是 TemplateControl 类(Page 类本身就是从该类派生出来的)的一个受保护的虚拟成员。任何针对 .aspx 资源而动态生成的处理程序都重写 FrameworkInitialize。在此方法中,该页面的完整控件树得以构建。
接下来,ProcessRequest 使该页面经历若干阶段:初始化,加载视图状态信息和回发数据,加载页面的用户代码并执行回发服务器端事件。随后,该页面进入呈现模式:收集更新后的视图状态;生成 HTML 代码然后将其发送到输出控制台。最后,卸载页面,并认为已完成对该请求的处理。
在各个阶段中,页面都会激发一些 Web 控件和用户定义的代码所能截获并处理的事件。其中的一些事件是嵌入式控件专用的,因而并不能在 .aspx 代码级进行处理。
如果页面想要处理某个事件,它应该显式地注册相应的处理程序。然而,为了向后兼容早期的 Visual Basic 编程风格,ASP.NET 也支持一种隐式的事件挂起形式。在默认情况下,页面将尝试把特定的方法名与事件匹配起来;如果找到匹配的方法,就认为该方法是该事件的处理程序。ASP.NET 提供了六个方法名的特定识别。它们是 Page_Init、Page_Load、Page_DataBind、Page_PreRender 和 Page_Unload。这些方法被当作是 Page 类所提供的相应事件的处理程序。HTTP 运行时将自动把这些方法与页面事件绑定起来,这样一来,开发人员就不必编写所需的粘接代码。例如,名为 Page_Load 的方法与页面的 Load 事件绑定,就像已编写以下代码一样。
- this.Load += new EventHandler(this.Page_Load);
这种自动识别特殊名称的功能由 @Page 指令的 AutoEventWireup 属性控制。如果将该属性设置为 false,则任何想要处理某个事件的应用程序都需显式地连接到该页面事件。如果页面不使用自动事件关联功能,就不必进行额外的操作以匹配各名称和事件,从而其性能也稍有提升。应该注意的是,所有的 Microsoft Visual Studio.NET 项目在创建时都禁用了 AutoEventWireup 属性。然而,此属性的默认设置为 true,意味着诸如 Page_Load 等方法会被识别并被绑定到相关的事件。
页面的执行过程包括下面表格中所列的一系列阶段,并以具有一些应用程序级事件和/或受保护且可重写的方法为特征。
表格 1. ASP.NET页面生存周期中的关键事件
阶段 |
页面事件 |
可重写方法 |
---|---|---|
页面初始化 |
Init |
|
加载视图状态 |
LoadViewState | |
处理回发数据 |
实现 IPostBackDataHandler 接口的任何控件中的 LoadPostData 方法 | |
加载页面 |
Load |
|
回发更改通知 |
实现 IPostBackDataHandler 接口的任何控件中的 RaisePostDataChangedEvent 方法 | |
处理回发事件 |
控件所定义的任何回发事件 |
实现了 IPostBackEventHandler 接口的任何控件的 RaisePostBackEvent 方法 |
页面呈现前阶段 |
PreRender |
|
保存视图状态 |
SaveViewState | |
呈现页面 |
Render | |
卸载页面 |
Unload |
在页面级上,以上所列的某些阶段是不可见的,并仅影响服务器控件编写者和那些凑巧要创建从 Page 派生的类的开发人员。页面向外界发送的活动信号仅包括 Init、Load、PreRender、Unload 以及嵌入式控件所定义的所有回发事件。
ASP.NET页面生存周期:执行的各个阶段
页面生存周期中的第一个阶段是初始化。这一阶段的标志就是 Init 事件,在成功创建页面的控件树后,对应用程序激发这个事件。换而言之,当 Init 事件发生时,在 .aspx 源文件中静态声明的所有控件都已实例化并取其默认值。控件可挂起 Init 事件,以便初始化在传入的 Web 请求的生存周期中所需的任何设置。例如,此时控件可以加载外部模板文件或设置各个事件的处理程序。应该注意到,这时还没有视图状态信息可供使用。
在初始化之后,页面框架立即加载该页面的视图状态。所谓视图状态就是一些名称/值对的集合,控件和页面本身可将那些对所有 Web 请求都必须始终有效的任何信息存储在其中。视图状态表示页面的调用上下文。一般情况下,其中包含上次在服务器中处理该页面时各控件的状态。首次在会话中请求页面时,视图状态为空。在默认情况下,视图状态被存储在一个隐藏字段中,而该字段是自行添加到页面中的。该字段名称为 __VIEWSTATE。通过重写 LoadViewState 方法(Control 类的一个受保护且可重写的方法)组件开发人员可控制如何还原视图状态以及如何将其内容映射到内部状态。
有些方法(如 LoadPageStateFromPersistenceMedium 及其相对的 SavePageStateToPersistenceMedium)可用于将视图状态加载并保存到别的存储介质(例如会话、数据库或服务器端的文件)中。与 LoadViewState 不同,上述方法仅在派生自 Page 的各个类中才可使用。
一旦还原了视图状态,页面树中的各个控件的状态就与浏览器上次呈现该页面时这些控件所处的状态相同。下一步包括更新这些控件的状态以加入客户端的变更。回发数据处理阶段使各个控件有机会更新其状态,以便准确地反映相应的 HTML 元素在客户端的状态。例如,一个服务器 TextBox 控件对应的 HTML 元素是 <input type=text>。在回发数据阶段,TextBox 控件将检索 <input> 标记的当前值并用它刷新其内部状态。每个控件负责从已发送的数据中提取相应值,并更新其某些属性。TextBox 控件将更新其 Text 属性,而 CheckBox 控件将刷新其 Checked 属性。服务器控件和 HTML 元素之间的匹配关系由二者的 ID 确定。
在回发数据处理阶段结束时,页面中的所有控件都根据客户端上所输入的更改来更新原先的状态。此时,对页面激发 Load 事件。
如果在处理两个不同的请求时某个敏感的属性被修改,则页面上可能有些控件需要完成某些任务。例如,如果在客户端修改了某个文本框控件的文本,则该控件激发 TextChanged 事件。如果利用来自客户端的值对该控件的一个或多个属性进行修改,每个控件可以决定激发一个适当的事件。对控件而言,如果这些更改是至关重要的,则这些控件实现 IPostBackDataHandler 接口,在 Load 事件之后立即调用该接口的 LoadPostData 方法。通过编写 LoadPostData 方法的代码,一个控件可以确认自最近一次请求以来是否发生了任何关键的更改,并激发自己的更改事件。
页面生存周期内的关键事件就是:它被调用来执行与客户端上所激发的某个事件相关联的服务器端代码。当用户单击某个按钮时,页面回发数据。已发送值的集合中包含该按钮(该按钮启动整个操作)的 ID。如果已知该控件实现了 IPostBackEventHandler 接口(按钮和链接按钮将实现此接口),则页面框架调用 RaisePostBackEvent 方法。此方法所进行的操作取决于相应控件的类型。对于按钮和链接按钮,此方法查找 Click 事件处理程序并运行相关的委托。
在处理回发事件后,页面就准备进行呈现。这一阶段的标志是 PreRender 事件。各个控件可利用这个很好的时机,以便执行任何需要在保存视图状态和呈现输出结果的前一刻完成的最后一些更新操作。下一个状态为 SaveViewState,在这一状态中所有控件以及页面本身可以刷新自己的 ViewState 集合的内容。所得到的视图状态随后得以序列化、进行哈希运算、进行 Base64 编码并关联到 __VIEWSTATE 隐藏字段。
通过重写 Render 方法,即可更改各个控件的呈现机制。该方法获取一个 HTML 编写器对象,并使用该对象聚集所有将针对该控件生成的 HTML 文本。Page 类的 Render 方法的默认实现方式包括对所有成员控件的递归调用。对于每个控件,页面都调用 Render 方法并将 HTML 输出放入高速缓存。
一个页面的最后生存标志就是 Unload 事件,该事件在页面对象被解除之前发生。在此事件中,您应该释放可能占用的任何关键资源(例如,文件、图形对象、数据库连接)。
终于,在此事件之后,浏览器收到 HTTP 响应数据包并显示页面。
【编辑推荐】