ASP.NET 页面对象模型

 

迪诺·埃斯波西托
Wintellect

2003 年 8 月

适用于:
    Microsoft® ASP.NET

摘要:了解围绕 ASP.NET 网页构建的事件模型,以及网页在 HTML 时经历的各个阶段。 ASP.NET HTTP 运行时控制对象的管道,这些对象首先将请求的 URL 转换为页面类的动态实例,然后转换为纯 HTML 文本。 发现具有页面生命周期特征的事件,以及控件和页面作者如何干预以更改标准行为。 ) (6 个打印页

目录

简介
Real Page 类
页面生命周期
执行阶段
总结

简介

对命中 Microsoft® Internet Information Services (IIS) 的 Microsoft® ASP.NET 页面的每个请求都会移交给 ASP.NET HTTP 管道。 HTTP 管道是一系列托管对象,这些对象按顺序处理请求,并实现从 URL 到纯 HTML 文本的转换。 HTTP 管道的入口点是 HttpRuntime 类。 ASP.NET 基础结构为每个托管在工作进程内的 AppDomain 创建一个此类实例, (请记住,工作进程为每个当前运行) ASP.NET 应用程序维护一个不同的 AppDomain。

HttpRuntime 类从内部池中选取 HttpApplication 对象,并将其设置为处理请求。 HTTP 应用程序管理器完成main任务是找出实际处理请求的类。 当请求针对 .aspx 资源时,处理程序是一个页面处理程序,即继承自 Page 的类的实例。 资源类型和处理程序类型之间的关联存储在应用程序的配置文件中。 更确切地说,默认的映射集在 <machine.config 文件的 httpHandlers> 部分中定义。 但是,应用程序可以在本地web.config文件中自定义其自己的 HTTP 处理程序列表。 下面的行演示了定义 .aspx 资源的 HTTP 处理程序的代码。

<add verb="*" path="*.aspx" type="System.Web.UI.PageHandlerFactory"/>

扩展可以与处理程序类相关联,或者通常与处理程序工厂类相关联。 在所有情况下,负责请求的 HttpApplication 对象获取实现 IHttpHandler 接口的对象。 如果根据 HTTP 处理程序解析关联资源/类,则返回的类将直接实现 接口。 如果资源绑定到处理程序工厂,则需要执行额外的步骤。 处理程序工厂类实现 IHttpHandlerFactory 接口,其 GetHandler 方法将返回基于 IHttpHandler 的对象。

HTTP 运行时如何关闭圆圈并处理页面请求? IHttpHandler 接口具有 ProcessRequest 方法。 通过在表示所请求页面的 对象上调用此方法,ASP.NET 基础结构将启动将为浏览器生成输出的进程。

Real Page 类

特定页面的 HTTP 处理程序的类型取决于 URL。 首次调用 URL 时,将编写一个新类并将其动态编译为程序集。 类的源代码是检查 .aspx 源的解析过程的结果。 类定义为命名空间 ASP 的一部分,并被赋予一个模拟原始 URL 的名称。 例如,如果 URL 终结点为 page.aspx,则类的名称为 ASP。Page_aspx。 不过,可以通过在 @Page 指令中设置 ClassName 属性,以编程方式控制类名。

HTTP 处理程序的基类为 Page。 此类定义所有页面处理程序共享的最小方法和属性集。 Page 类实现 IHttpHandler 接口。

在某些情况下,实际处理程序的基类不是 Page ,而是不同的类。 例如,如果使用代码隐藏,则会发生这种情况。 代码隐藏是一种开发技术,可将页面所需的代码隔离到单独的 C# 或 Microsoft Visual Basic® .NET 类中。 页面的代码是实际创建页面行为的事件处理程序和帮助程序方法集。 可以使用脚本 runat=server> 标记以内联<方式定义此代码,也可以将其放置在外部类(代码隐藏类)中。 代码隐藏类是从 Page 继承的类,并使用额外的方法对其进行专用化。 指定后,代码隐藏类将用作 HTTP 处理程序的基类。

HTTP 处理程序不基于 Page 的另一种情况是,应用程序的配置文件在 pages> 节中包含 PageBaseType 属性的<重定义。

<pages PageBaseType="Classes.MyPage, mypage" />

PageBaseType 属性指示类型和包含页处理程序基类的程序集。 此类派生自 Page,可以自动为处理程序赋予一组自定义和扩展的方法和属性。

页面生命周期

完全标识 HTTP 页面处理程序类后,ASP.NET 运行时调用处理程序的 ProcessRequest 方法来处理请求。 通常,无需更改 方法的实现,因为它由 Page 类提供。

此实现首先调用 FrameworkInitialize 方法, 该方法为页面生成控件树。 方法是 TemplateControl 类( Page 本身派生自的类)的受保护虚拟成员。 .aspx 资源的任何动态生成的处理程序都会替代 FrameworkInitialize。 在此方法中,将生成页面的整个控件树。

接下来, ProcessRequest 使页面传输的各个阶段:初始化、加载视图状态信息和回发数据、加载页面的用户代码以及执行回发服务器端事件。 之后,页面进入呈现模式:收集更新的视图状态;生成 HTML 代码,然后发送到输出控制台。 最后,卸载页面并认为请求已完全得到处理。

在各个阶段,页面会触发 Web 控件和用户定义的代码可以截获和处理的一些事件。 其中一些事件特定于嵌入控件,随后无法在 .aspx 代码级别处理。

想要处理特定事件的页面应显式注册相应的处理程序。 但是,为了与早期的 Visual Basic 编程样式向后兼容,ASP.NET 还支持某种形式的隐式事件挂钩。 默认情况下,页面尝试将特殊方法名称与事件匹配;如果找到匹配项,则该方法被视为 事件的处理程序。 ASP.NET 提供对六个方法名称的特殊识别。 它们是 Page_InitPage_LoadPage_DataBindPage_PreRenderPage_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 方法
页面加载 加载  
回发更改通知   任何实现 IPostBackDataHandler 接口的控件中的 RaisePostDataChangedEvent 方法
回发事件处理 控件定义的任何回发事件 任何实现 IPostBackEventHandler 接口的控件中的 RaisePostBackEvent 方法
页面预呈现阶段 PreRender  
查看状态保存   SaveViewState
页面呈现   呈现
页面卸载 卸载  

上面列出的某些阶段在页面级别不可见,仅影响服务器控件的作者和恰好创建从 Page 派生的类的开发人员。 InitLoadPreRenderUnload 以及嵌入控件定义的所有回发事件是页面发送到外部世界的唯一生命信号。

执行阶段

页面生命周期的第一个阶段是初始化。 此阶段的特点是 Init 事件,该事件在成功创建页面的控制树后将触发到应用程序。 换句话说, 当 Init 事件到达时,已在 .aspx 源文件中静态声明的所有控件都已实例化并保留其默认值。 控件可以挂接 Init 事件,以初始化传入 Web 请求的生存期内所需的任何设置。 例如,此时控件可以加载外部模板文件或设置事件的处理程序。 应注意到目前还没有可用的视图状态信息。

初始化后,页面框架立即加载页面的视图状态。 视图状态是名称/值对的集合,其中控件和页面本身存储必须跨 Web 请求持久化的任何信息。 视图状态表示页面的调用上下文。 通常,它包含上次在服务器上处理页面时控件的状态。 在会话中首次请求页面时,视图状态为空。 默认情况下,视图状态存储在以无提示方式添加到页面的隐藏字段中。 此字段的名称__VIEWSTATE。 通过重写 LoadViewState 方法( Control 类上的受保护可重写方法),组件开发人员可以控制如何还原视图状态及其内容如何映射到内部状态。

LoadPageStateFromPersistenceMedium 及其对应的 SavePageStateToPersistenceMedium 等方法可用于将视图状态加载并保存到备用存储介质(例如会话、数据库或服务器端文件)。 与 LoadViewState 不同,上述方法仅在派生自 Page 的类中可用。

还原视图状态后,页面树中的控件的状态与上次将页面呈现到浏览器时的状态相同。 下一步包括更新其状态以合并客户端更改。 回发数据处理阶段使控件有机会更新其状态,以便准确反映客户端上相应 HTML 元素的状态。 例如,服务器 TextBox 控件在 input type=text> 元素中<具有其 HTML 对应项。 在回发数据阶段,TextBox 控件将检索输入>标记的<当前值,并使用它刷新其内部状态。 每个控件负责从已发布的数据中提取值并更新其某些属性。 TextBox 控件将更新其 Text 属性,而 CheckBox 控件将刷新其 Checked 属性。 服务器控件和 HTML 元素之间的匹配项在两者的 ID 上找到。

在回发数据处理阶段结束时,页面中的所有控件都反映在客户端上输入的更改更新的以前状态。 此时, Load 事件将触发到页面。

如果跨两个不同的请求修改敏感属性,则页面中可能存在需要完成某些任务的控件。 例如,如果在客户端上修改了文本框控件的文本,该控件将触发 TextChanged 事件。 如果使用来自客户端的值修改了其一个或多个属性,则每个控件都可以做出触发相应事件的决策。 这些更改至关重要的控件实现 IPostBackDataHandler 接口,其 LoadPostData 方法在 Load 事件之后立即调用。 通过对 LoadPostData 方法进行编码,控件将验证自上次请求以来是否发生了任何关键更改,并触发其自己的更改事件。

页面生命周期中的关键事件是调用它以执行与客户端上触发的事件关联的服务器端代码。 当用户单击按钮时,页面会发回。 已发布值的集合包含启动整个操作的按钮的 ID。 如果已知控件实现 IPostBackEventHandler 接口 (按钮和链接按钮将执行) ,则页面框架调用 RaisePostBackEvent 方法。 此方法的作用取决于控件的类型。 对于按钮和链接按钮,方法查找 Click 事件处理程序并运行关联的委托。

处理回发事件后,页面将准备呈现。 此阶段由 PreRender 事件发出信号。 这是控件执行需要在保存视图状态和呈现输出之前立即执行的任何最后一分钟更新操作的好时机。 下一个状态是 SaveViewState,其中邀请所有控件和页面本身刷新其自己的 ViewState 集合的内容。 然后,生成的视图状态经过序列化、哈希处理、Base64 编码,并与__VIEWSTATE隐藏字段相关联。

可以通过重写 Render 方法来更改各个控件的 呈现 机制。 方法采用 HTML 编写器对象,并使用它来累积要为控件生成的所有 HTML 文本。 Page 类的 Render 方法的默认实现包括对所有构成控件的递归调用。 对于每个控件,页面调用 Render 方法并缓存 HTML 输出。

页面生存期的最后一个标志是在关闭页面对象之前到达的 Unload 事件。 在这种情况下,应释放可能具有 (的任何关键资源,例如文件、图形对象数据库连接) 。

最后,在此事件之后,浏览器将接收 HTTP 响应数据包并显示页面。

总结

由于事件机制,ASP.NET 页对象模型特别具有创新性。 网页由控件组成,这些控件生成基于 HTML 的丰富用户界面,并通过事件与用户交互。 在 Web 应用程序上下文中设置事件模型具有挑战性。 令人吃惊的是,客户端生成的事件是使用服务器端代码解析的,并且此输出显示为同一 HTML 页面,仅经过正确修改。

若要理解此模型,必须了解页面生命周期的各个阶段,以及 HTTP 运行时如何实例化和使用页面对象。

关于作者

迪诺·埃斯波西托 是意大利罗马的培训师和顾问。 Dino 是 Wintellect 团队的成员,专门从事 ASP.NET 和 ADO.NET,大部分时间都花在欧洲和美国进行教学和咨询。 特别是,Dino 管理 Wintellect 的 ADO.NET 课件,并为 MSDN 杂志撰写“前沿”栏目。 在 联系 dinoe@wintellect.com

© Microsoft Corporation. 保留所有权利。