您要在 ASP.NET 中构建 Web 应用程序,您希望通过使用内置的 Page Controller(页面控制器)来利用 ASP.NET 的事件驱动特性。
实现策略
默认情况下,Page Controller 模式中所描述的概念是在 ASP.NET 中实现的。ASP.NET 页面框架实现这些概念所采取的方式使得在客户端上捕获事件、将其传输到服务器并调用适当方法这一系列操作的基本机制是自动进行的,并且对实现者来说是不可见的。页面控制器是可扩展的,因为它会在生命周期的特定点上公开各种事件(请参阅此模式后面的"页面生命周期"),因此,与应用程序具体相关的操作可以在适当的时候运行。
例如,假定用户正在与包含一个按钮服务器控件的 Web 窗体页进行交互(请参阅此模式后面的"简单页面示例")。当用户单击按钮控件时,一个事件将作为 HTTP 投递内容传送到服务器,在那里,ASP.NET 页面框架会解释投递的信息,并将引发的事件与适当的事件处理程序相关联。框架自动调用该按钮的适当事件处理程序,作为框架的正常处理的一部分。因此,您不再需要实现此功能。此外,您还可以使用内置控制器,或者,您可以用自己自定义的控制器来代替内置控制器(请参阅 Front Controller)。
页面生命周期
下面按发生顺序列出了页面生命周期中最常见的各个阶段。其中还包括引发的特定事件,以及处理请求时在各个阶段可能执行的一些典型操作:
ASP.NET 页面框架初始化(事件:Init)。这是生命周期的第一个步骤,该步骤将初始化 ASP.NET 运行库以便为响应请求做好准备。
用户代码初始化(事件:Load)。您应该执行与应用程序具体相关的常见任务,例如,当页面控制器引发 Load 事件时打开数据库连接。您可以假设:引发 Load 事件后,服务器控件已创建并完成初始化、状态已还原并且窗体控件反映了客户端的更改。 [Reilly02]
与应用程序相关的事件处理。在此阶段,您应该执行与应用程序相关的处理,以响应控制器引发的事件。 .
清理(事件:Unload)。该页面已完成生成,现在可以丢弃。您应该关闭 Load 事件打开的任何数据库连接,丢弃任何不再需要的对象。在连接对象被作为垃圾回收后,Microsoft?.NET Framework 将自动关闭数据库连接。不过,您对何时进行垃圾回收没有任何控制权。因此,显式关闭数据库连接以充分利用数据库连接池是一个很好的做法。
注意:还有几个页面处理阶段没有在这里列出。不过,这些阶段不用于大多数页面处理情况。
简单页面示例
第一个示例是一个简单页面,它接受来自用户的输入,然后在屏幕上显示该输入。该示例说明了 ASP.NET 用于实现服务器控件的事件驱动模型。
图 1: 简单页面
当用户键入他或她的名字、然后单击"Click Here"按钮后,键入的名字将直接出现在按钮下面,如图 2 所示。
图 2: 显示用户输入的简单页面
在 ASP.NET 网页中,用户界面编程分为两个不同的部分:可视组件(或视图)和结合了模型和控制器的逻辑。这种划分将页面的可视部分(视图)同与页面交互的、页面后面的代码(模型和控制器)分离开来。
可视元素称为 Web 窗体页。该页面由包含静态 HTML 服务器控件或 ASP.NET 服务器控件(或同时包含这两种控件)的文件构成。在此示例中,Web 窗体页名为 SimplePage.aspx,它由以下代码组成:
<%@ Page language="c#" Codebehind="SimplePage.aspx.cs" AutoEventWireup="false" Inherits="SimplePage" %>
<HTML>
<body>
<form id="Form1" runat="server">
Name:<asp:textbox id="name" runat="server" />
<p />
<asp:button id="MyButton" text="Click Here" OnClick="SubmitBtn_Click" runat="server" />
<p />
<span id="mySpan" runat="server"></span>
</form>
</body>
</HTML>
Web 窗体页的逻辑由为了与窗体进行交互而创建的代码构成。编程逻辑放在一个与用户界面文件分离的文件中。此文件被称为"代码隐藏"文件,文件名是 SimplePage.aspx.cs:
using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
public class SimplePage : System.Web.UI.Page
{
protected System.Web.UI.WebControls.TextBox name;
protected System.Web.UI.WebControls.Button MyButton;
protected System.Web.UI.HtmlControls.HtmlGenericControl mySpan;
public void SubmitBtn_Click(Object sender, EventArgs e)
{
mySpan.InnerHtml = "Hello, " + name.Text + ".";
}
}
此代码的用途是通知页面控制器:当用户单击按钮后,将向服务器发送一个请求,并执行 SubmitBtn_Click 函数。
此实现显示了连接到控制器所提供的事件是多么简单。它还说明,用这种方式编写的代码更易于理解,因为应用程序逻辑没有与管理事件调度的低级代码结合起来。
公用外观示例
下面的示例使用页面控制器的典型实现策略来提供显示动态内容的横幅,该横幅在应用程序的每一页上显示已验证的用户的电子邮件地址(该地址是从数据库检索的)。
站点内的所有页面对象所继承的基类中包含了公用实现。图 3 显示了站点中的一个网页。
图 3: 显示动态内容的横幅
站点中的各个页面负责呈现自己的内容,而基类则负责呈现头信息。因为各个页面是从基类继承的,所以它们都具有相同的功能。
此实现使用了称为 Template Method的设计模式。该模式在一个操作中定义了一个算法的框架,而将一些步骤交给子类完成。Template Method 允许子类重新定义算法的某些步骤,而不必更改该算法的结构。 [Gamma95]
将 Template Method 应用于此问题需要将公用代码从各个页面移到一个基类中。这样可以确保公用代码放在一个地方,并且很容易维护。在此示例中,基类名为 BasePage 并负责将 Page_Load 方法连接到 Load 事件。与 BasePage 相关的工作(即从数据库检索用户的电子邮件地址和设置站点名)完成后,Page_Load 函数将调用名为 PageLoadEvent 的方法。子类实现 PageLoadEvent,以执行它们自己的特定 Load 功能。图 4 显示了此解决方案的结构。
图 4: 代码隐藏页面实现的结构
请求网页时,ASP.NET 运行库会触发 Load 事件,该事件再调用 BasePage 的 Page_Load 方法。BasePage 方法检索所需数据