Skip to content

12. Dienstleistungen Web

12.1. Einführung

Im vorigen Kapitel haben wir mehrere TCP/IP-Client-Server-Anwendungen vorgestellt. Da Client und Server Textzeilen austauschen, können sie in jeder beliebigen Sprache geschrieben werden. Der Client muss lediglich das vom Server erwartete Dialogprotokoll kennen.

Webdienste sind ebenfalls TCP/IP-Serveranwendungen. Sie weisen die folgenden Merkmale auf:

  • Sie werden von Webservern gehostet, und das Client-Server-Austauschprotokoll ist HTTP (HyperText Transport Protocol), ein Protokoll, das auf TCP/IP aufsetzt.
  • Der Webdienst verfügt über ein Standard-Dialogprotokoll, unabhängig vom angebotenen Dienst. Ein Webdienst bietet verschiedene Dienste S1, S2, .., Sn an. Jeder dieser Dienste erwartet vom Client bereitgestellte Parameter und gibt ein Ergebnis an den Client zurück. Für jeden Dienst muss der Client Folgendes wissen:
    • den genauen Namen der Abteilung, falls
    • die Liste der zu übergebenden Parameter und deren Typ
    • den Typ des vom Dienst zurückgegebenen Ergebnisses

Sobald diese Elemente bekannt sind, folgt der Client-Server-Dialog dem gleichen Format, unabhängig davon, welcher Webdienst abgefragt wird. Auf diese Weise wird die Client-Programmierung standardisiert.

  • Aus Sicherheitsgründen zum Schutz vor Angriffen aus dem Internet verfügen viele Organisationen über private Netzwerke und öffnen auf ihren Servern nur bestimmte Ports für das Internet: im Wesentlichen Port 80 für den Webdienst. Alle anderen Ports sind gesperrt. Client-Server-Anwendungen, wie sie im vorigen Kapitel vorgestellt wurden, werden innerhalb des privaten Netzwerks (Intranet) aufgebaut und sind von außen in der Regel nicht zugänglich. Durch das Hosten eines Dienstes auf einem Webserver wird dieser für die gesamte Internet-Community zugänglich.
  • Der Webdienst kann als Remote-Objekt modelliert werden. Die angebotenen Dienste werden dann zu Methoden dieses Objekts. Ein Client kann auf dieses Remote-Objekt zugreifen, als wäre es lokal. Dadurch wird die gesamte Netzwerkkommunikationsschicht verborgen, und Sie können einen Client entwickeln, der von dieser Schicht unabhängig ist. Wenn sich die Netzwerkschicht ändert, muss der Client nicht angepasst werden.
  • Wie bei den im vorigen Kapitel vorgestellten TCP/IP-Client-Server-Anwendungen können Client und Server in jeder beliebigen Sprache geschrieben werden. Sie tauschen Textzeilen aus. Diese bestehen aus zwei Teilen:
    • für das HTTP-Protokoll erforderliche Header
    • dem Nachrichtentext. Bei einer Antwort vom Server an den Client liegt dieser im XML-Format (eXtensible Markup Language) vor. Bei einer Anfrage vom Client an den Server kann der Nachrichtentext verschiedene Formen annehmen, darunter auch XML. Die XML-Anfrage des Clients kann ein spezielles Format namens SOAP (Simple Object Access Protocol) haben. In diesem Fall folgt auch die Serverantwort dem SOAP-Format.

Die Architektur einer auf einem Webdienst basierenden Client-Server-Anwendung sieht wie folgt aus:

Dies ist eine Erweiterung der 3-Schichten-Architektur, der spezielle Netzwerkkommunikationsklassen hinzugefügt werden. Eine ähnliche Architektur haben wir bereits in Abschnitt 11.9.1 bei der grafischen Windows-Client/Server-Anwendung Tcp d'impôts kennengelernt.

Lassen Sie uns diese allgemeinen Grundlagen anhand eines ersten Beispiels erläutern.

12.2. Ein erster Webdienst mit Visual Web Developer

Wir werden eine erste Client/Server-Anwendung mit der folgenden vereinfachten Architektur erstellen:

12.2.1. Die Serverseite

Wir haben bereits erwähnt, dass ein Webdienst von einem Webserver gehostet wird. Das Schreiben eines Webdienstes fällt in den allgemeinen Rahmen der serverseitigen Webprogrammierung. Wir hatten bereits Gelegenheit, Web-Clients zu schreiben, was ebenfalls Webprogrammierung ist, diesmal jedoch auf der Client-Seite. Der Begriff Webprogrammierung bezieht sich meist eher auf serverseitige Programmierung als auf clientseitige Programmierung. Um Webdienste oder, allgemeiner gesagt, Webanwendungen zu entwickeln, ist Visual C# nicht das richtige Werkzeug. Wir werden Visual Developer verwenden, eine der Express-Versionen von Visual Studio 2008, die unter [1] zum Download [2] bereitsteht: [http://msdn.microsoft.com/en-fr/express/future/bb421473(en-us).aspx] (Mai 2008):

  • [1]: Download-Adresse
  • [2]: Registerkarte „Downloads“
  • [3]: Visual Developer 2008 herunterladen

Um einen ersten Webdienst zu erstellen, gehen Sie nach dem Start von Visual Developer wie folgt vor:

  • [1]: Wählen Sie die Option „Datei / Neue Website“
  • [2]: Wählen Sie einen ASP.NET-Webdienst
  • [3]: Wählen Sie die Entwicklungssprache: C#
  • [4]: Geben Sie den Ordner an, in dem das Projekt erstellt werden soll
  • [5]: Das in Visual Web Developer erstellte Projekt
  • [6]: Der Projektordner auf der Festplatte

Eine Webanwendung ist in Web Developer wie folgt aufgebaut:

  • ein Stammverzeichnis, das die Dokumente der Website enthält (statische Webseiten im HTML-Format, Bilder, dynamische Webseiten im .aspx-Format, Webdienste im .asmx-Format usw.). Ebenfalls enthalten ist die Datei [web.config], die Konfigurationsdatei für die Webanwendung. Sie erfüllt dieselbe Funktion wie die Datei [App.config] für Windows-Anwendungen und ist auf dieselbe Weise aufgebaut.
  • ein Ordner [App_Code], der Klassen und Schnittstellen der Website zum Kompilieren enthält.
  • einen Ordner [App_Data], in dem Daten abgelegt werden, die von [App_Code]-Klassen verwendet werden. Dieser könnte beispielsweise eine SQL Server *.mdf-Datenbank enthalten.

[Service.asmx] ist der Webdienst, dessen Erstellung wir angefordert haben. Er enthält nur die folgende Zeile:


<%@ WebService Language="C#" CodeBehind="~/App_Code/Service.cs" Class="Service" %>

Der obige Quellcode ist für den Webserver bestimmt, auf dem die Anwendung gehostet wird. Im Produktionsmodus ist dieser Server in der Regel IIS (Internet Information Server), der Webserver von Microsoft. Visual Web Developer bindet einen schlanken Webserver für die Verwendung im Entwicklungsmodus ein. Die vorangehende Anweisung teilt dem Webserver mit:

  • [Service.asmx] ist ein Webdienst (Direktive WebService)
  • in C# geschrieben ist (Attribut „Language“)
  • dass sich der C#-Code für den Webdienst in [~/App_Code/Service.cs] befindet (Attribut CodeBehind). Dort wird der Webserver ihn kompilieren.
  • dass die Klasse, die den Webdienst implementiert, den Namen „Service“ trägt (Attribut „Class“)

Der von Visual Developer generierte C#-Code [Service.cs] des Webdienstes lautet wie folgt:


using System.Web.Services;
 
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
// To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line. 
// [System.Web.Script.Services.ScriptService]
public class Service : System.Web.Services.WebService
{
    public Service () {
 
        //Uncomment the following line if using designed components 
         //InitializeComponent(); 
    }
 
    [WebMethod]
    public string HelloWorld() {
        return "Hello World";
    }
 
}

Die Klasse Service ähnelt einer klassischen C#-Klasse, wobei jedoch einige Punkte zu beachten sind:

  • Zeile 7: Die Klasse leitet sich von der in System.Web.Services definierten Klasse WebService ab. Diese Vererbung ist nicht immer zwingend erforderlich. Insbesondere in diesem Beispiel könnte darauf verzichtet werden.
  • Zeile 3: Der Klasse selbst ist ein Attribut [WebService(Namespace="http://tempuri.org/")] vorangestellt, das dem Webdienst einen Namespace zuweisen soll. Ein Klassenanbieter weist seinen Klassen einen Namespace zu, um ihnen einen eindeutigen Namen zu geben und Konflikte mit Klassen anderer Anbieter zu vermeiden, die möglicherweise denselben Namen haben. Das Gleiche gilt für Webdienste. Jeder Webdienst muss durch einen eindeutigen Namen identifiziert werden, hier durch http://tempuri.org/. Dieser Name kann beliebig sein. Es muss sich nicht um eine HTTP-URI handeln.
  • Zeile 15: Der Methode HelloWorld ist ein [WebMethod] vorangestellt, das dem Compiler mitteilt, dass die Methode für Remote-Clients des Webdienstes sichtbar gemacht werden muss. Eine Methode, der dieses Attribut nicht vorangestellt ist, ist für Clients des Webdienstes nicht sichtbar. Dies könnte eine interne Methode sein, die von anderen Methoden verwendet wird, aber nicht zur Veröffentlichung bestimmt ist.
  • Zeile 9: Service-Konstruktor web. Er ist in unserer Anwendung nutzlos.

Die generierte Klasse [Service.cs] wird wie folgt umgewandelt:


using System.Web.Services;
 
[WebService(Namespace = "http://st.istia.univ-angers.fr")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class Service : System.Web.Services.WebService
{
    [WebMethod]
    public string DisBonjourALaDame(string nomDeLaDame) {
        return string.Format("Bonjour Mme {0}", nomDeLaDame);
    }
 
}

Die für die Webanwendung generierte Konfigurationsdatei [web.config] sieht wie folgt aus:


<?xml version="1.0"?>
<!-- 
    Note: As an alternative to hand editing this file you can use the 
    web admin tool to configure settings for your application. Use
    the Website->Asp.Net Configuration option in Visual Studio.
    A full list of settings and comments can be found in 
    machine.config.comments usually located in 
    \Windows\Microsoft.Net\Framework\v2.x\Config 
-->
<configuration>
 
 
    <configSections>
      <sectionGroup name="system.web.extensions" type="System.Web.Configuration.SystemWebExtensionsSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
...
      </sectionGroup>
    </configSections>  
 
 
    <appSettings/>
    <connectionStrings/>
...
</configuration>

Die Datei ist 140 Zeilen lang. Sie ist komplex, daher werden wir sie nicht näher erläutern. Wir belassen sie so, wie sie ist. Oben sind die Elemente <configuration>, <configSections>, <sectionGroup>, <appSettings> und <connectionString> aufgeführt, die wir in der Datei [App.config] von Windows-Anwendungen vorgefunden haben.

Wir haben einen funktionsfähigen Webdienst, der ausgeführt werden kann:

  • [1,2]: Klicken Sie mit der rechten Maustaste auf [Service.asmx] und wählen Sie die Option, die Seite in einem Browser anzuzeigen
  • [3]: Visual Web Developer startet seinen integrierten Webserver und platziert dessen Symbol in der unteren rechten Ecke der Taskleiste. Der Webserver wird auf einem zufälligen Port gestartet, hier 1906. Die angezeigte URI /WsHello ist der Name der Website [4].

Visual Web Developer hat außerdem einen Browser gestartet, um die angeforderte Seite anzuzeigen, nämlich [Service.asmx]:

  • in [1] die URI der Seite. Wir finden die URI der Website [http://localhost:1906/WsHello], gefolgt von der der Seite /Service.asmx.
  • In [2] teilte die Endung .asmx dem Webserver mit, dass es sich nicht um eine normale Webseite (Endung .aspx) handelte, die eine HTML-Seite erzeugt, sondern um eine Webdienst-Seite. Daraufhin generiert er automatisch eine Webseite mit einem Link für jede Methode des Webdienstes mit dem Attribut [WebMethod]. Über diese Links können Sie die Methoden testen.

Klicken Sie auf den obigen Link [2], um zur folgenden Seite zu gelangen:

  • Beachten Sie in [1] die URI [http://localhost:1906/WsHello/Service.asmx?op=DisBonjourALaDame] der neuen Seite. Dies ist die URI des Webdienstes mit einem Parameter op=M, wobei M der Name einer der Webdienstmethoden ist.
  • Erinnern Sie sich an die Signatur der Methode [DisBonjourALaDame]:

    public string DisBonjourALaDame(string nomDeLaDame) ;

Die Methode akzeptiert einen Parameter vom Typ string und gibt ebenfalls einen string zurück. Die Seite ermöglicht es uns, die Methode [DisBonjourALaDame] auszuführen: In [2] legen wir den Wert des Parameters nomDeLaDame fest und in [3] fordern wir die Ausführung der Methode an. Wir erhalten folgendes Ergebnis:

  • Beachten Sie in [1], dass die URI in der Antwort nicht mit der in der Anfrage identisch ist. Sie hat sich geändert.
  • In [2] ist die Serverantwort „web“. Bitte beachten Sie folgende Punkte:
    • Es handelt sich um eine XML-Antwort, nicht um HTML
    • Das Ergebnis der Methode [DisBonjourALaDame] ist in einem <string>-Tag gekapselt, das seinen Typ angibt.
    • Das <string>-Tag hat ein Attribut xmlns (XML-Namensraum), das den Namensraum angibt, den wir unserem Webdienst zugewiesen haben (Zeile 1 unten).

[WebService(Namespace = "http://st.istia.univ-angers.fr")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class Service : System.Web.Services.WebService

Um herauszufinden, wie der Webbrowser seine Anfrage gestellt hat, sehen Sie sich den HTML-Code im Testformular an:

...
<span>
  <p class="intro">Click <a href="Service.asmx">here</a> for a complete list of operations.</p>
  <h2>DisBonjourALaDame</h2>
  <p class="intro"></p>

  <h3>Test</h3>

         To test the operation using the HTTP POST protocol, click the 'Invoke' button.

   <form action='http://localhost:1906/WsHello/Service.asmx/DisBonjourALaDame' method="POST">                                      
      <table>
              <tr>
                    <td>Parameter</td>
                    <td>Value</td>
                </tr>
                <tr>
               <td>nomDeLaDame:</td>
               <td><input type="text" size="50" name="nomDeLaDame"></td>
           </tr>
                <tr>
                   <td></td>
                  <td align="right"> <input type="submit" value="Invoke" class="button"></td>
           </tr>
      </table>
    </form>
<span>
...
  • Zeile 11: Formularwerte (Tag form) werden (Attribut method) an die URL [ http://localhost:1906/WsHello/Service.asmx/DisBonjourALaDame] (Attribut action) gesendet.
  • Zeile 19: Das Eingabefeld heißt nomDeLaDame (Attribut name).

Durch die Anforderung des Webdienstes [/Service.asmx] konnten wir dessen Methoden testen und uns ein grundlegendes Verständnis des Datenaustauschs zwischen Client und Server verschaffen.

12.2.2. Der Kundenbereich

Es ist möglich, den oben genannten Remote-Webdienst-Client mit einem einfachen TCP/IP-Client zu implementieren. Hier ist beispielsweise der Client-Server-Dialog, der mit einem Putty-Client erstellt wurde, der mit dem Remote-Webdienst (localhost,1906) verbunden ist:

POST /WsHello/Service.asmx/DisBonjourALaDame HTTP/1.1
Host: localhost
Content-Type: application/x-www-form-urlencoded
Content-Length: 23

HTTP/1.1 100 Continue
Server: ASP.NET Development Server/9.0.0.0
Date: Sat, 10 May 2008 08:36:41 GMT
Content-Length: 0

nomDeLaDame=Carla+Bruni
HTTP/1.1 200 OK
Server: ASP.NET Development Server/9.0.0.0
Date: Sat, 10 May 2008 08:36:47 GMT
X-AspNet-Version: 2.0.50727
Cache-Control: private, max-age=0
Content-Type: text/xml; charset=utf-8
Content-Length: 119
Connection: Close

<?xml version="1.0" encoding="utf-8"?>
<string xmlns="http://st.istia.univ-angers.fr">Bonjour Mme Carla Bruni</string>
  • Zeilen 1–5: Vom Kunden gesendete Nachrichten
  • Zeile 1: POST-Befehl
  • Zeilen 6–10: Antwort des Servers. Das bedeutet, dass der Client die POST-Werte senden kann.
  • Zeile 11: Im Formular gesendete Werte param1=val1&param2=val2& .... Einige Zeichen müssen zu den in einer URL zulässigen Zeichen gehören. Dies ist das, was wir zuvor als „kodierte URL“ bezeichnet haben. Hier hat das Formular einen einzigen Parameter namens nomDeLaDame. Der gesendete Wert hat insgesamt 23 Zeichen. Diese Größe muss im HTTP-Header in Zeile 4 angegeben werden.
  • Zeilen 12–22: Serverantwort
  • Zeile 22: Das Ergebnis der Web-Methode [DisBonjourALaDame].

Mit Visual C# können Sie einen Assistenten verwenden, um den Client für einen Remote-Webdienst zu generieren. Das werden wir uns nun ansehen.

Die obige Ebene [1] wird durch ein Visual Studio C#-Projekt vom Typ Windows-Anwendung mit dem Namen ClientWsHello implementiert:

  • In [1] ist „ClientWsHello“ in Visual C#
  • In [2] lautet der Standard-Namespace des Projekts „Customer“ (Rechtsklick auf das Projekt / Eigenschaften / Anwendung). Dieser Namespace wird zum Erstellen des Client-Namespaces verwendet, der generiert wird.
  • In [3] klicken Sie mit der rechten Maustaste auf das Projekt, um eine Referenz zu einem Remote-Webdienst hinzuzufügen
  • in [4] die URI des zuvor erstellten Webdienstes festlegen
  • Verbinden Sie in [4b] Visual C# mit dem in [4] angegebenen Webdienst. Visual C# ruft die Beschreibung des Webdienstes ab und verwendet sie, um einen Client zu generieren.
  • in [5] kann Visual C#, sobald die Beschreibung des Webdienstes abgerufen wurde, dessen öffentliche Methoden anzeigen
  • in [6] geben Sie einen Namespace für den zu generierenden Client an. Dieser wird dem in [2] definierten Namespace hinzugefügt. Auf diese Weise lautet der Client-Namespace „Client.WsHello“.
  • in [6b], um den Assistenten zu validieren.
  • In [7] erscheint die Referenz auf den Webdienst WsHello im Projekt. Außerdem wurde eine Konfigurationsdatei [app.config] erstellt.
  • In [8] können Sie alle Projektdateien anzeigen.
  • In [9] enthält die Referenz auf den Webdienst WsHello verschiedene Dateien, auf die wir hier nicht näher eingehen werden. Wir werden uns jedoch die Datei [Reference.cs] ansehen, die den C#-Code für den generierten Client enthält:

namespace Client.WsHello {
...
    public partial class ServiceSoapClient : System.ServiceModel.ClientBase<Client.WsHello.ServiceSoap>, Client.WsHello.ServiceSoap {
 
        public ServiceSoapClient() {
        }
...        
        public string DisBonjourALaDame(string nomDeLaDame) {
            Client.WsHello.DisBonjourALaDameRequest inValue = new Client.WsHello.DisBonjourALaDameRequest();
            inValue.Body = new Client.WsHello.DisBonjourALaDameRequestBody();
            inValue.Body.nomDeLaDame = nomDeLaDame;
            Client.WsHello.DisBonjourALaDameResponse retVal = ((Client.WsHello.ServiceSoap)(this)).DisBonjourALaDame(inValue);
            return retVal.Body.DisBonjourALaDameResult;
        }
    }
}
  • Zeile 1: Der generierte Kundennamensraum lautet Client.WsHello. Wenn Sie diesen Namensraum ändern möchten, ist dies der richtige Ort dafür.
  • Zeile 3: Die Klasse „ServiceSoapClient“ ist die generierte Client-Klasse. Es handelt sich um eine Proxy-Klasse in dem Sinne, dass sie vor der Windows-Anwendung verbirgt, dass ein Remote-Webdienst verwendet wird. Die Windows-Anwendung nutzt den WsHello über die lokale Klasse „Client.WsHello.ServiceSoapClient“. Um eine Instanz des Clients zu erstellen, verwenden Sie den Konstruktor in Zeile 5:
Client.WsHello.ServiceSoapClient client=new Client.WsHello.ServiceSoapClient();
  • Zeile 8: Die Methode DisBonjourALaDame ist das clientseitige Gegenstück zum Webdienst DisBonjourALaDame. Die Windows-Anwendung nutzt die Remote-Methode DisBonjourALaDame über die lokale Methode Client.WsHello.ServiceSoapClient.DisBonjourALaDame in folgender Form:
string bonjour=client.DisBonjourALaDame("Carla Bruni");

Die generierte [app.config]-Datei sieht wie folgt aus:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.serviceModel>
        <bindings>
         ....
        </bindings>
        <client>
            <endpoint address="http://localhost:1906/WsHello/Service.asmx"... />
        </client>
    </system.serviceModel>
</configuration>

Aus dieser Datei behalten wir nur Zeile 8 bei, die die URI des Webdienstes enthält. Wenn sich diese URI ändert, muss der Windows-Client nicht neu kompiliert werden. Ändern Sie einfach die URI in der Datei [app.config].

Kehren wir nun zur Architektur der Windows-Anwendung zurück, die wir erstellen möchten:

Wir haben die [client]-Schicht des Webdienstes erstellt. Die [ui]-Schicht wird wie folgt aussehen:

Nr.
Typ
Name
Rolle
1
Textfeld
textBoxNomDame
Name der Dame
2
Schaltfläche
buttonSalutations
um eine Verbindung zum Webdienst WsHello remote herzustellen und die Methode DisBonjourALaDame abzufragen.
3
Beschriftung
labelBonjour
das vom Webdienst zurückgegebene Ergebnis

Der Formularcode [Form1.cs] lautet wie folgt:


using System;
using System.Windows.Forms;
using Client.WsHello;
 
namespace ClientSalutations {
    public partial class Form1 : Form {
        public Form1() {
            InitializeComponent();
        }
 
        private void buttonSalutations_Click(object sender, EventArgs e) {
             // hourglass
            Cursor=Cursors.WaitCursor;
             // web query service
            labelBonjour.Text = new ServiceSoapClient().DisBonjourALaDame(textBoxNomDame.Text.Trim());
             // normal slider
            Cursor = Cursors.Arrow;
        }
    }
}
  • Zeile 15: Der Webdienst-Client wird instanziiert. Er ist vom Typ `Client.WsHello.ServiceSoapClient`. Der Namespace `Client.WsHello` wird in Zeile 3 deklariert. Die lokale Methode `ServiceSoapClient().DisBonjourALaDame` wird aufgerufen. Wir wissen, dass sie die gleichnamige Remote-Methode im Webdienst aufruft.

12.3. Ein Webdienst für arithmetische Operationen

Wir werden eine zweite Client/Server-Anwendung mit der folgenden vereinfachten Architektur erstellen:

Der vorherige Webdienst bot eine einzige Methode an. Wir betrachten einen Webdienst, der vier arithmetische Operationen anbieten wird:

  1. addieren(a,b), was a+b ergibt
  2. soustraire(a,b), das a-b zurückgibt
  3. multiplier(a,b), das a*b zurückgibt
  4. diviser(a,b), das a/b zurückgibt

die über die folgende grafische Benutzeroberfläche abgefragt werden:

  • in [1], die auszuführende Operation
  • in [2,3]: die Operanden
  • in [4], die Schaltfläche für den Webdienstaufruf
  • in [5], das Ergebnis des Webdienstes

12.3.1. Die Serverseite

Wir erstellen ein Webdienstprojekt mit Visual Web Developer:

  • In [1] wurde die Webanwendung WsOperations
  • in [2] wurde die Webanwendung WsOperations wie folgt umgestaltet:
  • Die Webseite [Service.asmx] wurde in [Operations.asmx] umbenannt
  • Die Klasse [Service.cs] wurde in [Operations.cs] umbenannt
  • Die Datei [web.config] wurde entfernt, um zu zeigen, dass sie nicht unbedingt erforderlich ist.

Die Webseite [Service.asmx] enthält die folgende Zeile:


<%@ WebService Language="C#" CodeBehind="~/App_Code/Operations.cs" Class="Operations" %>

Der Webdienst wird von der folgenden Klasse [Operations.cs] bereitgestellt:


using System.Web.Services;
 
[WebService(Namespace = "http://st.istia.univ-angers.fr/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class Operations : System.Web.Services.WebService
{
    [WebMethod]
    public double Ajouter(double a, double b)
    {
        return a + b;
    }
 
    [WebMethod]
    public double Soustraire(double a, double b)
    {
        return a - b;
    }
 
    [WebMethod]
    public double Multiplier(double a, double b)
    {
        return a * b;
    }
 
    [WebMethod]
    public double Diviser(double a, double b)
    {
        return a / b;
    }
 
}

Um den Webdienst online zu stellen, gehen wir wie in [3] beschrieben vor. Anschließend erhalten wir die Testseite für die 4 Methoden des Webdienstes WsOperations:

Image

Leser sind eingeladen, alle 4 Methoden auszuprobieren.

12.3.2. Der Kundenbereich

Mit Visual C# erstellen wir eine Windows-Anwendung namens ClientWsOperations:

  • in [1], die ClientWsOperations in Visual C#
  • in [2] lautet der Standard-Namespace des Projekts „Customer“ (Rechtsklick auf das Projekt / Eigenschaften / Anwendung). Dieser Namespace wird verwendet, um den Client-Namespace zu erstellen, der generiert wird.
  • in [3], klicken Sie mit der rechten Maustaste auf das Projekt, um eine Referenz zu einem vorhandenen Webdienst hinzuzufügen
  • In [4] legen Sie die URI des zuvor erstellten Webdienstes fest. Sehen Sie dazu in das Adressfeld des Browsers, der die Testseite des Webdienstes anzeigt.
  • In [4b] verbinden Sie Visual C# mit dem in [4] angegebenen Webdienst. Visual C# ruft die Beschreibung des Webdienstes ab und verwendet sie, um einen Client zu generieren.
  • in [5] kann Visual C#, sobald die Beschreibung des Webdienstes abgerufen wurde, dessen öffentliche Methoden anzeigen
  • in [6] geben Sie einen Namespace für den zu generierenden Client an. Dieser wird dem in [2] definierten Namespace hinzugefügt. Auf diese Weise lautet der Client-Namespace „Client.WsOperations“.
  • Klicken Sie auf [6b], um den Assistenten zu bestätigen.
  • In [7] erscheint die Referenz auf den Webdienst WsOperations im Projekt. Außerdem wurde eine Konfigurationsdatei [app.config] erstellt.

Beachten Sie, dass der generierte Client vom Typ Client.WsOperations.OperationsSoapClient ist, wobei

  • Client.WsOperations der Namespace des Service-Client-Web
  • Operations die Klasse des Remote-Webdienstes ist.

Obwohl es eine logische Möglichkeit gibt, diesen Namen zu bilden, ist es oft einfacher, ihn in der Datei [Reference.cs] zu finden, die standardmäßig eine versteckte Datei ist. Ihr Inhalt lautet wie folgt:


namespace Client.WsOperations {
 ...
    public partial class OperationsSoapClient : System.ServiceModel.ClientBase<Client.WsOperations.OperationsSoap>, Client.WsOperations.OperationsSoap {
 
        public OperationsSoapClient() {
        }
...
        public double Ajouter(double a, double b) {
            ...
        }
 
        public double Soustraire(double a, double b) {
            ...
        }
 
        public double Multiplier(double a, double b) {
            ...
        }
 
        public double Diviser(double a, double b) {
            ...
        }
    }
}

Auf die Methoden Add, Subtract, Multiply und Divide des Remote-Webdienstes wird über die gleichnamigen Proxy-Methoden (Zeilen 8, 12, 16, 20) des Clients vom Typ Client.WsOperations.OperationsSoapClient (Zeile 3) zugegriffen.

Nun muss nur noch die grafische Benutzeroberfläche erstellt werden:

Nr.
Typ
Name
Rolle
1
ComboBox
comboBoxOperations
Liste der arithmetischen Operationen
2
Textfeld
textBoxA
Zahl a
3
Textfeld
textBoxB
Zahl b
4
Schaltfläche
buttonExecute
fragt den Remote-Webdienst ab
5
Label
labelResult
das Ergebnis der Operation

Der Code für [Form1.cs] lautet wie folgt:


using System;
using System.Windows.Forms;
using Client.WsOperations;
 
namespace ClientWsOperations {
    public partial class Form1 : Form {
         // operations table
        private string[] opérations = { "Ajouter", "Soustraire", "Multiplier", "Diviser" };
         // department web to contact
        private OperationsSoapClient opérateur = new OperationsSoapClient();
 
         // manufacturer
        public Form1() {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e) {
             // combo filling of operations
            comboBoxOperations.Items.AddRange(opérations);
            comboBoxOperations.SelectedIndex = 0;
        }
 
        private void buttonExécuter_Click(object sender, EventArgs e) {
            // checking operation parameters a and b
            textBoxMessage.Text = "";
            bool erreur = false;
            Double a = 0;
            if (!Double.TryParse(textBoxA.Text, out a)) {
                textBoxMessage.Text += "Nombre a erroné...";
            }
            Double b = 0;
            if (!Double.TryParse(textBoxB.Text, out b)) {
                textBoxMessage.Text += String.Format("{0}Nombre b erroné...", Environment.NewLine);
            }
            if (erreur) {
                return;
            }
             // operation execution
            Double c=0;
            try {
                switch (comboBoxOperations.SelectedItem.ToString()) {
                    case "Ajouter":
                        c=opérateur.Ajouter(a, b);
                        break;
                    case "Soustraire":
                        c=opérateur.Soustraire(a, b);
                        break;
                    case "Multiplier":
                        c=opérateur.Multiplier(a, b);
                        break;
                    case "Diviser":
                        c=opérateur.Diviser(a, b);
                        break;
                }
                 // result display
                labelRésultat.Text = c.ToString();
            } catch (Exception ex) {
                textBoxMessage.Text = ex.Message;
            }
        }
    }
}
  • Zeile 3: Namespace des Remote-Webdienst-Clients
  • Zeile 10: Der Client des Remote-Webdienstes wird gleichzeitig mit dem Formular instanziiert
  • Zeilen 17–21: Die Kombinationsliste „Operationen“ wird beim ersten Laden des Formulars ausgefüllt
  • Zeile 23: Ausführung der vom Benutzer angeforderten Operation
  • Zeilen 25–37: Überprüfung, ob die Eingaben a und b reelle Zahlen sind
  • Zeilen 41–54: Eine Switch-Anweisung zur Ausführung der vom Benutzer angeforderten Remote-Operation
  • Zeilen 43, 46, 49, 52: Der lokale Client wird abgefragt. Im Hintergrund fragt er den Remote-Webdienst ab.

12.4. Ein Webdienst zur Steuerberechnung

Wir sind zurück mit der mittlerweile bekannten Steuerberechnungsanwendung. Als wir das letzte Mal damit gearbeitet haben, haben wir sie zu einem Remote-TCP-Server gemacht, der über das Internet aufgerufen werden konnte. Jetzt haben wir sie in einen Webdienst umgewandelt.

Die Architektur von Version 8 sah wie folgt aus:

Die Architektur von Version 9 wird ähnlich sein:

Diese Architektur ähnelt der von Version 8, die in Abschnitt 11.9.1 beschrieben wird, wobei jedoch Server und Client-TCP durch ein Service-Web und dessen Proxy-Client ersetzt werden. Wir werden die gesamten Schichten [ui], [metier] und [dao] aus Version 8 übernehmen.

12.4.1. Die Serverseite

Wir erstellen ein Webdienstprojekt mit Visual Web Developer:

  • In [1] wurde die Webanwendung WsImpot generiert
  • in [2] wurde die Webanwendung WsImpot wie folgt umgestaltet:
    • Die Webseite [Service.asmx] wurde in [ServiceImpot.asmx] umbenannt
    • Die Klasse [Service.cs] wurde in [ServiceImpot.cs] umbenannt

Die Webseite [ServiceImpot.asmx] enthält die folgende Zeile:


<%@ WebService Language="C#" CodeBehind="~/App_Code/ServiceImpot.cs" Class="ServiceImpot" %>

Der Webdienst wird von der folgenden Klasse [ServiceImpot.cs] bereitgestellt:


using System.Web.Services;
 
[WebService(Namespace = "http://st.istia.univ-angers.fr/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class ServiceImpot : System.Web.Services.WebService
{
 
    [WebMethod]
    public int CalculerImpot(bool marié, int nbEnfants, int salaire)
    {
        return 0;
    }
 
}

Der Webdienst macht nur den CalculerImpot in Zeile 9 verfügbar.

Kehren wir zur Client/Server-Architektur von Version 8 zurück:

Das Visual Studio-Projekt auf dem Server [1] sah wie folgt aus:

  • in [1], das Projekt. Es umfasste die folgenden Elemente:
    • [ServeurImpot.cs]: der TCP/IP-Steuerberechnungsserver in Form einer Konsolenanwendung.
    • [dbimpots.sdf]: die in Abschnitt 9.8.5 beschriebene SQL Server Compact-Datenbank der Version 7.
    • [App.config]: Anwendungskonfigurationsdatei.
  • In [2] enthält der Ordner [lib] die für das Projekt benötigte DLL:
    • [ImpotsV7-dao]: die [dao]-Schicht in Version 7
    • [ImpotsV7-metier]: die [metier]-Schicht in Version 7
    • [antlr.runtime, CommonLogging, Spring.Core] für Spring
  • In [3] verweist das Projekt auf

Die [metier]- und [dao]-Schichten dieser Version existieren bereits: Es handelt sich um die in den Versionen 7 und 8 verwendeten. Sie liegen im DLL-Format vor, das wir wie folgt in das Projekt integrieren:

  • In [1] wurde der Ordner [lib] des Servers der Version 8 in das Webdienstprojekt der Version 9 kopiert.
  • In [2] ändern wir die Seiteneigenschaften, um die DLL aus dem Ordner [lib] [4] zu den Projektreferenzen [3] hinzuzufügen.

Nach diesem Vorgang verfügen wir über alle für den Server [1] erforderlichen Ebenen:

Während die Serverelemente [1], [server], [metier], [dao], [entities] und [spring] alle im Visual Studio-Projekt vorhanden sind, fehlt uns das Element, das sie beim Start der Webanwendung instanziiert. In Version 8 übernahm eine Hauptklasse mit der statischen Methode [Main] die Instanziierung der Schichten mithilfe von Spring. In einer Webanwendung ist die Klasse, die eine ähnliche Aufgabe übernehmen kann, die mit der [ Global.asax] verknüpfte Klasse:

  • In [1] wird dem Web-Projekt ein neues Element hinzugefügt
  • in [2] wählen wir die globale Anwendungsklasse
  • in [3] wird der für dieses Element vorgeschlagene Standardname
  • in [4] bestätigen wir das Hinzufügen
  • in [5] wurde das neue Element in die

Sehen wir uns den Inhalt der Datei [Global.asax] an:


<%@ Application Language="C#" %>
 
<script runat="server">
 
    void Application_Start(object sender, EventArgs e) 
    {
        // Code that runs on application startup
    }
 
    void Application_End(object sender, EventArgs e) 
    {
        //  Code that runs on application shutdown
    }
 
    void Application_Error(object sender, EventArgs e) 
    { 
        // Code that runs when an unhandled error occurs
    }
 
    void Session_Start(object sender, EventArgs e) 
    {
        // Code that runs when a new session is started
    }
 
    void Session_End(object sender, EventArgs e) 
    {
        // Code that runs when a session ends. 
    }
 
</script>

Die Datei ist eine Mischung aus Webserver-Tags (Zeilen 1, 3, 30) und C#-Code. Dies war die einzige Methode, die mit ASP verwendet wurde, dem Vorläufer von ASP.NET, der aktuellen Technologie von Microsoft für die Webprogrammierung. Mit ASP.NET kann diese Methode weiterhin verwendet werden, ist jedoch nicht die Standardmethode. Die Standardmethode ist die „CodeBehind“-Methode, die wir bereits in Webdienst-Seiten gesehen haben, zum Beispiel hier in [ServiceImport.asmx]:


<%@ WebService Language="C#" CodeBehind="~/App_Code/ServiceImpot.cs" Class="ServiceImpot" %>

CodeBehind gibt den Speicherort des Quellcodes für die Seite [ServiceImpot.asmx] an. Ohne dieses Attribut befände sich der Quellcode auf der Seite [ServiceImpot.asmx] mit einer Syntax, die der in [Global.asax] ähnelt. Wir werden die Datei [Global.asax] nicht in ihrer generierten Form beibehalten, aber ihr Code ermöglicht es uns zu verstehen, wozu sie dient:

  • Die mit Global.asax verknüpfte Klasse wird beim Start der Anwendung instanziiert. Ihre Lebensdauer entspricht der der gesamten Anwendung. Konkret bedeutet dies, dass sie erst verschwindet, wenn der Webserver gestoppt wird.
  • Als Nächstes wird die Methode Application_Start ausgeführt. Dies ist der einzige Zeitpunkt, zu dem sie ausgeführt wird. Sie wird daher verwendet, um Objekte zu instanziieren, die von allen Benutzern gemeinsam genutzt werden. Diese Objekte werden platziert:
    • entweder in den statischen Feldern der mit Global.asax verbundenen Klasse. Da diese Klasse permanent vorhanden ist, kann jede Anfrage von jedem Benutzer Informationen daraus lesen.
    • oder im Container Application. Dieser Container wird ebenfalls beim Start der Anwendung erstellt, und seine Lebensdauer entspricht der der Anwendung.
      • Um Daten in diesen Container zu schreiben, verwenden wir Application["key"]=value;
      • Um sie abzurufen, schreiben wir T value=(T)Application["key"];, wobei T der Typ des Werts ist.
  • Die Methode Session_Start wird jedes Mal ausgeführt, wenn ein neuer Benutzer eine Anfrage stellt. Wie erkennen wir einen neuen Benutzer? Jeder Benutzer (in der Regel ein Browser) erhält ein Session-Token, eine für jeden Benutzer eindeutige Zeichenfolge. Bei jeder neuen Anfrage sendet der Benutzer das empfangene Session-Token zurück. Dadurch kann der Webserver den Benutzer erkennen. Im Verlauf der verschiedenen Anfragen eines Benutzers können benutzerspezifische Daten in der Session gespeichert werden:
    • Um Daten in diesen Container zu schreiben, schreiben wir Session["key"]=value;
    • Um sie abzurufen, schreiben wir T value=(T)Session["key"];, wobei T der Typ des Werts ist.

Standardmäßig ist die Lebensdauer einer Sitzung auf 20 Minuten Benutzerinaktivität begrenzt (d. h. der Benutzer hat sein Sitzungstoken seit 20 Minuten nicht zurückgesendet).

  • Die Methode Application_Error wird ausgeführt, wenn eine von der Webanwendung nicht behandelte Ausnahme an den Webserver gesendet wird.
  • Andere Methoden werden seltener verwendet.

Nach diesen allgemeinen Erläuterungen: Was können wir für Sie tun? Global.asax? Wir verwenden die Methode Application_Start, um die in den DLLs [ImpotsV7-metier, ImpotsV7-dao] enthaltenen Schichten [metier], [dao] und [entites] zu initialisieren. Wir werden Spring verwenden, um sie zu instanziieren. Die auf diese Weise erstellten Schichtenreferenzen werden dann in statischen Feldern in der mit Global.asax verknüpften Klasse gespeichert.

Zunächst verschieben wir den C#-Code aus der Datei „Global.asax“ in eine eigene Klasse. Das Projekt entwickelt sich wie folgt:

In [1] wird die Datei [Global.asax] mit der Klasse [Global.cs] [2] durch die folgende einzelne Zeile verknüpft:


<%@ Application Language="C#" Inherits="WsImpot.Global"%>

Inherits="WsImpot.Global" gibt an, dass die mit Global.asax verknüpfte Klasse von WsImpot.Global erbt. Diese Klasse ist in [Global.cs] wie folgt definiert:


using System;
using Metier;
using Spring.Context.Support;
namespace WsImpot
{
    public class Global : System.Web.HttpApplication
    {
         // business layer
        public static IImpotMetier Metier;
 
         // method executed at application startup
        private void Application_Start(object sender, EventArgs e)
        {
            // instantiations [metier] and [dao] layers
            Metier = ContextRegistry.GetContext().GetObject("metier") as IImpotMetier;
        }
    }
}
  • Zeile 4: Klassen-Namespace
  • Zeile 6: die Klasse Global. Sie können ihr einen beliebigen Namen geben. Wichtig ist, dass sie von System.Web.HttpApplication abgeleitet ist.
  • Zeile 9: ein öffentliches statisches Feld, das einen Verweis auf die [Metier]-Schicht enthält.
  • Zeile 12: Die Methode Application_Start, die beim Start der Anwendung ausgeführt wird.
  • Zeile 15: Spring wird verwendet, um die Datei [web.config] auszulesen, in der es die Objekte findet, die instanziiert werden müssen, um die [metier]- und [dao]-Schichten zu erstellen. Es gibt keinen Unterschied zwischen der Verwendung von Spring mit [App.config] in einer Windows-Anwendung und der Verwendung von Spring mit [web.config] in einer Webanwendung. [web.config] und [App.config] haben zudem die gleiche Struktur. Zeile 15 speichert die Referenz auf die [metier]-Schicht im statischen Feld von Zeile 9, sodass diese Referenz für alle Abfragen aller Benutzer verfügbar ist.

Die [web.config]-Datei sieht wie folgt aus:


<?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 name="dao" type="Dao.DataBaseImpot, ImpotsV7-dao">
        <constructor-arg index="0" value="MySql.Data.MySqlClient"/>
        <constructor-arg index="1" value="Server=localhost;Database=bdimpots;Uid=admimpots;Pwd=mdpimpots;"/>
        <constructor-arg index="2" value="select limite, coeffr, coeffn from tranches"/>
      </object>
      <object name="metier" type="Metier.ImpotMetier, ImpotsV7-metier">
        <constructor-arg index="0" ref="dao"/>
      </object>
    </objects>
  </spring>
</configuration>

Dies ist die [App.config]-Datei, die in Version 7 der Anwendung verwendet und in Abschnitt 9.8.4 behandelt wurde.

  • Zeilen 16–20: Definieren eine [dao]-Schicht, die mit einer MySQL5-Datenbank arbeitet. Diese Datenbank wurde in Abschnitt 9.8.1 beschrieben.
  • Zeilen 21–23: Definieren die [metier]-Schicht

Zurück zum Server-Rätsel:

Beim Start der Anwendung wurden die [metier]- und [dao]-Schichten instanziiert. Die Lebensdauer dieser Schichten entspricht der der Anwendung selbst. Wann wird der Webservice instanziiert? Jedes Mal, wenn eine Anfrage an ihn gestellt wird. Am Ende der Anfrage wird das Objekt, das sie bedient hat, gelöscht. Auf den ersten Blick ist ein Webservice also zustandslos. Er kann zwischen zwei Anfragen keine Informationen in Feldern speichern, die zu ihm gehören. Er kann Informationen in der Sitzung des Benutzers speichern. Dazu müssen die von ihm bereitgestellten Methoden mit einem speziellen Tag versehen werden:


    [WebMethod(EnableSession=true)]
    public int CalculerImpot(bool marié, int nbEnfants, int salaire)
....

Oben in Zeile 1 wird CalculerImpot der Zugriff auf den zuvor erwähnten Container „Session“ gewährt. In unserer Anwendung benötigen wir dieses Attribut nicht. Der Webdienst WsImpot wird daher bei jeder Anfrage instanziiert und ist zustandslos.

Wir können nun den Code [ServiceImpot.cs] schreiben, der den Webdienst implementiert:


using System.Web.Services;
using WsImpot;
 
[WebService(Namespace = "http://st.istia.univ-angers.fr/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class ServiceImpot : System.Web.Services.WebService
{
 
    [WebMethod]
    public int CalculerImpot(bool marié, int nbEnfants, int salaire)
    {
        return Global.Metier.CalculerImpot(marié, nbEnfants, salaire);
    }
 
}
  • Zeile 10: die eindeutige Webservice-Methode
  • Zeile 12: Wir verwenden den CalculerImpot der [metier]-Schicht. Ein Verweis auf diese Schicht befindet sich im statischen Feld der Klasse Trade Global. Dieses gehört zum WsImpot (Zeile 2).

Wir sind nun bereit, den Webservice zu starten. Zunächst müssen wir SGBD MySQL5 ausführen, damit auf die Datenbank bdimpots zugegriffen werden kann. Sobald dies geschehen ist, starten wir [1] den Webservice:

Der Browser zeigt dann die Seite [2] an. Wir folgen dem Link:

Wir weisen jedem der drei Parameter der Methode *CalculerImpot* einen Wert zu und fordern die Ausführung der Methode an. Wir erhalten das folgende Ergebnis, das korrekt ist:

Image

12.4.2. Ein grafischer Windows-Client für den Remote-Webdienst

Nachdem der Webdienst nun geschrieben ist, wenden wir uns dem Client zu. Sehen wir uns die Architektur der Client/Server-Anwendung noch einmal an:

Wir müssen nun den Client schreiben [2]. Die grafische Oberfläche wird identisch mit der von Version 8 sein:

Um den [Client]-Teil von Version 9 zu schreiben, gehen wir vom [Client]-Teil von Version 8 aus und nehmen dann die notwendigen Änderungen vor. Wir duplizieren das in Abschnitt 11.9.4.1 behandelte Visual Studio-Projekt, benennen es in ClientWsImpot um und laden es in Visual Studio :

Die Visual Studio-Lösung für Version 8 bestand aus 2 Projekten:

  • dem [metier] [1]-Projekt, bei dem es sich um einen TCP-Client des TCP-Steuerberechnungsservers handelte
  • dem [ui] [2]-GUI-Projekt.

Die vorzunehmenden Änderungen sind wie folgt:

  • Das [metier]-Projekt muss nun Kunde eines Webdienstes sein
  • Das [ui]-Projekt muss auf die DLL der neuen [metier]-Schicht verweisen
  • die Konfiguration der [metier]-Schicht in [App.config] muss geändert werden.

12.4.2.1. Die neue Ebene [metier]

  • in [1] ist IImpotMetier die Schnittstelle der [metier]-Schicht und ImpotMetierTcp deren Implementierung durch einen Client-TCP
  • In [2] entfernen wir ImpotMetierTcp. Wir müssen eine weitere Implementierung von IImpotMetier erstellen, die als Client eines Webdienstes fungiert.
  • In [3] bezeichnen wir Customer als den Standard-Namespace des [metier]-Projekts. Die DLL, die generiert wird, wird [ImpotsV9-metier.dll] heißen.
  • In [4] erstellen wir eine Referenz auf den Webdienst WsImpot.
  • In [5] konfigurieren und validieren wir diese.
  • In [6] wurde die Referenz auf die Webdienstdatei WsImpot erstellt und eine [app.config]-Datei generiert.

In der versteckten Datei [Reference.cs]:

  • Der Namespace lautet Client.WsImpot
  • Die Client-Klasse heißt ServiceImpotSoapClient
  • sie verfügt über eine Methode mit eindeutiger Signatur:

        public int CalculerImpot(bool marié, int nbEnfants, int salaire) ;

Nun muss nur noch die Schnittstelle IImpotMetier implementiert werden:


namespace Metier {
    public interface IImpotMetier {
        int CalculerImpot(bool marié, int nbEnfants, int salaire);
    }
}

Wir implementieren es als Nächstes mit ImpotMetierWs:


using System.Net.Sockets;
using System.IO;
using Client.WsImpot;
 
namespace Metier {
    public class ImpotMetierWs : IImpotMetier {
 
         // remote web customer service
        private ServiceImpotSoapClient client = new ServiceImpotSoapClient();
 
         // tAX CALCULATION
        public int CalculerImpot(bool marié, int nbEnfants, int salaire) {
            return client.CalculerImpot(marié, nbEnfants, salaire);
        }
 
    }
}
  • Zeile 6: Die Klasse ImpotMetierWs implementiert die Schnittstelle IImpotMetier.
  • Zeile 9: Bei der Erstellung einer Instanz von ImpotMetierWs wird das Feld „customer“ mit einer Client-Instanz des Web-Steuerberechnungsdienstes initialisiert.
  • Zeile 12: Es muss lediglich die Schnittstelle IImpotMetier implementiert werden.
  • Zeile 13: Wir verwenden die Methode „CalculerImpot“ des Clients des Remote-Webdienstes zur Steuerberechnung. Letztendlich ist es die Methode „CalculerImpot“ des Remote-Webdienstes, die aufgerufen wird.

Die DLL des Projekts kann generiert werden:

  • in [1], das [Kunden]-Projekt in seinem Endzustand
  • in [2], Generierung der DLL des Projekts
  • in [3] befindet sich die DLL „ImpotsV9-metier.dll“ im Projektordner „/bin/Release“.

12.4.2.2. Die neue [ui]-Ebene

Die [client]-Ebene des Clients wurde bereits geschrieben. Nun müssen wir die [ui]-Ebene schreiben. Zurück zum Visual Studio-Projekt:

  • In [1] wird das [ui]-Projekt aus Version 8
  • in [2] wird die DLL „ImpotsV8-metier“ der alten [metier]-Schicht durch die DLL „ImpotsV9-metier“ der neuen Schicht ersetzt
  • in [3] wird die DLL „ImpotsV9-metier“ zu den Projektreferenzen hinzugefügt.

Die zweite Änderung erfolgt in [App.config]. Beachten Sie, dass diese Datei von Spring verwendet wird, um die [metier]-Schicht zu instanziieren. Da sich dies geändert hat, muss die Konfiguration von [App.config] angepasst werden. Andererseits muss [App.config] so konfiguriert werden, dass der Remote-Webdienst zur Steuerberechnung erreicht wird. Diese Konfiguration wurde in der Datei [app.config] des [metier]-Projekts generiert, als die Referenz auf den Remote-Webdienst hinzugefügt wurde.

Die Datei [App.config] sieht somit wie folgt aus:


<?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 name="metier" type="Metier.ImpotMetierWs, ImpotsV9-metier">
            </object>
        </objects>
    </spring>
 
     <!-- web service -->
    <system.serviceModel>
        <bindings>
            <basicHttpBinding>
                <binding name="ServiceImpotSoap" closeTimeout="00:01:00" openTimeout="00:01:00"
                        receiveTimeout="00:10:00" sendTimeout="00:01:00" allowCookies="false"
                        bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
                        maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
                        messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
                        useDefaultWebProxy="true">
                    <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
                            maxBytesPerRead="4096" maxNameTableCharCount="16384" />
                    <security mode="None">
                        <transport clientCredentialType="None" proxyCredentialType="None"
                                realm="" />
                        <message clientCredentialType="UserName" algorithmSuite="Default" />
                    </security>
                </binding>
            </basicHttpBinding>
        </bindings>
        <client>
            <endpoint address="http://localhost:2172/WsImpot/ServiceImpot.asmx"
                    binding="basicHttpBinding" bindingConfiguration="ServiceImpotSoap"
                    contract="WsImpot.ServiceImpotSoap" name="ServiceImpotSoap" />
        </client>
    </system.serviceModel>
</configuration>
  • Zeilen 15–18: Spring instanziiert nur ein Objekt, die Schicht [metier]
  • Zeile 16: Die [metier]-Schicht wird durch die Klasse [Metier.ImportMetierWs] instanziiert, die sich in der DLL ImpotsV9-metier befindet.
  • Zeilen 22–46: Client-Konfiguration für den Remote-Webdienst. Dies ist ein Kopieren/Einfügen des Inhalts der Datei [app.config] aus dem Projekt [metier].

Wir sind startklar. Führen Sie die Anwendung mit Strg-F5 aus (der Webdienst muss gestartet sein, die Datenbank MySQL5 muss gestartet sein, die Port-Einstellung in Zeile 42 oben muss korrekt sein):

  

12.5. Ein Web-Client für den Web-Steuerberechnungsdienst

Kehren wir zur Architektur der soeben erstellten Client-Server-Anwendung zurück:

Die obige [ui]-Schicht wurde durch einen grafischen Windows-Client implementiert. Wir implementieren sie nun mit einer Webschnittstelle:

 

Dies ist eine wichtige Änderung für die Benutzer. Derzeit kann unsere Client/Server-Anwendung, Version 9, mehrere Clients gleichzeitig bedienen. Dies ist eine Verbesserung gegenüber Version 8, die jeweils nur einen Client bedienen konnte. Die Einschränkung besteht darin, dass Benutzer, die den Web-Steuerberechnungsdienst nutzen möchten, den von uns entwickelten Windows-Client auf ihren Arbeitsstationen installiert haben müssen. In dieser neuen Version, die wir als Version 10 bezeichnen, können Benutzer über ihren Browser auf den Web-Steuerberechnungsdienst zugreifen.

In der oben dargestellten Architektur:

  • bleibt die Serverseite unverändert. Sie bleibt so, wie sie in Version 9 ist.
  • Auf der Client-Seite bleibt die [Service-Client-Web]-Schicht unverändert. Sie wurde in der DLL [ImpotsV9-metier] gekapselt. Wir werden diese DLL wiederverwenden.
  • Letztendlich besteht die einzige Änderung darin, eine Windows-GUI durch eine Weboberfläche zu ersetzen.

Wir werden uns die serverseitige Webprogrammierung genauer ansehen. Da es nicht das Ziel dieses Dokuments ist, Webprogrammierung zu lehren, werden wir versuchen, den von uns gewählten Ansatz zu erläutern, ohne jedoch zu sehr ins Detail zu gehen. Dieser Abschnitt wird daher einen Hauch von „Magie“ enthalten. Wir halten diesen Schritt jedoch für sinnvoll, um ein neues Beispiel für eine mehrschichtige Architektur zu zeigen, bei der eine der Schichten geändert wird.

Die Architektur von Version 10 sieht wie folgt aus:

Wir haben bereits alle Schichten, mit Ausnahme von [web]. Um besser zu verstehen, was getan werden soll, müssen wir die Client-Architektur genauer beschreiben. Sie wird wie folgt aussehen:

  • Der Webnutzer hat ein Webformular in seinem Browser
  • Dieses Formular wird an den Web-1-Server gesendet, der es über die [web]-Schicht verarbeitet
  • Die [web]-Schicht benötigt die Client-Dienste des Remote-Webdienstes, die in [ImpotsV9-metier.dll] gekapselt sind.
  • Der Client des Remote-Webdienstes kommuniziert mit dem Web-2-Server, auf dem der Remote-Webdienst gehostet wird.
  • Die Antwort des Remote-Webdienstes wird an die Webschicht des Clients weitergeleitet, die sie in eine Seite formatiert und an den Benutzer sendet.

Unsere Aufgabe hier ist daher:

  • Erstellen des Webformulars, das der Benutzer in seinem Browser sieht
  • die Webanwendung zu schreiben, die die Anfrage des Benutzers verarbeitet und eine Antwort in Form einer neuen Webseite sendet. Diese entspricht im Grunde dem Formular, dem wir den zu zahlenden Steuerbetrag hinzugefügt haben
  • das „Bindeglied“ zu schreiben, das dafür sorgt, dass alles zusammen funktioniert.

All dies wird mithilfe einer neuen Website erfolgen, die mit Visual Web Developer erstellt wurde:

  • [1]: Wählen Sie die Option „Datei / Neue Website“
  • [2]: Wählen Sie eine ASP.NET-Website
  • [3]: Wählen Sie die Entwicklungssprache: C#
  • [4]: Geben Sie den Ordner an, in dem das Projekt erstellt werden soll
  • [5]: Das in Visual Web Developer erstellte Projekt
    • [Default.aspx] ist eine Webseite, die als Standardseite bezeichnet wird. Sie wird ausgeliefert, wenn die URL http://.../ClientAspImpot ohne Angabe eines Dokuments aufgerufen wird. Diese Seite enthält das Formular zur Steuerberechnung, das der Benutzer in seinem Browser sieht.
    • [Default.aspx.cs] ist die mit der Seite verknüpfte Klasse, die das an den Benutzer gesendete Formular generiert und es verarbeitet, sobald es ausgefüllt und validiert wurde.
    • [web.config] ist die Anwendungskonfigurationsdatei. Anders als bisher werden wir sie beibehalten.

Kehren wir nun zu der Architektur zurück, die wir erstellen müssen:

  • [1] wird durch [Default.aspx] implementiert
  • [2] wird durch [Default.aspx.cs] implementiert
  • [3] wird durch die DLL [ImpotV9-metier] implementiert

Beginnen wir mit der Implementierung von Ebene [3]. Dazu sind mehrere Schritte erforderlich:

  • In [1] wird der Ordner [lib] des Windows 9-Grafikclients in den Projektordner [ClientAspWsImpot] kopiert. Dies erfolgt über den Windows Explorer. Um diesen Ordner in der Web Developer-Lösung anzuzeigen, aktualisieren Sie die Lösung mit der Schaltfläche [2].
  • Fügen Sie sie anschließend den Projektreferenzen hinzu [3,4,5]. Die referenzierten DLLs werden automatisch in den Ordner /bin des Projekts kopiert [6].

Wir verfügen nun über die für die Ausführung von Spring erforderlichen DLLs, und die Client-Schicht für den Remote-Webdienst wurde ebenfalls implementiert. Der Code für Letzteres ist zwar vorhanden, die Konfiguration muss jedoch noch vorgenommen werden. In Version 9 erfolgte die Konfiguration über die folgende Datei [App.config]:


<?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 name="metier" type="Metier.ImpotMetierWs, ImpotsV9-metier">
            </object>
        </objects>
    </spring>
 
     <!-- web service -->
    <system.serviceModel>
        <bindings>
            <basicHttpBinding>
...
            </basicHttpBinding>
        </bindings>
        <client>
            <endpoint address="http://localhost:2172/WsImpot/ServiceImpot.asmx"
                    binding="basicHttpBinding" bindingConfiguration="ServiceImpotSoap"
                    contract="WsImpot.ServiceImpotSoap" name="ServiceImpotSoap" />
        </client>
    </system.serviceModel>
</configuration>

Wir übernehmen diese Konfiguration und integrieren sie wie folgt in die Datei [web.config]:


<?xml version="1.0"?>
<configuration>
 
 
    <configSections>
      <sectionGroup name="system.web.extensions"...>
...
      </sectionGroup>
       <!-- start Spring section -->
        <sectionGroup name="spring">
          <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
          <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
        </sectionGroup>
       <!-- end Spring section -->
    </configSections>
 
   <!-- start Spring configuration -->
  <spring>
    <context>
      <resource uri="config://spring/objects" />
    </context>
    <objects xmlns="http://www.springframework.net">
      <object name="metier" type="Metier.ImpotMetierWs, ImpotsV9-metier">
      </object>
    </objects>
  </spring>
   <!-- end Spring configuration -->
 
   <!-- start remote web service client configuration -->
  <system.serviceModel>
    <bindings>
      <basicHttpBinding>
...
      </basicHttpBinding>
    </bindings>
    <client>
      <endpoint address="http://localhost:2172/WsImpot/ServiceImpot.asmx"
                    binding="basicHttpBinding" bindingConfiguration="ServiceImpotSoap"
                    contract="WsImpot.ServiceImpotSoap" name="ServiceImpotSoap" />
    </client>
  </system.serviceModel>
   <!-- end remote web service client configuration -->
 
   <!-- other configurations already present in the generated web.config -->
...
</configuration>

Beachten Sie, dass Zeile 37 sich auf den Port des Remote-Webdienstes bezieht. Dieser Port kann sich ändern, da Visual Developer den Webdienst auf einem zufälligen Port startet.

Kehren wir nun zur Architektur des Web-Clients zurück, den wir erstellen müssen:

  • [1] wird durch [Default.aspx] implementiert
  • [2] wird durch [Default.aspx.cs] implementiert
  • [3] wurde durch die DLL [ImpotV9-metier] implementiert

Wir haben soeben die Ebene [3] implementiert. Fahren wir nun mit der Weboberfläche [1] fort, die von der Seite [Default.aspx] implementiert wird. Doppelklicken Sie auf die Seite [Default.aspx], um in den Entwurfsmodus zu wechseln.

Es gibt zwei Möglichkeiten, eine Webseite zu erstellen:

  • grafisch wie in [2]. Sie müssen dann in [1] den Modus [Design] auswählen. Diese Schaltflächenleiste befindet sich am unteren Rand der Statusleiste des Webseiten-Editors.
  • mit einer Tag-Sprache wie in [3]. Sie müssen dann in [1] den Modus [Source] auswählen.

Die Modi [Design] und [Quelle] sind bidirektional: Eine im Modus [Design] vorgenommene Änderung wird in eine Änderung im Modus [Quelle] übertragen und umgekehrt. Beachten Sie, dass das im Browser anzuzeigende Webformular wie folgt aussieht:

  • in [1] das im Browser angezeigte Formular
  • in [2] die Komponenten, aus denen es aufgebaut ist
  • in [3] die Formular-Entwurfsseite. Sie enthält die folgenden Elemente:
    • Zeile A: zwei Optionsfelder mit den Namen „RadioButtonOui“ und „RadioButtonNon“
    • Zeile B: ein Eingabefeld namens „TextBoxEnfants“ und eine Beschriftung namens „LabelErreurEnfants“
    • Zeile C: ein Eingabefeld namens „TextBoxSalaire“ und eine Beschriftung namens „LabelErreurSalaire“
    • Zeile D, eine Beschriftung namens LabelImpot
    • Zeile E, zwei Schaltflächen mit den Namen ButtonCalculer und ButtonEffacer

Sobald eine Komponente auf der Entwurfsfläche platziert wurde, kann auf ihre Eigenschaften zugegriffen werden:

  • in [1], Zugriff auf die Eigenschaften der Komponente
  • in [2], das Eigenschaftenfenster für die Komponente [LabelErreurEnfants ]
  • in [3], (ID) ist der Name der Komponente
  • in [4] haben wir den Beschriftungszeichen die Farbe Rot zugewiesen.

Es reicht nicht aus, Komponenten einfach auf dem Formular zu platzieren und dann ihre Eigenschaften festzulegen. Sie müssen auch ihr Layout organisieren. In einer grafischen Windows-Oberfläche ist dieses Layout absolut. Ziehen Sie die Komponente einfach an die gewünschte Stelle. Auf einer Webseite ist das anders, komplexer, aber auch leistungsfähiger. Dieser Aspekt wird hier nicht behandelt.

Der durch diesen Entwurf generierte Quellcode [Default.aspx] lautet wie folgt:


<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title>Calculer votre impôt</title>
</head>
<body bgcolor="#ffff99">
  <h2>
    Calculer votre impôt</h2>
  <form id="form1" runat="server">
  <asp:ScriptManager ID="ScriptManager2" runat="server" EnablePartialRendering="true" />
  <asp:UpdatePanel runat="server" ID="UpdatePanelPam">
    <ContentTemplate>
      <div>
      </div>
      <table>
        <tr>
          <td>
            Etes-vous marié(e)
          </td>
          <td>
            <asp:RadioButton ID="RadioButtonOui" runat="server" GroupName="statut" Text="Oui" />
            <asp:RadioButton ID="RadioButtonNon" runat="server" GroupName="statut" Text="Non"
              Checked="True" />
          </td>
        </tr>
        <tr>
          <td>
            Nombre d&#39;enfants
          </td>
          <td>
            <asp:TextBox ID="TextBoxEnfants" runat="server" Columns="3"></asp:TextBox>
          </td>
          <td>
            <asp:Label ID="LabelErreurEnfants" runat="server" ForeColor="#FF3300"></asp:Label>
          </td>
        </tr>
        <tr>
          <td>
            Salaire annuel
          </td>
          <td>
            <asp:TextBox ID="TextBoxSalaire" runat="server" Columns="8"></asp:TextBox>
          </td>
          <td>
            <asp:Label ID="LabelErreurSalaire" runat="server" ForeColor="#FF3300"></asp:Label>
          </td>
        </tr>
        <tr>
          <td>
            Impôt à payer
          </td>
          <td>
            <asp:Label ID="LabelImpot" runat="server" BackColor="#99CCFF"></asp:Label>
          </td>
        </tr>
      </table>
      <br />
      <table>
        <tr>
          <td>
            <asp:Button ID="ButtonCalculer" runat="server" Text="Calculer" OnClick="ButtonCalculer_Click" />
          </td>
          <td>
            <asp:Button ID="ButtonEffacer" runat="server" Text="Effacer" OnClick="ButtonEffacer_Click" />
          </td>
          <td>
            &nbsp;
          </td>
        </tr>
      </table>
      </div>
    </ContentTemplate>
  </asp:UpdatePanel>
  </form>
</body>
</html>

Die Formularkomponenten sind durch die Zeilen 23, 24, 33, 36, 44, 47, 55, 63 und 66 gekennzeichnet. Der Rest dient im Wesentlichen der Formatierung.

Kommen wir zurück zu der Architektur, die wir erstellen müssen:

  • [1] wurde durch [Default.aspx] implementiert
  • [2] wird von [Default.aspx.cs] implementiert
  • [3] wurde durch die DLL [ImpotV9-metier] implementiert

Die Ebenen [1] und [3] sind nun implementiert. Wir müssen noch die Ebene [2] schreiben, die das Formular generiert, es an den Benutzer sendet, es verarbeitet, wenn der Benutzer das ausgefüllte Formular zurücksendet, die Ebene [3] zur Berechnung der Steuer verwendet, die Web-Antwortseite generiert und sie an den Benutzer zurücksendet. Der Code [Default.aspx.cs] übernimmt all diese Aufgaben:


using System;
using WsImpot;
 
public partial class _Default : System.Web.UI.Page
{
    protected void ButtonCalculer_Click(object sender, EventArgs e)
    {
  ...
    }
    protected void ButtonEffacer_Click(object sender, EventArgs e)
    {
...
    }
}

Der Code ähnelt stark dem eines klassischen Windows-Formulars. Dies ist der Hauptvorteil der ASP.NET-Technologie: Es gibt keine Trennung zwischen dem Windows-Programmiermodell und dem Web-ASP.NET-Programmiermodell. Merken Sie sich einfach das folgende Diagramm:

Wenn der Benutzer in [1] auf die Schaltfläche [Berechnen] klickt, wird die Prozedur ButtonCalculer_Click in Zeile 6 von [Default.aspx.cs] ausgeführt. In der Zwischenzeit:

  • werden die Werte des ausgefüllten Formulars über das HTTP-Protokoll vom Browser an den Webserver übertragen
  • analysiert der ASP.NET-Server die Anfrage und leitet sie an die Seite [Default.aspx] weiter
  • wird die Seite [Default.aspx] instanziiert.
  • Seine Komponenten (RadioButtonOui, RadioButtonNon, TextBoxEnfants, TextBoxSalaire, LabelErreurEnfants, LabelErreurSalaire, LabelImpot) werden dank eines Mechanismus namens „ViewState“ mit den Werten initialisiert, die sie hatten, als das Formular ursprünglich an den Browser gesendet wurde.
  • Die übermittelten Werte werden ihren Komponenten (RadioButtonOui, RadioButtonNon, TextBoxEnfants, TextBoxSalaire) zugewiesen. Hat der Benutzer beispielsweise die Anzahl der Kinder auf 2 gesetzt, erhalten wir TextBoxEnfants.Text="2".
  • Wenn die Seite [Default.aspx] über eine Methode [Page_Load] verfügt, wird diese ausgeführt
  • Die Methode [ButtonCalculer_Click] in Zeile 6 wird ausgeführt, wenn die Schaltfläche [Calculate] angeklickt wird
  • Die Methode [ButtonEffacer_Click] in Zeile 10 wird ausgeführt, wenn die Schaltfläche [Delete] angeklickt wird

Zwischen dem Moment, in dem der Benutzer ein Ereignis in seinem Browser auslöst, und dem Moment, in dem es in [Default.aspx.cs] verarbeitet wird, liegt eine große Komplexität. Diese Komplexität ist verborgen, und wir können so tun, als existiere sie nicht, wenn wir die Ereignisbehandler auf der Webseite schreiben. Wir dürfen jedoch niemals vergessen, dass zwischen dem Ereignis und seinem Handler ein Netzwerk besteht und dass es nicht in Frage kommt, Mausereignisse wie Mouse_Move zu verarbeiten, die kostspielige Client-Server-Roundtrips verursachen würden ...

Der Code zur Verwaltung von Klicks auf die Schaltflächen [Calculate] und [Delete] ist derselbe, der auch für eine herkömmliche Windows-Anwendung geschrieben worden wäre:


protected void ButtonCalculer_Click(object sender, EventArgs e)
    {
         // data verification
        int nbEnfants;
        bool erreur = false;
        if (!int.TryParse(TextBoxEnfants.Text.Trim(), out nbEnfants) || nbEnfants < 0)
        {
            LabelErreurEnfants.Text = "Valeur incorrecte...";
            erreur = true;
        }
        int salaire;
        if (!int.TryParse(TextBoxSalaire.Text.Trim(), out salaire) || salaire < 0)
        {
            LabelErreurSalaire.Text = "Valeur incorrecte...";
            erreur = true;
        }
         // mistake?
        if (erreur) return;
         // erase any errors
        LabelErreurEnfants.Text = "";
        LabelErreurSalaire.Text = "";
         // marital status
        bool marié = RadioButtonOui.Checked;
         // tAX CALCULATION
        try
        {
            LabelImpot.Text = String.Format("{0} euros",Global.Metier.CalculerImpot(marié, nbEnfants, salaire));
        }
        catch (Exception ex)
        {
            LabelImpot.Text = ex.Message;
        }
    }
  • Um diesen Code zu verstehen, müssen Sie wissen,
    • Zu Beginn der Ausführung entspricht das Formular [Default.aspx] dem vom Benutzer ausgefüllten Formular. Das bedeutet, dass die Felder (RadioButtonOui, RadioButtonNon, TextBoxEnfants, TextBoxSalaire) die vom Benutzer eingegebenen Werte enthalten.
    • Am Ende der Ausführung wird dem Benutzer dieselbe Seite [Default.aspx] zurückgegeben. Dies geschieht automatisch.

Die Prozedur ButtonCalculer_Click muss daher auf den aktuellen Werten der Felder (RadioButtonOui, RadioButtonNon, TextBoxEnfants, TextBoxSalaire) basieren und die Werte aller Felder (RadioButtonOui, RadioButtonNon, TextBoxEnfants, TextBoxSalaire, LabelErreurEnfants, LabelErreurSalaire, LabelImpot) der neuen Seite [Default.aspx], die dem Benutzer zurückgegeben wird.

Dieser Code weist keine besonderen Schwierigkeiten auf. Lediglich Zeile 27 bedarf einer Erläuterung. Sie verwendet das Feld Global.Metier in CalculerImpot, das noch nicht definiert wurde. Wir werden gleich darauf zurückkommen.

Die Methode ButtonEffacer_Click lautet wie folgt:


    protected void ButtonEffacer_Click(object sender, EventArgs e)
    {
         // raz form
        TextBoxEnfants.Text = "";
        TextBoxSalaire.Text = "";
        LabelImpot.Text = "";
        LabelErreurEnfants.Text = "";
        LabelErreurSalaire.Text = "";
}

Kommen wir zurück zu der Architektur, die wir erstellen müssen:

  • [1] wurde durch [Default.aspx] implementiert
  • [2] wurde durch [Default.aspx.cs] implementiert
  • [3] wurde durch die DLL [ImpotV9-metier] implementiert

Jetzt muss nur noch das „Bindeglied“ zwischen diesen drei Schichten geschaffen werden. Im Wesentlichen sind dies:

  • die Instanziierung der Ebene [3] beim Start der Anwendung
  • einen Verweis darauf an einer Stelle platzieren, an der die Webseite [Default.aspx.cs] ihn jedes Mal abrufen kann, wenn sie instanziiert und zur Berechnung der Steuer aufgefordert wird.

Dies ist kein neues Problem. Es ist bereits bei der Erstellung des Remote-Webdienstes aufgetreten und wurde in Abschnitt 12.4.1 behandelt. Wir wissen, dass die Lösung darin besteht:

  • Erstellen einer Datei [Global.asax], die mit einer Klasse [Global.cs] verknüpft ist
  • die Ebene [3] in der Methode Application_Start aus [Global.cs] instanziieren
  • die Referenz auf die Ebene [3] in ein statisches Feld der Klasse [Global.cs] setzen, da die Lebensdauer dieser Klasse der Lebensdauer der Anwendung entspricht.

Infolgedessen entwickelt sich unser Webprojekt wie folgt:

  • in [1], Datei [Global.asax].
  • in [2] den zugehörigen Code [Global.cs]. Der Ordner [App_Code], in dem sich diese Datei befindet, ist in der Weblösung standardmäßig nicht vorhanden. Erstellen Sie ihn über [3].

Die Datei Global.asax lautet wie folgt:


<%@ Application Language="C#" Inherits="WsImpot.Global"%>

Der Code [Global.cs] lautet wie folgt:


using System;
using Metier;
using Spring.Context.Support;
namespace WsImpot
{
    public class Global : System.Web.HttpApplication
    {
         // business layer
        public static IImpotMetier Metier;
 
         // method executed at application startup
        private void Application_Start(object sender, EventArgs e)
        {
            // instantiations [metier] and [dao] layers
            Metier = ContextRegistry.GetContext().GetObject("metier") as IImpotMetier;
        }
    }
}
  • Zeile 6: Die Klasse heißt Global und ist Teil von WsImpot (Zeile 4). Ihr vollständiger Name lautet WsImpot.Global, und genau dieser Name muss in Inherits from Global.asax angegeben werden.
  • Zeile 6: Wir wissen, dass die mit der Klasse „Global.asax“ verbundene Klasse von der Klasse „System.Web.HttpApplication“ abgeleitet sein muss.
  • Zeile 12: Die Methode Application_Start wird beim Start der Webanwendung ausgeführt.
  • Zeile 15: Wir integrieren die [Metier]-Schicht (Schicht [3] der im Aufbau befindlichen Anwendung) mithilfe von Spring und der folgenden Konfiguration in [web.config]:

  <!-- objets Spring -->
  <spring>
    <context>
      <resource uri="config://spring/objects" />
    </context>
    <objects xmlns="http://www.springframework.net">
      <object name="metier" type="Metier.ImpotMetierWS, ImpotsV9-metier">
      </object>
    </objects>
</spring>

Die Klasse [Metier.ImpotMetierWS] in Zeile (g) oben befindet sich in [ImpotsV9-metier.dll].

Die Referenz der erstellten [metier]-Ebene wird in das statische Feld in Zeile 9 eingetragen. Dieses Feld wird in Zeile 27 von ButtonCalculer_Click verwendet:


LabelImpot.Text = String.Format("{0} euros",Global.Metier.CalculerImpot(marié, nbEnfants, salaire));

Wir sind bereit für einen Test. Wir müssen das Datenbankmanagementsystem MySQL5 und den Remote-Webdienst starten und den Port notieren, auf dem dieser läuft:

  

Sobald dies erledigt ist, überprüfen Sie, ob in der Datei [web.config] des Web-Clients der Port des Remote-Webdienstes korrekt ist:

Sobald dies erledigt ist, kann der Web-Client des Remote-Webdienstes durch Drücken von Strg-F5 gestartet werden:

  

12.6. Ein Java-Konsolen-Client für den Webdienst zur Steuerberechnung

Um zu zeigen, dass Webdienste von Clients in jeder beliebigen Sprache aufgerufen werden können, schreiben wir einen einfachen Java-Konsolen-Client. Die Architektur der Client-Server-Anwendung sieht wie folgt aus:

  • Der Client [1] wird in Java geschrieben
  • Server [2] ist in C# geschrieben

Zunächst werden wir eine Kleinigkeit an unserem Webdienst zur Steuerberechnung ändern. Die aktuelle Definition in [ServiceImpot.cs] lautet wie folgt:


...
public class ServiceImpot : System.Web.Services.WebService
{
 
    [WebMethod]
    public int CalculerImpot(bool marié, int nbEnfants, int salaire)
    {
        return Global.Metier.CalculerImpot(marié, nbEnfants, salaire);
    }
 
}

Die Tests haben gezeigt, dass die Hervorhebung des Parameters „marié“ in den Zeilen 6 und 8 ein Problem für die Interoperabilität zwischen Java und C# darstellen könnte. Wir verwenden daher die folgende neue Definition:


...
public class ServiceImpot : System.Web.Services.WebService
{
 
    [WebMethod]
    public int CalculerImpot(bool marie, int nbEnfants, int salaire)
    {
        return Global.Metier.CalculerImpot(marie, nbEnfants, salaire);
    }
 
}

Dieser Dienst wird in einem neuen Web Developer-Projekt namens WsImpotsSansAccents abgelegt. Der Webdienst erhält dann die URL [/WsImpotSansAccents/ServiceImpot.asmx].

Image

Um den Java-Client zu schreiben, verwenden wir die NetBeans-IDE [http://www.netbeans.org/]:

  • Erstellen Sie in [1] ein neues Projekt
  • Wählen Sie in [2,3] den Projekttyp „Java-Anwendung“ aus.
  • in [4] zum nächsten Schritt übergehen
  • in [5] geben Sie dem Projekt einen Namen
  • in [6] geben Sie den Ordner an, in dem ein Unterordner mit dem Projektnamen erstellt werden soll
  • in [7] benennen Sie die Klasse, die den beim Start der Anwendung ausgeführten Code enthält
  • Füllen Sie in [8] das
  • in [9]: das generierte Java-Projekt
  • in [10]: Klicken Sie mit der rechten Maustaste auf das Projekt, um den Web-Client für den Steuerberechnungsdienst zu generieren
  • in [11], die URL der Datei, die den Web-Steuerberechnungsdienst beschreibt:

http://localhost:1089/WsImpotSansAccents/ServiceImpot.asmx?WSDL

Diese URL ist die des Dienstes [ServiceImpot.asmx], an die wir den Parameter ?WSDL anhängen. Das unter dieser URL befindliche Dokument beschreibt in XML-Sprache, was der Dienst [15] leisten kann. Es handelt sich um ein Standardelement eines Webdienstes.

  • In [12] das Paket (entspricht dem C#-Namespace), in das die zu generierenden Klassen abgelegt werden sollen
  • in [13] den Standardwert beibehalten
  • in [14] vervollständigen Sie das
  • in [16] wurde der importierte Webdienst in das Java-Projekt integriert. Er unterstützt die beiden Kommunikationsprotokolle SOAP und SOAP12.
  • in [17] die [Main]-Klasse, in der wir den generierten Client verwenden werden
  • In [18] fügen wir nun etwas Code in die [main]-Methode ein. Setzen Sie den Cursor an die Stelle, an der der Code eingefügt werden soll, klicken Sie mit der rechten Maustaste und wählen Sie die Option [19]
  • In [20] geben Sie an, dass Sie den Aufrufcode für die CalculerImpot des Remote-Steuerberechnungsdienstes generieren möchten, und klicken Sie dann auf „OK“.

Der in [Main] generierte Code lautet wie folgt:

public class Main {

    public static void main(String[] args) {
         // TODO code application logic here
       try { // Call Web Service Operation
        wsimpot.ServiceImpot service = new wsimpot.ServiceImpot();
        wsimpot.ServiceImpotSoap port = service.getServiceImpotSoap();
         // TODO initialize WS operation arguments here
        boolean marie = false;
        int nbEnfants = 0;
        int salaire = 0;
         // TODO process result here
        int result = port.calculerImpot(marie, nbEnfants, salaire);
        System.out.println("Result = "+result);
      } catch (Exception ex) {
         // TODO handle custom exceptions here
      }
    }
}

Der generierte Code zeigt, wie die CalculerImpot des Remote-Steuerberechnungsdienstes aufgerufen wird. Wenn wir eine Parallele zu dem ziehen, was wir in C# gesehen haben, entspricht die Variable „port“ in Zeile 7 dem in C# verwendeten Client. Wir werden diesen Code nicht weiter kommentieren. Wir werden ihn wie folgt umgestalten:

import wsimpot.ServiceImpot;
public class Main {
    public static void main(String[] args) {
      try {
        // call the CalculerImpot function of the web service
        System.out.println(String.format("Montant à payer : %d euros", new ServiceImpot().getServiceImpotSoap().calculerImpot(true, 2, 60000)));
      } catch (Exception ex) {
        System.out.println(String.format("L'erreur suivante s'est produite %s",ex.getMessage()));
      }
    }
}
  • Zeile 1: Wir importieren ServiceImpot, das den vom Assistenten generierten Client darstellt.
  • Zeile 6: Wir rufen die Remote-Methode CalculerImpot auf, indem wir die im generierten Code in main angegebene Vorgehensweise befolgen.

Die in der Konsole zur Laufzeit (F6) erhaltenen Ergebnisse lauten wie folgt:

init:
deps-jar:
wsimport-init:
wsimport-client-check-ServiceImpot.asmx:
wsimport-client-ServiceImpot.asmx:
wsimport-client-generate:
wsimport-client-compile:
Compiling 1 source file to C:\data\2007-2008\netbeans\ClientNetbeansPourServiceImpotDotNet\build\classes
compile:
run:
Montant à payer : 4282 euros
BUILD SUCCESSFUL (total time: 7 seconds)