Skip to content

9. Die [SimuPaie]-Anwendung – Version 5 – ASP.NET / Webdienst


Literaturempfehlung: Referenz [2], Einführung in C# 2008, Kapitel 10 „Webdienste“


9.1. Die neue Architektur der Anwendung

Die Schichtenarchitektur der Pam-Anwendung sieht derzeit wie folgt aus:

Wir werden sie wie folgt weiterentwickeln:

Während in der bisherigen Architektur die Schichten [Web], [Business] und [DAO] in derselben .NET-Virtual Machine liefen, wird in der neuen Architektur die [Web]-Schicht in einer anderen Virtual Machine laufen als die [Business]- und [DAO]-Schichten. Dies ist beispielsweise der Fall, wenn sich die [Web]-Schicht auf Maschine M1 und die [Business]- und [DAO]-Schichten auf Maschine M2 befinden. Hier haben wir eine Client/Server-Architektur:

  • Der Server besteht aus den [Business]- und [DAO]-Schichten. Da es sich um einen Webdienst handelt, benötigt er Webserver Nr. 2, um zu laufen.
  • Der Client besteht aus der [Web]-Schicht. Für den Betrieb benötigt er Webserver Nr. 1.
  • Client und Server kommunizieren über das TCP/IP-Netzwerk unter Verwendung des HTTP/SOAP-Protokolls. Dazu müssen der Architektur zwei neue Schichten hinzugefügt werden:
    • die [S]-Schicht, die als Webdienst fungiert. Der Webdienst empfängt Anfragen von Remote-Clients und nutzt die [Business]- und [DAO]-Schichten, um diese zu erfüllen. Es gibt viele Möglichkeiten, einen TCP/IP-Dienst aufzubauen. Der Webdienst bietet zwei Vorteile:
      • Er nutzt das HTTP-Protokoll, das durch Unternehmens- und Regierungsfirewalls zugelassen ist
      • er nutzt ein standardisiertes HTTP/SOAP-Subprotokoll, das von vielen Entwicklungsplattformen implementiert wird: .NET, Java, PHP, Flex usw. Somit kann ein Webdienst von .NET-, Java-, PHP-, Flex- usw. Clients „genutzt“ (der Standardbegriff) werden
    • Schicht [C], die als Client für den Remote-Webdienst fungiert. Ihre Aufgabe besteht darin, mit dem Webdienst [S] zu kommunizieren.

Diese neue Architektur lässt sich ohne großen Aufwand aus den vorherigen ableiten:

  • Die [Business]- und [DAO]-Schichten bleiben unverändert
  • Die [Web]-Schicht entwickelt sich leicht weiter, vor allem um auf Entitäten wie „Employee“ und „Payroll“ zu verweisen, die zu Entitäten der Client-Schicht [C] geworden sind. Diese Entitäten entsprechen denen in den [Business]- oder [DAO]-Schichten, gehören jedoch zu anderen Namespaces.
  • Die Server-Schicht [S] ist eine Klasse, die die IPamMetier-Schnittstelle der [Business]-Schicht implementiert. Diese Implementierung ruft einfach die entsprechenden Methoden der [Business]-Schicht auf. Die von der Server-Schicht [S] implementierten Methoden werden für Remote-Clients „offengelegt“, die sie aufrufen können.
  • Die Client-Schicht [C] wird von Visual Studio generiert.

Die Prinzipien der neuen Architektur lauten wie folgt:

  • Die [Web]-Schicht kommuniziert weiterhin mit der [Business]-Schicht, als wäre diese lokal. Um dies zu erreichen, implementiert die Client-Schicht [C] die IPamMetier-Schnittstelle der eigentlichen [Business]-Schicht und präsentiert sich der [Web]-Schicht als lokale [Business]-Schicht. Abgesehen von dem zuvor erwähnten Namespace-Problem bleibt die [Web]-Schicht unverändert. Dies ist der Vorteil der Arbeit in Schichten. Hätten wir eine einschichtige Anwendung entwickelt, wäre eine umfassende Überarbeitung erforderlich gewesen.
  • Die Client-Schicht [C] leitet die Anfragen der [Web]-Schicht transparent an den Remote-Webdienst [S] weiter. Sie übernimmt alle Aspekte der „Netzwerkkommunikation“. Sie empfängt eine Antwort vom Remote-Webdienst, die sie so formatiert, dass sie in der von der [Web]-Schicht erwarteten Form an diese zurückgegeben wird.
  • Auf der Serverseite empfängt der Webdienst [S] Befehle von seinen Remote-Clients. Er formatiert diese so, dass die Methoden der IPamMetier-Schnittstelle in der [Business]-Schicht aufgerufen werden. Sobald er die Antwort von der [Business]-Schicht erhalten hat, formatiert er sie, um sie über das Netzwerk an den Client [C] zu übertragen. Die [Business]- und [DAO]-Schichten müssen nicht geändert werden.

9.2. Das Visual Web Developer-Projekt für den Webdienst

Wir erstellen ein neues Projekt mit Visual Web Developer:

  • In [1] wählen wir ein C#-Webprojekt aus
  • In [2] wählen wir „ASP.NET-Webdienstanwendung“
  • In [3] benennen wir das Webprojekt
  • in [4] geben wir einen Speicherort für dieses Projekt an
  • In [1] das generierte Projekt. Es handelt sich um ein Standard-Webprojekt mit den folgenden Details:
    • Wir haben festgelegt, dass es sich bei dem Projekt um einen „Webdienst“ handelt. Ein Webdienst sendet keine HTML-Webseiten an seine Clients, sondern Daten im XML-Format. Daher wurde die Seite [Default.aspx], die normalerweise generiert wird, nicht erstellt.
  • In [2] wurde eine Datei [Service1.asmx] mit folgendem Inhalt generiert:

<%@ WebService Language="C#" CodeBehind="Service1.asmx.cs" Class="pam_v5_webservice.Service1" %>
  • (Fortsetzung)
    • - Das WebService-Tag gibt an, dass [Service.asmx] ein Webdienst ist
    • - Das CodeBehind-Attribut gibt den Speicherort des Quellcodes für diesen Webdienst an
    • - Das Attribut „Class“ gibt den Namen der Klasse an, die den Webdienst im Quellcode implementiert

Der Quellcode [Service.asmx.cs] für den Standard-Webdienst lautet wie folgt:


using System.Web.Services;
 
namespace pam_v5_webservice
{
  /// <summary>
  /// Service summary description1
  /// </summary>
  [WebService(Namespace = "http://tempuri.org/")]
  [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
  [System.ComponentModel.ToolboxItem(false)]
  // To allow this Web service to be called from a script using ASP.NET AJAX, remove the comment marks from the following line. 
  // [System.Web.Script.Services.ScriptService]
  public class Service1 : System.Web.Services.WebService
  {
 
    [WebMethod]
    public string HelloWorld()
    {
      return "Hello World";
    }
  }
}
  • Zeile 8: Die WebService-Annotation, die bewirkt, dass die Klasse Service1 in Zeile 13 als Webdienst verfügbar gemacht wird. Ein Webdienst gehört zu einem Namespace, um zu verhindern, dass zwei Webdienste weltweit denselben Namen haben. Wir müssen diesen Namespace später ändern.
  • Zeile 13: Die Klasse „Service1“ leitet sich von der WebService-Klasse des .NET-Frameworks ab.
  • Zeile 16: Die WebMethod-Annotation stellt sicher, dass die auf diese Weise annotierte Methode für Remote-Clients verfügbar ist, die sie dann aufrufen können.
  • Zeilen 17–20: Die HelloWorld-Methode ist eine Demonstrationsmethode. Wir werden sie später entfernen. Sie ermöglicht es uns, erste Tests durchzuführen und die Visual Studio-Tools sowie bestimmte Schlüsselkonzepte im Zusammenhang mit Webdiensten zu erkunden.
  • In [1] führen wir den Webdienst [Service.asmx] aus
  • VS Web Developer hat seinen integrierten Webserver gestartet und lauscht auf einem zufälligen Port, in diesem Fall 1599. Anschließend wurde die URL [2] vom Webserver angefordert. Dies ist die URL für eine Webdienst-Testseite.
  • In [3] finden Sie einen Link, über den Sie die Webdienst-Beschreibungsdatei anzeigen können. Diese Datei, die aufgrund ihrer Endung (.wsdl) als WSDL-Datei (Web Service Description Language) bezeichnet wird, ist eine XML-Datei, die die vom Webdienst bereitgestellten Methoden beschreibt. Aus dieser WSDL-Datei können Clients folgende Informationen entnehmen:
    • den Namespace des Webdienstes
    • die Liste der vom Webdienst bereitgestellten Methoden
    • die von jeder Methode erwarteten Parameter
    • die von jeder Methode zurückgegebene Antwort
  • in [4] die einzige vom Webdienst bereitgestellte Methode .
  • in [5], der Inhalt der WSDL-Datei, die über den Link [3] abgerufen wurde. Beachten Sie die URL [6]. Die Kenntnis dieser URL ist für Webservice-Clients erforderlich.
  • In [7] können Sie auf der Seite, die Sie über den Link [4] aufrufen, die Methode [HelloWorld] des Webdienstes aufrufen
  • In [8] das erhaltene Ergebnis: eine XML-Antwort. Beachten Sie die URL der Methode [9].

Die Betrachtung der vorherigen Seiten hilft uns zu verstehen, wie eine Webdienstmethode aufgerufen wird und welche Art von Antwort sie zurückgibt. Dies ermöglicht es uns, HTTP-Clients zu schreiben, die mit dem Webdienst kommunizieren können. Die meisten modernen IDEs ermöglichen die automatische Generierung dieses HTTP-Clients, wodurch dem Entwickler das Schreiben erspart bleibt. Dies gilt insbesondere für Visual Studio Express.

Bevor wir mit diesem Projekt fortfahren, ändern wir den Standard-Namespace, der bei der Generierung von Klassen verwendet wird:

Wenn wir die Projekteigenschaften auswählen (Rechtsklick auf das Projekt / Eigenschaften), sehen wir [1] den Namen der Projektassembly und [2] ihren Standard-Namespace.

Sobald dies erledigt ist,

  • ändern wir in [Service1.asmx.cs] den Klassen-Namespace:

using System.Web.Services;
 
namespace pam_v5
{
...
  public class Service1 : System.Web.Services.WebService
  {
...
  }
}
  • In [Service.asmx] ändern wir außerdem den für die Klasse [Service1] verwendeten Namespace (Rechtsklick / Quelltext anzeigen):

<%@ WebService Language="C#" CodeBehind="Service1.asmx.cs" Class="pam_v5.Service1" %>

Kehren wir zur Architektur unserer Anwendung zurück:

  • Die [S]-Schicht ist der Webservice. Sie stellt lediglich die Methoden der [Business]-Schicht für Remote-Clients bereit. Dies ist die Schicht, die wir derzeit entwickeln.
  • Die [C]-Schicht ist der HTTP-Client des Webdienstes. Dies ist die Schicht, die IDEs automatisch generieren können.
  • Die [web]-Schicht behandelt die [C]-Schicht als lokale [business]-Schicht, wenn wir sicherstellen, dass die [C]-Schicht die Schnittstelle der entfernten [business]-Schicht implementiert.

Im Folgenden sehen wir, dass unser Webdienst:

  • die Methoden der [Business]-Schicht bereitstellt
  • mit der [Business]-Schicht kommunizieren, die wiederum mit der [DAO]-Schicht kommuniziert.

Das Projekt muss daher die DLLs für die [Business]- und [DAO]-Schichten verwenden. Es entwickelt sich wie folgt:

  • In [1] fügen wir Verweise auf das Projekt hinzu
  • in [2] wählen wir die üblichen DLLs aus dem Ordner [lib] aus. Wir stellen sicher, dass ihre Eigenschaft „In lokalen Ordner kopieren“ auf „True“ gesetzt ist. Die ausgewählten DLLs sind diejenigen, die die [Business]- und [DAO]-Schichten mit NHibernate-Unterstützung implementieren.

Eine Webanwendung vom Typ „ASP.NET-Webdienst“ kann genau wie eine klassische „ASP.NET-Website“-Anwendung über eine globale Anwendungsklasse „Global.asax“ verfügen. Wir haben die Vorteile einer solchen Klasse bereits kennengelernt:

  • Sie wird beim Start der Anwendung instanziiert und verbleibt im Speicher
  • sie kann somit Daten speichern, die von allen Clients gemeinsam genutzt werden und schreibgeschützt sind. In unserer Anwendung wird sie, wie in den vorherigen, die vereinfachte Liste der Mitarbeiter speichern. Dadurch wird vermieden, dass diese Liste bei einer Client-Anfrage aus der Datenbank abgerufen werden muss.
  • Klicken Sie in [1] mit der rechten Maustaste auf das Projekt
  • in [2] die Option [Neues Element hinzufügen]
  • Wählen Sie in [3] [Globale Anwendungsklasse]
  • In [4] wurde die Datei [Global.asax] zum Projekt hinzugefügt

Der Inhalt der Datei [Global.asax] lautet wie folgt:


<%@ Application Codebehind="Global.asax.cs" Inherits="pam_v5.Global" Language="C#" %>

Der Inhalt der Datei [Global.asax.cs] lautet wie folgt:


using System;
 
namespace pam_v5
{
  public class Global : System.Web.HttpApplication
  {
 
    protected void Application_Start(object sender, EventArgs e)
    {
 
    }
...
  }
}

Was sollten wir in der Application_Start-Methode tun? Genau dasselbe wie in früheren Webanwendungen. Kehren wir zur Anwendungsarchitektur zurück und platzieren wir die [Global]-Klasse dort:

In der obigen Abbildung,

  • wird die Klasse [Global] beim Start des Webdienstes instanziiert. Sie verbleibt im Speicher, solange der Webdienst aktiv ist.
  • Die Klasse [Global] instanziiert die Schichten [business] und [DAO] in ihrer Methode [Application_Start]
  • Um die Leistung zu verbessern, speichert die Klasse [Global] die vereinfachte Liste der Mitarbeiter in einem internen Feld. Sie ruft die Liste der Mitarbeiter aus diesem Feld ab.
  • Der Webdienst wird bei jeder Client-Anfrage instanziiert. Er verschwindet, nachdem er diese Anfrage bearbeitet hat. Er kommuniziert nicht direkt mit der [business]-Schicht, sondern mit der [Global]-Klasse. Letztere implementiert die Schnittstelle der [business]-Schicht.

Die Klasse [Global] ähnelt derjenigen, die bereits für frühere Anwendungen erstellt wurde:


using System;
using Pam.Dao.Entites;
using Pam.Metier.Entites;
using Pam.Metier.Service;
using Spring.Context.Support;
 
namespace pam_v5
{
  public class Global : System.Web.HttpApplication
  {
    // --- static application data ---
    public static Employe[] Employes;
    public static IPamMetier PamMetier = null;
 
    protected void Application_Start(object sender, EventArgs e)
    {
      // instantiation layer [metier]
      PamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
      // retrieve the simplified employee table 
      Employes = PamMetier.GetAllIdentitesEmployes();
    }
 
    // simplified list of employees
    static public Employe[] GetAllIdentitesEmployes()
    {
      return Employes;
    }
 
    // employee salary
    static public FeuilleSalaire GetSalaire(string SS, double heuresTravaillées, int joursTravailles)
    {
      return PamMetier.GetSalaire(SS, heuresTravaillées, joursTravailles);
    }
  }
}

Die Klasse [Global] implementiert die Schnittstelle [IPamMetier], dies wird jedoch in der Deklaration nicht ausdrücklich angegeben:


  public class Global : System.Web.HttpApplication, IPamMetier

Tatsächlich sind die Methoden „GetAllEmployeeIDs“ (Zeile 24) und „GetSalary“ (Zeile 30) statisch, während die Methoden der Schnittstelle „IPamMetier“ dies nicht sind. Daher kann die Klasse „Global“ die Schnittstelle „IPamMetier“ nicht implementieren. Außerdem ist es nicht möglich, die Methoden „GetAllEmployeeIDs“ und „GetSalary“ als nicht-statisch zu deklarieren. Dies liegt daran, dass auf sie über den Klassennamen und nicht über eine Instanz der Klasse zugegriffen wird.

  • Zeile 15: Die Methode Application_Start ähnelt der der [Global]-Klassen, die in früheren Versionen behandelt wurden. Sie instanziiert die [business]-Schicht (Zeile 18) und initialisiert anschließend (Zeile 20) das Mitarbeiter-Array aus Zeile 12.
  • Zeile 24: Die Methode „GetAllEmployeeIDs“ gibt einfach das Array der Mitarbeiter aus Zeile 12 zurück. Dies ist der Vorteil, wenn man es zu Beginn der Anwendung gespeichert hat.
  • Zeile 30: Die Methode GetSalaire ruft die gleichnamige Methode in der [business]-Schicht auf.

Um die [business]-Schicht zu instanziieren (Zeile 18), verwendet die [Global]-Klasse das Spring-Framework. Dies wird durch die Datei [Web.config] konfiguriert, die mit der des vorherigen Projekts identisch ist: Sie konfiguriert Spring und NHibernate so, dass die [business]- und [DAO]-Schichten des Webdienstes instanziiert werden.

Kehren wir zur Architektur unserer Client/Server-Anwendung zurück:

Auf der Serverseite muss nun nur noch der [S]-Webdienst selbst geschrieben werden. Kehren wir zur Anwendungsarchitektur zurück:

sehen wir, dass auf der Serverseite alle Schichten, die der [Business]-Schicht vorangehen, deren Schnittstelle IPamMetier implementieren. Dies ist nicht zwingend erforderlich, stellt jedoch einen logischen Ansatz dar. Diese Argumentation lässt sich auf die Clientseite übertragen, auf den Client [C] des Webdienstes [S]. Somit implementieren alle Schichten, die die [Web]-Schicht von der [Business]-Schicht trennen, die Schnittstelle IPamMetier. Wir können daher sagen, dass wir zu einer dreischichtigen Anwendung zurückgekehrt sind:

  • die Präsentationsschicht [Web] [1]
  • die [Business]-Schicht [2]
  • die Datenzugriffsschicht [3]

Die Implementierung des Webdienstes [Service1.asmx.cs] könnte wie folgt aussehen:


using System.Web.Services;
using Pam.Dao.Entites;
using Pam.Metier.Entites;
using Pam.Metier.Service;
 
namespace pam_v5
{
  [WebService(Namespace = "http://st.istia.univ-angers.fr/")]
  [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
  [System.ComponentModel.ToolboxItem(false)]
  public class Service1 : System.Web.Services.WebService, IPamMetier
  {
 
    // list of all employee identities 
    [WebMethod]
    public Employe[] GetAllIdentitesEmployes()
    {
      return Global.GetAllIdentitesEmployes();
    }
 
    // ------- salary calculation 
    [WebMethod]
    public FeuilleSalaire GetSalaire(string ss, double heuresTravaillees, int joursTravailles)
    {
      return Global.GetSalaire(ss, heuresTravaillees, joursTravailles);
    }
  }
}
  • Zeile 8: Die Klasse ist mit dem Attribut [WebService] versehen, und wir weisen dem Webdienst-Namespace einen Namen zu
  • Zeile 11: Die Klasse [Service1] erbt von der Klasse [WebService] und implementiert die Schnittstelle [IPamMetier]
  • Zeilen 15 und 22: Jede Methode der Klasse ist mit dem Attribut [WebMethod] versehen, um sie für Remote-Clients verfügbar zu machen. Standardmäßig sind alle öffentlichen Methoden eines Webdienstes verfügbar. Die Attribute in den Zeilen 15 und 22 sind daher hier optional. Um die Schnittstelle [IPamMetier] zu implementieren, ruft jede Methode einfach die gleichnamige Methode in der Klasse [Global] auf.

Wir sind bereit, den Webdienst auszuführen:

  • In [1] wird das Projekt neu generiert
  • in [2] wählen wir den Webdienst [Service1.asmx] aus und zeigen ihn im Browser an [3]
  • in [4] die angezeigte Webseite. Sie zeigt die Webdienstmethoden an.
  • In [4] folgen wir dem Link [GetAllIdentitesEmployes] und gelangen in [5] zur Testseite für diese Methode.
  • in [6] die URL der Methode
  • In [7] die Schaltfläche [Call], mit der die Methode getestet wird. Diese Methode benötigt keine Parameter.
  • In [8] das vom Webdienst zurückgegebene XML-Ergebnis. In diesem Ergebnis sind nur die Eigenschaften SS, LastName und FirstName der Employee-Objekte relevant, da die Methode [GetAllEmployeeIDs] nur diese Eigenschaften abfragt. Diese Methode gibt jedoch ein Array von Employee-Objekten zurück. In [8] sehen wir, dass die numerischen Eigenschaften Id und Version im zurückgegebenen XML-Stream enthalten sind, nicht jedoch die Eigenschaften mit einem Nullwert: Address, City, ZipCode, Allowances.

Wir haben einen aktiven Webdienst. Wir werden nun einen C#-Client dafür schreiben. Dazu benötigen wir die URI der WSDL-Datei des Webdienstes. Diese erhalten wir von der Seite, die bei der Ausführung von [Service.asmx] zunächst angezeigt wird:

  • in [1] die Webdienst-URI
  • in [2] den Link zu seiner WSDL-Datei
  • in [3] der Wert dieses Links

9.3. Das C#-Projekt für einen NUnit-Client des Webdienstes

Wir erstellen ein C#-Projekt (mit Visual C#, nicht mit Visual Web Developer) für den Webdienst-Client. Dies wird ein NUnit-Test-Client sein. Daher wird das Projekt vom Typ „Class Library“ sein.

  • In [1] erstellen wir ein C#-Projekt vom Typ „Class Library“
  • In [2] benennen wir das Projekt
  • In [3] löschen wir [Class1.cs].
  • In [4] das neue Projekt.
  • In den Projekteigenschaften legen wir auf der Registerkarte [Anwendung] [5] den Namespace des Projekts fest. Jede von der IDE generierte Klasse wird in diesem Namespace liegen.

Wir speichern unser neues Projekt an einem Ort unserer Wahl:

 

Sobald dies erledigt ist, generieren wir den Client für den Remote-Webdienst. Um zu verstehen, was wir nun tun werden, müssen wir uns noch einmal die derzeit im Aufbau befindliche Client-Server-Architektur ansehen:

Die IDE generiert die Client-Schicht [C] anhand der URI der WSDL-Datei [S] des Webdienstes. Erinnern Sie sich daran, dass die URI dieser Datei zuvor notiert wurde. Wir gehen wie folgt von der Web- us:

  • Klicken Sie in [1] mit der rechten Maustaste auf den Zweig „References“ und fügen Sie eine Dienstreferenz hinzu
  • Geben Sie in [2] die zuvor notierte URL der WSDL-Datei des Webdienstes ein. Der Dienst muss laufen, falls er dies noch nicht tut.
  • Fordern Sie in [3] die Erkennung des Webdienstes über dessen WSDL-Datei an
  • In [4] den erkannten Webdienst
  • In [5] die vom Webdienst bereitgestellten Methoden.
  • In [6] den Namespace, in dem Sie die Klassen und Schnittstellen des zu generierenden Clients ablegen möchten.
  • Bestätigen Sie den Assistenten

  • In [1] die generierte Client-Datei ( ). Doppelklicken Sie darauf, um den Inhalt anzuzeigen.
  • In [2] werden im Objekt-Explorer die Klassen und Schnittstellen des Namespace „Client.WsPam“ angezeigt. Dies ist der Namespace des generierten Clients.
  • In [3] die Klasse, die den Webdienst-Client implementiert.
  • In [4] die vom Client [Service1SoapClient] implementierten Methoden. Hier finden Sie die beiden Methoden des Remote-Webdienstes [5] und [6].
  • In [2] sehen Sie die Entitätsdiagramme für die Schichten:
    • [business]: Payroll, PayrollItems
    • [DAO]: Employee, Contributions, Allowances

Beachten Sie im weiteren Verlauf, dass sich diese Darstellungen der Remote-Entitäten auf der Client-Seite und im Namespace PamV5Client.WsPam befinden.

Sehen wir uns die Methoden und Eigenschaften an, die von einer dieser Entitäten bereitgestellt werden:

  • In [1] wählen wir die lokale Klasse [Employee] aus
  • In [2] sehen wir die Eigenschaften der entfernten Entität [Employee] sowie private Felder, die für die spezifischen Anforderungen der lokalen Entität verwendet werden.

Kehren wir zu unserer C#-Anwendung zurück. Wir fügen ihr eine NUnit-Testklasse hinzu:

  • in [1] die hinzugefügte [NUnit]-Klasse. Die [NUnit]-Klasse benötigt das NUnit-Framework und daher einen Verweis auf dessen DLL. Wir gehen hier davon aus, dass das NUnit-Framework auf dem Rechner installiert ist (http://nunit.org/).
  • In [2] fügen wir dem Projekt einen Verweis hinzu
  • auf der Registerkarte [3] .NET, die die auf dem Rechner registrierten DLLs auflistet, wählen wir [4], die [nunit.framework]-DLL, Version 2.4.6 oder höher.

Zusätzlich verwenden wir Spring, um den lokalen Client [C] des Webdienstes [S] zu instanziieren:

Der Verweis auf die Spring-DLL kann auf die gleiche Weise wie für das NUnit-Framework hinzugefügt werden, sofern die DLLs bereits auf dem Rechner registriert sind (http://www.springframework.net/download.html).

Wir gehen anders vor. Wir verwenden den Ordner [lib] aus früheren Projekten, der die von Spring benötigten DLLs enthielt, und fügen die Spring-Referenz zum Projekt hinzu:

Werfen wir noch einmal einen Blick auf die Architektur des derzeit in Entwicklung befindlichen Clients:

Oben sehen wir, dass der Test-Client [1] mit einer erweiterten [Business]-Schicht [2] verbunden ist. Diese Schicht verfügt über dieselben Methoden wie die Remote-[Business]-Schicht. Wir können daher die Testklasse verwenden, die wir bereits beim Testen der [Business]-Schicht im C#-Projekt [pam-metier-dao-nhibernate] kennengelernt haben:


using NUnit.Framework;
using Pam.Dao.Entites;
using Pam.Metier.Entites;
using Pam.Metier.Service;
using Spring.Context.Support;
 
namespace Pam.Metier.Tests {
 
    [TestFixture()]
    public class NunitTestPamMetier : AssertionHelper {
 
        // the [metier] layer to test 
        private IPamMetier pamMetier;
 
        // manufacturer
        public NunitTestPamMetier() {
            // layer instantiation [dao]
            pamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
        }
 
 
        [Test]
        public void GetAllIdentitesEmployes() {
            // audit no. of employees 
            Expect(2, EqualTo(pamMetier.GetAllIdentitesEmployes().Length));
        }
 
        [Test]
        public void GetSalaire1() {
            // wage sheet calculation 
            FeuilleSalaire feuilleSalaire = pamMetier.GetSalaire("254104940426058", 150, 20);
            // checks 
            Expect(368.77, EqualTo(feuilleSalaire.ElementsSalaire.SalaireNet).Within(1E-06));
            // non-existent employee payslip 
            bool erreur = false;
            try {
                feuilleSalaire = pamMetier.GetSalaire("xx", 150, 20);
            } catch (PamException) {
                erreur = true;
            }
            Expect(erreur, True);
        }
 
    }
}

Es sind einige Änderungen vorzunehmen:

  • In Zeile 18 instanziieren wir die [business]-Schicht mithilfe des Spring-Frameworks. Die Klasse ist in beiden Fällen nicht identisch. Hier ist die lokale [business]-Schicht eine Instanz der Klasse [PamV5Client.WsPam.Service1SoapClient], die von der IDE generiert wurde. Daher ist Spring in der Datei [app.config] des C#-Projekts wie folgt konfiguriert:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
 
    <configSections>
        <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="PamV5Client.WsPam.Service1SoapClient, pam-v5-client-csharp-webservice"/>
        </objects>
    </spring>
 
 
    <system.serviceModel>
...
  • In Zeile 16 oben ist das [pammetier]-Objekt eine Instanz der Klasse [PamV5Client.WsPam.Service1SoapClient], die sich in der Assembly [pam-v5-client-csharp-webservice] befindet. Um die erste Information zu finden, gehen Sie einfach zurück zur Definition der Klasse [Service1SoapClient] im Objekt-Explorer (Abschnitt 9.3):
  • in [2] die Implementierungsklasse der lokalen [business]-Schicht und in [1] deren Namespace
  • in [3], in den Projekteigenschaften, den Assembly-Namen, die zweite Information, die zur Konfiguration des Spring-Objekts [pammetier] benötigt wird.

Kehren wir zum Code für die Instanziierung der lokalen [Business]-Schicht in [NUnit.cs] zurück:


        // the [metier] layer to test 
        private IPamMetier pamMetier;
 
        // manufacturer
        public NunitTestPamMetier() {
            // layer instantiation [dao]
            pamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
        }
 

Zeile 7: Die Remote-[Business]-Schicht war vom Typ IPamMetier. Hier ist die [Business]-Schicht vom Typ [Service1SoapClient]:


public class Service1SoapClient : System.ServiceModel.ClientBase<Service1Soap>

Wir sehen, dass die Klasse Service1SoapClient die Schnittstelle IPamMetier nicht implementiert, obwohl sie Methoden mit denselben Namen bereitstellt. Wir müssen daher die Instanziierung der lokalen [Business]-Schicht wie folgt schreiben:


        // the [metier] layer to test 
        private Service1SoapClient pamMetier;
 
        // manufacturer
        public NunitTestPamMetier() {
            // instantiation layer [metier]
            pamMetier = ContextRegistry.GetContext().GetObject("pammetier") as Service1SoapClient;
}

Eine weitere Änderung, die vorgenommen werden muss:


        [Test]
        public void GetSalaire1() {
...
            try {
                feuilleSalaire = pamMetier.GetSalaire("xx", 150, 20);
            } catch (PamException) {
                erreur = true;
            }
            Expect(erreur, True);
        }
 

Der obige Code verwendet in Zeile 6 den Typ „PamException“, der auf der Client-Seite nicht existiert. Wir werden ihn durch seine übergeordnete Klasse, den Typ „Exception“, ersetzen.


        [Test]
        public void GetSalaire1() {
...
            try {
                 feuilleSalaire = pamMetier.GetSalaire("xx", 150, 20);
            } catch (Exception) {
                erreur = true;
            }
            Expect(erreur, True);
        }
 

Schließlich sind die importierten Namespaces nicht mehr identisch:


using System;
using PamV5Client.WsPam;
using NUnit.Framework;
using Spring.Context.Support;

Sobald dies erledigt ist, kann das Projekt „Class Library“ kompiliert werden. Dabei wird die folgende DLL erstellt:

  • in [1], dem Ordner [bin/Release] des C#-Projekts
  • in [2] die DLL des Projekts.

Der NUnit-Test wird dann vom NUnit-Framework ausgeführt (die MySQL-Datenbank dbpam_nhibernate muss für den Test aktiv sein):

  • in [3] und [4] wird die DLL [2] in die NUnit-Testanwendung geladen
  • in [5] wird die Testklasse ausgewählt und ausgeführt [6]
  • in [7] die Ergebnisse eines erfolgreichen Tests

Wir haben nun einen funktionierenden Webdienst.