Skip to content

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]。会出现以下错误:

1
2
3
4
5
6
7
Erreur    1    Le type ou le nom d'espace de noms 'Dao' n'existe pas dans l'espace de noms 'Pam' (une référence d'assembly est-elle manquante ?)    C:\temp\pam-aspnet\pam-v6-client-webservice\Global.asax.cs    3    11    pam-v6-client-webservice
Erreur    2    Le type ou le nom d'espace de noms 'Metier' n'existe pas dans l'espace de noms 'Pam' (une référence d'assembly est-elle manquante ?)    C:\temp\pam-aspnet\pam-v6-client-webservice\Global.asax.cs    4    11    pam-v6-client-webservice
Erreur    3    Le type ou le nom d'espace de noms 'Dao' n'existe pas dans l'espace de noms 'Pam' (une référence d'assembly est-elle manquante ?)    c:\temp\pam-aspnet\pam-v6-client-webservice\default.aspx.cs    5    11    pam-v6-client-webservice
Erreur    4    Le type ou le nom d'espace de noms 'Spring' est introuvable (une directive using ou une référence d'assembly est-elle manquante ?)    C:\temp\pam-aspnet\pam-v6-client-webservice\Global.asax.cs    6    7    pam-v6-client-webservice
Erreur    5    Le type ou le nom d'espace de noms 'Metier' n'existe pas dans l'espace de noms 'Pam' (une référence d'assembly est-elle manquante ?)    c:\temp\pam-aspnet\pam-v6-client-webservice\default.aspx.cs    6    11    pam-v6-client-webservice
Erreur    6    Le type ou le nom d'espace de noms 'Employe' est introuvable (une directive using ou une référence d'assembly est-elle manquante ?)    C:\temp\pam-aspnet\pam-v6-client-webservice\Global.asax.cs    13    19    pam-v6-client-webservice
Erreur    7    Le type ou le nom d'espace de noms 'IPamMetier' est introuvable (une directive using ou une référence d'assembly est-elle manquante ?)    C:\temp\pam-aspnet\pam-v6-client-webservice\Global.asax.cs    14    19    pam-v6-client-webservice

要理解并修正这些错误,需回顾正在构建的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.aspxGlobal.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]:FeuilleSalaireElementsSalaire
    • [dao]:EmployeCotisationsIndemnites

接下来,需要记住这些远程实体的映像位于客户端,且位于 pam_v6.WsPam 命名空间中。

让我们来考察其中一个实体所暴露的方法和属性:

  • 在 [1] 中,选择本地类 [Employe]
  • 在 [2] 中,可以看到远程实体 [Employe] 的属性,以及用于本地实体自身需求的私有字段。

让我们查看存在错误的 [Global.asax.cs] 的代码:

  • 第 3 行和第 4 行使用的命名空间在服务器上存在,但在客户端上不存在。
  • 第 13 行使用了一个名为 [Employe] 的类,但未为其声明正确的命名空间
  • 第 14 行使用的 IPamMetier 接口在客户端中未被识别。

我们在之前研究的 C# 客户端中已经遇到过类似的问题。

在架构中:

  • 本地 [metier] 层由类型为 pam_v6.WsPam.Service1SoapClient 的客户端 [C] 实现,该客户端虽具有同名方法,但并未实现 IPamMetier 接口。
  • 处理的实体 (EmployeIndemnitesCotisations)位于命名空间 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版的副本)。