Skip to content

8. [SimuPaie] 应用程序 – 版本 4 – ASP.NET / 多视图 / 单页面


推荐阅读:参考文档 [1],使用 ASP.NET 1.1 进行开发 WEB,相关段落:

  • 服务器组件与应用程序控制器

  • 采用 ASP 服务器组件的 MVC 应用示例


现在,我们将研究一个基于先前探讨的三层应用程序 ASP.NET 衍生而来的版本,该版本为其增添了新功能。我们的应用程序架构演变如下:

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

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

这里采用的是所谓的 MVC(模型-视图-控制器)Web 架构:

  • [Application] 是控制器。它处理来自客户端的所有请求。
  • [Saisies, Simulation, Simulations, ...] 代表视图。一个 .NET 格式的视图,是由标准 ASP / HTML 代码构成的,其中包含需要初始化的组件。 需要提供给这些组件的值构成了视图的模型

我们将在此以如下方式实现 MVC 设计模式:

  • 视图将作为 [View] 组件存在于单页 [Default.aspx] 中
  • 因此,控制器即为该单页的代码 [Default.aspx.cs]。

只有基础应用程序才能支持这种 MVC 的实现。因为每次请求时,[Default.aspx] 页面中的所有组件都会被实例化,也就是所有的视图。 在发送响应时,应用程序的控制代码会通过仅显示相应的 [View] 组件并隐藏其他组件,来选择其中一个视图。 如果应用程序包含大量视图,页面[Default.aspx]将拥有众多组件,其实例化成本可能变得难以承受。此外,由于视图过多,该页面的[Design]模式可能会变得难以管理。 此类架构适用于视图较少且由单人开发的应用程序。在适用情况下,它能非常简便地构建 MVC 架构。这正是我们在新版本中将要探讨的内容。

8.1. 应用程序的视图

向用户展示的视图包括:

  • 显示模拟表单的视图 [VueSaisies]
  • 视图 [VueSimulation],用于显示模拟的详细结果:
  • 视图 [VueSimulations],显示客户执行的模拟列表
  • 视图 [VueSimulationsVides] 显示客户没有或已无模拟:
  • 视图 [VueErreurs] 显示存在一个或多个错误:

8.2. [web] 层的 Visual Web Developer 项目

[web] 层的 Visual Web Developer 项目如下:

  • 在 [1] 中:
  • 应用程序的配置文件 [Web.config]——与前一个应用程序的配置文件完全相同。
  • 管理 Web 应用程序事件的文件 [Global.cs](此处指应用程序的启动)——与前一个应用程序的配置文件完全相同,只是它还管理用户会话的启动。
  • 应用程序的表单 [Default.aspx]——包含应用程序的各个视图。
  • 在 [2] 中包含项目引用——与上一版本完全相同

8.3. 文件 [Global.cs]

文件 [Global.cs] 负责管理 Web 应用程序的事件,与上一版应用程序中的该文件完全相同,只是它还额外管理用户会话的启动:

Global.cs


using System;
using System.Web;
using Pam.Dao.Entites;
using Pam.Metier.Service;
using Spring.Context.Support;
using System.Collections.Generic;
using istia.st.pam.web;

namespace pam_v4
{
    public class Global : HttpApplication
    {

        // --- 应用程序静态数据 ---
        public static Employe[] Employes;
        public static string Msg = string.Empty;
        public static bool Erreur = false;
        public static IPamMetier PamMetier = null;

        // 应用程序启动
        public void Application_Start(object sender, EventArgs e)
        {
...
        }

        // 启动用户会话 
        public void Session_Start(object sender, EventArgs e)
        {
            // 在会话中放入一个空的模拟列表 
            Session["simulations"] = new List<Simulation>();
        } 

    }
}
  • 第 27-34 行:管理会话的启动。我们将在此会话中放入用户执行的模拟列表。
  • 第30行:创建了一个空的模拟列表。模拟是一个类型为[Simulation]的对象,我们稍后将详细说明。
  • 第 31 行:将模拟列表放入与键“simulations”关联的会话中

8.4. [Simulation] 类

[Simulation] 类型的对象用于封装模拟表中的一行:

其代码如下:


namespace Pam.Web
{

    public class Simulation
    {
        // 模拟数据 
        public string Nom { get; set; }
        public string Prenom { get; set; }
        public double HeuresTravaillees { get; set; }
        public int JoursTravailles { get; set; }
        public double SalaireBase { get; set; }
        public double Indemnites { get; set; }
        public double CotisationsSociales { get; set; }
        public double SalaireNet { get; set; }

        // 构造函数 
        public Simulation()
        {

        }

        public Simulation(string nom, string prenom, double heuresTravailllees, int joursTravailles, double salaireBase, double indemnites, double cotisationsSociales, double salaireNet)
        {
            {
                this.Nom = nom;
                this.Prenom = prenom;
                this.HeuresTravaillees = heuresTravailllees;
                this.JoursTravailles = joursTravailles;
                this.SalaireBase = salaireBase;
                this.Indemnites = indemnites;
                this.CotisationsSociales = cotisationsSociales;
                this.SalaireNet = salaireNet;
            }
        }
    }
}

该类的字段对应于模拟表的列。

8.5. 页面 [Default.aspx]

8.5.1. 概览

页面 [Default.aspx] 包含多个 [View] 组件,每个视图对应一个。其骨架如下:


<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="pam_v4.PagePam" %>

<!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>Simulateur de paie</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>
            <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 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" OnClick="LinkButtonFaireSimulation_Click">
                                    | Faire la simulation<br /></asp:LinkButton>
                       <asp:LinkButton ID="LinkButtonEffacerSimulation" runat="server"
                                     CausesValidation="False" OnClick="LinkButtonEffacerSimulation_Click">
                                    | Effacer la simulation<br /></asp:LinkButton>
                       <asp:LinkButton ID="LinkButtonVoirSimulations" runat="server" 
                                CausesValidation="False" OnClick="LinkButtonVoirSimulations_Click">
                                    | Voir les simulations<br /></asp:LinkButton>
                        <asp:LinkButton ID="LinkButtonFormulaireSimulation" runat="server"
                                CausesValidation="False" OnClick="LinkButtonFormulaireSimulation_Click">
                                | Retour au formulaire de simulation<br /></asp:LinkButton>
                        <asp:LinkButton ID="LinkButtonEnregistrerSimulation" runat="server"
                                CausesValidation="False" OnClick="LinkButtonEnregistrerSimulation_Click">
                                    | Enregistrer la simulation<br /></asp:LinkButton>
                        <asp:LinkButton ID="LinkButtonTerminerSession" runat="server"
                                CausesValidation="False" OnClick="LinkButtonTerminerSession_Click">
                                    | Terminer la session<br /></asp:LinkButton>
                    </td>
            </table>
            <hr />
            <asp:MultiView ID="Vues1" ActiveViewIndex="0" runat="server">
                <asp:View ID="VueSaisies" runat="server">
...
                </asp:View>
            </asp:MultiView>
            <asp:MultiView ID="Vues2" runat="server">
                <asp:View ID="VueSimulation" runat="server">
...
                </asp:View>
                <asp:View ID="VueSimulations" runat="server">
...
                </asp:View>
                <asp:View ID="VueSimulationsVides" runat="server">
...
                </asp:View>
                <asp:View ID="VueErreurs" runat="server">
...
                </asp:View>
            </asp:MultiView>
        </ContentTemplate>
    </asp:UpdatePanel>
    </form>
</body>
</html>
  • 第 10 行:用于配置 Ajax 扩展的标签
  • 第 11-73 行:由 Ajax 调用更新的 UpdatePanel 容器
  • 第 12-72 行:容器 UpdatePanel 的内容
  • 第 13-52 行:将出现在每个视图中的页眉。它以链接列表的形式向用户展示可执行的操作列表。
  • 第 33 行:请注意 CausesValidation="False" 属性,该属性确保在点击链接时,页面验证器不会被隐式执行。当该属性缺失时,其默认值为 True。 页面验证可通过服务器端执行的代码中调用 Page.Validate 操作显式进行。
  • 第 53-57 行:一个 [MultiView] 组件,包含一个唯一的视图 [VueSaisies]。因此,在第 53 行中硬编码了要显示的视图编号:ActiveViewIndex="0"
  • 第53-67行:一个[MultiView]组件,包含四个视图:第59-61行的视图[VueSimulation], 视图 [VueSimulations](第 62-64 行),视图 [VueSimulationsVides](第 65-67 行),视图 [VueErreurs](第 68-70 行)。 [MultiView] 组件每次仅显示一个视图。要显示 Vues2 组件的第 i 个视图,需编写以下代码:
Vues2.ActiveViewIndex=i

8.5.2. 的头部

页眉由以下组件构成:

编号
类型
名称
角色
1
LinkButton
LinkButtonFaireSimulation
请求进行模拟计算
2
LinkButton
LinkButtonEffacerSimulation
清除输入表单
3
LinkButton
LinkButtonVoirSimulations
显示已进行的模拟列表
4
LinkButton
LinkButtonFormulaireSimulation
返回输入表单
5
LinkButton
LinkButtonEnregistrerSimulation
将当前模拟保存到模拟列表中
6
LinkButton
LinkButtonTerminerSession
退出当前会话

8.5.3. 视图 [Saisies]

名为 [VueSaisies] 的组件 [View] 如下所示:

编号
类型
名称
角色
1
DropDownList
ComboBoxEmployes
包含员工姓名列表
2
TextBox
TextBoxHeures
实际工作小时数
3
TextBox
TextBoxJours
工作天数 – 整数
4
RequiredFieldValidator
RequiredFieldValidatorHeures
检查字段 [2] [TextBoxHeures] 是否为空
5
RegularExpressionValidator
RegularExpressionValidatorHeures
验证字段 [2] [TextBoxHeures] 是否为 >=0 的实数
6
RequiredFieldValidator
RequiredFieldValidatorJours
验证字段 [3] [TextBoxJours] 是否不为空
7
RegularExpressionValidator
RegularExpressionValidatorJours
检查字段 [3] [TextBoxJours] 是否为 >=0 的整数

8.5.4. 视图 [Simulation]

名为 [VueSimulation] 的组件 [View] 如下所示:

Image

它仅由 [Label] 组件构成,其中 ID 组件已在上文列出。

8.5.5. 视图 [Simulations]

名为 [VueSimulations] 的组件 [View] 如下所示:

编号
类型
名称
角色
1
GridView
GridViewSimulations
包含模拟列表

组件 [GridViewSimulations] 的属性已按以下方式定义:

  • 在 [1] 中:右键单击 [GridView] / 选项 [Mise en forme automatique]
  • 转换为 [2]:为 [GridView] 选择显示类型
  • 在 [3] 中:选择 [GridView] 的属性
  • 在 [4] 中:编辑 [GridView] 的列
  • 在 [5] 中:添加一个类型为 [BoundField] 的列,该列将与 [GridView] 行中要显示的对象的某个公共属性绑定。 此处显示的对象将是一个 [Simulation] 类型的对象。
  • 在 [6] 中:指定列标题
  • 在 [7] 中:指定将与该列关联的 [Simulation] 类属性的名称。
  • [DataFormatString] 指定了该列中显示的值应采用何种格式。

[GridViewSimulations] 组件的列具有以下属性:

编号
属性
2
类型:BoundField、HeaderText:名称,DataField:名称
3
类型:BoundField、HeaderText:姓,DataField:
4
类型:BoundField、HeaderText:工作小时数、DataField:HeuresTravaillees
5
类型:BoundField、HeaderText:工作天数,DataField:JoursTravailles
6
类型:BoundField、HeaderText:基本工资,DataField:SalaireBase、DataFormatString: {0:C}(货币格式,C=货币)——将显示欧元符号。
7
类型:BoundField, HeaderText:津贴,数据字段:Indemnites, DataFormatString:{0:C}
8
类型:BoundField、HeaderText:社会保险费,DataField:CotisationsSociales、DataFormatString:{0:C}
9
类型:BoundField, HeaderText:工资,DataField:SalaireNet, DataFormatString:{0:C}

请注意,字段 [DataField] 必须与类 [Simulation] 的现有属性相对应。本阶段完成后,所有类型为 [BoundField] 的列均已创建:

  • 在 [1] 中:为 [GridView] 创建的列
  • 为 [2]:当开发人员像我们刚才那样自行定义列时,应禁用列的自动生成。

接下来我们需要创建链接列 [Retirer]:

  • 在 [1] 中:添加一个类型为 [CommandField / Supprimer]
  • 在 [2] 中:ButtonType=Link,以便在该列中显示链接而非按钮
  • 在 [3] 中:CausesValidation=False,点击链接不会触发页面上可能存在的验证控件。因为删除模拟无需进行任何数据验证。
  • 在 [4] 中:仅显示删除链接。
  • 在 [5] 中:该链接的文本

8.5.6. 视图 [SimulationsVides]

名为 [VueSimulationsVides] 的组件 [View] 仅包含文本:

 

8.5.7. 视图 [Erreurs]

名为 [VueErreurs] 的组件 [View] 如下所示:

编号
类型
名称
角色
1
中继器
RptErreurs
显示错误消息列表

组件 [Repeater] 允许针对数据源中的每个对象(通常为集合)重复执行代码 ASP.NET / HTML。 该代码直接定义在页面 ASP.NET 的源代码中:


                <asp:Repeater ID="RptErreurs" runat="server">
                    <ItemTemplate>
                        <li>
                            <%# Container.DataItem %>
                        </li>
                    </ItemTemplate>
</asp:Repeater>
  • 第 2 行:<ItemTemplate> 定义了将针对数据源中的每个元素重复执行的代码。
  • 第 4 行:显示表达式 Container.DataItem 的值,该表达式指代数据源中的当前元素。 由于该元素是一个对象,因此使用该对象的 ToString 方法将其包含在页面的 HTML 流中。 我们的对象集合将是一个包含错误消息的 List(Of String) 集合。第 3-5 行将 <li>Message</li> 序列包含到页面的 HTML 流中。

8.6. 控制器 [Default.aspx.cs]

8.6.1. 概述

让我们回到应用程序的 MVC 架构:

  • [Default.aspx.cs] 是单页的控制代码,[Default.aspx] 是应用程序的控制器。
  • [Global] 是 [HttpApplication] 类型的对象,它负责初始化应用程序,并持有对 [métier] 层的引用。

控制器 [Default.aspx.cs] 的代码框架如下:


using System.Collections.Generic;
...

public partial class PagePam : Page
{

    private void setVues(bool boolVues1, bool boolVues2, int index)
    {
        // 显示所请求的视图 
        // boolVues1:如果多视图“Vues1”应可见,则为 true 
        // boolVues1:如果多视图“视图2”应可见,则为true 
        // index:要显示的 Vues2 视图索引 
...
    }

    private void setMenu(bool boolFaireSimulation, bool boolEnregistrerSimulation, bool boolEffacerSimulation, bool boolFormulaireSimulation, bool boolVoirSimulations, bool boolTerminerSession)
    {
        // 设置菜单选项 
        // 每个布尔值被赋值给对应链接的 Visible 属性 
...
    }

    // 加载页面 
    protected void Page_Load(object sender, System.EventArgs e)
    {
        // 处理初始请求 
        if (!IsPostBack)
        {
...
        }
    }

    protected void LinkButtonFaireSimulation_Click(object sender, System.EventArgs e)
    {
...
    }

    protected void LinkButtonEffacerSimulation_Click(object sender, System.EventArgs e)
    {
....
    }

    protected void LinkButtonVoirSimulations_Click(object sender, System.EventArgs e)
    {
...
    }

    protected void LinkButtonEnregistrerSimulation_Click(object sender, System.EventArgs e)
    {
...
    }

    protected void LinkButtonTerminerSession_Click(object sender, System.EventArgs e)
    {
...
    }

    protected void LinkButtonFormulaireSimulation_Click(object sender, System.EventArgs e)
    {
...
    }


    protected void GridViewSimulations_RowDeleting(object sender, GridViewDeleteEventArgs e)
    {
...
    }
}

在用户的初始请求(GET)中,仅处理第24至31行中的事件Load。对于通过菜单链接发起的后续请求(POST),将处理两个事件:

  1. 事件 Load(第24-31行),但布尔值 Page.IsPostback(第27行)的检测结果导致不会执行任何操作。
  2. 与被点击链接相关的事件:
  • 第 33-36 行:处理对链接 [1] 的点击
  • 第38-41行:处理对链接 [2] 的点击
  • 第 43-46 行:处理对链接 [3] 的点击
  • 第 58-61 行:处理对链接 [4] 的点击
  • 第 48-51 行:处理对链接 [5] 的点击
  • 第 53-56 行:处理对链接 [6] 的点击

为了提取经常出现的代码片段,创建了两个内部方法:

  • setVues,第7-14行:设置要显示的视图
  • setMenu,第 16-21 行:设置要显示的菜单选项

8.6.2. Load 事件


推荐阅读:参考文档 [1],使用 ASP.NET 进行开发 WEB 1.1

  • 章节“[Repeater] 组件与数据绑定”。

用户首先看到的是空表单视图:

应用程序的初始化需要访问数据源,此过程可能会失败。在这种情况下,首屏将显示错误页面:

事件 [Load] 的处理方式与之前的 ASP.NET 版本类似:


    // 加载页面 
    protected void Page_Load(object sender, System.EventArgs e)
    {
        // 处理初始请求 
        if (!IsPostBack)
        {
            // 初始化错误? 
            if (Global.Erreur)
            {
                // 显示视图 [erreurs] 
...
                // 菜单定位 
                ...
                return;
            }
            // 将员工姓名加载到下拉列表中 
...
            // 菜单定位 
...
            // 视图显示 [saisie] 
 ...
        }
}

问题:请补充上述代码


8.6.3. 操作:进行模拟

下文中,标注为 (1) 的屏幕为用户请求界面,标注为 (2) 的屏幕为 Web 应用程序发送给用户的响应界面。用户可从主页开始进行模拟:

- (1):用户发起模拟请求
- (2):模拟结果

处理此操作的流程可能如下所示:


    protected void LinkButtonFaireSimulation_Click(object sender, System.EventArgs e)
    {
        // 计算工资 
        // 页面有效吗? 
        Page.Validate();
        if (!Page.IsValid)
        {
            // 显示视图 [saisie] 
            ...
            return;
        }
        // 页面有效 - 获取输入数据 
        double HeuresTravaillées = ...;
        int JoursTravaillés = ...;
        // 计算员工工资 
        FeuilleSalaire feuillesalaire;
        try
        {
            feuillesalaire = ...
        }
        catch (PamException ex)
        {
            // 显示视图 [erreurs] 
...
            return;
        }
        // 将结果存入会话 
        Session["simulation"] = ...
        // 显示结果 
...
        // 显示视图 [saisie, employe, salaire] 
        ...
        // 显示菜单 
        ...
}

问题:补充上述代码


8.6.4. 操作:保存模拟

模拟完成后,用户可请求保存:

- (1):申请保存当前模拟
- (2):模拟已保存,并显示已执行的模拟列表

处理此操作的流程可能如下所示:


    protected void LinkButtonEnregistrerSimulation_Click(object sender, System.EventArgs e)
    {
        // 将当前模拟保存到会话中的模拟列表中 
        ...
        // 显示视图 [simulations] 
        ...
}

问题:请补充上述代码


8.6.5. 操作:返回模拟表单


推荐阅读:参考 [1]、[Développement WEB avec ASP.NET 1.1 ]:隐藏字段 _VIEWSTATE 的作用


显示模拟列表后,用户可请求返回模拟表单:

需注意,屏幕 (2) 显示的是表单的原始输入状态。请记住,这些不同的视图属于同一页面。 在不同请求之间,如果组件的 EnableViewStatetrue 属性已设置,则其值将通过 ViewState 机制予以保留。

处理此操作的程序可能如下所示:


    protected void LinkButtonFormulaireSimulation_Click(object sender, System.EventArgs e)
    {
        // 显示视图 [saisie] 
        ...
        // 菜单定位 
        ...
}

问题:请补充上述代码


8.6.6. 操作:清除模拟

返回模拟表单后,用户可请求清除现有输入:

处理此操作的程序可能如下所示:


    protected void LinkButtonEffacerSimulation_Click(object sender, System.EventArgs e)
    {
        // 表单的 RAZ 
        ...
}

问题:请补充上述代码


8.6.7. 操作:查看模拟结果

用户可以请求查看自己已进行的模拟:

处理此操作的程序可能如下所示:


protected void LinkButtonVoirSimulations_Click(object sender, System.EventArgs e)
    {
        // 在会话中获取模拟
        ...
        // 是否有模拟? 
        if (...)
        {
            // 可见视图 [simulations] 
...
        }
        else
        {
            // 视图 [SimulationsVides] 
            ...
        }
        // 固定菜单 
        ...
    }

问题:补充上述代码


8.6.8. 操作:删除模拟

用户可以请求删除一个模拟:

处理此操作的流程 [GridViewSimulations_RowDeleting ] 可能如下所示:


    protected void GridViewSimulations_RowDeleting(object sender, GridViewDeleteEventArgs e)
    {
        // 从会话中检索模拟
        ...
        // 删除指定的模拟(e.RowIndex 是被删除行的编号)
        ...
        // 是否还有模拟? 
        if (...)
        {
            // 填写 GridView 
 ...
        }
        else
        {
            // 视图 [SimulationsVides] 
            ...
        }
}

问题:请补充上述代码


8.6.9. 操作:结束会话

用户可以请求结束其模拟会话。这将放弃其会话内容并显示一个空表单:

处理此操作的程序可能如下所示:


    protected void LinkButtonTerminerSession_Click(object sender, System.EventArgs e)
    {
        // 退出会话 
        ...
        // 显示视图 [saisies] 
        ...
        // 菜单定位 
        ...
}

问题:请补全上述代码


实践作业:在机器上部署前面的Web应用程序。为其添加Ajax功能。