4. 应用程序 [SimuPaie] – 版本 1 – ASP.NET
4.1. Introduction
我们希望编写一个名为 .NET 的应用程序,让用户能够模拟计算某市镇“幼儿之家”协会的保育员工资。
工资计算表单 (ASP.NET)将呈现如下样式:
![]() |
应用程序 ASP.NET 的架构如下:
![]() |
- 在首次向应用程序发起请求时,将实例化一个从类型 [System.Web.HttpApplication] 派生而来的 [Global] 类型对象。 该对象将读取 Web 应用程序的配置文件 [web.config],并将数据库中的某些数据缓存起来(即上文所述的操作 0)。
- 当首次向应用程序的唯一页面 [Default.aspx] 发出请求(操作 1)时,将处理事件 [Load]。该事件负责处理员工下拉列表的填充。 下拉列表所需的数据从已将其缓存的对象 [Global] 中获取。即上文中的操作 2。操作 4 将初始化好的页面发送给用户。
- 当向页面 [Default.aspx] 发起薪资计算请求(操作 1)时,将再次处理事件 [Load]。 此时不会执行任何操作,因为这是 POST 类型的请求,而处理程序 [Pam_Load] 被设计为在此情况下不执行任何操作(通过布尔值 IsPostBack 实现)。 处理完事件 [Load] 后,接下来处理的是按钮 [Salaire] 上的事件 [Click]。 该事件需要一些未在 [Global] 中缓存的数据。因此,事件处理程序会向数据库请求这些数据。这就是上文中的操作 3。随后进行薪资计算,操作 4 将结果发送给用户。
4.2. Visual Web Developer 2008 项目
![]() |
- 在 [1] 中,创建了一个新项目
- 在 [2] 中,类型为 [Web / Application Web ASP.NET]
- 在 [3] 中,为项目命名
- 在 [4] 中,指定其名称,并在 [5] 中指定其位置。将为该项目创建一个名为 [c:\temp\pam-aspnet\pam-v1-adonet] 的文件夹。
![]() |
- 在 [5] 中,Visual Web Developer 项目
- 在 [6] 中,项目属性(右键单击项目 / 属性 / 应用程序)。
- 在 [7] 中,即项目生成时将生成的程序集名称
- 在 [8] 中,我们希望为项目类设置的默认命名空间。 在文件 [Default.aspx.cs] 和 [Default.aspx.designer.cs] 中定义的类 [_Default] 则创建在从项目名称派生的命名空间 [pam_v1_adonet] 中。 我们可以直接在上述两个文件的代码中修改该命名空间:
[Default.aspx.cs]
using System;
....
namespace pam_v1
{
public partial class _Default : System.Web.UI.Page
{
[Default.aspx.designer.cs]
//------------------------------------------------------------------------------
// <自动生成>
// 此代码由工具生成。
// 运行时版本:2.0.50727.3603
//
// 对该文件所做的修改可能会导致行为异常,并且如果
// 代码重新生成时,这些修改将被丢失。
// </auto-generated>
//------------------------------------------------------------------------------
namespace pam_v1 {
public partial class _Default {
.........
文件 [Default.aspx] 的标记也需进行修改:
![]() |
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="pam_v1._Default" %>
...
上文中的 Inherits 属性指代在 [Default.aspx.cs] 和 [Default.aspx.designer.cs] 文件中定义的类。其中使用了 pam_v1 命名空间。
4.2.1. 表单 [Default.aspx]
表单 [Default.aspx] 的外观如下:
![]() |
组件如下:
编号 | 类型 | 名称 | 作用 |
DropDownList | ComboBoxEmployes | 包含员工姓名列表 | |
TextBox | TextBoxHeures | 实际工作小时数 | |
TextBox | TextBoxJours | 工作天数 – 整数 | |
按钮 | ButtonSalaire | 请求计算工资 | |
TextBox | TextBoxErreur | 致用户的通知 ReadOnly=true, TextMode=MultiLine | |
标签 | LabelNom | 在 (1) 中选定的员工姓名 | |
标签 | LabelPrénom | 在(1)中选定的员工名字 | |
标签 | LabelAdresse | 地址 | |
标签 | LabelVille | 城市 | |
标签 | LabelCP | 邮政编码 | |
标签 | LabelIndice | 索引 | |
标签 | LabelCSGRDS | 缴费率CSGRDS | |
标签 | LabelCSGD | 缴费率 CSGD | |
标签 | LabelRetraite | 养老保险缴费率 | |
标签 | LabelSS | 社会保险缴费率 | |
标签 | LabelSH | (11)中所示指数的基本时薪 | |
标签 | LabelEJ | (11)中所示指数的每日生活津贴 | |
标签 | LabelRJ | 针对(11)中所示指数的每日餐费补贴 | |
标签 | LabelCongés | 适用于基本工资的带薪休假补贴率 | |
标签 | LabelSB | 基本工资金额 | |
标签 | LabelCS | 应缴社会保险费金额 | |
标签 | LabelIE | 受托儿童抚养费金额 | |
标签 | LabelIR | 受托儿童的餐费金额 | |
标签 | LabelSN | 应付给员工的净工资 |
该表格还包含两个类型为 [Panel] 的容器:
包含组件 (5) TextBoxErreur | |
包含组件 (6) 至 (24) |
类型为 [Panel] 的组件可通过其布尔属性 [Panel].Visible 进行编程控制,使其可见或不可见。
4.2.2. 输入验证
要计算工资,用户:
- 在 [1] 中选择一名员工
- 在 [2] 中输入工作小时数。该数值可以是小数,例如 2.5 表示 2 小时 30 分钟。
- 在 [3] 中输入工作日数。该数值为整数。
- 点击按钮 [4] 查询工资
![]() |
当用户在 [2] 和 [3] 中输入错误数据时,系统会在用户切换输入字段的瞬间进行验证。 因此,下图的屏幕截图是在用户点击按钮 [Salaire] 之前捕获的:
![]() |
要实现上述行为,需要满足两个条件:
- 验证组件的 EnableClientScript 属性必须设置为 true:
![]() |
- 显示该页面的浏览器必须能够执行页面中嵌入的 JavaScript 代码 HTML。
如果客户端浏览器未自行验证数据的有效性,则数据仅在浏览器将表单输入提交至服务器时才会被验证。届时,位于服务器端并处理浏览器请求的代码将负责验证数据的有效性。 需要提醒的是,即使客户端浏览器中显示的页面嵌入了执行相同验证的 JavaScript 代码,也必须始终进行此项验证。因为服务器无法确保发给它的 POST 请求确实来自该页面,因此无法保证数据验证已经完成。
验证器列表如下:
编号 | 类型 | 名称 | 角色 |
RequiredFieldValidator | RequiredFieldValidatorHeures | 检查字段 [2] [TextBoxHeures] 是否为空 | |
RangeValidator | RangeValidatorHeures | 检查字段 [2] [TextBoxHeures] 是否为 [0, 200] 区间内的实数 | |
RequiredFieldValidator | RequiredFieldValidatorJours | 验证字段 [3] [TextBoxJours] 是否不为空 | |
RangeValidator | RangeValidatorJours | 检查字段 [3] [TextBoxJours] 是否为 [0,31] 区间内的整数 |
待完成的工作:构建页面 [Default.aspx]。首先放置两个容器 [PanelErreurs] 和 [PanelSalaire],以便随后将它们应包含的组件放置其中。
4.2.3. 应用程序的实体
![]() |
读取后,表 [cotisations]、[employes]、[indemnites] 中的行将被放入类型为 [Cotisations] 的对象中, [Employe] 和 [Indemnites] 类型的对象中,这些对象的定义如下:
namespace Pam.Entites
{
public class Cotisations
{
// 自动属性
public double CsgRds { get; set; }
public double Csgd { get; set; }
public double Secu { get; set; }
public double Retraite { get; set; }
// 构造函数
public Cotisations()
{
}
public Cotisations(double csgRds, double csgd, double secu, double retraite)
{
CsgRds = csgRds;
Csgd = csgd;
Secu = secu;
Retraite = retraite;
}
// ToString
public override string ToString()
{
return string.Format("[{0},{1},{2},{3}]", CsgRds, Csgd, Secu, Retraite);
}
}
}
namespace Pam.Entites
{
public class Employe
{
// 自动属性
public string SS { get; set; }
public string Nom { get; set; }
public string Prenom { get; set; }
public string Adresse { get; set; }
private string Ville { get; set; }
private string CodePostal { get; set; }
private int Indice { get; set; }
// 制造商
public Employe()
{
}
public Employe(string ss, string nom, string prenom, string adresse, string codePostal, string ville, int indice)
{
SS = ss;
Nom = nom;
Prenom = prenom;
Adresse = adresse;
CodePostal = codePostal;
Ville = ville;
Indice = indice;
}
// ToString
public override string ToString()
{
return string.Format("[{0},{1},{2},{3},{4},{5},{6}]", SS, Nom, Prenom, Adresse, Ville, CodePostal, Indice);
}
}
}
namespace Pam.Entites
{
public class Indemnites
{
// 自动属性
public int Indice { get; set; }
public double BaseHeure { get; set; }
public double EntretienJour { get; set; }
public double RepasJour { get; set; }
public double IndemnitesCP { get; set; }
// 构造函数
public Indemnites()
{
}
public Indemnites(int indice, double baseHeure, double entretienJour, double repasJour, double indemnitesCP)
{
Indice = indice;
BaseHeure = baseHeure;
EntretienJour = entretienJour;
RepasJour = repasJour;
IndemnitesCP = indemnitesCP;
}
// 标识
public override string ToString()
{
return string.Format("[{0}, {1}, {2}, {3}, {4}]", Indice, BaseHeure, EntretienJour, RepasJour, IndemnitesCP);
}
}
}
4.2.4. 应用程序配置
用于配置应用程序的文件 [Web.config] 内容如下:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
...
</configSections>
<connectionStrings>
<add name="dbpamSqlServer2005" connectionString="Data Source=.\SQLEXPRESS;AttachDbFilename=C:\data\...\dbpam.mdf;User Id=sa;Password=msde;Connect Timeout=30;" providerName="System.Data.SqlClient"/>
</connectionStrings>
<appSettings>
<add key="selectEmploye" value="select NOM,PRENOM,ADRESSE,VILLE,CODEPOSTAL,INDICE from EMPLOYES where SS=@SS"/>
<add key="selectEmployes" value="select PRENOM, NOM, SS from EMPLOYES"/>
<add key="selectCotisations" value="select CSGRDS,CSGD,SECU,RETRAITE from COTISATIONS"/>
<add key="SelectIndemnites" value="select INDICE,BASEHEURE,ENTRETIENJOUR,REPASJOUR,INDEMNITESCP from INDEMNITES"/>
</appSettings>
<system.web>
<!--
Définissez compilation debug="true" pour insérer des symboles
de débogage dans la page compilée. Comme ceci
affecte les performances, définissez cette valeur à true uniquement
lors du développement.
-->
<compilation debug="false">
...
</configuration>
- 第 9 行:定义连接 SQL 服务器的连接字符串
- 第 13-16 行:定义应用程序使用的 SQL 查询,以避免将其硬编码在代码中。
- 第 13 行:配置了 SQL 查询。该参数的表示法是 SQL Server 特有的。
待完成工作:将这些参数引入文件 [Web.config]。第 9 行将根据 [dbpam.mdf] 数据库的实际路径进行调整。
4.2.5. 应用程序初始化
ASP.NET 应用程序的初始化由文件 [Global.asax.cs] 完成。该文件的结构如下:
![]() |
- 在 [1] 中,向项目添加一个新元素
- 在 [2] 中,添加全局应用程序类,其默认名称为 [Global.asax] [3]
![]() |
- 在 [4] 中,生成文件 [Global.asax] 及其关联类 [Global.asax.cs]
- 在 [5] 中,显示 [Global.asax] 的标记
<%@ Application Codebehind="Global.asax.cs" Inherits="pam_v1.Global" Language="C#" %>
类 [pam_v1.Global] 在文件 [Global.asax.cs] 中定义。针对我们的问题,其定义如下:
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data.SqlClient;
using Pam.Entites;
namespace pam_v1
{
public class Global : System.Web.HttpApplication
{
// --- 应用程序静态数据 ---
public static Employe[] Employes;
public static Cotisations Cotisations;
public static Dictionary<int, Indemnites> Indemnites = new Dictionary<int, Indemnites>();
public static string Msg = string.Empty;
public static bool Erreur = false;
public static string ConnectionString = null;
// 应用程序启动
public void Application_Start(object sender, EventArgs e)
{
...
try
{
// 登录
ConnectionString = ...
using (SqlConnection connexion = new SqlConnection(ConnectionString))
{
connexion.Open();
// 获取员工列表并将其放入静态数组 [Employes]
...
// 从静态变量中获取缴费率 [Cotisations]
...
// 从静态字典中提取津贴,存储在静态字典 [Indemnites] 中
...
// 操作成功
Msg = "Base chargée...";
}
}
catch (Exception ex)
{
// 记录错误
Msg = string.Format("L'erreur suivante s'est produite lors de l'accès à la base de données : {0}", ex);
Erreur = true;
}
}
}
}
- 第 20 行:Web 应用程序启动时将执行方法 [Application_Start]。该方法仅执行一次。
- 第 12-17 行:类的公共字段和静态字段。静态字段由该类的所有实例共享。 因此,如果创建了多个 [Global] 类的实例,它们都将共享同一个静态字段 [Employes],可通过引用 [Global.Employes]、c.a.d 访问。 [NomDeClasse].ChampStatique. [Global] 是一个类名。因此它是一种数据类型。该名称是任意的。该类始终继承自 [System.Web.HttpApplication]。
让我们回到 [Global] 类。有人可能会问,是否有必要将其字段声明为静态。事实上,在某些情况下,[Global] 类可能会有多个实例,因此将所有这些实例需要共享的字段设为静态是合理的。
还有另一种方法可以在 Web 应用程序的不同页面之间共享“应用程序”作用域的数据。因此,员工数组 Employes 可以按以下方式存储在过程 Application_Start 中:
Application.Add("employes",Employes);
在所有 [Application] 应用程序中,ASP.NET 默认是 [Global.asax.cs] 定义的类的一个实例的引用。Application 是一个可以存储任何类型对象的容器。 随后,可以在 Web 应用程序的任意页面代码中通过以下方式获取数组 Employes:
Employe[] employes=Application.Get["employes"] as Employe[];
由于容器 Application 存储了各种类型的对象,因此获取到的类型为 Object,随后需要对其进行类型转换。 虽然这种方法依然可行,但通过 [Global] 对象的静态类型字段共享数据,可以避免类型转换,并让编译器进行类型检查,从而为开发人员提供帮助。本文将采用这种方法。
所有用户共享的数据如下:
- 第12行:类型为[Employe]的对象数组,用于存储所有员工的简化列表(SS、NOM、PRENOM)
- 第13行:类型为[Cotisations]的对象,用于存储缴费率
- 第14行:字典,用于存储与员工各项指数相关的津贴。该字典将按员工指数进行索引,其值类型为[Indemnites]
- 第15行:一条消息,指示初始化结果(成功或出错)
- 第16行:一个布尔值,用于指示初始化是否以错误结束
- 第17行:连接数据库的字符串。
问题:完善类 [Application_Start] 的代码。
4.3. 表单 [Default.aspx] 的事件
4.3.1. 过程 [Page_Load]
当表单 [Default.aspx] 加载时,它会从数组 [Global.Employes](第 12 行)中检索员工姓名,并将它们放入下拉列表 [1] 中:
![]() |
- 下拉列表 [1] 已填充
- TextBox [5] 表示数据库已正确读取
如果应用程序启动时发生初始化错误,TextBox [5] 会显示:
![]() |
问题:编写网页 [Default.aspx] 中的 [Page_Load] 过程,该过程在应用程序启动时执行,以确保上述功能正常运行。
4.3.2. 工资计算
点击按钮 [4] 将触发以下管理程序的执行:
protected void ButtonSalaire_Click(object sender, System.EventArgs e)
该管理程序首先会验证在 [2] 和 [3] 中输入的数据是否有效。如果其中任何一项有误,系统将如前所述发出错误提示。 一旦验证了 [2] 和 [3] 中的输入并确认有效,应用程序应显示在 [1] 中选定用户的补充数据及其工资(参见第 4.1 节的屏幕截图)。
问题:编写过程 [ButtonSalaire_Click] 的代码。













