摘要了解为 ASPNET Web 页面建立的事件模型以及 Web 页面转变为 HTML 过程中的各个阶段ASPNET HTTP 运行时负责管理对象管道这些对象首先将请求的 URL 转换成 Page 类的具体实例然后再将这些实例转换成纯 HTML 文本本文将探讨那些作为页面生命周期标志的事件以及控件和页面编写者如何干预并改变标准行为
简介
对由 Microsoft? Internet 信息服务 (IIS) 处理的 Microsoft? ASPNET 页面的每个请求都会被移交到 ASPNET HTTP 管道HTTP 管道由一系列托管对象组成这些托管对象按顺序处理请求并将 URL 转换为纯 HTML 文本HTTP 管道的入口是 HttpRuntime 类ASPNET 结构为辅助进程中的每个 AppDomain 创建一个此类的实例(请注意辅助进程为每个当前正在运行的 ASPNET 应用程序维护一个特定的 AppDomain)
HttpRuntime 类从内部池中获取 HttpApplication 对象并安排此对象来处理请求HTTP 应用程序管理器完成的主要任务就是找到将真正处理请求的类当请求 aspx 资源时处理程序就是页面处理程序即从 Page 继承的类的实例资源类型和处理程序类型之间的关联关系存储在应用程序的配置文件中更确切地说默认的映射集是在 machineconfig 文件的 <httpHandlers> 部分定义的但是应用程序可以在本地的 webconfig 文件中自定义自己的 HTTP 处理程序列表以下这一行代码就是用来为 aspx 资源定义 HTTP 处理程序的
<add verb=* path=*aspx type=SystemWebUIPageHandlerFactory/>
扩展名可以与处理程序类相关联并且更多是与处理程序工厂类相关联在所有情况下负责处理请求的 HttpApplication 对象都会获得一个实现 IHttpHandler 接口的对象如果根据 HTTP 处理程序来解析关联的资源/类则返回的类将直接实现接口如果资源被绑定到处理程序工厂则还需要额外的步骤处理程序工厂类实现 IHttpHandlerFactory 接口此接口的 GetHandler 方法将返回一个基于 IHttpHandler 的对象
HTTP 运行时是如何结束这个循环并处理页面请求的?ProcessRequest 方法在 IHttpHandler 接口中非常重要通过对代表被请求页面的对象调用此方法ASPNET 结构会启动将生成浏览器输出的进程
真正的 Page 类
特定页面的 HTTP 处理程序类型取决于 URL首次调用 URL 时将构建一个新的类这个类被动态编译为一个程序集检查 aspx 资源的分析进程的结果是类的源代码该类被定义为命名空间 ASP 的组成部分并且被赋予了一个模拟原始 URL 的名称例如如果 URL 的终点是 pageaspx则类的名称就是 ASPPage_aspx不过类的名称可以通过编程方式来控制方法是在 @Page 指令中设置 ClassName 属性
HTTP 处理程序的基类是 Page这个类定义了由所有页面处理程序共享的方法和属性的最小集合Page 类实现 IHttpHandler 接口
在很多情况下实际处理程序的基类并不是 Page而是其他的类例如如果使用了代码分离就会出现这种情况代码分离是一项开发技术它可以将页面所需的代码隔离到单独的 C# 和 Microsoft Visual Basic? NET 类中页面的代码是一组事件处理程序和辅助方法这些处理程序和方法真正决定了页面的行为可以使用 <script runat=server> 标记对此代码进行内联定义或者将其放置在外部类(代码分离类)中代码分离类是从 Page 继承并使用额外的方法的类被指定用作 HTTP 处理程序的基类
还有一种情况HTTP 处理程序也不是基于 Page 的即在应用程序配置文件的 <pages> 部分中包含了 PageBaseType 属性的重新定义
<pages PageBaseType=ClassesMyPage mypage />
PageBaseType 属性指明包含页面处理程序的基类的类型和程序集从 Page 导出的这个类可以自动赋予处理程序扩展的自定义方法和属性集
页面的生命周期
完全识别 HTTP 页面处理程序类后ASPNET 运行时将调用处理程序的 ProcessRequest 方法来处理请求通常情况下无需更改此方法的实现因为它是由 Page 类提供的
此实现将从调用为页面构建控件树的 FrameworkInitialize 方法开始FrameworkInitialize 方法是 TemplateControl 类(Page 本身从此类导出)的一个受保护的虚拟成员所有为 aspx 资源动态生成的处理程序都将覆盖 FrameworkInitialize在此方法中构建了页面的整个控件树
接下来ProcessRequest 使页面经历了各个阶段初始化加载视图状态信息和回发数据加载页面的用户代码以及执行回发服务器端事件之后页面进入显示模式收集更新的视图状态生成 HTML 代码并随后将代码发送到输出控制台最后卸载页面并认为请求处理完毕
在各个阶段中页面会触发少数几个事件这些事件可以由 Web 控件和用户定义的代码截取并进行处理其中的一些事件是嵌入式控件专用的因此无法在 aspx 代码级进行处理
要处理特定事件的页面应该明确注册一个适合的处理程序不过为了向后兼容早期的 Visual Basic 编程风格ASPNET 也支持隐式事件挂钩的形式默认情况下页面会尝试将特定的方法名称与事件相匹配如果实现匹配则认为此方法就是匹配事件的处理程序ASPNET 提供了六种方法名称的特定识别它们是 Page_InitPage_LoadPage_DataBindPage_PreRender 和 Page_Unload这些方法被认为是由 Page 类提供的相应事件的处理程序HTTP 运行时会自动将这些方法绑定到页面事件这样开发人员就不必再编写所需的粘接代码了例如如果命名为 Page_Load 的方法绑定到页面的 Load 事件则可省去以下代码
thisLoad += new EventHandler(thisPage_Load);
对特定名称的自动识别是由 @Page 指令的 AutoEventWireup 属性控制的如果该属性设置为 false则要处理事件的所有应用程序都需要明确连接到页面事件不使用自动绑定事件的页面性能会稍好一些因为不需要额外匹配名称与事件请注意所有 Microsoft Visual Studio? NET 项目都是在禁用 AutoEventWireup 属性的情况下创建的但是该属性的默认设置是 true即 Page_Load 等方法会被识别并被绑定到相关联的事件
下表中按顺序列出了页面的执行包括的几个阶段执行的标志是一些应用程序级的事件和/或受保护并可覆盖的方法
表 ASPNET 页面生命中的关键事件
以上所列的阶段中有些在页面级是不可见的并且仅对服务器控件的编写者和要创建从 Page 导出的类的开发人员有意义InitLoadPreRenderUnload再加上由嵌入式控件定义的所有回发事件就构成了向外发送页面的各个阶段标记
执行的各个阶段
页面生命周期中的第一个阶段是初始化这个阶段的标志是 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 事件
页面中可能会有一些控件当其某个敏感属性在两个不同的请求中被修改时需要完成特定的任务例如如果 TextBox 控件的文本在客户端被修改则此控件将触发 TextChanged 事件每个控件在其一个或多个属性被修改为客户端输入的值时都可以决定触发相应的事件对于这些更改对其非常关键的控件控件实现 IPostBackDataHandler 接口此接口的 LoadPostData 方法是在 Load 事件后立即调用的通过对 LoadPostData 方法进行编码控件将验证自上次请求后是否发生了关键更改并触发自己的更改事件
页面生命周期中的关键事件是被调用以执行服务器端代码的事件此代码与客户端触发的事件相关联当用户单击按钮时将回发页面回发值的集合中包括启动整个操作的按钮的 ID如果控件实现 IPostBackEventHandler 接口(如按钮和链接按钮)页面框架将调用 RaisePostBackEvent 方法此方法的行为取决于控件的类型就按钮和链接按钮而言此方法将查找 Click 事件处理程序并运行相关的委托
处理完回发事件之后页面就可以显示了这个阶段的标志是 PreRender 事件控件可以利用这段时间来执行那些需要在保存视图状态和显示输出的前一刻执行的更新操作下一个状态是 SaveViewState在此状态中所有控件和页面本身都将更新自己 ViewState 集合的内容然后将得到序列化散列Base 编码的视图状态而且此视图状态与隐藏字段 __VIEWSTATE 相关联
通过覆盖 Render 方法可以改变各个控件的显示机制此方法接受 HTML 书写器对象并使用此对象来积累所有要为控件生成的 HTML 文本Page 类的 Render 方法的默认实现包括对所有成员控件的递归调用对于每个控件页面都将调用 Render 方法并缓存 HTML 输出
页面生命中的最后一个标志是 Unload 事件在页面对象消除之前发生在此事件中您应该释放所有可能占用的关键资源(例如文件图形对象数据库连接等)
在此事件之后也就是最后浏览器接收 HTTP 响应数据包并显示页面
小结
ASPNET 页面对象模型因其事件机制而显得格外新颖独特Web 页面由控件组成这些控件既可以产生丰富的基于 HTML 的用户界面又可以通过事件与用户交互以前在 Web 应用程序的上下文中设置事件模型是件有挑战性的工作可我们惊奇的看到客户端生成的事件可以由服务器端的代码来解决而且只进行一些相应的修改后此过程仍可以输出相同的 HTML 页面
掌握这个模型对于了解页面生命周期的各个阶段以及页面对象如何被 HTTP 运行时实例化并使用是非常重要的