11. 应用程序 [SimuPaie] – 第 7 版 – ASP.NET / 多视图 / 多页面
推荐阅读:参考 [1],使用 ASP.NET 1.1 进行 WEB 开发,第 1 段:示例
现在,我们将研究一个在功能上与之前探讨的三层应用程序 ASP.NET 完全相同的版本 [pam-v4-3tier-nhibernate-multivues-monopage],但我们将对其架构进行如下修改: 在之前的版本中,视图由单个页面 ASPX 实现,而在这里,它们将由三个页面 ASPX 实现。
先前应用程序的架构如下:
![]() |
这里采用的是 MVC 架构(模型-视图-控制器):
- [Default.aspx.cs] 包含控制器代码。页面 [Default.aspx] 是客户端的唯一对接点,处理来自客户端的所有请求。
- [Saisies, Simulation, Simulations, ...] 代表视图。这些视图在此通过 [Default.aspx] 页面的 [View] 组件实现。
新版本的架构如下:
![]() |
- 仅 [web] 层发生变更
- 界面(即向用户展示的内容)保持不变。
- 控制器代码在上一版本中全部位于 [Default.aspx.cs] 中,现已分散到多个页面:
- [MasterPage.master]:该页面提取了各视图共有的内容:顶部横幅及其菜单选项
- [Formulaire.aspx]:展示模拟表单并管理该表单上操作的页面
- [Simulations.aspx]:展示模拟列表并管理该页面上操作的页面
- [Erreurs.aspx]:应用程序初始化错误时显示的页面。该页面不支持任何操作。
可以认为这里采用的是多控制器架构 MVC,而上一版本的架构则是单控制器架构 MVC。
客户端请求的处理流程如下:
- 客户端向应用程序发起请求。该请求发送至两个页面之一:[Formulaire.aspx, Simulations.aspx]。
- 被请求的页面处理该请求。 为此,该页面可能需要 [métier] 层的协助;若需与数据库交换数据,该层又可能需要 [dao] 层的协助。应用程序从 [métier] 层接收响应。
- 根据该响应,它选择 (3) 要发送给客户端的视图(即响应),并向客户端提供 (4) 其所需的信息(即模板)。
- 响应被发送给客户端 (5)
11.1. 应用程序的视图
向用户展示的视图包括:
- - 视图 [VueSaisies],用于展示模拟表单
![]() |
- - 视图 [VueSimulation],用于显示模拟的详细结果:
![]() |
- - 视图 [VueSimulations],显示客户执行的模拟列表
![]() |
- - 视图 [VueSimulationsVides] 显示客户没有或已无模拟:
![]() |
- 视图 [VueErreurs] 显示应用程序初始化错误:
![]() |
11.2. 在多控制器环境中生成视图
在上一版本中,所有视图均由单一页面 [Default.aspx] 生成。 该页面包含两个组件 [MultiView],视图由属于这两个组件的 [View] 组件组合而成。
当视图较少时,这种架构很有效,但一旦构成不同视图的组件数量增多,其局限性便显现出来:实际上,每次向唯一的 [Default.aspx] 页面发出请求时,该页面的所有组件都会被实例化,尽管实际上只有其中一部分会被用于生成给用户的响应。 因此,每次新请求都会产生不必要的计算开销,当页面组件总数较大时,这种开销会变得非常明显。
因此,一个解决方案是将视图分布在不同的页面上。这就是我们在此采取的做法。让我们研究两种不同的视图生成情况:
- 请求发送到页面 P1,该页面生成响应
- 向页面 P1 发起请求,该页面要求页面 P2 生成响应
11.2.1. 情况 1:单控制器/视图页面
在情况 1 中,我们回到了上一版本的单控制器架构,其中页面 [Default.aspx] 即为页面 P1:
![]() |
- 客户端向页面 P1 发起请求 (1)
- 页面 P1 处理该请求。为此,它可能需要 [métier] 层(2)的协助,而该层若需与数据库交换数据,又可能需要 [dao] 层的协助。 应用程序收到来自 [métier] 层的响应。
- 根据该响应,它选择 (3) 要发送给客户端的视图(即响应),并向客户端提供 (4) 其所需的信息(即模板)。 这里需要从页面 P1 中选择要显示的组件 [Panel] 或 [View],并初始化它们所包含的组件。
- 响应被发送给客户端(5)
以下是该应用中的两个示例:
[page Formulaire.aspx]
![]() |
- 在 [1] 中:用户在请求页面 [Formulaire.aspx] 后,请求了一次模拟
- 在 [2] 中:页面 [Formulaire.aspx] 处理了该请求,并自行生成响应,显示了一个在 [1] 中未显示的组件 [View]
[page Simulations.aspx]
![]() |
- 在 [1] 中:用户在请求页面 [Simulations.aspx] 后,希望从
- 在 [2] 中:页面 [Simulations.aspx] 处理了该请求,并自行生成响应,重新显示了新的模拟列表。
![]() |
11.2.2. 情况 2:一个控制器页面,一个控制器/视图页面
情况 2 可能涵盖多种架构。我们将选择以下架构:
![]() |
- 客户端向页面 P1 发起请求 (1)
- 页面 P1 处理该请求。为此,它可能需要 [métier] 层的协助(2),而该层若需与数据库交换数据,则可能需要 [dao] 层的协助。 应用程序收到来自 [métier] 层的响应。
- 根据该响应,它选择 (3) 要发送给客户端的视图(即响应),并向客户端提供 (4) 其所需的信息(即模板)。 此时,需要生成的视图并非由 P1 页面生成,而是由 P2 页面生成。为了执行操作 (3) 和 (4),P1 页面有两种可能:
- 通过操作 [Server.Transfer(" P2.aspx ")] 将执行转移到页面 P2。 在这种情况下,系统可以将用于页面 P2 的模板放入请求上下文 [Context.Items[" clé "]=valeur] 中,或放入用户的会话中 [Session.[" clé "]=valeur]中。 此时将实例化页面 P2,例如在处理其事件 Load 时,它可以通过操作 [valeur=(Type)Context.Items[" clé "]] 或 [valeur=(Type)Session[" clé "]] 操作获取,具体取决于情况,其中 Type 是与该键关联的值的类型。 如果无需保留模型值以供客户端未来查询,则通过上下文 Context 传输值最为合适。
- 通过操作 [Response.Redirect(" P2.aspx ")] 要求客户端重定向至页面 P2。 在这种情况下,页面 P1 会将用于页面 P2 的模板放入会话中,因为请求上下文 Context 会在每次请求结束时被删除。 然而在此处,重定向将导致客户端对 P1 的首次请求结束,并触发该客户端向 P2 发出第二次请求。这形成了两个连续的请求。 众所周知,会话是跨请求保留“记忆”的一种手段。除了会话之外,还有其他解决方案。
- 无论P2如何接管,随后都会回到情况1:P2已收到一个请求并即将处理(5),且将自行生成响应(6、7)。 也可以设想,P2页面在处理完请求后,会将控制权移交给P3页面,以此类推。
以下是该应用中的一个示例:
![]() |
- 转至 [1]:请求页面 [Formulaire.aspx] 的用户要求查看模拟列表
- 在 [2] 中:页面 [Formulaire.aspx] 处理此请求,并将客户端重定向至页面 [Simulations.aspx]。正是后者向用户提供响应。 与其让客户端进行重定向,页面 [Formulaire.aspx] 本可以将客户端的请求转发至页面 [Simulations.aspx]。 在这种情况下,[2] 页面显示的 URL 将与 [1] 页面相同。因为浏览器始终显示最后请求的 URL:
- 在 [1] 中请求的操作指向页面 [Formulaire.aspx]。浏览器会通过 POST 跳转至该页面。
- 如果页面 [Formulaire.aspx] 处理该请求,然后通过 [Server.Transfer(" Simulations.aspx ")] 将其转发至页面 [Simulations.aspx],则仍处于同一请求中。 此时,浏览器将显示 [2],即 URL 页面(该页面由 [Formulaire.aspx] 页面重定向而来,而 [Formulaire.aspx] 页面又由 POST 页面重定向而来)。
- 如果页面 [Formulaire.aspx] 处理了该请求,然后通过 [Response.Redirect(" Simulations.aspx ")] 将其重定向到页面 [Simulations.aspx], 浏览器将发起第二次请求,即 GET 请求,目标为 [Simulations.aspx]。 随后,浏览器将在 [2] 页面上显示 URL,该页面来自 [Simulations.aspx],而 GET 正是发往 [Simulations.aspx] 的请求。 上方的[2]屏幕截图正是如此展示的。
11.3. [web]层级的Visual Web Developer项目
[web]层级的Visual Web Developer项目如下:
![]() |
- 在 [1] 中,我们可以看到:
- 该应用程序的配置文件 [Web.config] 与应用程序 [pam-v4-3tier-nhibernate-multivues-monopage] 的配置文件完全相同。
- 页面 [Default.aspx]——仅将客户端重定向至页面 [Formulaire.aspx]
- 页面 [Formulaire.aspx] 向用户展示模拟表单,并处理与该表单相关的操作
- 页面 [Simulations.aspx]:向用户展示其模拟列表,并处理与该页面相关的操作
- 页面 [Erreurs.aspx],向用户展示一个页面,提示在启动 Web 应用程序时遇到错误。
- 在 [2] 中可以看到项目的引用。
让我们回到新项目的架构:
![]() |
与项目 [pam-v4-3tier-nhibernate-multivues-monopage] 相比,仅视图发生了变化。因此,新项目继承了该项目的部分文件:
- 配置文件 [Web.config]
- 被引用的 DLL[pam-dao-nhibernate, pam-metier-dao-nhibernate, Spring.Core, NHibernate]
- 全局应用程序类 [Global.asax]
- 文件夹 [images, ressources, pam]
为了与正在构建的项目保持一致,我们将确保视图和全局应用程序类的命名空间为 [pam-v7]:
![]() |
11.4. 页面呈现代码
11.4.1. 主页面 [MasterPage.master]
第 11.1 节中介绍的应用程序视图具有一些公共部分,这些部分可以提取到一个主页面中,在 Visual Studio 中称为 Master Page。 例如,下文中的视图 [VueSaisies] 和 [VueSimulationsVides],分别由页面 [Formulaire.aspx] 和 [Simulations.aspx] 生成:
![]() |
这两个视图的共同点在于顶部横幅(标题和菜单选项)。所有将呈现给用户的视图都是如此:它们都将拥有相同的顶部横幅。为了让不同的页面共享同一个布局片段,有多种解决方案,包括以下几种:
- 将该通用片段放入用户组件中。这是 ASP.NET 1.1 版本的主要技术
- 将该公共片段放入主页面。该技术自 ASP.NET 2.0 版本起引入。本文中采用的就是此方法。
要在 Web 应用程序中创建主页面,可按以下步骤操作:
- 右键单击项目 / 添加新元素 / 主页面:
![]() |
添加主页面后,系统会默认在Web应用程序中生成三个文件:
- [MasterPage.master]:主页面的布局代码
- [MasterPage.master.cs]:主页的控件代码
- [Masterpage.Master.designer.cs]:主页面的组件声明
Visual Studio 在 [MasterPage.master] 中生成的代码如下:
<%@ Master Language="C#" AutoEventWireup="true" CodeBehind="MasterPage.master.cs" Inherits="pam_v7.MasterPage" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Untitled Page</title>
<asp:ContentPlaceHolder id="head" runat="server">
</asp:ContentPlaceHolder>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:ContentPlaceHolder id="ContentPlaceHolder1" runat="server">
</asp:ContentPlaceHolder>
</div>
</form>
</body>
</html>
- 第 1 行:<%@ Master ... %> 标签用于将该页面定义为主页面。页面的控件代码将位于由 CodeBehind 属性定义的文件中,且该页面将继承由 Inherits 属性定义的类。
- 第 12-18 行:主页面的表单
- 第14-16行:一个空容器,在我们的应用程序中,它将包含页面[Formulaire.aspx, Simulations.aspx, Erreurs.aspx]。 客户端收到的响应始终是同一页面,即主页面,其中容器 [ContentPlaceHolder1] 将接收由 [Formulaire.aspx, Simulations.aspx, Erreurs.aspx] 页面之一提供的 HTML 数据流。 因此,要更改发送给客户端的页面外观,只需修改主页的外观即可。
- 第 8-9 行:一个空容器,子页面可通过该容器自定义 <head>...</head> 头部。
该源代码的视觉呈现(“设计”选项卡)如下图 (1) 所示。此外,借助工具栏 [Standard] 中的组件 [ContentPlaceHolder] (2),可以添加任意数量的容器。
![]() |
Visual Studio 在 [MasterPage.master.cs] 中生成的控件代码如下:
using System;
public partial class MasterPage : System.Web.UI.MasterPage
{
protected void Page_Load(object sender, EventArgs e)
{
}
}
- 第 3 行:页面 [MasterPage.master] 中 <%@ Master ... %> 指令的 [Inherits] 属性所引用的类继承自类 [System.Web.UI.MasterPage]
在上文中,我们可以看到方法 Page_Load 处理主页面的事件 Load。主页面内部将包含另一个页面。这两个页面的事件 Load 会按什么顺序发生? 这是一条通用规则:组件的 Load 事件发生在其容器的事件之前。因此,在此处,插入到主页面中的页面的 Load 事件将先于主页面本身的事件发生。
若要让 生成一个以先前页面[MasterPage.master]作为母版页的页面,可按以下步骤操作:
![]() |
- 在 [1] 中:右键单击母版页,然后选择 [Ajouter une page de contenu]
- 生成 [2]:系统将生成一个默认页面,此处为 [WebForm1.aspx]。
[WebForm1.aspx] 的呈现代码如下:
<%@ Page Title="" Language="C#" MasterPageFile="~/MasterPage.Master" AutoEventWireup="true" CodeBehind="WebForm1.aspx.cs" Inherits="pam_v7.WebForm1" %>
<asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
</asp:Content>
- 第 1 行:Page 指令及其属性
- MasterPageFile:指由该指令描述的页面的母版文件。符号 ~ 表示项目文件夹。
- 其余参数为常规网页参数 ASP
- 第2-3行:<asp:Content>标签通过ContentPlaceHolderID属性与母版页中的<asp:ContentPlaceHolder>指令逐一关联。 位于上述第2-3行之间的组件,在运行时将被放置在主页面中ID ContentPlaceHolder1的容器内。
通过重命名生成的页面 [WebForm1.aspx],可以构建以 [MasterPage.master] 为母版页的各个页面。
对于我们的应用程序 [SimuPaie],主页的视觉效果如下:
![]() |
编号 | 类型 | 名称 | 角色 |
面板(上图中的粉色部分) | 页眉 | 页面标题 | |
面板(上图为黄色) | 内容 | 页面内容 | |
LinkButton | LinkButtonFaireSimulation | 请求进行模拟计算 | |
LinkButton | LinkButtonEffacerSimulation | 清除输入表单 | |
LinkButton | LinkButtonVoirSimulations | 显示已进行的模拟列表 | |
LinkButton | LinkButtonFormulaireSimulation | 返回输入表单 | |
LinkButton | LinkButtonEnregistrerSimulation | 将当前模拟保存到模拟列表中 | |
LinkButton | LinkButtonTerminerSession | 结束当前会话 |
相应的源代码如下:
<%@ Master Language="C#" AutoEventWireup="true" CodeBehind="MasterPage.master.cs" Inherits="pam_v7.MasterPage" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
<title>Application PAM</title>
</head>
<body background="ressources/standard.jpg">
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server" EnablePartialRendering="true" />
<asp:UpdatePanel runat="server" ID="UpdatePanelPam" UpdateMode="Conditional">
<ContentTemplate>
<asp:Panel ID="entete" runat="server" BackColor="#FFE0C0">
<table>
<tr>
<td>
<h2>
Simulateur de calcul de paie</h2>
</td>
<td>
<label>
 </label>
<asp:UpdateProgress ID="UpdateProgress1" runat="server">
<ProgressTemplate>
<img alt="" src="images/indicator.gif" />
<asp:Label ID="Label5" runat="server" BackColor="#FF8000"
EnableViewState="False" Text="Calcul en cours. Patientez ....">
</asp:Label>
</ProgressTemplate>
</asp:UpdateProgress>
</td>
<td>
<asp:LinkButton ID="LinkButtonFaireSimulation" runat="server"
CausesValidation="False">| Faire la simulation<br />
</asp:LinkButton>
<asp:LinkButton ID="LinkButtonEffacerSimulation" runat="server"
CausesValidation="False">| Effacer la simulation<br />
</asp:LinkButton>
<asp:LinkButton ID="LinkButtonVoirSimulations" runat="server"
CausesValidation="False">| Voir les simulations<br />
</asp:LinkButton>
<asp:LinkButton ID="LinkButtonFormulaireSimulation" runat="server"
CausesValidation="False">| Retour au formulaire de simulation<br />
</asp:LinkButton>
<asp:LinkButton ID="LinkButtonEnregistrerSimulation" runat="server"
CausesValidation="False">| Enregistrer la simulation<br />
</asp:LinkButton>
<asp:LinkButton ID="LinkButtonTerminerSession" runat="server"
CausesValidation="False">| Terminer la session<br />
</asp:LinkButton>
</td>
</tr>
</table>
<hr />
</asp:Panel>
<div>
<asp:Panel ID="contenu" runat="server" BackColor="#FFFFC0">
<asp:ContentPlaceHolder ID="ContentPlaceHolder1" runat="server">
</asp:ContentPlaceHolder>
</asp:Panel>
</div>
</ContentTemplate>
</asp:UpdatePanel>
</form>
</body>
</html>
- 第 1 行:请注意主页面的类名:MasterPage
- 第8行:为页面定义背景图片。
- 第 9-64 行:表单
- 第 10 行:用于实现 Ajax 效果的组件 ScriptManager
- 第 11-63 行:容器 AJax
- 第12-62行:Ajax化内容
- 第13-55行:Panel组件 [entete]
- 第 57-60 行:Panel 组件 [contenu]
- 第 58-59 行:ID [ContentPlaceHolder1] 组件,其中将包含封装页面 [Formulaire.aspx, Simulations.aspx, Erreurs.aspx]
要构建此页面,可在 [entete] 面板中插入 来自第8.5.2节所述的[pam-v4-3tier-nhibernate-multivues-monopage]版本中,[Default.aspx]页面的[VueEntete]视图的ASPX代码。
11.4.2. 页面 [Formulaire.aspx]
要生成此页面,将遵循第 11.4.1 节所述的方法,并将生成的 [WebForm1.aspx] 页面重命名为 [Formulaire.aspx]。正在构建的 [Formulaire.aspx] 页面的视觉效果如下:
![]() |
页面 [Formulaire.aspx] 的视觉效果包含两个元素:
- [1] 主页面及其容器 [ContentPlaceHolder1] (2)
- [2] 页面中放置在 [ContentPlaceHolder1] 容器内的组件。这些组件与前一个应用程序中的完全相同。
该页面的源代码如下:
<%@ Page Language="C#" MasterPageFile="~/MasterPage.master" AutoEventWireup="true"
CodeBehind="Formulaire.aspx.cs" Inherits="pam_v7.PageFormulaire" Title="Simulation de calcul de paie : formulaire" %>
<%@ MasterType VirtualPath="~/MasterPage.master" %>
<asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" runat="Server">
<div>
<table>
<tr>
<td>
Employé
</td>
<td>
Heures travaillées
</td>
<td>
Jours travaillés
</td>
<td>
</td>
</tr>
...
</asp:Content>
- 第 1 行:指令 Page 及其属性 MasterPageFile
- 第 4 行:母版页的控件类可以公开字段和属性。封装页面可以通过 Master.[champ] 或 Master.[propriété] 语法访问这些字段和属性。 页面的 Master 属性以 [System.Web.UI.MasterPage] 类型的实例形式指定主页面。因此,在我们的示例中,实际上应写为 (MasterPage)(Master)。[champ] 或 (MasterPage)(Master).[propriété]。若在页面第 4 行插入指令 MasterType,即可避免这种类型转换。 该指令的 VirtualPath 属性指定了母版页的文件。编译器由此可识别母版页类(此处为 [MasterPage] 类型)所暴露的公共字段、属性及方法。
- 第 5-22 行:将插入到主页面容器 [ContentPlaceHolder1] 中的内容。
可以通过将第 8.5.3 节中描述的视图 [VueSaisies] 的内容以及第 8.5.4 节中描述的视图 [VueSimulation] 的内容(第 6-21 行)作为内容来构建该页面。
11.4.3. 页面 [Simulations.aspx]
要生成此页面,需遵循第11.4.1节所述的方法,并将生成的页面[WebForm1.aspx]重命名为[Simulations.aspx]。正在构建的页面[Simulations.aspx]的外观如下:
![]() |
页面 [Simulations.aspx] 的视觉效果包含两个元素:
- [1] 主页面及其容器 [ContentPlaceHolder1]
- [2] 页面中放置在 [ContentPlaceHolder1] 容器内的组件。这些组件与前一个应用程序中的组件完全相同。
该页面的源代码如下:
<%@ Page Language="C#" MasterPageFile="~/MasterPage.master" AutoEventWireup="true"
CodeBehind="Simulations.aspx.cs" Inherits="pam_v7.PageSimulations" Title="Pam : liste des simulations" %>
<%@ MasterType VirtualPath="~/MasterPage.master" %>
<asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" runat="Server">
<asp:MultiView ID="MultiView1" runat="server">
<asp:View ID="View1" runat="server">
<h2>
Liste de vos simulations</h2>
<p>
<asp:GridView ID="GridViewSimulations" runat="server" ...>
...
</asp:GridView>
</p>
</asp:View>
<asp:View ID="View2" runat="server">
<h2>
La liste de vos simulations est vide</h2>
</asp:View>
</asp:MultiView><br />
</asp:Content>
可以通过将第 8.5.5 节中描述的视图 [VueSimulations] 的内容以及第 8.5.6 节中描述的视图 [VueSimulationsVides] 的内容(第 5-21 行)作为页面内容来构建此页面。
11.4.4. 页面 [Erreurs.aspx]
要生成此页面,需遵循第 11.4.1 节所述的方法,并将生成的页面 [WebForm1.aspx] 重命名为 [Erreurs.aspx]。正在构建的页面 [Erreurs.aspx] 的视觉效果如下:
![]() |
页面 [Erreurs.aspx] 的视觉效果包含两个元素:
- [1] 主页面及其容器 [ContentPlaceHolder1]
- [2] 页面中放置在 [ContentPlaceHolder1] 容器内的组件。这些组件与前一个应用程序中的组件完全相同。
该页面的源代码如下:
<%@ Page Language="C#" MasterPageFile="~/MasterPage.master" AutoEventWireup="true"
CodeBehind="Erreurs.aspx.cs" Inherits="pam_v7.PageErreurs" Title="Pam : erreurs" %>
<%@ MasterType VirtualPath="~/MasterPage.master" %>
<asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
<h3>Les erreurs suivantes se sont produites au démarrage de l'application</h3>
<ul>
<asp:Repeater id="rptErreurs" runat="server">
<ItemTemplate>
<li>
<%# Container.DataItem %>
</li>
</ItemTemplate>
</asp:Repeater>
</ul>
</asp:Content>
11.5. 页面验证码
11.5.1. 概述
让我们回到应用程序的架构:
![]() |
- [Global] 是类型为 [HttpApplication] 的对象,它负责初始化(步骤 0)应用程序。该类与上一版本完全相同。
- 上一版本中全部位于 [Default.aspx.cs] 中的控制器代码,现已分布在多个页面中:
- [MasterPage.master]:[Formulaire.aspx, Simulations.aspx, Erreurs.aspx] 页面的主页面。其中包含菜单。
- [Formulaire.aspx]:展示模拟表单并管理该表单上操作的页面
- [Simulations.aspx]:显示模拟列表并管理该页面上操作的页面
- [Erreurs.aspx]:应用程序初始化失败时显示的页面。该页面不支持任何操作。
处理客户请求的流程如下:
- 客户向应用程序发起请求。通常情况下,请求会发送至 [Formulaire.aspx, Simulations.aspx] 中的任意一页,但客户也可以请求 [Erreurs.aspx] 页面。必须预先考虑这种情况。
- 被请求的页面处理该请求(步骤 1)。为此,它可能需要 [métier] 层的协助(步骤 2);如果需要与数据库交换数据,该层本身可能还需要 [dao] 层的协助。 应用程序收到来自 [métier] 层的响应。
- 根据该响应,它选择(步骤 3)要发送给客户端的视图(即响应),并向其提供(步骤 4)所需的信息(模型)。我们看到了生成此响应的三种可能:
- 请求的页面 (D) 即为作为响应发送的页面 (R)。此时构建响应 (R) 的模板,即为页面 (D) 中的某些组件赋予其在响应中应有的值。
- 请求的页面 (D) 并非作为响应发送的页面 (R)。此时页面 (D) 可以:
- 通过指令 Server.Transfer(" R ") 将执行流转移至页面 (R)。 此时可通过 Context.Items(" 键 ")=值 将模板放入上下文中,或较少见地通过 Session.Items(" 键 ")=值 将其放入会话中
- 通过指令 Response.redirect(" R ") 将客户端重定向至页面 (R)。此时,该模板可被放置在会话中,但不能放置在上下文中。
- 响应发送给客户端(步骤 5)
每个 [MasterPage.master, Formulaire.aspx, Simulations.aspx, Erreurs.aspx] 页面将响应以下一个或多个事件:
- Init:页面生命周期中的首个事件
- Load:页面加载时触发
- Click:点击主页面菜单中的某个链接
我们从主页面开始,依次处理各个页面。
11.5.2. 页面 [MasterPage.master] 的控制代码
11.5.2.1. 类框架
主页的控制代码框架如下:
using System.Web.UI.WebControls;
namespace pam_v7
{
public partial class MasterPage : System.Web.UI.MasterPage
{
// 菜单
public LinkButton OptionFaireSimulation
{
get { return LinkButtonFaireSimulation; }
}
...
// 固定菜单
public void SetMenu(bool boolFaireSimulation, bool boolEnregistrerSimulation, bool boolEffacerSimulation, bool boolFormulaireSimulation, bool boolVoirSimulations, bool boolTerminerSession)
{
....
}
// 选项管理[Terminer la session]
protected void LinkButtonTerminerSession_Click(object sender, System.EventArgs e)
{
....
}
// 初始化主页面
protected void Page_Init(object sender, System.EventArgs e)
{
....
}
}
}
}
- 第 5 行:该类名为 [MasterPage],并继承自系统类 [System.Web.UI.MasterPage]。
- 第 9-14 行:菜单的 6 个选项作为类的公共属性进行定义
- 第16-19行:公共方法 SetMenu 将允许 [Formulaire.aspx, Simulations.aspx, Erreurs.aspx] 页面设置主页的菜单
- 第22-25行:处理[LinkButtonTerminerSession]链接点击事件的程序
- 第28-31行:主页的Init事件处理过程
11.5.2.2. 类的公共属性
using System.Web.UI.WebControls;
namespace pam_v7
{
public partial class MasterPage : System.Web.UI.MasterPage
{
// 菜单
public LinkButton OptionFaireSimulation
{
get { return LinkButtonFaireSimulation; }
}
public LinkButton OptionEffacerSimulation
{
get { return LinkButtonEffacerSimulation; }
}
public LinkButton OptionEnregistrerSimulation
{
get { return LinkButtonEnregistrerSimulation; }
}
public LinkButton OptionVoirSimulations
{
get { return LinkButtonVoirSimulations; }
}
public LinkButton OptionTerminerSession
{
get { return LinkButtonTerminerSession; }
}
public LinkButton OptionFormulaireSimulation
{
get { return LinkButtonFormulaireSimulation; }
}
...
}
}
要理解这段代码,需要回顾构成主页的组件:
![]() |
编号 | 类型 | 名称 | 作用 |
面板(上图中的粉色部分) | 页眉 | 页面标题 | |
面板(上图为黄色) | 内容 | 页面内容 | |
LinkButton | LinkButtonFaireSimulation | 请求进行模拟计算 | |
LinkButton | LinkButtonEffacerSimulation | 清除输入表单 | |
LinkButton | LinkButtonVoirSimulations | 显示已进行的模拟列表 | |
LinkButton | LinkButtonFormulaireSimulation | 返回输入表单 | |
LinkButton | LinkButtonEnregistrerSimulation | 将当前模拟保存到模拟列表中 | |
LinkButton | LinkButtonTerminerSession | 结束当前会话 |
组件 1 至 6 无法在其所在页面之外访问。第 9 至 37 行中的属性旨在使其对外部类(此处指应用程序中其他页面的类)可见。
11.5.2.3. 方法 SetMenu
公共方法 SetMenu 允许 [Formulaire.aspx, Simulations.aspx, Erreurs.aspx] 页面设置主页面的菜单。其代码非常简单:
// 固定菜单
public void SetMenu(bool boolFaireSimulation, bool boolEnregistrerSimulation, bool boolEffacerSimulation, bool boolFormulaireSimulation, bool boolVoirSimulations, bool boolTerminerSession)
{
// 设置菜单选项
LinkButtonFaireSimulation.Visible = boolFaireSimulation;
LinkButtonEnregistrerSimulation.Visible = boolEnregistrerSimulation;
LinkButtonEffacerSimulation.Visible = boolEffacerSimulation;
LinkButtonVoirSimulations.Visible = boolVoirSimulations;
LinkButtonFormulaireSimulation.Visible = boolFormulaireSimulation;
LinkButtonTerminerSession.Visible = boolTerminerSession;
}
11.5.2.4. 主页面的事件处理
主页面将处理两个事件:
- Init 事件,即页面生命周期中的首个事件
- Click 事件,发生在 [LinkButtonTerminerSession] 链接上
主页面还有五个其他链接:[LinkButtonFaireSimulation, LinkButtonEnregistrerSimulation, LinkButtonEffacerSimulation, LinkButtonVoirSimulations, LinkButtonFormulaireSimulation]。以 [LinkButtonFaireSimulation] 链接为例,让我们看看点击该链接时应执行哪些操作:
- 在页面 [Formulaire.aspx] 中验证输入的数据(小时、天数)
- 计算工资
- 在页面 [Formulaire.aspx] 中显示结果
操作 1 和 3 需要访问页面 [Formulaire.aspx] 的组件。但实际情况并非如此。事实上,主页面对其容器 [ContentPlaceHolder1] 中可能插入的页面组件一无所知。 在本例中,应由页面 [Formulaire.aspx] 处理对链接 [LinkButtonFaireSimulation] 的点击,因为发生该事件时显示的正是该页面。它如何获知该事件?
- 由于链接 [LinkButtonFaireSimulation] 不属于页面 [Formulaire.aspx],因此无法在 [Formulaire.aspx] 中编写常规处理程序:
private void LinkButtonFaireSimulation_Click(object sender, System.EventArgs e)
{
...
}
可在 [Formulaire.aspx] 中使用以下代码绕过此问题:
using System.Collections.Generic;
...
namespace pam_v7
{
public partial class Formulaire : System.Web.UI.Page
{
// 加载页面
protected void Page_Load(object sender, System.EventArgs e)
{
// 事件管理器
Master.OptionFaireSimulation.Click += OptFaireSimulation_Click;
Master.OptionEffacerSimulation.Click += OptEffacerSimulation_Click;
Master.OptionVoirSimulations.Click += OptVoirSimulations_Click;
Master.OptionEnregistrerSimulation.Click += OptEnregistrerSimulation_Click;
...
}
// 计算工资
private void OptFaireSimulation_Click(object sender, System.EventArgs e)
{
....
}
// 清除模拟
private void OptEffacerSimulation_Click(object sender, System.EventArgs e)
{
...
}
protected void OptVoirSimulations_Click(object sender, System.EventArgs e)
{
...
}
protected void OptEnregistrerSimulation_Click(object sender, System.EventArgs e)
{
...
}
}
}
- 第 12-15 行:当页面 [Formulaire.aspx] 触发事件 Load 时,母版页的类 [MasterPage] 已被实例化。 其公共属性 Optionxx 已可访问,且类型为 LinkButton,这是一个支持事件 Click 的组件。我们将以下方法与这些 Click 事件关联:
- OptFaireSimulation_Click 用于链接 LinkButtonFaireSimulation 上的事件 Click
- OptEffacerSimulation_Click 用于链接 LinkButtonEffacerSimulation 上的事件 Click
- OptVoirSimulations_Click 对应 Click 事件,链接为 LinkButtonVoirSimulations
- OptEnregistrerSimulation_Click 对应 Click 事件,链接为 LinkButtonEnregistrerSimulation
菜单中六个链接的 Click 事件管理将按以下方式分配:
- 页面 [Formulaire.aspx] 将管理链接 [LinkButtonFaireSimulation, LinkButtonEnregistrerSimulation, LinkButtonEffacerSimulation, LinkButtonVoirSimulations]
- 页面 [Simulations.aspx] 将管理链接 [LinkButtonFormulaireSimulation]
- 主页面 [MasterPage.master] 将管理链接 [LinkButtonTerminerSession]。对于此事件,它实际上无需知道其封装的页面。
11.5.2.5. 主页面的 Init 事件
应用程序中的三个页面 [Formulaire.aspx, Simulations.aspx, Erreurs.aspx] 均以 [MasterPage.master] 作为主页面。设主页面为 M,被封装页面为 E。当客户端请求页面 E 时,将按以下顺序发生以下事件:
- E.Init
- M.Init
- E.Load
- M.Load
- ...
我们将利用页面 M 上的事件 Init 来执行一些代码,无论目标页面 E 是哪个,这些代码都应尽早执行。为了了解这段代码,让我们回顾一下应用程序的整体架构:
![]() |
上图中,[Global] 是类型为 [HttpApplication] 的对象,该对象负责初始化应用程序。该类与 [pam-v4-3tier-nhibernate-multivues-monopage] 版本中的类相同:
using System;
...
namespace pam_v7
{
public class Global : System.Web.HttpApplication
{
// --- 应用程序静态数据 ---
public static Employe[] Employes;
public static IPamMetier PamMetier = null;
public static string Msg;
public static bool Erreur = false;
// 启动应用程序
public void Application_Start(object sender, EventArgs e)
{
...
}
public void Session_Start(object sender, EventArgs e)
{
...
}
}
}
如果类 [Global] 无法正确初始化应用程序,它会设置两个静态公共变量:
- 第 12 行中的布尔变量 Erreur 被设置为 vrai
- 第 11 行中的 Msg 变量包含一条详细说明所遇错误的消息
当用户请求 [Formulaire.aspx, Simulations.aspx] 中的任意页面,而应用程序尚未正确初始化时, 该请求应被转发或重定向至页面 [Erreurs.aspx],该页面将显示类 [Global] 中的错误消息。可通过多种方式处理此情况:
- 在每个 [Formulaire.aspx, Simulations.aspx] 页面的事件处理程序 Init 或 Load 中进行初始化错误检测
- 在上述两页的主页中,于事件处理程序 Init 或 Load 中执行初始化错误测试。此方法的优势在于将初始化错误测试集中到一个位置。
我们选择在主页面的事件处理程序 Init 中执行初始化错误检查:
protected void Page_Init(object sender, System.EventArgs e)
{
// 事件管理器
LinkButtonTerminerSession.Click += LinkButtonTerminerSession_Click;
// 初始化错误?
if (Global.Erreur)
{
// 封装的页面是错误页面吗?
bool isPageErreurs =...;
// 如果显示的是错误页面,则保持原样;否则将客户端重定向至错误页面
if (!isPageErreurs)
Response.Redirect("Erreurs.aspx");
return;
}
}
一旦请求了 [Formulaire.aspx, Simulations.aspx, Erreurs.aspx] 中的任意一页,上述代码就会立即执行。 如果请求的页面是 [Formulaire.aspx, Simulations.aspx],我们只需(第 12 行)将客户端重定向到页面 [Erreurs.aspx],该页面负责显示类 [Global] 的错误消息。 如果请求的页面是 [Erreurs.aspx],则不应进行此重定向:应让页面 [Erreurs.aspx] 显示出来。 因此,我们需要在主页面的 [Page_Init] 方法中确定该页面所封装的具体页面。
让我们回顾主页的组件树:
...
<body background="ressources/standard.jpg">
<form id="form1" runat="server">
<asp:Panel ID="entete" runat="server" BackColor="#FFE0C0" Width="1239px" >
...
</asp:Panel>
<div>
<asp:Panel ID="contenu" runat="server" BackColor="#FFFFC0">
<asp:ContentPlaceHolder ID="ContentPlaceHolder1" runat="server">
</asp:ContentPlaceHolder>
</asp:Panel>
</div>
</form>
</body>
</html>
- 第 1-13 行:ID 为 "form1" 的容器
- 第 4-6 行:ID 为 "entete" 的容器,包含在 ID 为 "form1" 的容器中
- 第 8-11 行:ID 为 "contenu" 的容器,包含在 ID 为 "form1" 的容器中
- 第 9-10 行:ID 为 "ContentPlaceHolder1" 的容器,包含在 ID 为 "contenu" 的容器中
页面 E 被封装在主页面 M 中,位于 ID 为 "ContentPlaceHolder1" 的容器内。要引用该页面 E 中 ID 为 C 的组件,应写为:
this.FindControl("form1").FindControl("contenu").FindControl("ContentPlaceHolder1").FindControl("C");
页面 [Erreurs.aspx] 的组件树如下:
<%@ Page Language="C#" MasterPageFile="~/MasterPage.master" AutoEventWireup="true"
CodeBehind="Erreurs.aspx.cs" Inherits="pam_v7.PageErreurs" Title="Pam : erreurs" %>
<%@ MasterType VirtualPath="~/MasterPage.master" %>
<asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
<h3>Les erreurs suivantes se sont produites au démarrage de l'application</h3>
<ul>
<asp:Repeater id="rptErreurs" runat="server">
<ItemTemplate>
<li>
<%# Container.DataItem %>
</li>
</ItemTemplate>
</asp:Repeater>
</ul>
</asp:Content>
当页面 [Erreurs.aspx] 与母版页 M 合并时, 上述 <asp:Content> 标签的内容(第 5-16 行)将被整合到页面 M 中 ID 为 "ContentPlaceholder1" 的 <asp:ContentPlaceHolder> 标签中, 该页面的组件树结构随即变为:
- 第 12 行:[rptErreurs] 组件可用于判断主页面 M 是否包含页面 [Erreurs.aspx]。因为该组件仅存在于该页面中。
以上说明足以理解主页面中 [Page_Init] 过程的代码:
protected void Page_Init(object sender, System.EventArgs e)
{
// 事件管理器
LinkButtonTerminerSession.Click += LinkButtonTerminerSession_Click;
// 初始化错误?
if (Global.Erreur)
{
// 封装的页面是错误页面吗?
bool isPageErreurs = this.FindControl("form1").FindControl("contenu").FindControl("ContentPlaceHolder1").FindControl("rptErreurs") != null;
// 如果显示的是错误页面,则保持原样;否则将客户端重定向到错误页面
if (!isPageErreurs)
Response.Redirect("Erreurs.aspx");
return;
}
}
- 第 4 行:将事件处理程序关联到链接 LinkButtonTerminerSession 上的事件 Click。该处理程序位于类 MasterPage 中。
- 第 6 行:检查类 [Global] 是否已设置其布尔值 Erreur
- 第 9 行:如果是,布尔值 IsPageErreurs 表示主页面中封装的页面是否为页面 [Erreurs.aspx]
- 第12行:如果主页面中封装的页面不是[Erreurs.aspx],则将客户端重定向至该页面;否则不执行任何操作。
11.5.2.6. 链接 [LinkButtonTerminerSession] 的点击事件
![]() |
当用户点击上图(1)中的链接 [Terminer la session] 时,需清空会话内容并显示一个空表单(2)。
该事件处理程序的代码可能如下:
protected void LinkButtonTerminerSession_Click(object sender, System.EventArgs e)
{
// 终止会话
Session.Abandon();
// 显示视图 [formulaire]
Response.Redirect("Formulaire.aspx");
}
- 第 4 行:放弃当前会话
- 第 6 行:将客户端重定向至页面 [Formulaire.aspx]
可以看出,该代码未调用 [Formulaire.aspx, Simulations.aspx, Erreurs.aspx] 页面中的任何组件。因此,该事件可由主页面本身进行处理。
11.5.3. 页面 [Erreurs.aspx] 的控制代码
页面 [Erreurs.aspx] 的控制代码可能如下:
using System.Collections.Generic;
namespace pam_v7
{
public partial class Erreurs : System.Web.UI.Page
{
protected void Page_Load(object sender, System.EventArgs e)
{
// 初始化错误?
if (Global.Erreur)
{
// 正在准备页面模板 [erreurs]
List<string> erreursInitialisation = new List<string>();
erreursInitialisation.Add(Global.Msg);
// 将错误列表与相应组件关联
rptErreurs.DataSource = erreursInitialisation;
rptErreurs.DataBind();
}
// 固定菜单
Master.SetMenu(false, false, false, false, false, false);
}
}
}
需要提醒的是,页面 [Erreurs.aspx] 的唯一作用是在应用程序初始化出现错误时显示该错误:
- 第 10 行:检测初始化是否以错误结束
- 第 13-14 行:如果是,则将错误消息(Global.Msg)放入列表 [ErreursInitialisation] 中
- 第 16-17 行:请求组件 [rptErreurs] 显示该列表
- 第20行:无论是否发生错误,主页菜单的选项均不显示,因此用户无法从该页面发起任何新操作。
如果用户直接请求页面 [Erreurs.aspx](这在应用程序的正常使用中是不应该发生的),会发生什么情况? 通过分析 [MasterPage.master.cs] 和 [Erreurs.aspx.cs] 的代码,可以发现:
- 如果初始化出现错误,系统会显示该错误
- 若未发生初始化错误,用户将收到仅包含 [MasterPage.master] 页眉且未显示任何菜单选项的页面。
11.5.4. [Formulaire.aspx]页面的控制代码
11.5.4.1. 类框架
页面 [Formulaire.aspx] 的控制代码骨架可能如下:
using Pam.Metier.Entites;
...
partial class PageFormulaire : System.Web.UI.Page
{
// 加载页面
protected void Page_Load(object sender, System.EventArgs e)
{
// 事件管理器
Master.OptionFaireSimulation.Click += OptFaireSimulation_Click;
Master.OptionEffacerSimulation.Click += OptEffacerSimulation_Click;
Master.OptionVoirSimulations.Click += OptVoirSimulations_Click;
Master.OptionEnregistrerSimulation.Click += OptEnregistrerSimulation_Click;
....
}
// 计算工资
private void OptFaireSimulation_Click(object sender, System.EventArgs e)
{
....
}
// 清除模拟
private void OptEffacerSimulation_Click(object sender, System.EventArgs e)
{
...
}
protected void OptVoirSimulations_Click(object sender, System.EventArgs e)
{
....
}
protected void OptEnregistrerSimulation_Click(object sender, System.EventArgs e)
{
...
}
}
页面控制代码 [Formulaire.aspx] 处理五个事件:
- 页面上的事件 Load
- 主页面中链接 [LinkButtonFaireSimulation] 上的事件 Click
- 主页面中链接 [LinkButtonEffacerSimulation] 上的事件 Click
- 主页面中链接 [LinkButtonEnregistrerSimulation] 上的事件 Click
- 主页面中链接 [LinkButtonVoirSimulations] 上的事件 Click
11.5.4.2. 页面的 Load 事件
该页面事件处理程序 Load 的骨架代码可能如下:
protected void Page_Load(object sender, System.EventArgs e)
{
// 事件管理器
Master.OptionFaireSimulation.Click += OptFaireSimulation_Click;
Master.OptionEffacerSimulation.Click += OptEffacerSimulation_Click;
Master.OptionVoirSimulations.Click += OptVoirSimulations_Click;
Master.OptionEnregistrerSimulation.Click += OptEnregistrerSimulation_Click;
// 显示视图 [saisies]
...
// 主页面菜单定位
...
// 处理请求 GET
if (!IsPostBack)
{
// 将员工姓名加载到下拉列表中
...
// 初始化视图 [saisies],若存在则使用会话中存储的输入
....
}
}
为阐明第 17 行注释,可参考以下示例:
![]() |
![]() |
- 在 [1] 中,请求查看模拟列表。已在 [A, B, C] 中进行了输入。
- 在 [2] 中,可以看到列表
- 在 [3] 中,请求返回表单
- 在 [4] 中,表单状态与离开时一致。由于存在两个请求(1,2)和(3,4),这意味着:
- 从 [1] 切换到 [2] 时,[1] 中的输入内容已被保存
- 在从 [3] 切换到 [4] 时,这些数据被恢复。正是 [Formulaire.aspx] 中的 [Page_Load] 过程执行了此恢复操作。
问题:请参考[pam-v4-3tier-nhibernate-multivues-monopage]版本的注释和代码,完善Page_Load过程
11.5.4.3. 菜单链接点击事件管理
主页面链接事件处理程序 Click 的框架如下:
// 计算工资
private void OptFaireSimulation_Click(object sender, System.EventArgs e)
{
// Ajax 效果
Thread.Sleep(3000);
// 页面有效吗?
Page.Validate();
if (!Page.IsValid)
{
// 显示视图 [saisie]
...
}
// 页面有效 - 获取输入数据
...
// 计算员工工资
FeuilleSalaire feuillesalaire;
try
{
feuillesalaire = ...;
}
catch (PamException ex)
{
// 遇到问题
...
return;
}
// 将结果存入会话
Session["simulation"] = ...;
// 将输入数据存入会话
...
// 显示
...
// 显示视图
...
// 显示菜单 MasterPage
...
}
// 清除模拟
private void OptEffacerSimulation_Click(object sender, System.EventArgs e)
{
// 面板显示[saisie]
...
// 选择第一位员工
...
}
protected void OptVoirSimulations_Click(object sender, System.EventArgs e)
{
// 将输入数据存入会话
...
// 显示视图[simulations]
Response.Redirect("simulations.aspx");
}
protected void OptEnregistrerSimulation_Click(object sender, System.EventArgs e)
{
// 将当前模拟保存到用户会话中
...
// 显示视图 [simulations]
Response.Redirect("simulations.aspx");
}
问题:参考注释及版本 [pam-v4-3tier-nhibernate-multivues-monopage] 的代码,完善上述过程的代码
11.5.5. 页面控制代码 [Simulations.aspx]
页面 [Simulations.aspx] 的控制代码框架可能如下:
using System.Collections.Generic;
using Pam.Web;
using System.Web.UI.WebControls;
partial class PageSimulations : System.Web.UI.Page
{
// 模拟
private List<Simulation> simulations;
// 加载页面
protected void Page_Load(object sender, System.EventArgs e)
{
// 事件管理器
Master.OptionFormulaireSimulation.Click += OptFormulaireSimulation_Click;
GridViewSimulations.RowDeleting += GridViewSimulations_RowDeleting;
// 从会话中检索模拟
simulations = ...;
// 是否有模拟?
if (simulations.Count != 0)
{
// 显示第一个视图
...
// 填充网格视图
...
}
else
{
// 第二个视图
...
}
// 固定菜单
...
}
protected void GridViewSimulations_RowDeleting(object sender, System.Web.UI.WebControls.GridViewDeleteEventArgs e)
{
// 从会话中获取模拟
List<Simulation> simulations = ...;
// 删除指定的模拟(e.RowIndex 代表 GridView 中被删除行的编号)
..
// 是否还有模拟?
if (simulations.Count != 0)
{
// 填充网格视图
...
}
else
{
// 视图 [SimulationsVides]
...
}
}
protected void OptFormulaireSimulation_Click(object sender, System.EventArgs e)
{
// 显示视图 [formulaire]
Response.Redirect("formulaire.aspx");
}
}
问题:参考注释和版本 [pam-v4-3tier-nhibernate-multivues-monopage] 的代码,完善上述过程的代码
11.5.6. 页面控制代码 [Default.aspx]
可以在应用程序中预设一个 [Default.aspx] 页面,以便用户在未指定具体页面的情况下请求应用程序的 URL,如下所示:
![]() |
请求 [1] 收到了页面 [Formulaire.aspx] 的响应(2)。已知请求(1)默认由应用程序的页面 [Default.aspx] 处理。 要获得 (2),只需让 [Default.aspx] 将客户端重定向到页面 [Formulaire.aspx] 即可。可通过以下代码实现:
partial class _Default : System.Web.UI.Page
{
protected void Page_Init(object sender, System.EventArgs e)
{
// 重定向至数据录入表单
Response.Redirect("Formulaire.aspx");
}
}
而展示页面 [Default.aspx] 仅包含将其与 [Default.aspx.cs] 关联的指令:
<%@ Page Language="C#" AutoEventWireup="true"
CodeBehind="Default.aspx.cs" Inherits="pam_v7._Default" Title="Untitled Page" %>






























