Skip to content

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。

客户端请求的处理流程如下:

  1. 客户端向应用程序发起请求。该请求发送至两个页面之一:[Formulaire.aspx, Simulations.aspx]。
  2. 被请求的页面处理该请求。 为此,该页面可能需要 [métier] 层的协助;若需与数据库交换数据,该层又可能需要 [dao] 层的协助。应用程序从 [métier] 层接收响应。
  3. 根据该响应,它选择 (3) 要发送给客户端的视图(即响应),并向客户端提供 (4) 其所需的信息(即模板)。
  4. 响应被发送给客户端 (5)

11.1. 应用程序的视图

向用户展示的视图包括:

  • - 视图 [VueSaisies],用于展示模拟表单
  • - 视图 [VueSimulation],用于显示模拟的详细结果:
  • - 视图 [VueSimulations],显示客户执行的模拟列表
  • - 视图 [VueSimulationsVides] 显示客户没有或已无模拟:
  • 视图 [VueErreurs] 显示应用程序初始化错误:

11.2. 在多控制器环境中生成视图

在上一版本中,所有视图均由单一页面 [Default.aspx] 生成。 该页面包含两个组件 [MultiView],视图由属于这两个组件的 [View] 组件组合而成。

当视图较少时,这种架构很有效,但一旦构成不同视图的组件数量增多,其局限性便显现出来:实际上,每次向唯一的 [Default.aspx] 页面发出请求时,该页面的所有组件都会被实例化,尽管实际上只有其中一部分会被用于生成给用户的响应。 因此,每次新请求都会产生不必要的计算开销,当页面组件总数较大时,这种开销会变得非常明显。

因此,一个解决方案是将视图分布在不同的页面上。这就是我们在此采取的做法。让我们研究两种不同的视图生成情况:

  1. 请求发送到页面 P1,该页面生成响应
  2. 向页面 P1 发起请求,该页面要求页面 P2 生成响应

11.2.1. 情况 1:单控制器/视图页面

在情况 1 中,我们回到了上一版本的单控制器架构,其中页面 [Default.aspx] 即为页面 P1:

  1. 客户端向页面 P1 发起请求 (1)
  2. 页面 P1 处理该请求。为此,它可能需要 [métier] 层(2)的协助,而该层若需与数据库交换数据,又可能需要 [dao] 层的协助。 应用程序收到来自 [métier] 层的响应。
  3. 根据该响应,它选择 (3) 要发送给客户端的视图(即响应),并向客户端提供 (4) 其所需的信息(即模板)。 这里需要从页面 P1 中选择要显示的组件 [Panel] 或 [View],并初始化它们所包含的组件。
  4. 响应被发送给客户端(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 可能涵盖多种架构。我们将选择以下架构:

  1. 客户端向页面 P1 发起请求 (1)
  2. 页面 P1 处理该请求。为此,它可能需要 [métier] 层的协助(2),而该层若需与数据库交换数据,则可能需要 [dao] 层的协助。 应用程序收到来自 [métier] 层的响应。
  3. 根据该响应,它选择 (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 发出第二次请求。这形成了两个连续的请求。 众所周知,会话是跨请求保留“记忆”的一种手段。除了会话之外,还有其他解决方案。
  4. 无论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],主页的视觉效果如下:

编号
类型
名称
角色
A
面板(上图中的粉色部分)
页眉
页面标题
B
面板(上图为黄色)
内容
页面内容
1
LinkButton
LinkButtonFaireSimulation
请求进行模拟计算
2
LinkButton
LinkButtonEffacerSimulation
清除输入表单
3
LinkButton
LinkButtonVoirSimulations
显示已进行的模拟列表
4
LinkButton
LinkButtonFormulaireSimulation
返回输入表单
5
LinkButton
LinkButtonEnregistrerSimulation
将当前模拟保存到模拟列表中
6
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>
                &nbsp;&nbsp;&nbsp</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]:应用程序初始化失败时显示的页面。该页面不支持任何操作。

处理客户请求的流程如下:

  1. 客户向应用程序发起请求。通常情况下,请求会发送至 [Formulaire.aspx, Simulations.aspx] 中的任意一页,但客户也可以请求 [Erreurs.aspx] 页面。必须预先考虑这种情况。
  2. 被请求的页面处理该请求(步骤 1)。为此,它可能需要 [métier] 层的协助(步骤 2);如果需要与数据库交换数据,该层本身可能还需要 [dao] 层的协助。 应用程序收到来自 [métier] 层的响应。
  3. 根据该响应,它选择(步骤 3)要发送给客户端的视图(即响应),并向其提供(步骤 4)所需的信息(模型)。我们看到了生成此响应的三种可能:
    • 请求的页面 (D) 即为作为响应发送的页面 (R)。此时构建响应 (R) 的模板,即为页面 (D) 中的某些组件赋予其在响应中应有的值。
    • 请求的页面 (D) 并非作为响应发送的页面 (R)。此时页面 (D) 可以:
      • 通过指令 Server.Transfer(" R ") 将执行流转移至页面 (R)。 此时可通过 Context.Items(" 键 ")=值 将模板放入上下文中,或较少见地通过 Session.Items(" 键 ")=值 将其放入会话中
      • 通过指令 Response.redirect(" R ") 将客户端重定向至页面 (R)。此时,该模板可被放置在会话中,但不能放置在上下文中。
  4. 响应发送给客户端(步骤 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; }
    }

...
  }
}

要理解这段代码,需要回顾构成主页的组件:

编号
类型
名称
作用
A
面板(上图中的粉色部分)
页眉
页面标题
B
面板(上图为黄色)
内容
页面内容
1
LinkButton
LinkButtonFaireSimulation
请求进行模拟计算
2
LinkButton
LinkButtonEffacerSimulation
清除输入表单
3
LinkButton
LinkButtonVoirSimulations
显示已进行的模拟列表
4
LinkButton
LinkButtonFormulaireSimulation
返回输入表单
5
LinkButton
LinkButtonEnregistrerSimulation
将当前模拟保存到模拟列表中
6
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] 链接为例,让我们看看点击该链接时应执行哪些操作:

  1. 在页面 [Formulaire.aspx] 中验证输入的数据(小时、天数)
  2. 计算工资
  3. 在页面 [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> 标签中, 该页面的组件树结构随即变为:

...
<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">
              <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:ContentPlaceHolder>
            </asp:Panel>
        </div>
    </form>
</body>
</html>
  • 第 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] 处理五个事件:

  1. 页面上的事件 Load
  2. 主页面中链接 [LinkButtonFaireSimulation] 上的事件 Click
  3. 主页面中链接 [LinkButtonEffacerSimulation] 上的事件 Click
  4. 主页面中链接 [LinkButtonEnregistrerSimulation] 上的事件 Click
  5. 主页面中链接 [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" %>