10. 应用程序 [SimuPaie] – 版本 6 – Web 服务的客户端 ASP.NET
10.1. 应用程序架构
我们将NUnit测试的客户端/服务器架构演进如下:
![]() |
在 [1] 中,测试 NUnit 被第 8 版的 Web 应用程序所取代。需要注意的是,该架构包含两个未显示的 Web 服务器:
- 一个运行 Web 服务 [S] 的 Web 服务器。该服务将在 Visual Web Developer 的第一个实例中运行。
- 一个运行 Web 客户端 [1] 的 Web 服务器。它将在第二个 Visual Web Developer 实例中运行。
10.2. 客户端 [web] 的 Visual Web Developer 项目
我们创建一个新的 Web 项目 ASP.NET:
![]() |
- 在 [1] 中,我们选择 C# Web 项目
- 在 [2] 中,我们选择“Web 应用程序 ASP.NET”
- 在 [3] 中,为 Web 项目命名
- 在 [4] 中,我们为该项目指定了一个位置
- 在 [5] 中,已创建该项目
我们修改了项目的部分属性:
![]() |
- 在 [1] 中,指定将生成的程序集名称
- 在 [2] 中,指定将要创建的类和接口的默认命名空间
要构建项目 [pam-v6-client-webservice],可以从 Web 项目 [pam-v4-3tier-nhibernate-multivues-monopage] 中提取文件 [Global.asax] 和 [Default.aspx],并将它们复制到项目 [pam-v6-client-webservice] 中。首先使用Windows资源管理器进行此操作:
![]() |
- 到 [1],将项目文件夹 [pam-v4-3tier-nhibernate-multivues-monopage]
- 复制为 [2],项目文件夹 [pam-v6-client-webservice] 在复制完成后
- [images, pam, ressources]
- [Global.asax, Global.asax.cs]文件
- [Default.aspx, Default.aspx.cs, Default.aspx.designer.cs]文件
- 在 [3] 中,在 Visual Studio Express 中显示项目中的所有文件,以显示新添加的文件
- 在 [4] 中,将添加的文件夹和文件纳入项目
![]() |
- 在 [5] 中,新建项目 [pam-v6-client-webservice]
此时,可以首次生成项目 [6]。会出现以下错误:
要理解并修正这些错误,需回顾正在构建的Web项目架构:
![]() |
项目 [pam-v6-client-webservice] 即上图中 [web] [1] 层。 可以看出,该层与Web服务的客户端[C]进行通信,该客户端我们将很快生成。
- 错误 2、5、7 的原因在于,[pam-v4] 的代码引用了 [metier] 层中的 DLL, 而 DLL 目前位于服务器端而非客户端。
- 错误 1 和 3 的原因相同,但这次涉及的是 [dao] 层中的 DLL
- 错误 4 源于 [Global.asax] 的代码使用了 Spring,而我们的项目未引用 Spring 中的 DLL。我们将添加此引用。
- 错误 6 是因为类 [Employe] 定义在 [dao] 层中,而该层在客户端并不存在。
命名空间错误 1、2、4、5、6、7 将通过生成 Web 服务的 C 客户端来解决。错误 3 通过在项目中添加对 Spring 的 DLL 的引用来解决。我们可以按以下步骤操作:
![]() |
- 在 [1] 中,向项目添加对 [pam-v6-client-webservice] 的引用
- 在 [4] 中,已引用 [lib] 文件夹中的 DLL 和 [Spring.Core]。
在 Spring 中添加此引用后,项目生成时的错误减少了一个。其余所有错误均源于引用了 [metier] 和 [dao] 层对象的代码行,这些对象现已位于服务器上。 我们将看到,这些缺失的对象将在 Web 服务的 [C] 客户端中生成。
![]() |
在生成 Web 服务的 [C] 客户端之前,我们将修改现有各类别的命名空间。它们目前位于 [pam-v4] 命名空间中。 我们将该命名空间更改为 [pam-v6]。涉及的文件如下:Default.aspx.cs、Default.aspx.designer.cs、Global.asax.cs。例如:
....
namespace pam_v6
{
public class Global : System.Web.HttpApplication
{
// --- 应用程序静态数据 ---
public static Employe[] Employes;
public static IPamMetier PamMetier = null;
....
第 2 行,命名空间 pam_v4 已被替换为命名空间 pam_v6。
此外,还需修改文件 Default.aspx 和 Global.asax 的标记:
![]() |
[Default.aspx] 的标记变为:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="pam_v6.PagePam" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
.....
第 1 行,Inherits 属性指定了文件 [Default.aspx.cs] 的类名。我们需要更改命名空间。
[Global.asax] 的标记变为:
<%@ Application Codebehind="Global.asax.cs" Inherits="pam_v6.Global" Language="C#" %>
上文中,我们更改了属性 Inherits 的命名空间。
现在,我们生成 Web 服务的客户端 [C]。
![]() |
要执行后续操作,必须在另一个 Visual Web Developer 实例中启动 Web 服务 [S] [pam-v5-webservice]。
![]() |
- 在 [1] 中,向项目 [pam-v6-client-webservice] 添加对 Web 服务 [pam-v5-webservice] 的引用
- 在 [2] 中,引用了 Web 服务 [pam-v5-webservice] 的 URI。这是该 Web 服务中文件 WSDL 的 URL。 在构建 Web 服务 [pam-v5-webservice] 时,已说明如何获取该 URL。
- 在 [3] 中,要求向导处理 [2] 中指定的 WSDL 文件
- 在 [4] 中,列出了在 [Service1Soap] 中发现的 Web 服务,以及在 [5] 中该服务所暴露的远程方法。
- 在 [6] 中,我们指定了将生成 [C] 客户端类和接口的命名空间
- 启动 [C] 客户端的生成
![]() |
- 在 [1] 中,即生成的客户端。双击该文件即可查看其内容。
- 在 [2] 中,对象浏览器中显示了命名空间 pam_v6.WsPam 的类和接口。这是生成的客户端的命名空间。
- 在 [3] 中,是实现 Web 服务客户端的类。
- 在 [4] 中,显示了由客户端 [Service1SoapClient] 实现的方法。其中包含远程 Web 服务的两个方法:[5] 和 [6]。
- 在 [2] 中,包含各图层实体的图像:
- [metier]:FeuilleSalaire、ElementsSalaire
- [dao]:Employe、Cotisations、Indemnites
接下来,需要记住这些远程实体的映像位于客户端,且位于 pam_v6.WsPam 命名空间中。
让我们来考察其中一个实体所暴露的方法和属性:
![]() |
- 在 [1] 中,选择本地类 [Employe]
- 在 [2] 中,可以看到远程实体 [Employe] 的属性,以及用于本地实体自身需求的私有字段。
让我们查看存在错误的 [Global.asax.cs] 的代码:
![]() |
- 第 3 行和第 4 行使用的命名空间在服务器上存在,但在客户端上不存在。
- 第 13 行使用了一个名为 [Employe] 的类,但未为其声明正确的命名空间
- 第 14 行使用的 IPamMetier 接口在客户端中未被识别。
我们在之前研究的 C# 客户端中已经遇到过类似的问题。
在架构中:
![]() |
- 本地 [metier] 层由类型为 pam_v6.WsPam.Service1SoapClient 的客户端 [C] 实现,该客户端虽具有同名方法,但并未实现 IPamMetier 接口。
- 处理的实体 (Employe、Indemnites、Cotisations)位于命名空间 pam_v6.WsPam 中
[Global.asax.cs]的代码演变如下:
using System;
using System.Collections.Generic;
using Pam.Web;
using Spring.Context.Support;
using pam_v6.WsPam;
namespace pam_v6
{
public class Global : System.Web.HttpApplication
{
// --- 应用程序静态数据 ---
public static Employe[] Employes;
public static Service1SoapClient PamMetier = null;
public static string Msg;
public static bool Erreur = false;
// 应用程序启动
public void Application_Start(object sender, EventArgs e)
{
// 配置文件处理
try
{
// [metier] 层的实例化
PamMetier = ContextRegistry.GetContext().GetObject("pammetier") as Service1SoapClient;
// 员工简明列表
Employes = PamMetier.GetAllIdentitesEmployes();
// 成功
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;
}
}
public void Session_Start(object sender, EventArgs e)
{
// 在会话中放入一个空的模拟列表
List<Simulation> simulations = new List<Simulation>();
Session["simulations"] = simulations;
}
}
}
第 24 行通过 Spring 实例化了 [C] 层。此实例化所需的配置在 [web.config] 中完成:
<configSections>
<sectionGroup name="system.web.extensions" type="System.Web.Configuration.SystemWebExtensionsSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
...
</sectionGroup>
<sectionGroup name="spring">
<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
</sectionGroup>
</configSections>
<spring>
<context>
<resource uri="config://spring/objects" />
</context>
<objects xmlns="http://www.springframework.net">
<object id="pammetier" type="pam_v6.WsPam.Service1SoapClient,pam-v6-client-webservice"/>
</objects>
</spring>
第 16 行中,[metier] 层是 [pam_v6.WsPam.Service1SoapClient] 类的实例,该类可在 DLL [pam-v6-client-webservice] 中找到。 需要提醒的是,我们已配置项目 [pam-v6-client-webservice] 以生成此 DLL。
[Default.aspx.cs] 中仍存在一些错误:
![]() | ![]() |
- 第 5、6 行:这些命名空间已不存在。实体现在位于命名空间 pam_v6 或 pam_v6.WsPam 中。
- 第 98 行:生成的客户端 [C] 未继承自 [dao] 层的 [PamException] 类。由于 Web 服务未暴露该异常,因此无法进行继承。 我们选择用其父类 Exception 替换 PamException。
代码变为:
...
using System.Web.UI.WebControls;
using pam_v6.WsPam;
namespace pam_v6
{
public partial class PagePam : Page
{
...
...
try
{
feuillesalaire = Global.PamMetier.GetSalaire(DropDownListEmployes.SelectedValue, HeuresTravaillées, JoursTravaillés);
}
catch (Exception ex)
{
...
修正这些错误后,我们可以运行该Web应用程序:
![]() |
- 在 [1] 中,远程 Web 服务的 Web 客户端 URL
- 在 [2] 中,员工下拉列表已填充。其中包含的数据来自 Web 服务。
我们邀请读者测试此第6版(第4版的副本)。

















