8. [SimuPaie] 应用程序 – 版本 4 – ASP.NET / 多视图 / 单页面
推荐阅读:参考文档 [1],使用 ASP.NET 1.1 进行开发 WEB,相关段落:
-
服务器组件与应用程序控制器
-
采用 ASP 服务器组件的 MVC 应用示例
现在,我们将研究一个基于先前探讨的三层应用程序 ASP.NET 衍生而来的版本,该版本为其增添了新功能。我们的应用程序架构演变如下:
![]() |
客户端请求的处理流程如下:
- 客户向应用程序发起请求。
- 应用程序处理该请求。 为此,它可能需要 [métier] 层的协助;若需与数据库交换数据,该层又可能需要 [dao] 层的协助。应用程序从 [métier] 层接收响应。
- 根据该响应,它选择 (3) 要发送给客户端的视图(即响应),并向客户端提供 (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>
 </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 个视图,需编写以下代码:
8.5.2. 的头部
页眉由以下组件构成:
![]() |
编号 | 类型 | 名称 | 角色 |
LinkButton | LinkButtonFaireSimulation | 请求进行模拟计算 | |
LinkButton | LinkButtonEffacerSimulation | 清除输入表单 | |
LinkButton | LinkButtonVoirSimulations | 显示已进行的模拟列表 | |
LinkButton | LinkButtonFormulaireSimulation | 返回输入表单 | |
LinkButton | LinkButtonEnregistrerSimulation | 将当前模拟保存到模拟列表中 | |
LinkButton | LinkButtonTerminerSession | 退出当前会话 |
8.5.3. 视图 [Saisies]
名为 [VueSaisies] 的组件 [View] 如下所示:
![]() |
编号 | 类型 | 名称 | 角色 |
DropDownList | ComboBoxEmployes | 包含员工姓名列表 | |
TextBox | TextBoxHeures | 实际工作小时数 | |
TextBox | TextBoxJours | 工作天数 – 整数 | |
RequiredFieldValidator | RequiredFieldValidatorHeures | 检查字段 [2] [TextBoxHeures] 是否为空 | |
RegularExpressionValidator | RegularExpressionValidatorHeures | 验证字段 [2] [TextBoxHeures] 是否为 >=0 的实数 | |
RequiredFieldValidator | RequiredFieldValidatorJours | 验证字段 [3] [TextBoxJours] 是否不为空 | |
RegularExpressionValidator | RegularExpressionValidatorJours | 检查字段 [3] [TextBoxJours] 是否为 >=0 的整数 |
8.5.4. 视图 [Simulation]
名为 [VueSimulation] 的组件 [View] 如下所示:

它仅由 [Label] 组件构成,其中 ID 组件已在上文列出。
8.5.5. 视图 [Simulations]
名为 [VueSimulations] 的组件 [View] 如下所示:
![]() |
编号 | 类型 | 名称 | 角色 |
GridView | GridViewSimulations | 包含模拟列表 |
组件 [GridViewSimulations] 的属性已按以下方式定义:
![]() |
- 在 [1] 中:右键单击 [GridView] / 选项 [Mise en forme automatique]
- 转换为 [2]:为 [GridView] 选择显示类型
![]() |
- 在 [3] 中:选择 [GridView] 的属性
- 在 [4] 中:编辑 [GridView] 的列
- 在 [5] 中:添加一个类型为 [BoundField] 的列,该列将与 [GridView] 行中要显示的对象的某个公共属性绑定。 此处显示的对象将是一个 [Simulation] 类型的对象。
![]() |
- 在 [6] 中:指定列标题
- 在 [7] 中:指定将与该列关联的 [Simulation] 类属性的名称。
- [DataFormatString] 指定了该列中显示的值应采用何种格式。
[GridViewSimulations] 组件的列具有以下属性:
编号 | 属性 |
类型:BoundField、HeaderText:名称,DataField:名称 | |
类型:BoundField、HeaderText:姓,DataField:名 | |
类型:BoundField、HeaderText:工作小时数、DataField:HeuresTravaillees | |
类型:BoundField、HeaderText:工作天数,DataField:JoursTravailles | |
类型:BoundField、HeaderText:基本工资,DataField:SalaireBase、DataFormatString: {0:C}(货币格式,C=货币)——将显示欧元符号。 | |
类型:BoundField, HeaderText:津贴,数据字段:Indemnites, DataFormatString:{0:C} | |
类型:BoundField、HeaderText:社会保险费,DataField:CotisationsSociales、DataFormatString:{0:C} | |
类型: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] 如下所示:
![]() |
编号 | 类型 | 名称 | 角色 |
中继器 | 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),将处理两个事件:
- 事件 Load(第24-31行),但布尔值 Page.IsPostback(第27行)的检测结果导致不会执行任何操作。
- 与被点击链接相关的事件:
![]() |
- 第 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 应用程序发送给用户的响应界面。用户可从主页开始进行模拟:
![]() |
![]() |
![]() |
处理此操作的流程可能如下所示:
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. 操作:保存模拟
模拟完成后,用户可请求保存:
![]() |
![]() |
处理此操作的流程可能如下所示:
protected void LinkButtonEnregistrerSimulation_Click(object sender, System.EventArgs e)
{
// 将当前模拟保存到会话中的模拟列表中
...
// 显示视图 [simulations]
...
}
问题:请补充上述代码
8.6.5. 操作:返回模拟表单
推荐阅读:参考 [1]、[Développement WEB avec ASP.NET 1.1 ]:隐藏字段 _VIEWSTATE 的作用
显示模拟列表后,用户可请求返回模拟表单:
![]() |
需注意,屏幕 (2) 显示的是表单的原始输入状态。请记住,这些不同的视图属于同一页面。 在不同请求之间,如果组件的 EnableViewState 至 true 属性已设置,则其值将通过 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功能。

































