4. [SimuPaie] 应用程序 – 版本 1 – ASP.NET
4.1. 简介
我们希望编写一个 .NET 应用程序,允许用户模拟某市“Maison de la petite enfance”协会中儿童保育人员的薪资计算。

该 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] 事件后,接下来处理 [Salary] 按钮的 [Click] 事件。该事件需要 [Global] 中未缓存的数据。因此,事件处理程序从数据库中检索数据。这就是上文中的操作 3。随后计算薪资,操作 4 将结果发送给用户。
4.2. Visual Web Developer 2008
![]() |
- 在 [1] 中,创建一个新项目
- 在 [2] 中,选择类型为 [Web / ASP.NET Web Application]
- 在 [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]
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime version :2.0.50727.3603
//
// Changes made to this file may result in incorrect behavior and will be lost if
// the code is regenerated.
// </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] 表单
![]() |
其组件如下:
编号 | 类型 | 名称 | 作用 |
下拉列表 | 员工下拉列表 | 包含员工姓名列表 | |
文本框 | TextBoxHours | 实际工作小时数 | |
文本框 | TextBoxDays | 工作天数 – 整数 | |
按钮 | ButtonSalary | 计算薪资 | |
文本框 | 错误文本框 | 用户信息提示 ReadOnly=true, TextMode=MultiLine | |
标签 | 标签名称 | 在 (1) 中选定的员工姓名 | |
标签 | LabelFirstName | 在(1)中选定的员工的名字 | |
标签 | LabelAddress | 地址 | |
标签 | 标签城市 | 城市 | |
标签 | 邮政编码 | 邮政编码 | |
标签 | 标签索引 | 索引 | |
标签 | 标签CSGRDS | CSGRDS 贡献率 | |
标签 | CSGD 标签 | CSGD 贡献率 | |
标签 | 退休标签 | 退休供款率 | |
标签 | 标签SS | 社会保障缴费率 | |
标签 | LabelSH | (11)中所示指数的基准时薪 | |
标签 | 标签EJ | 第(11)项所示指数的每日生活津贴 | |
标签 | LabelRJ | 第(11)项所示指数的每日伙食津贴 | |
标签 | LabelVacation | 适用于基本工资的带薪休假津贴率 | |
标签 | 标签SB | 基本工资金额 | |
标签 | 标签CS | 应缴纳的社会保险费金额 | |
标签 | 标签IE | 育儿津贴金额 | |
标签 | 标签IR | 受托儿童的伙食津贴金额 | |
标签 | 标签SN | 应支付给员工的净工资 |
该表单还包含两个 [Panel] 容器:
包含 (5) 个 TextBoxError 组件 | |
包含组件 (6) 至 (24) |
可以通过其布尔属性 [Panel].Visible 通过编程方式将 [Panel] 组件设为可见或隐藏。
4.2.2. 输入验证
要计算薪资,用户需:
- 在 [1] 中选择一名员工
- 在 [2] 中输入工作小时数。该数值可以是小数,例如 2.5 表示 2 小时 30 分钟。
- 在 [3] 中输入工作日数。该数值为整数。
- 点击 [4] 按钮计算薪资
![]() |
当用户在[2]和[3]中输入错误数据时,系统会在用户切换输入字段的瞬间进行验证。因此,下图截图是在用户点击[工资]按钮之前拍摄的:
![]() |
上述行为需要满足两个条件:
- 验证组件的 EnableClientScript 属性必须设置为 true:
![]() |
- 显示该页面的浏览器必须能够执行嵌入在 HTML 页面中的 JavaScript 代码。
如果客户端浏览器本身不验证数据的有效性,则数据仅在浏览器将表单条目提交至服务器时才会被验证。此时,处理浏览器请求的服务器端代码将负责验证数据的有效性。 请注意,即使客户端浏览器中显示的页面包含执行相同验证的 JavaScript 代码,也必须始终执行此验证。这是因为服务器无法确定其收到的 POST 请求是否确实来自该页面,因此也无法确定数据是否已经过验证。
验证者名单如下:
编号 | 类型 | 名称 | 角色 |
必填字段验证器 | 必填字段验证器(小时) | 检查 [2] [TextBoxHeures] 字段是否为空 | |
范围验证器 | RangeValidatorHours | 检查字段 [2] [TextBoxHours] 是否为 [0, 200] 范围内的实数 | |
必填字段验证器 | 必填字段验证器(天数) | 检查字段 [3] [TextBoxDays] 是否不为空 | |
范围验证器 | RangeValidatorDays | 检查 [3] [TextBoxDays] 字段是否为 [0,31] 范围内的整数 |
任务:构建 [Default.aspx] 页面。首先,放置两个容器 [PanelErrors] 和 [PanelSalary],以便随后放置它们应包含的组件。
4.2.3. 应用程序实体
![]() |
读取后,来自 [contributions]、[employees] 和 [allowances] 表的行将存储在类型为 [Contributions]、[Employee] 和 [Allowances] 的对象中,这些对象定义如下:
namespace Pam.Entites
{
public class Cotisations
{
// automatic properties
public double CsgRds { get; set; }
public double Csgd { get; set; }
public double Secu { get; set; }
public double Retraite { get; set; }
// manufacturers
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
{
// automatic properties
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; }
// manufacturers
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
{
// automatic properties
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; }
// manufacturers
public Indemnites()
{
}
public Indemnites(int indice, double baseHeure, double entretienJour, double repasJour, double indemnitesCP)
{
Indice = indice;
BaseHeure = baseHeure;
EntretienJour = entretienJour;
RepasJour = repasJour;
IndemnitesCP = indemnitesCP;
}
// identity
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 Server 数据库的连接字符串
- 第 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
{
// --- static application data ---
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;
// application startup
public void Application_Start(object sender, EventArgs e)
{
...
try
{
// connection
ConnectionString = ...
using (SqlConnection connexion = new SqlConnection(ConnectionString))
{
connexion.Open();
// retrieve the list of employees and place it in the static array [Employees]
...
// contribution rates are retrieved from the static variable [Contributions]
...
// retrieve allowances from the static dictionary [Allowances]
...
// we succeeded
Msg = "Base chargée...";
}
}
catch (Exception ex)
{
// we note the error
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 行:类的 public 和 static 字段。static 字段由类的所有实例共享。因此,如果创建了多个 [Global] 类的实例,它们都将共享同一个 static 字段 [Employees],可通过引用 [Global.Employees] 访问,即 [ClassName].StaticField。 [Global] 是类名,因此它是一种数据类型。该名称可任意设定。该类始终继承自 [System.Web.HttpApplication]。
让我们回到 [Global] 类。有人可能会质疑是否必须将其字段声明为静态。事实上,在某些情况下,[Global] 类可能会有多个实例,这正是将所有这些实例都需要共享的字段声明为静态的理由。
还有另一种方法可以在 Web 应用程序的不同页面之间共享“应用程序”作用域的数据。因此,Employees 数组可以如下所示存储在 Application_Start 过程之中:
Application.Add("employes",Employes);
默认情况下,在任何 ASP.NET 应用程序中,[Application] 都是对 [Global.asax.cs] 中定义的类的实例的引用。Application 是一个能够存储任何类型对象的容器。随后,可以在 Web 应用程序中任何页面的代码中按如下方式检索 Employees 数组:
Employe[] employes=Application.Get["employes"] as Employe[];
由于 Application 容器存储任何类型的对象,因此我们获取到的对象类型为 Object,必须进行类型转换。虽然这种方法始终可用,但通过 [Global] 对象的类型化静态字段共享数据可以避免类型转换,并允许编译器执行类型检查以辅助开发人员。本文将采用这种方法。
所有用户共享的数据如下:
- 第 12 行:一个 [Employee] 对象数组,用于存储所有员工的简化列表(SSN、LAST_NAME、FIRST_NAME)
- 第 13 行:类型为 [Contributions] 的对象,用于存储缴费率
- 第 14 行:用于存储与各类员工索引相关联的津贴的字典。该字典将按员工索引进行索引,其值类型为 [Allowances]
- 第 15 行:一条消息,用于指示初始化是否成功完成或出现错误
- 第 16 行:一个布尔值,用于指示初始化是否以错误结束
- 第 17 行:数据库连接字符串。
问题:请完成 [Application_Start] 类的代码。
4.3. [Default.aspx] 表单的事件
4.3.1. [P age_Load] 过程
当 [Default.aspx] 表单加载时,它会从 [Global.Employees] 数组中检索员工姓名(第 12 行),并将这些姓名填入下拉列表 [1] 中:
![]() |
- 下拉列表 [1] 已填充完毕
- 文本框 [5] 表明数据库已正确读取
如果应用程序启动时发生初始化错误,文本框 [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] 过程的代码。











