10. Webdienste
10.1. Einführung
Im vorigen Kapitel haben wir mehrere TCP/IP-Client-Server-Anwendungen vorgestellt. Da die Clients und der Server Textzeilen austauschen, können sie in jeder beliebigen Programmiersprache geschrieben werden. Der Client muss lediglich das vom Server erwartete Kommunikationsprotokoll kennen. Webdienste sind TCP/IP-Serveranwendungen mit folgenden Merkmalen:
- Sie werden von Webservern gehostet, und das Protokoll für den Client-Server-Austausch ist daher HTTP (HyperText Transport Protocol), ein Protokoll, das auf TCP-IP aufbaut.
- Der Webdienst verfügt unabhängig vom angebotenen Dienst über ein Standard-Kommunikationsprotokoll. Ein Webdienst bietet verschiedene Dienste an: S1, S2, …, Sn. Jeder dieser Dienste erwartet vom Client übergebene Parameter und liefert diesem ein Ergebnis zurück. Für jeden Dienst muss der Client Folgendes wissen:
- den genauen Namen des Dienstes, falls
- die Liste der Parameter, die bereitgestellt werden müssen, sowie deren Typ
- den Typ des vom Dienst zurückgegebenen Ergebnisses
Sobald diese Elemente bekannt sind, folgt der Client-Server-Dialog unabhängig vom abgefragten Webdienst dem gleichen Format. Die Programmierung der Clients wird dadurch standardisiert.
- Aus Sicherheitsgründen vor Angriffen aus dem Internet verfügen viele Organisationen über private Netzwerke und öffnen nur bestimmte Ports ihrer Server für das Internet: im Wesentlichen Port 80 des Webdienstes. Alle anderen Ports sind gesperrt. Daher werden Client-Server-Anwendungen, wie sie im vorigen Kapitel vorgestellt wurden, innerhalb des privaten Netzwerks (Intranet) aufgebaut und sind in der Regel von außen nicht zugänglich. Die Bereitstellung eines Dienstes auf einem Webserver macht ihn für die gesamte Internet-Community zugänglich.
- Der Webdienst lässt sich als entferntes Objekt modellieren. Die angebotenen Dienste werden dann zu Methoden dieses Objekts. Ein Client kann auf dieses entfernte Objekt zugreifen, als wäre es lokal. Dadurch wird der gesamte Teil der Netzwerkkommunikation verborgen, und es kann ein von dieser Schicht unabhängiger Client entwickelt werden. Sollte sich diese Schicht ändern, muss der Client nicht angepasst werden. Dies ist ein enormer Vorteil und wahrscheinlich der wichtigste Trumpf der Webdienste.
- Wie bei den im vorigen Kapitel vorgestellten TCP/IP-Client-Server-Anwendungen können Client und Server in einer beliebigen Sprache geschrieben werden. Sie tauschen Textzeilen aus. Diese bestehen aus zwei Teilen:
- die für das Protokoll HTTP erforderlichen Kopfzeilen
- den Hauptteil der Nachricht. Bei einer Antwort des Servers an den Client hat dieser das Format XML (eXtensible Markup Language). Bei einer Anfrage des Clients an den Server kann der Hauptteil der Nachricht verschiedene Formen annehmen, darunter XML. Die Anfrage XML des Clients kann ein spezielles Format haben, das als SOAP (Simple Object Access Protocol) bezeichnet wird. In diesem Fall folgt auch die Antwort des Servers dem Format SOAP.
10.2. Browser und XML
Webdienste senden XML an ihre Clients. Browser können beim Empfang dieses XML-Datenstroms unterschiedlich reagieren. Der Internet Explorer verfügt über ein vordefiniertes Stylesheet, das die Anzeige ermöglicht. Netscape Communicator verfügt hingegen nicht über dieses Stylesheet und zeigt den empfangenen XML-Code nicht an. Man muss den Quellcode der empfangenen Seite anzeigen, um Zugriff auf den XML-Code zu erhalten. Hier ein Beispiel für den folgenden XML-Code:
<?xml version="1.0" encoding="utf-8"?>
<string xmlns="st.istia.univ-angers.fr">bonjour de nouveau !</string>
Der Internet Explorer zeigt die folgende Seite an:

während Netscape Navigator Folgendes anzeigt:

Betrachtet man den Quellcode der von Netscape empfangenen Seite, erhält man:

Netscape hat zwar dasselbe wie der Internet Explorer empfangen, es jedoch anders dargestellt. Im weiteren Verlauf werden wir für die Screenshots den Internet Explorer verwenden.
10.3. Ein erster Webdienst
Wir werden Webdienste anhand eines sehr einfachen Beispiels kennenlernen, das in drei Varianten vorliegt.
10.3.1. Version 1
Für diese erste Version verwenden wir VS.NET, das den Vorteil bietet, ein sofort einsatzbereites Webdienst-Grundgerüst zu generieren. Sobald wir diese Architektur verstanden haben, können wir anfangen, auf eigenen Beinen zu stehen. Das wird Gegenstand der folgenden Versionen sein.
Erstellen wir mit VS.NET ein neues Projekt mit der Option [Fichier/Nouveau/Projet]:

Beachten Sie dabei folgende Punkte:
- Der Projekttyp ist Visual Basic (linker Rahmen)
- Die Projektvorlage ist „Webdienst ASP.NET“ (rechter Rahmen)
- Der Speicherort ist frei wählbar. Hier wird der Webdienst auf einem lokalen Webserver mit der Bezeichnung IIS gehostet. Die Adresse lautet daher http://localhost/[chemin], wobei URL noch festgelegt werden muss. Hier wählen wir den Pfad http://localhost/polyvbnet/demo. VS.NET erstellt daraufhin einen Ordner für dieses Projekt. Wo? Der Server IIS verfügt über ein Stammverzeichnis für die Baumstruktur der von ihm bereitgestellten Webdokumente. Nennen wir dieses Stammverzeichnis <IISroot>. Es entspricht dem URL http://localhost. Daraus lässt sich ableiten, dass die URL http://localhost/polyvbnet/demo dem Ordner <IISroot>/polyvbnet/demo zugeordnet wird. <IISroot> ist normalerweise der Ordner \inetpub\wwwroot auf dem Laufwerk, auf dem IIS installiert wurde. In unserem Beispiel ist dies Laufwerk E. Der von VS.NET erstellte Ordner ist somit der Ordner e:\inetpub\wwwroot\polyvbnet\demo:

Wie immer wurden übermäßig viele Ordner erstellt. Diese sind nicht immer von Bedeutung. Wir werden nur diejenigen erläutern, die wir zu einem bestimmten Zeitpunkt benötigen. VS.NET hat ein Projekt erstellt:

Wir finden einige der Dateien wieder, die im physischen Projektordner vorhanden sind. Am interessantesten für uns ist die Datei mit der Endung „asmx“. Dies ist die Endung für Webdienste. Ein Webdienst wird von VS.NET wie eine Windows-Anwendung verwaltet, nämlich c.a.d – eine Anwendung mit einer grafischen Benutzeroberfläche und Code zu deren Verwaltung. Aus diesem Grund haben wir ein Entwurfsfenster:

Ein Webdienst verfügt normalerweise nicht über eine grafische Benutzeroberfläche. Er stellt ein Objekt dar, das aus der Ferne aufgerufen werden kann. Er verfügt über Methoden, die von Anwendungen aufgerufen werden. Wir betrachten ihn daher als klassisches Objekt mit der Besonderheit, dass er aus der Ferne über das Netzwerk instanziiert werden kann. Daher werden wir das von VS.NET angezeigte Entwurfsfenster nicht verwenden. Wenden wir uns stattdessen dem Code des Dienstes zu, indem wir die Option „Ansicht/Code“ nutzen:

Es sind mehrere Punkte zu beachten:
- Die Datei heißt Service1.asmx.vb und nicht Service1.asmx. Wir werden etwas später auf den Inhalt der Datei Service1.asmx zurückkommen.
- Es erscheint ein Codefenster, das dem ähnelt, das wir hatten, als wir Windows-Anwendungen mit VS.NET erstellt haben
Der von VS.NET generierte Code lautet wie folgt:
Imports System.Web.Services
<System.Web.Services.WebService(Namespace := "http://tempuri.org/demo/Service1")> _
Public Class Service1
Inherits System.Web.Services.WebService
#Region „Vom Web-Service-Designer generierter Code“
Public Sub New()
MyBase.New()
'Dieser Aufruf wird vom Web-Service-Designer benötigt.
InitializeComponent()
'Fügen Sie Ihren Initialisierungscode nach dem Aufruf von InitializeComponent() ein
End Sub
'Vom Web-Service-Designer erforderlich
Private components As System.ComponentModel.IContainer
'REMARQUE: Die folgende Prozedur wird vom Web-Service-Designer benötigt
'Sie kann mithilfe des Webdienst-Designers geändert werden.
'Ändern Sie sie nicht mit dem Code-Editor.
<System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
components = New System.ComponentModel.Container()
End Sub
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
'CODEGEN: Diese Vorgehensweise wird vom Web-Service-Designer benötigt
'Ändern Sie dies nicht mit dem Code-Editor.
If disposing Then
If Not (components Is Nothing) Then
components.Dispose()
End If
End If
MyBase.Dispose(disposing)
End Sub
#Ende des Bereichs
' EXEMPLE DE SERVICE WEB
' Das Beispiel für den Dienst HelloWorld() gibt die Zeichenfolge „Hello World“ zurück.
' Um das Projekt zu generieren, kommentieren Sie die folgenden Zeilen nicht aus, speichern Sie das Projekt und generieren Sie es.
' Um diesen Webdienst zu testen, stellen Sie sicher, dass die .asmx-Datei als Startseite festgelegt ist
' und klicken Sie auf F5.
'
'<WebMethod()> Public Function HelloWorld() As String
' HelloWorld = „Hello World“
' End Function
End Class
Zunächst ist festzuhalten, dass es sich hier um eine Klasse handelt, nämlich die Klasse „Service1“, die von der Klasse „WebService“ abgeleitet ist:
Dies führt dazu, dass wir den Namensraum „System.Web.Services“ importieren müssen:
Imports System.Web.Services
Der Deklaration der Klasse ist ein Kompilierungsattribut vorangestellt:
<System.Web.Services.WebService(Namespace := "http://tempuri.org/demo/Service1")> _
Public Class Service1
Inherits System.Web.Services.WebService
Das Attribut System.Web.Services.WebService() gibt an, dass es sich bei der nachfolgenden Klasse um einen Webdienst handelt. Dieses Attribut akzeptiert verschiedene Parameter, darunter einen namens NameSpace. Er dient dazu, den Webdienst in einen Namensraum einzuordnen. Man kann sich nämlich vorstellen, dass es weltweit mehrere Webdienste mit dem Namen „meteo“ gibt. Wir benötigen eine Möglichkeit, sie voneinander zu unterscheiden. Dies wird durch den Namensraum ermöglicht. Der eine könnte „[espacenom1].meteo“ heißen, der andere „[espacenom2].meteo“. Hier handelt es sich um ein Konzept, das den Namensräumen von Klassen ähnelt. VS.NET hat automatisch Code generiert und diesen in einen Bereich des Quellcodes eingefügt:
Wenn man sich diesen Code ansieht, erkennt man denselben, den der Designer generierte, als wir Windows-Anwendungen entwickelten. Diesen Code kann man schlicht und einfach entfernen, wenn keine grafische Benutzeroberfläche vorhanden ist, was bei Webdiensten der Fall sein wird.
Die Lektion endet mit einem Beispiel dafür, wie ein Webdienst aussehen könnte:
#Ende Region
' EXEMPLE DE SERVICE WEB
' Das Dienstbeispiel HelloWorld() gibt die Zeichenfolge „Hello World“ zurück.
' Um das Projekt zu generieren, kommentieren Sie die folgenden Zeilen nicht aus, speichern Sie das Projekt und generieren Sie es.
' Um diesen Webdienst zu testen, stellen Sie sicher, dass die .asmx-Datei als Startseite festgelegt ist
' und klicken Sie auf F5.
'
'<WebMethod()> Public Function HelloWorld() As String
' HelloWorld = „Hello World“
' End Function
Auf der Grundlage des eben Gesagten bereinigen wir den Code, sodass er wie folgt aussieht:
Imports System.Web.Services
<System.Web.Services.WebService(Namespace:="st.istia.univ-angers.fr")> _
Public Class Bonjour
Inherits System.Web.Services.WebService
<WebMethod()> Public Function Bonjour() As String
Return "bonjour !"
End Function
End Class
Jetzt ist die Sache etwas übersichtlicher.
- Ein Webdienst ist eine Klasse, die von der Klasse WebService abgeleitet ist
- Die Klasse wird durch das Attribut <System.Web.Services.WebService(Namespace:="st.istia.univ-angers.fr")> qualifiziert. Wir ordnen unseren Dienst also dem Namensraum st.istia.univ-angers.fr zu.
- Die Methoden der Klasse werden durch das Attribut <WebMethod()> qualifiziert, das angibt, dass es sich um eine Methode handelt, die remote über das Netzwerk aufgerufen werden kann
Die Klasse, die unseren Webdienst bereitstellt, heißt also „Bonjour“ und verfügt über eine einzige Methode, die ebenfalls „Bonjour“ heißt und eine Zeichenkette zurückgibt. Wir sind bereit für einen ersten Test.
- Starten wir den Webserver IIS, falls dies noch nicht geschehen ist
- verwenden wir die Option „Debuggen/Ohne Debugging ausführen“. VS.NET
VS.NET kompiliert nun die gesamte Anwendung, startet einen Browser (oft den Internet Explorer, falls vorhanden) und zeigt die URL http://localhost/polyvbnet/demo/Service1.asmx an:

Warum die URL http://localhost/polyvbnet/demo/Service1.asmx? Weil dies die einzige .asmx-Datei im Projekt war:

Hätte es mehrere .asmx-Dateien gegeben, hätten wir angeben müssen, welche zuerst ausgeführt werden sollte. Dazu klickt man mit der rechten Maustaste auf die betreffende .asmx-Datei und wählt die Option [Définir comme page de démarrage].

Vielleicht interessiert es Sie, was die Datei service1.asmx enthält. Tatsächlich haben wir mit VS.NET an der Datei service1.asmx.vb gearbeitet und nicht an der Datei service1.asmx. Diese Datei befindet sich im Projektordner:

Öffnen wir sie mit einem Texteditor (Notepad oder einem anderen). Wir erhalten folgenden Inhalt:
Die Datei enthält eine einfache Anweisung an den Server IIS, die angibt:
- dass es sich um einen Webdienst handelt (Schlüsselwort WebService)
- dass die Sprache der Klasse dieses Webdienstes Visual Basic ist (Language="vb")
- dass der Quellcode dieser Klasse in der Datei Service1.asmx.vb zu finden ist (Codebehind="Service1.asmx.vb")
- dass die Klasse, die den Dienst implementiert, demo.Bonjour heißt (Class="demo.Bonjour"). Man beachte, dass VS.NET die Klasse „Bonjour“ im Namensraum „demo“ platziert hat, der auch der Name des Projekts ist.
Kehren wir nun zu der Seite zurück, die unter der URL http://localhost/polyvbnet/demo/Service1.asmx aufgerufen wird:

Wer hat den Code HTML der obigen Seite geschrieben? Nicht wir, das wissen wir. Es ist IIS, das Webdienste auf standardisierte Weise präsentiert. Diese Seite bietet uns zwei Links an. Folgen wir dem ersten, [Description du service]:

Ups… das ist ein ziemlich kryptisches XML. Beachten wir dennoch das URL
http://localhost/polyvbnet/demo/Service1.asmx?WSDL. Öffnen Sie einen Browser und geben Sie diese URL direkt ein. Sie erhalten dasselbe Ergebnis wie zuvor. Wir erinnern uns also daran, dass die URL http://serviceweb?WSDL Zugriff auf die Beschreibung XML des Webdienstes gewährt. Kehren wir zur Startseite zurück und wählen wir den Link [Bonjour] aus. Denken wir daran, dass „Bonjour“ eine Methode des Webdienstes ist. Hätten wir mehrere Methoden gehabt, wären sie alle hier aufgeführt worden. Wir gelangen auf die folgende neue Seite:

Wir haben die angezeigte Seite bewusst gekürzt, um unsere Demonstration nicht zu überfrachten. Beachten wir noch einmal die angezeigte URL:
Wenn wir diese URL direkt in einen Browser eingeben, erhalten wir dasselbe Ergebnis wie oben. Wir werden dazu aufgefordert, die Schaltfläche „[Appeler]“ zu verwenden. Machen wir das. Wir gelangen auf eine neue Seite:

Das ist wieder XML. Dort finden wir zwei Informationen, die bereits in unserem Webdienst vorhanden waren:
- den Namensraum st.istia.univ-angers.fr unseres Dienstes
<System.Web.Services.WebService(Namespace:="st.istia.univ-angers.fr")>
- den von der Methode „Bonjour“ zurückgegebenen Wert:
Return "bonjour !"
Was haben wir gelernt?
- wie man einen S-Webdienst schreibt
- wie man ihn aufruft
Wir beschäftigen uns nun damit, einen Webdienst ohne die Hilfe von VS.NET zu schreiben.
10.3.2. Version 2
Im vorherigen Beispiel hat VS.NET vieles von selbst erledigt. Ist es möglich, einen Webdienst ohne dieses Tool zu erstellen? Die Antwort lautet „Ja“, und das zeigen wir nun. Mit einem Texteditor erstellen wir den folgenden Webdienst:
Imports System.Web.Services
<System.Web.Services.WebService(Namespace:="st.istia.univ-angers.fr")> _
Public Class Bonjour2
Inherits System.Web.Services.WebService
<WebMethod()> Public Function getBonjour() As String
Return "bonjour de nouveau !"
End Function
End Class
Die Klasse heißt „Bonjour2“ und verfügt über eine Methode namens „getBonjour“. Sie wurde in die Datei „demo2.vb“ eingefügt, die sich wiederum im Verzeichnisbaum des Servers „IIS“ im Ordner „E:\Inetpub\wwwroot\polyvbnet\demo2“ befindet. Es handelt sich um eine klassische VB.NET-Klasse, die somit kompiliert werden kann:
dos>vbc /out:demo2 /t:library /r:system.dll /r:system.web.services.dll demo2.vb
dos>dir
02/03/2004 18:04 286 demo2.vb
02/03/2004 18:10 77 demo2.asmx
02/03/2004 18:12 3 072 demo2.dll
Wir legen die Baugruppe demo2.dll in einem Ordner namens „bin“ ab (dieser Name ist obligatorisch):
Nun erstellen wir die Datei demo2.asmx. Diese wird von den Web-Clients aufgerufen. Ihr Inhalt lautet wie folgt:
Diese Anweisung ist uns bereits bekannt. Sie besagt, dass:
- die Webdienstklasse „Bonjour2“ heißt und sich in der Assembly demo2.dll befindet. IIS sucht an verschiedenen Orten nach dieser Assembly, insbesondere im Ordner „bin“ des Webdienstes. Deshalb haben wir dort die Assembly „demo2.dll“ abgelegt.
Nun können wir verschiedene Tests durchführen. Wir vergewissern uns, dass IIS aktiv ist, und rufen mit einem Browser die URL http://localhost/polyvbnet/demo2/demo2.asmx auf:

Anschließend die URL http://localhost/polyvbnet/demo2/demo2.asmx?WSDL

Dann URL http://localhost/polyvbnet/demo2/demo2.asmx?op=getBonjour, wobei getBonjour der Name der einzigen Methode unseres Webdienstes ist:

Wir verwenden die oben genannte Schaltfläche [Appeler]:

Wir erhalten tatsächlich das Ergebnis des Aufrufs der Methode getBonjour des Webdienstes. Wir wissen nun, wie man einen Webdienst ohne vs.net aufbaut. Wir werden nun die Art und Weise, wie der Webdienst aufgebaut ist, außer Acht lassen und uns ausschließlich auf die grundlegenden Dateien konzentrieren.
10.3.3. Version 3
Die beiden vorherigen Versionen des Webdienstes [Bonjour] verwendeten zwei Dateien:
- eine .asmx-Datei, den Einstiegspunkt des Webdienstes
- eine .vb-Datei, die den Quellcode des Webdienstes enthält
Wir zeigen hier, dass die .asmx-Datei allein ausreicht. Der Code des Webdienstes demo3.asmx lautet wie folgt:
Wir stellen fest, dass sich der Quellcode des Dienstes nun direkt in der Quelldatei demo3.asmx befindet. Die Anweisung
verweist nicht mehr auf eine Klasse in einer externen Assembly, sondern auf eine Klasse, die sich in derselben Quelldatei befindet. Speichern wir diese im Ordner <IISroot>\polyvbnet\demo3:

Starten wir IIS und rufen wir die URL http://localhost/polyvbnet/demo3/demo3.asmx auf:

Wir stellen einen wesentlichen Unterschied zur vorherigen Version fest: Wir mussten den Code VB des Dienstes nicht kompilieren. IIS hat diese Kompilierung selbst über den auf demselben Rechner installierten Compiler VB.NET durchgeführt. Anschließend hat er die Seite ausgegeben. Sollte ein Kompilierungsfehler auftreten, wird dieser von IIS gemeldet:

10.3.4. Version 4
Wir befassen uns hier mit der Konfiguration des Servers IIS. Bisher haben wir unsere Webdienste immer im Stammverzeichnis <IISroot> des Servers IIS, hier [e:\inetpub\wwwroot], abgelegt. Wir zeigen hier, dass wir den Webdienst an beliebiger Stelle platzieren können. Dies geschieht mithilfe der virtuellen Ordner von IIS. Platzieren wir unseren Dienst im folgenden Ordner:

Der Ordner [D:\data\devel\vbnet\poly\chap9\demo3] befindet sich nicht in der Verzeichnisstruktur des Servers IIS. Dies muss dem Server mitgeteilt werden, indem ein virtueller Ordner „IIS“ angelegt wird. Starten wir IIS und wählen wir die untenstehende Option [Avancé] aus:

Es wird eine Liste virtueller Verzeichnisse angezeigt. Wir werden uns damit nicht näher befassen. Erstellen Sie ein neues virtuelles Verzeichnis mit der Schaltfläche [Ajouter] oben:

Mit der Schaltfläche [Parcourir] wählen wir den physischen Ordner aus, der den Webdienst enthält, in diesem Fall den Ordner [D:\data\devel\vbnet\poly\chap9\demo3]. Wir geben diesem Ordner einen logischen (virtuellen) Namen: [virdemo3]. Das bedeutet, dass die Dokumente im physischen Ordner [D:\data\devel\vbnet\poly\chap9\demo3] im Netzwerk über die URL [http://<machine>/virdemo3] zugänglich sind. Das oben gezeigte Dialogfeld enthält weitere Einstellungen, die wir unverändert lassen. Wir bestätigen die Eingaben. Der neue virtuelle Ordner erscheint in der Liste der virtuellen Ordner von IIS:

Nun öffnen wir einen Browser und rufen die URL [http://localhost/virdemo3/demo3.asmx] auf. Wir erhalten dasselbe Ergebnis wie zuvor:

10.3.5. Fazit
Wir haben verschiedene Vorgehensweisen zur Erstellung eines Webdienstes aufgezeigt. Im weiteren Verlauf werden wir die Methode aus Version 3 für die Erstellung des Dienstes und die Methode 4 für dessen Lokalisierung verwenden. Somit benötigen wir VS.NET nicht. Dennoch sei angemerkt, dass die Verwendung von VS.NET aufgrund der Unterstützung beim Debuggen von Vorteil ist. Es gibt kostenlose Tools zur Entwicklung von Webanwendungen, insbesondere das von Microsoft gesponserte Produkt WebMatrix, das unter URL [http://www.asp.net/webmatrix] zu finden ist. Es ist ein hervorragendes Tool, um ohne Vorabinvestitionen in die Webprogrammierung einzusteigen.
10.4. Ein Webdienst für Operationen
Wir betrachten einen Webdienst, der fünf Funktionen bietet:
- add(a,b), das a+b zurückgibt
- subtrahieren(a,b), das a-b zurückgibt
- multiplier(a,b), das a*b zurückgibt
- divide(a,b), der a/b zurückgibt
- „alles“(a,b), der das Array [a+b, a-b, a*b, a/b] zurückgibt
Der Code VB.NET für diesen Dienst lautet wie folgt:
<%@ WebService language="VB" class=operations %>
imports system.web.services
<WebService(Namespace:="st.istia.univ-angers.fr")> _
Public Class operations
Inherits WebService
<WebMethod> _
Function ajouter(a As Double, b As Double) As Double
Return a + b
End Function
<WebMethod> _
Function soustraire(a As Double, b As Double) As Double
Return a - b
End Function
<WebMethod> _
Function multiplier(a As Double, b As Double) As Double
Return a * b
End Function
<WebMethod> _
Function diviser(a As Double, b As Double) As Double
Return a / b
End Function
<WebMethod> _
Function toutfaire(a As Double, b As Double) As Double()
Return New Double() {a + b, a - b, a * b, a / b}
End Function
End Class
Wir greifen hier einige bereits gegebene Erläuterungen wieder auf, die jedoch einer Wiederholung oder Ergänzung bedürfen. Die Klasse operations ähnelt einer Klasse VB.NET, wobei jedoch einige Punkte zu beachten sind:
- Den Methoden ist ein Attribut <WebMethod()> vorangestellt, das dem Compiler angibt, welche Methoden „veröffentlicht“ werden sollen (c.a.d) und dem Client zur Verfügung gestellt werden sollen. Eine Methode, der dieses Attribut nicht vorangestellt ist, wäre für Remote-Clients unsichtbar. Dabei könnte es sich um eine interne Methode handeln, die von anderen Methoden verwendet wird, aber nicht zur Veröffentlichung bestimmt ist.
- Die Klasse leitet sich von der Klasse WebService ab, die im Namensraum System.Web.Services definiert ist. Diese Vererbung ist nicht immer zwingend erforderlich. Insbesondere in diesem Beispiel könnte man darauf verzichten.
- Der Klasse selbst ist ein Attribut <WebService(Namespace="st.istia.univ-angers.fr")> vorangestellt, das dem Webdienst einen Namensraum zuweisen soll. Ein Klassenanbieter weist seinen Klassen einen Namensraum zu, um ihnen einen eindeutigen Namen zu geben und so Konflikte mit Klassen anderer Anbieter zu vermeiden, die denselben Namen tragen könnten. Bei Webdiensten verhält es sich genauso. Jeder Webdienst muss durch einen eindeutigen Namen identifiziert werden können, in diesem Fall durch st.istia.univ-angers.fr.
- Wir haben keinen Konstruktor definiert. Daher wird implizit der Konstruktor der übergeordneten Klasse verwendet.
Der vorstehende Quellcode ist nicht direkt für den Compiler VB.NET bestimmt, sondern für den Webserver IIS. Er muss das Suffix .asmx tragen und im Verzeichnisbaum des Webservers gespeichert werden. Hier speichern wir ihn unter dem Namen operations.asmx im Ordner <IISroot>\polyvbnet\operations:

Diesem physischen Ordner ordnen wir den virtuellen Ordner IIS [operations] zu:
![]() | ![]() |
Rufen wir den Dienst mit einem Browser auf. Die abzufragende Datei URl ist [http://localhost/operations/operations.asmx]:

Wir erhalten ein Webdokument mit einem Link für jede der im Webdienst operations definierten Methoden. Folgen wir dem Link ajouter:

Auf der angezeigten Seite wird uns angeboten, die Methode ajouter zu testen, indem wir ihr die beiden erforderlichen Argumente a und b übergeben. Erinnern wir uns an die Definition der Methode ajouter:
Es ist zu beachten, dass die Seite die Namen der Argumente a und b übernommen hat, die in der Methodendefinition verwendet wurden. Wir klicken auf die Schaltfläche Appeler und erhalten die folgende Antwort in einem separaten Browserfenster:

Wenn man oben stattdessen [Affichage/Source] eingibt, erhält man den folgenden Code:

Wiederholen wir den Vorgang für die Methode [toutfaire]:

Wir erhalten die folgende Seite:

Verwenden wir die Schaltfläche [Appeler] oben:

In jedem Fall hat die Antwort des Servers folgende Form:
- Die Antwort hat das Format XML
- Zeile 1 ist standardmäßig und immer in der Antwort enthalten
- Die folgenden Zeilen hängen von der Art des Ergebnisses (double, ArrayOfDouble), der Anzahl der Ergebnisse und dem Namensraum des Webdienstes (hier st.istia.univ-angers.fr) ab.
Es gibt mehrere Methoden, um einen Webdienst abzufragen und dessen Antwort zu erhalten. Kehren wir zum URL des Dienstes zurück:

und folgen wir dem Link [ajouter]. Auf der angezeigten Seite werden zwei Methoden zur Abfrage der Funktion [ajouter] des Webdienstes vorgestellt:



Diese beiden Methoden für den Zugriff auf die Funktionen eines Webdienstes heißen jeweils: HTTP-POST und SOAP. Wir werden sie nun nacheinander betrachten.
Hinweis: In den ersten Versionen von VS.NET gab es eine dritte Methode namens HTTP-GET. Zum Zeitpunkt der Erstellung dieses Dokuments (März 2004) scheint diese Methode nicht mehr verfügbar zu sein. Das bedeutet, dass der von VS.NET generierte Webdienst keine Anfragen vom Typ GET akzeptiert. Das bedeutet jedoch nicht, dass man keine Webdienste schreiben kann, die Anfragen nach dem Muster GET akzeptieren, insbesondere mit anderen Tools als VS.NET oder einfach von Hand.
10.5. Ein HTTP-POST-Client
Wir folgen der vom Webdienst vorgeschlagenen Vorgehensweise:

Lassen Sie uns den Text erläutern. Zunächst muss der Web-Client die folgenden HTTP-Header senden:
Der Web-Client sendet eine Anfrage an POST an URL /operations/operations.asmx/ajouter gemäß dem Protokoll HTTP Version 1.1 | |
Hier wird der Zielrechner der Anfrage angegeben. In diesem Fall „localhost“. Dieser Header wurde durch Version 1.1 des Protokolls HTTP verbindlich vorgeschrieben | |
Hier wird angegeben, dass nach den Headern HTTP zusätzliche Parameter im URL-kodierten Format gesendet werden. Bei diesem Format werden bestimmte Zeichen durch ihren Hexadezimalcode ersetzt. | |
Dies ist die Zeichenanzahl der Parameterzeichenfolge, die nach den Headern HTTP gesendet wird. |
Auf die Header HTTP folgt eine Leerzeile, dann die Parameterzeichenfolge von POST mit [Content-Length] Zeichen in der Form a=XX&b=YY, wobei XX und YY die „url-kodierten“ Zeichenfolgen der Parameterwerte a und b sind. Wir wissen nun genug, um das oben Beschriebene mit unserem generischen TCP-Client nachzubilden, der bereits im Kapitel über die TCP/IP-Programmierung verwendet wurde:
- Wir starten IIS
- Der Dienst ist unter der URL [http://localhost/operations/operations.asmx] verfügbar
- wir verwenden den generischen TCP-Client in einem Fenster unter DOS
dos>clttcpgenerique localhost 80
Commandes :
POST /operations/operations.asmx/ajouter HTTP/1.1
HOST: localhost
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-length: 7
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 13:55:17 GMT
<-- X-Powered-By: ASP.NET
<--
a=2&b=3
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 13:55:26 GMT
<-- X-Powered-By: ASP.NET
<-- Connection: close
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 90
<--
<-- <?xml version="1.0" encoding="utf-8"?>
<-- <double xmlns="st.istia.univ-angers.fr">5</double>
[fin du thread de lecture des réponses du serveur]
fin
[fin du thread d'envoi des commandes au serveur]
Zunächst ist anzumerken, dass wir den Header [Connection: close] hinzugefügt haben, um den Server anzuweisen, die Verbindung nach dem Senden der Antwort zu schließen. Dies ist hier erforderlich. Wenn dies nicht angegeben wird, hält der Server die Verbindung standardmäßig offen. Die Antwort des Servers besteht jedoch aus einer Folge von Textzeilen, von denen die letzte nicht mit einem Zeilenendezeichen endet. Unser generischer Client TCP liest Textzeilen, die mit einem Zeilenendezeichen enden, mit der Methode ReadLine. Wenn der Server die Verbindung nach dem Senden der letzten Zeile nicht schließt, bleibt der Client hängen, da er auf ein Zeilenendezeichen wartet, das nicht kommt. Wenn der Server die Verbindung schließt, wird die Methode ReadLine des Clients beendet und der Client bleibt nicht hängen.
Unmittelbar nach dem Empfang der leeren Zeile, die das Ende der Header signalisiert (HTTP), sendet der Server (IIS) eine erste Antwort:
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 13:55:17 GMT
<-- X-Powered-By: ASP.NET
<--
Diese Antwort, die ausschließlich aus den Kopfzeilen HTTP besteht, teilt dem Kunden mit, dass er die 7 Zeichen senden kann, die er senden wollte. Was wir tun:
Hier ist zu beachten, dass unser TCP-Client mehr als 7 Zeichen sendet, da er diese mit einem Zeilenendezeichen (WriteLine) versendet. Das stellt für den Server kein Problem dar, da er von den empfangenen Zeichen nur die ersten 7 übernimmt und die Verbindung anschließend geschlossen wird (Connection: close). Diese überzähligen Zeichen wären problematisch gewesen, wenn die Verbindung offen geblieben wäre, da sie dann als Teil des nächsten Befehls des Clients interpretiert worden wären. Sobald die Parameter empfangen wurden, sendet der Server seine Antwort:
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 13:55:26 GMT
<-- X-Powered-By: ASP.NET
<-- Connection: close
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 90
<--
<-- <?xml version="1.0" encoding="utf-8"?>
<-- <double xmlns="st.istia.univ-angers.fr">5</double>
Wir verfügen nun über die notwendigen Elemente, um einen Client für unseren Webdienst zu programmieren. Es handelt sich um einen Konsolen-Client mit dem Namen httpPost2, der wie folgt verwendet wird:
dos>httpPost2 http://localhost/operations/operations.asmx
Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser] a b
ajouter 6 7
--> POST /operations/operations.asmx/ajouter HTTP/1.1
--> Host: localhost:80
--> Content-Type: application/x-www-form-urlencoded
--> Content-Length: 7
--> Connection: Keep-Alive
-->
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:38 GMT
<-- X-Powered-By: ASP.NET
<--
--> a=6&b=7
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:38 GMT
<-- X-Powered-By: ASP.NET
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 91
<--
<-- <?xml version="1.0" encoding="utf-8"?>
<-- <double xmlns="st.istia.univ-angers.fr">13</double>
[résultat=13]
soustraire 8 9
--> POST /operations/operations.asmx/soustraire HTTP/1.1
--> Host: localhost:80
--> Content-Type: application/x-www-form-urlencoded
--> Content-Length: 7
--> Connection: Keep-Alive
-->
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:47 GMT
<-- X-Powered-By: ASP.NET
<--
--> a=8&b=9
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:47 GMT
<-- X-Powered-By: ASP.NET
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 91
<--
<-- <?xml version="1.0" encoding="utf-8"?>
<-- <double xmlns="st.istia.univ-angers.fr">-1</double>
[résultat=-1]
fin
dos>
Der Client wird aufgerufen, indem ihm der Parameter URL des Webdienstes übergeben wird:
Anschließend liest der Client die über die Tastatur eingegebenen Befehle ein und führt sie aus. Diese haben das folgende Format:
wobei „Funktion“ die aufgerufene Webdienstfunktion ist (addieren, subtrahieren, multiplizieren, dividieren) und „a“ und „b“ die Werte, auf die diese Funktion angewendet wird. Zum Beispiel:
Daraufhin sendet der Client die erforderliche Anfrage HTTP an den Webserver und erhält eine Antwort. Der Austausch zwischen Client und Server wird auf dem Bildschirm dargestellt, um den Ablauf besser verständlich zu machen:
ajouter 6 7
--> POST /operations/operations.asmx/ajouter HTTP/1.1
--> Host: localhost:80
--> Content-Type: application/x-www-form-urlencoded
--> Content-Length: 7
--> Connection: Keep-Alive
-->
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:38 GMT
<-- X-Powered-By: ASP.NET
<--
--> a=6&b=7
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:38 GMT
<-- X-Powered-By: ASP.NET
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 91
<--
<-- <?xml version="1.0" encoding="utf-8"?>
<-- <double xmlns="st.istia.univ-angers.fr">13</double>
[résultat=13]
Oben ist der bereits bekannte Datenaustausch mit dem generischen TCP-Client zu sehen, mit einem Unterschied: Der Header „HTTP Connection: Keep-Alive“ fordert den Server auf, die Verbindung nicht zu schließen. Diese bleibt somit für den nächsten Vorgang des Clients offen, der sich daher nicht erneut mit dem Server verbinden muss. Dies zwingt ihn jedoch dazu, eine andere Methode als ReadLine() zu verwenden, um die Antwort des Servers zu lesen, da bekannt ist, dass es sich dabei um eine Folge von Zeilen handelt, von denen die letzte nicht mit einem Zeilenendezeichen endet. Sobald die gesamte Antwort des Servers empfangen wurde, analysiert der Client diese, um das Ergebnis der angeforderten Operation zu finden und anzuzeigen:
Sehen wir uns den Code unseres Clients an:
' Namensräume
Imports System
Imports System.Net.Sockets
Imports System.IO
Imports System.Text.RegularExpressions
Imports System.Collections
Imports Microsoft.VisualBasic
Imports System.Web
' Webdienst-Client „operations“
Public Module clientPOST
Public Sub Main(ByVal args() As String)
' Syntax
Const syntaxe As String = "pg URI"
Dim fonctions As String() = {"ajouter", "soustraire", "multiplier", "diviser"}
' Anzahl der Argumente
If args.Length <> 1 Then
erreur(syntaxe, 1)
End If
' man beachte die angeforderte URI
Dim URIstring As String = args(0)
' Verbindung zum Server herstellen
Dim uri As Uri = Nothing ' l'URI du service web
Dim client As TcpClient = Nothing ' la liaison tcp du client avec le serveur
Dim [IN] As StreamReader = Nothing ' le flux de lecture du client
Dim OUT As StreamWriter = Nothing ' le flux d'écriture du client
Try
' Verbindung zum Server
uri = New Uri(URIstring)
client = New TcpClient(uri.Host, uri.Port)
' Die Ein- und Ausgabeströme des Clients TCP werden erstellt
[IN] = New StreamReader(client.GetStream())
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
Catch ex As Exception
' URI falsch oder anderes Problem
erreur("L'erreur suivante s'est produite : " + ex.Message, 2)
End Try
' Erstellung eines Funktionsverzeichnisses für den Webdienst
Dim dicoFonctions As New Hashtable
Dim i As Integer
For i = 0 To fonctions.Length - 1
dicoFonctions.Add(fonctions(i), True)
Next i
' Die Benutzeranfragen werden über die Tastatur eingegeben
' in der Form „Funktion a b“
' sie enden mit dem Befehl „end“
Dim commande As String = Nothing ' commande tapée au clavier
Dim champs As String() = Nothing ' champs d'une ligne de commande
Dim fonction As String = Nothing ' nom d'une fonction du service web
Dim a, b As String ' les arguments des fonctions du service web
' Eingabeaufforderung an den Benutzer
Console.Out.WriteLine("Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser] a b")
' Fehlerbehandlung
Dim erreurCommande As Boolean
Try
' Schleife zur Erfassung der über die Tastatur eingegebenen Befehle
While True
' zunächst kein Fehler
erreurCommande = False
' Befehl auslesen
commande = Console.In.ReadLine().Trim().ToLower()
' Fertig?
If commande Is Nothing Or commande = "fin" Then
Exit While
End If
' Aufteilung des Befehls in Felder
champs = Regex.Split(commande, "\s+")
Try
' Es sind drei Felder erforderlich
If champs.Length <> 3 Then
Throw New Exception
End If
' Feld 0 muss eine erkannte Funktion sein
fonction = champs(0)
If Not dicoFonctions.ContainsKey(fonction) Then
Throw New Exception
End If
' Feld 1 muss eine gültige Zahl sein
a = champs(1)
Double.Parse(a)
' Feld 2 muss eine gültige Zahl sein
b = champs(2)
Double.Parse(b)
Catch
' Ungültiger Befehl
Console.Out.WriteLine("syntaxe : [ajouter|soustraire|multiplier|diviser] a b")
erreurCommande = True
End Try
' Die Anfrage wird an den Webdienst gesendet
If Not erreurCommande Then executeFonction([IN], OUT, uri, fonction, a, b)
End While
Catch e As Exception
Console.Out.WriteLine(("L'erreur suivante s'est produite : " + e.Message))
End Try
' Ende der Client-Server-Verbindung
Try
[IN].Close()
OUT.Close()
client.Close()
Catch
End Try
End Sub
...........
' Anzeige der Fehler
Public Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' Fehleranzeige
System.Console.Error.WriteLine(msg)
' Beendigung mit Fehler
Environment.Exit(exitCode)
End Sub
End Module
Hier finden sich Elemente, die wir bereits mehrfach gesehen haben und die keiner besonderen Erläuterung bedürfen. Sehen wir uns nun den Code der Methode executeFonction an, in der die Neuerungen enthalten sind:
' executeFonction
Public Sub executeFonction(ByVal [IN] As StreamReader, ByVal OUT As StreamWriter, ByVal uri As Uri, ByVal fonction As String, ByVal a As String, ByVal b As String)
' Führung der Funktion (a, b) auf dem Webdienst mit der URI URI
' Der Client-Server-Austausch erfolgt über die Datenströme IN und OUT
' Das Ergebnis der Funktion steht in der Zeile
' <double xmlns="st.istia.univ-angers.fr">double</double>
' vom Server gesendet
' Erstellung der Abfragezeichenfolge
Dim requête As String = "a=" + HttpUtility.UrlEncode(a) + "&b=" + HttpUtility.UrlEncode(b)
Dim nbChars As Integer = requête.Length
' Erstellung des Header-Arrays HTTP zum Senden
Dim entetes(5) As String
entetes(0) = "POST " + uri.AbsolutePath + "/" + fonction + " HTTP/1.1"
entetes(1) = "Host: " & uri.Host & ":" & uri.Port
entetes(2) = "Content-Type: application/x-www-form-urlencoded"
entetes(3) = "Content-Length: " & nbChars
entetes(4) = "Connection: Keep-Alive"
entetes(5) = ""
' Die Header HTTP werden an den Server gesendet
Dim i As Integer
For i = 0 To entetes.Length - 1
' Senden an den Server
OUT.WriteLine(entetes(i))
' Bildschirmanzeige
Console.Out.WriteLine(("--> " + entetes(i)))
Next i
' Die erste Antwort des Webservers wird gelesen: HTTP/1.1 100
Dim ligne As String = Nothing
' eine Zeile aus dem Lesestrom
ligne = [IN].ReadLine()
While ligne <> ""
'Echo
Console.Out.WriteLine(("<-- " + ligne))
' nächste Zeile
ligne = [IN].ReadLine()
End While
'Echo der letzten Zeile
Console.Out.WriteLine(("<-- " + ligne))
' Senden der Abfrageparameter
OUT.Write(requête)
' Echo
Console.Out.WriteLine(("--> " + requête))
' Erstellung des regulären Ausdrucks zur Ermittlung der Antwortgröße XML
' im Datenstrom der Antwort des Webservers
Dim modèleLength As String = "^Content-Length: (.+?)\s*$"
Dim RegexLength As New Regex(modèleLength) '
Dim MatchLength As Match = Nothing
Dim longueur As Integer = 0
' Auslesen der zweiten Antwort des Webservers nach dem Senden der Anfrage
' Der Wert der Zeile „Content-Length“ wird gespeichert
ligne = [IN].ReadLine()
While ligne <> ""
' Bildschirmoutput
Console.Out.WriteLine(("<-- " + ligne))
' Content-Length?
MatchLength = RegexLength.Match(ligne)
If MatchLength.Success Then
longueur = Integer.Parse(MatchLength.Groups(1).Value)
End If
' nächste Zeile
ligne = [IN].ReadLine()
End While
' Ausgabe der letzten Zeile
Console.Out.WriteLine("<--")
' Erstellung des regulären Ausdrucks zum Abrufen des Ergebnisses
' im Antwortstrom des Webservers
Dim modèle As String = "<double xmlns=""st.istia.univ-angers.fr"">(.+?)</double>"
Dim ModèleRésultat As New Regex(modèle)
Dim MatchRésultat As Match = Nothing
' Der Rest der Antwort des Webservers wird gelesen
Dim chrRéponse(longueur) As Char
[IN].Read(chrRéponse, 0, longueur)
Dim strRéponse As String = New [String](chrRéponse)
' Die Antwort wird in Textzeilen zerlegt
Dim lignes As String() = Regex.Split(strRéponse, ControlChars.Lf)
' die Textzeilen werden nach dem Ergebnis durchsucht
Dim strRésultat As String = "?" ' résultat de la fonction
For i = 0 To lignes.Length - 1
' Weiter
Console.Out.WriteLine(("<-- " + lignes(i)))
' Vergleich der aktuellen Zeile mit dem Muster
MatchRésultat = ModèleRésultat.Match(lignes(i))
' Wurde etwas gefunden?
If MatchRésultat.Success Then
' Das Ergebnis wird notiert
strRésultat = MatchRésultat.Groups(1).Value
End If
Next i
' Das Ergebnis wird angezeigt
Console.Out.WriteLine(("[résultat=" + strRésultat + "]" + ControlChars.Lf))
End Sub
Zunächst sendet der Client HTTP-POST seine Anfrage im Format POST:
' Erstellung der Abfragezeichenfolge
Dim requête As String = "a=" + HttpUtility.UrlEncode(a) + "&b=" + HttpUtility.UrlEncode(b)
Dim nbChars As Integer = requête.Length
' Erstellung des Header-Arrays HTTP zum Senden
Dim entetes(5) As String
entetes(0) = "POST " + uri.AbsolutePath + "/" + fonction + " HTTP/1.1"
entetes(1) = "Host: " & uri.Host & ":" & uri.Port
entetes(2) = "Content-Type: application/x-www-form-urlencoded"
entetes(3) = "Content-Length: " & nbChars
entetes(4) = "Connection: Keep-Alive"
entetes(5) = ""
' Die Header HTTP werden an den Server gesendet
Dim i As Integer
For i = 0 To entetes.Length - 1
' Senden an den Server
OUT.WriteLine(entetes(i))
' Bildschirmanzeige
Console.Out.WriteLine(("--> " + entetes(i)))
Next i
Im Header
muss die Größe der Parameter angegeben werden, die vom Client hinter den Headern HTTP gesendet werden:
Dazu wird der folgende Code verwendet:
' Erstellung der Abfragezeichenfolge
Dim requête As String = "a=" + HttpUtility.UrlEncode(a) + "&b=" + HttpUtility.UrlEncode(b)
Dim nbChars As Integer = requête.Length
Die Methode HttpUtility.UrlEncode(Zeichenkette) wandelt bestimmte Zeichen von chaîne in %n1n2 um, wobei n1n2 der Code ASCII des umgewandelten Zeichens ist. Die von dieser Umwandlung betroffenen Zeichen sind alle Zeichen, die in einer Abfrage eine besondere Bedeutung haben (Leerzeichen, das Gleichheitszeichen =, das Und-Zeichen &, ...). Hier ist die Methode HttpUtility.UrlEncode normalerweise nicht erforderlich, da a und b Zahlen sind, die keines dieser speziellen Zeichen enthalten. Sie wird hier lediglich als Beispiel verwendet. Sie benötigt den Namensraum System.Web. Sobald der Client seine Header HTTP gesendet hat:
--> POST /operations/operations.asmx/ajouter HTTP/1.1
--> Host: localhost:80
--> Content-Type: application/x-www-form-urlencoded
--> Content-Length: 7
--> Connection: Keep-Alive
-->
Der Server antwortet mit dem Header HTTP 100 Continue:
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:47 GMT
<-- X-Powered-By: ASP.NET
<--
Der Code liest lediglich diese erste Antwort ein und zeigt sie auf dem Bildschirm an:
' Die erste Antwort des Webservers wird gelesen: HTTP/1.1 100
Dim ligne As String = Nothing
' Eine Zeile aus dem Lesestrom
ligne = [IN].ReadLine()
While ligne <> ""
'Echo
Console.Out.WriteLine(("<-- " + ligne))
' nächste Zeile
ligne = [IN].ReadLine()
End While
'Echo der letzten Zeile
Console.Out.WriteLine(("<-- " + ligne))
Nachdem diese erste Antwort gelesen wurde, muss der Client seine Parameter senden:
Dies geschieht mit dem folgenden Code:
Der Server sendet daraufhin seine Antwort. Diese besteht aus zwei Teilen:
- den Kopfzeilen HTTP, die mit einer Leerzeile enden
- die Antwort im Format XML
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:38 GMT
<-- X-Powered-By: ASP.NET
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 91
<--
<-- <?xml version="1.0" encoding="utf-8"?>
<-- <double xmlns="st.istia.univ-angers.fr">13</double>
Zunächst liest der Client die Header HTTP, um darin die Zeile Content-Length zu finden und die Größe der Antwort XML (hier 90) zu ermitteln. Diese wird mithilfe eines regulären Ausdrucks ermittelt. Man hätte es auch anders und zweifellos effizienter machen können.
' Erstellung des regulären Ausdrucks zur Ermittlung der Antwortgröße XML
' im Datenstrom der Antwort des Webservers
Dim modèleLength As String = "^Content-Length: (.+?)\s*$"
Dim RegexLength As New Regex(modèleLength) '
Dim MatchLength As Match = Nothing
Dim longueur As Integer = 0
' Auslesen der zweiten Antwort des Webservers nach dem Senden der Anfrage
' Der Wert der Zeile „Content-Length“ wird gespeichert
ligne = [IN].ReadLine()
While ligne <> ""
' Bildschirmoutput
Console.Out.WriteLine(("<-- " + ligne))
' Content-Length?
MatchLength = RegexLength.Match(ligne)
If MatchLength.Success Then
longueur = Integer.Parse(MatchLength.Groups(1).Value)
End If
' nächste Zeile
ligne = [IN].ReadLine()
End While
' Ausgabe der letzten Zeile
Console.Out.WriteLine("<--")
Sobald man die Länge N der Antwort XML kennt, muss man nur noch N Zeichen aus dem Datenstrom IN der Serverantwort auslesen. Diese Zeichenkette aus N Zeichen wird für die Bildschirmüberwachung wieder in Textzeilen zerlegt. Unter diesen Zeilen sucht man nach der Zeile mit dem Ergebnis:
wobei auch hier wieder ein regulärer Ausdruck verwendet wird. Sobald das Ergebnis gefunden wurde, wird es angezeigt.
Der Client-Code endet wie folgt:
' Erstellung des regulären Ausdrucks zum Abrufen des Ergebnisses
' im Antwortstrom des Webservers
Dim modèle As String = "<double xmlns=""st.istia.univ-angers.fr"">(.+?)</double>"
Dim ModèleRésultat As New Regex(modèle)
Dim MatchRésultat As Match = Nothing
' Der Rest der Antwort des Webservers wird gelesen
Dim chrRéponse(longueur) As Char
[IN].Read(chrRéponse, 0, longueur)
Dim strRéponse As String = New [String](chrRéponse)
' Die Antwort wird in Textzeilen zerlegt
Dim lignes As String() = Regex.Split(strRéponse, ControlChars.Lf)
' die Textzeilen werden nach dem Ergebnis durchsucht
Dim strRésultat As String = "?" ' résultat de la fonction
For i = 0 To lignes.Length - 1
' Weiter
Console.Out.WriteLine(("<-- " + lignes(i)))
' Vergleich der aktuellen Zeile mit dem Muster
MatchRésultat = ModèleRésultat.Match(lignes(i))
' Wurde etwas gefunden?
If MatchRésultat.Success Then
' Das Ergebnis wird notiert
strRésultat = MatchRésultat.Groups(1).Value
End If
Next i
' Das Ergebnis wird angezeigt
Console.Out.WriteLine(("[résultat=" + strRésultat + "]" + ControlChars.Lf))
End Sub
10.6. Ein Client SOAP
Wir betrachten hier einen zweiten Client, der einen Client-Server-Dialog vom Typ SOAP (Simple Object Access Protocol) verwendet. Ein Beispiel für einen Dialog wird uns für die Funktion ajouter vorgestellt:


Die Anfrage des Clients ist eine Anfrage vom Typ POST. Wir werden also einige der Mechanismen des vorherigen Clients wiederfinden. Der Hauptunterschied besteht darin, dass der Client HTTP-POST die Parameter a und b in der Form
, sendet der Client SOAP diese in einem komplexeren Format XML:
POST /operations/operations.asmx HTTP/1.1
Host: localhost
Content-Type: text/xml; charset=utf-8
Content-Length: length
SOAPAction: "st.istia.univ-angers.fr/ajouter"
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ajouter xmlns="st.istia.univ-angers.fr">
<a>double</a>
<b>double</b>
</ajouter>
</soap:Body>
</soap:Envelope>
Als Antwort erhält er eine Antwort mit dem Code XML, die ebenfalls komplexer ist als die zuvor gezeigten Antworten:
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: length
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ajouterResponse xmlns="st.istia.univ-angers.fr">
<ajouterResult>double</ajouterResult>
</ajouterResponse>
</soap:Body>
</soap:Envelope>
Auch wenn die Anfrage und die Antwort komplexer sind, handelt es sich doch um denselben Mechanismus HTTP wie beim Mandanten HTTP-POST. Der Code für den Client SOAP kann somit an den des Clients HTTP-POST angelehnt werden. Hier ein Ausführungsbeispiel:
dos>clientsoap1 http://localhost/operations/operations.asmx
Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser] a b
ajouter 3 4
--> POST /operations/operations.asmx HTTP/1.1
--> Host: localhost:80
--> Content-Type: text/xml; charset=utf-8
--> Content-Length: 321
--> Connection: Keep-Alive
--> SOAPAction: "st.istia.univ-angers.fr/ajouter"
-->
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Thu, 04 Mar 2004 07:28:29 GMT
<-- X-Powered-By: ASP.NET
<--
--> <?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ajouter xmlns="st.istia.univ-angers.fr">
<a>3</a>
<b>4</b>
</ajouter>
</soap:Body>
</soap:Envelope>
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Thu, 04 Mar 2004 07:28:33 GMT
<-- X-Powered-By: ASP.NET
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 345
<--
<-- <?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-inst
ance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><ajouterResponse xmlns="st.istia.univ-angers.fr"><ajouterResult>7</ajouterResult></ajouterResponse
></soap:Body></soap:Envelope>
[résultat=7]
Nur die Methode executeFonction ändert sich. Der Client SOAP sendet die Header HTTP seiner Anfrage. Diese sind lediglich etwas komplexer als die von HTTP-POST:
ajouter 3 4
--> POST /operations/operations.asmx HTTP/1.1
--> Host: localhost:80
--> Content-Type: text/xml; charset=utf-8
--> Content-Length: 321
--> Connection: Keep-Alive
--> SOAPAction: "st.istia.univ-angers.fr/ajouter"
-->
Der Code, der sie generiert:
' executeFonction
Public Sub executeFonction(ByVal [IN] As StreamReader, ByVal OUT As StreamWriter, ByVal uri As Uri, ByVal fonction As String, ByVal a As String, ByVal b As String)
' führt die Funktion (a, b) auf dem Webdienst mit der URI URI aus
' Der Datenaustausch zwischen Client und Server erfolgt über die Datenströme IN und OUT
' Das Ergebnis der Funktion steht in der Zeile
' <double xmlns="st.istia.univ-angers.fr">double</double>
' vom Server gesendet
' Erstellung der Abfragezeichenfolge SOAP
Dim requêteSOAP As String = "<?xml version=" + """1.0"" encoding=""utf-8""?>" + ControlChars.Lf
requêteSOAP += "<soap:Envelope xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"" xmlns:soap=""http://schemas.xmlsoap.org/soap/envelope/"">" + ControlChars.Lf
requêteSOAP += "<soap:Body>" + ControlChars.Lf
requêteSOAP += "<" + fonction + " xmlns=""st.istia.univ-angers.fr"">" + ControlChars.Lf
requêteSOAP += "<a>" + a + "</a>" + ControlChars.Lf
requêteSOAP += "<b>" + b + "</b>" + ControlChars.Lf
requêteSOAP += "</" + fonction + ">" + ControlChars.Lf
requêteSOAP += "</soap:Body>" + ControlChars.Lf
requêteSOAP += "</soap:Envelope>"
Dim nbCharsSOAP As Integer = requêteSOAP.Length
' Erstellung des Header-Arrays HTTP zum Senden
Dim entetes(6) As String
entetes(0) = "POST " + uri.AbsolutePath + " HTTP/1.1"
entetes(1) = "Host: " & uri.Host & ":" & uri.Port
entetes(2) = "Content-Type: text/xml; charset=utf-8"
entetes(3) = "Content-Length: " & nbCharsSOAP
entetes(4) = "Connection: Keep-Alive"
entetes(5) = "SOAPAction: ""st.istia.univ-angers.fr/" + fonction + """"
entetes(6) = ""
' Die Header HTTP werden an den Server gesendet
Dim i As Integer
For i = 0 To entetes.Length - 1
' Senden an den Server
OUT.WriteLine(entetes(i))
' Bildschirmanzeige
Console.Out.WriteLine(("--> " + entetes(i)))
Next i
Nach Erhalt dieser Anfrage sendet der Server seine erste Antwort, die der Client anzeigt:
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Thu, 04 Mar 2004 07:28:29 GMT
<-- X-Powered-By: ASP.NET
<--
Der Lesecode dieser ersten Antwort lautet wie folgt:
' Die erste Antwort des Webservers wird gelesen: HTTP/1.1 100
Dim ligne As String = Nothing
' eine Zeile aus dem Lesestrom
ligne = [IN].ReadLine()
While ligne <> ""
'Echo
Console.Out.WriteLine(("<-- " + ligne))
' nächste Zeile
ligne = [IN].ReadLine()
End While 'while
'Echo der letzten Zeile
Console.Out.WriteLine(("<-- " + ligne))
Der Client sendet nun seine Parameter im Format XML in einem sogenannten „Umschlag“ SOAP:
--> <?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ajouter xmlns="st.istia.univ-angers.fr">
<a>3</a>
<b>4</b>
</ajouter>
</soap:Body>
</soap:Envelope>
Der Code:
Der Server sendet daraufhin seine endgültige Antwort:
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Thu, 04 Mar 2004 07:28:33 GMT
<-- X-Powered-By: ASP.NET
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 345
<--
<-- <?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-inst
ance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><ajouterResponse xmlns="st.istia.univ-angers.fr"><ajouterResult>7</ajouterResult></ajouterResponse
></soap:Body></soap:Envelope>
Der Kunde zeigt die empfangenen Kopfzeilen HTTP auf dem Bildschirm an und sucht dabei nach der Zeile Content-Length:
' Erstellung des regulären Ausdrucks zur Ermittlung der Größe der Antwort XML
' im Antwortstrom des Webservers
Dim modèleLength As String = "^Content-Length: (.+?)\s*$"
Dim RegexLength As New Regex(modèleLength) '
Dim MatchLength As Match = Nothing
Dim longueur As Integer = 0
' Auslesen der zweiten Antwort des Webservers nach dem Absenden der Anfrage
' Der Wert der Zeile „Content-Length“ wird gespeichert
ligne = [IN].ReadLine()
While ligne <> ""
' Bildschirmalarm
Console.Out.WriteLine(("<-- " + ligne))
' Content-Length?
MatchLength = RegexLength.Match(ligne)
If MatchLength.Success Then
longueur = Integer.Parse(MatchLength.Groups(1).Value)
End If
' nächste Zeile
ligne = [IN].ReadLine()
End While 'while
' Ausgabe der letzten Zeile
Console.Out.WriteLine("<--")
Sobald die Größe N der Antwort XML bekannt ist, liest der Client N Zeichen aus dem Antwortstrom des Servers, zerlegt die abgerufene Zeichenfolge in Textzeilen, um diese auf dem Bildschirm anzuzeigen, und sucht darin nach dem Tag XML des Ergebnisses: <ajouterResult>7</ajouterResult> und dieses anzuzeigen:
' Erstellung des regulären Ausdrucks zum Abrufen des Ergebnisses
' im Antwortstrom des Webservers
Dim modèle As String = "<" + fonction + "Result>(.+?)</" + fonction + "Result>"
Dim ModèleRésultat As New Regex(modèle)
Dim MatchRésultat As Match = Nothing
' Der Rest der Antwort des Webservers wird gelesen
Dim chrRéponse(longueur) As Char
[IN].Read(chrRéponse, 0, longueur)
Dim strRéponse As String = New [String](chrRéponse)
' Die Antwort wird in Textzeilen zerlegt
Dim lignes As String() = Regex.Split(strRéponse, ControlChars.Lf)
' die Textzeilen werden nach dem Ergebnis durchsucht
Dim strRésultat As String = "?" ' résultat de la fonction
For i = 0 To lignes.Length - 1
' Weiter
Console.Out.WriteLine(("<-- " + lignes(i)))
' Vergleich der aktuellen Zeile mit dem Muster
MatchRésultat = ModèleRésultat.Match(lignes(i))
' Wurde etwas gefunden?
If MatchRésultat.Success Then
' Das Ergebnis wird notiert
strRésultat = MatchRésultat.Groups(1).Value
End If
'nächste Zeile
Next i
' das Ergebnis wird angezeigt
Console.Out.WriteLine(("[résultat=" + strRésultat + "]" + ControlChars.Lf))
End Sub
10.7. Kapselung des Client-Server-Datenaustauschs
Nehmen wir an, unser Webdienst operations wird von verschiedenen Anwendungen genutzt. Es wäre sinnvoll, diesen eine Klasse zur Verfügung zu stellen, die als Schnittstelle zwischen der Client-Anwendung und dem Webdienst fungiert und den Großteil des Netzwerkverkehrs verbirgt, der für die meisten Entwickler nicht trivial ist. Damit ergäbe sich folgendes Schema:
![]() |
Die Client-Anwendung würde sich an die Client-Server-Schnittstelle wenden, um ihre Anfragen an den Webdienst zu stellen. Diese würde den gesamten erforderlichen Netzwerkverkehr mit dem Server abwickeln und das erhaltene Ergebnis an die Client-Anwendung zurückgeben. Letztere müsste sich nicht mehr um den Austausch mit dem Server kümmern, was ihre Programmierung erheblich vereinfachen würde.
10.7.1. Die Kapselungsklasse
Nach den Ausführungen in den vorangegangenen Abschnitten sind wir nun mit dem Datenaustausch zwischen Client und Server bestens vertraut. Wir haben sogar drei Methoden kennengelernt. Wir entscheiden uns dafür, die Methode SOAP zu kapseln. Die Klasse sieht wie folgt aus:
' Namensräume
Imports System
Imports System.Net.Sockets
Imports System.IO
Imports System.Text.RegularExpressions
Imports System.Collections
Imports System.Web
Imports Microsoft.VisualBasic
' clientSOAP des Webdienstes „operations“
Public Class clientSOAP
' Instanzvariablen
Private uri As uri = Nothing ' l'URI du service web
Private client As TcpClient = Nothing ' la liaison tcp du client avec le serveur
Private [IN] As StreamReader = Nothing ' le flux de lecture du client
Private OUT As StreamWriter = Nothing ' le flux d'écriture du client
' Funktionswörterbuch
Private dicoFonctions As New Hashtable
' Funktionsliste
Private fonctions As String() = {"ajouter", "soustraire", "multiplier", "diviser"}
' Ausführlichkeit
Private verbose As Boolean = False ' à vrai, affiche à l'écran les échanges client-serveur
' Konstruktor
Public Sub New(ByVal uriString As String, ByVal verbose As Boolean)
' Ausführliche Ausgabe
Me.verbose = verbose
' Verbindung zum Server
uri = New Uri(uriString)
client = New TcpClient(uri.Host, uri.Port)
' Eingabe- und Ausgabeströme des Clients erstellen TCP
[IN] = New StreamReader(client.GetStream())
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
' Erstellung des Funktionsverzeichnisses des Webdienstes
Dim i As Integer
For i = 0 To fonctions.Length - 1
dicoFonctions.Add(fonctions(i), True)
Next i
End Sub
' Schließen der Verbindung zum Server
Public Sub Close()
' Beendigung der Client-Server-Verbindung
[IN].Close()
OUT.Close()
client.Close()
End Sub
' executeFonction
Public Function executeFonction(ByVal fonction As String, ByVal a As String, ByVal b As String) As String
' Führung der Funktion (a, b) auf dem Webdienst mit der URI URI
' Der Client-Server-Austausch erfolgt über die Datenströme IN und OUT
' Das Ergebnis der Funktion steht in der Zeile
' <double xmlns="st.istia.univ-angers.fr">double</double>
' vom Server gesendet
' Ist die Funktion gültig?
fonction = fonction.Trim().ToLower()
If Not dicoFonctions.ContainsKey(fonction) Then
Return "[fonction [" + fonction + "] indisponible : (ajouter, soustraire,multiplier,diviser)]"
End If
' Sind die Argumente a und b gültig?
Dim doubleA As Double = 0
Try
doubleA = Double.Parse(a)
Catch
Return "[argument [" + a + "] incorrect (double)]"
End Try
Dim doubleB As Double = 0
Try
doubleB = Double.Parse(b)
Catch
Return "[argument [" + b + "] incorrect (double)]"
End Try
' Division durch Null?
If fonction = "diviser" And doubleB = 0 Then
Return "[division par zéro]"
End If
' Erstellung der Abfragezeichenfolge SOAP
Dim requêteSOAP As String = "<?xml version=" + """1.0"" encoding=""utf-8""?>" + ControlChars.Lf
requêteSOAP += "<soap:Envelope xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"" xmlns:soap=""http://schemas.xmlsoap.org/soap/envelope/"">" + ControlChars.Lf
requêteSOAP += "<soap:Body>" + ControlChars.Lf
requêteSOAP += "<" + fonction + " xmlns=""st.istia.univ-angers.fr"">" + ControlChars.Lf
requêteSOAP += "<a>" + a + "</a>" + ControlChars.Lf
requêteSOAP += "<b>" + b + "</b>" + ControlChars.Lf
requêteSOAP += "</" + fonction + ">" + ControlChars.Lf
requêteSOAP += "</soap:Body>" + ControlChars.Lf
requêteSOAP += "</soap:Envelope>"
Dim nbCharsSOAP As Integer = requêteSOAP.Length
' Erstellung des zu sendenden Header-Arrays HTTP
Dim entetes(6) As String
entetes(0) = "POST " + uri.AbsolutePath + " HTTP/1.1"
entetes(1) = "Host: " + uri.Host + ":" + uri.Port.ToString
entetes(2) = "Content-Type: text/xml; charset=utf-8"
entetes(3) = "Content-Length: " + nbCharsSOAP.ToString
entetes(4) = "Connection: Keep-Alive"
entetes(5) = "SOAPAction: ""st.istia.univ-angers.fr/" + fonction + """"
entetes(6) = ""
' Die Header HTTP werden an den Server gesendet
Dim i As Integer
For i = 0 To entetes.Length - 1
' Senden an den Server
OUT.WriteLine(entetes(i))
' Bildschirmausgabe
If verbose Then
Console.Out.WriteLine(("--> " + entetes(i)))
End If
Next i
' Die erste Antwort des Webservers wird gelesen: HTTP/1.1 100
Dim ligne As String = Nothing
' eine Zeile aus dem Lesestrom
ligne = [IN].ReadLine()
While ligne <> ""
'Echo
If verbose Then
Console.Out.WriteLine(("<-- " + ligne))
End If
' nächste Zeile
ligne = [IN].ReadLine()
End While
'Echo der letzten Zeile
If verbose Then
Console.Out.WriteLine(("<-- " + ligne))
End If
' Senden der Abfrageparameter
OUT.Write(requêteSOAP)
' Echo
If verbose Then
Console.Out.WriteLine(("--> " + requêteSOAP))
End If
' Erstellung des regulären Ausdrucks zur Ermittlung der Antwortgröße XML
' im Datenstrom der Antwort des Webservers
Dim modèleLength As String = "^Content-Length: (.+?)\s*$"
Dim RegexLength As New Regex(modèleLength) '
Dim MatchLength As Match = Nothing
Dim longueur As Integer = 0
' Auslesen der zweiten Antwort des Webservers nach dem Senden der Anfrage
' Der Wert der Zeile „Content-Length“ wird gespeichert
ligne = [IN].ReadLine()
While ligne <> ""
' Bildschirmalarm
If verbose Then
Console.Out.WriteLine(("<-- " + ligne))
End If
' Content-Length?
MatchLength = RegexLength.Match(ligne)
If MatchLength.Success Then
longueur = Integer.Parse(MatchLength.Groups(1).Value)
End If
' nächste Zeile
ligne = [IN].ReadLine()
End While
' Ausgabe der letzten Zeile
If verbose Then
Console.Out.WriteLine("<--")
End If
' Erstellung des regulären Ausdrucks zum Abrufen des Ergebnisses
' im Antwortstrom des Webservers
Dim modèle As String = "<" + fonction + "Result>(.+?)</" + fonction + "Result>"
Dim ModèleRésultat As New Regex(modèle)
Dim MatchRésultat As Match = Nothing
' Der Rest der Antwort des Webservers wird gelesen
Dim chrRéponse(longueur) As Char
[IN].Read(chrRéponse, 0, longueur)
Dim strRéponse As String = New [String](chrRéponse)
' Die Antwort wird in Textzeilen zerlegt
Dim lignes As String() = Regex.Split(strRéponse, ControlChars.Lf)
' die Textzeilen werden nach dem Ergebnis durchsucht
Dim strRésultat As String = "?" ' résultat de la fonction
For i = 0 To lignes.Length - 1
' Weiter
If verbose Then
Console.Out.WriteLine(("<-- " + lignes(i)))
End If ' comparaison ligne courante au modèle
MatchRésultat = ModèleRésultat.Match(lignes(i))
' Wurde es gefunden?
If MatchRésultat.Success Then
' Das Ergebnis wird notiert
strRésultat = MatchRésultat.Groups(1).Value
End If
Next i
' Das Ergebnis wird zurückgegeben
Return strRésultat
End Function
End Class
Wir finden hier nichts Neues im Vergleich zu dem, was wir bereits gesehen haben. Wir haben lediglich den Code des untersuchten Clients SOAP übernommen und ihn ein wenig umgestaltet, um daraus eine Klasse zu erstellen. Diese verfügt über einen Konstruktor und zwei Methoden:
' Hersteller
Public Sub New(ByVal uriString As String, ByVal verbose As Boolean)
' executeFonction
Public Function executeFonction(ByVal fonction As String, ByVal a As String, ByVal b As String) As String
' Schließen der Verbindung zum Server
Public Sub Close()
und hat folgende Attribute:
' Instanzvariablen
Private uri As Uri = Nothing ' l'URI du service web
Private client As TcpClient = Nothing ' la liaison tcp du client avec le serveur
Private [IN] As StreamReader = Nothing ' le flux de lecture du client
Private OUT As StreamWriter = Nothing ' le flux d'écriture du client
' Funktionswörterbuch
Private dicoFonctions As New Hashtable
' Funktionsliste
Private fonctions As String() = {"ajouter", "soustraire", "multiplier", "diviser"}
' Ausführliche Ausgabe
Private verbose As Boolean = False ' à vrai, affiche à l'écran les échanges client-serveur
Dem Konstruktor werden zwei Parameter übergeben:
- die URI des Webdienstes, mit dem er sich verbinden soll
- einen booleschen Wert verbose, der eigentlich festlegt, dass der Netzwerkverkehr auf dem Bildschirm angezeigt werden soll; andernfalls erfolgt keine Anzeige.
Während der Erstellung werden die Datenströme IN für den Netzwerk-Lesezugriff, OUT für den Netzwerk-Schreibzugriff sowie das Verzeichnis der vom Dienst verwalteten Funktionen erstellt. Sobald das Objekt erstellt ist, wird die Client-Server-Verbindung geöffnet und die Datenströme IN und OUT stehen zur Verfügung.
Mit der Methode Close lässt sich die Verbindung zum Server schließen.
Die Methode ExecuteFonction ist diejenige, die wir für den untersuchten Client SOAP geschrieben haben, bis auf einige Details:
- Die Parameter `uri`, `IN` und `OUT`, die zuvor als Parameter an die Methode übergeben wurden, müssen nun nicht mehr übergeben werden, da sie nun Instanzattribute sind, auf die alle Methoden der Instanz zugreifen können
- Die Methode ExecuteFonction, die zuvor einen Typ void zurückgab und das Ergebnis der Funktion auf dem Bildschirm anzeigte, gibt nun dieses Ergebnis und somit einen Typ string zurück.
In der Regel wird ein Client die Klasse clientSOAP wie folgt verwenden:
- Erstellung eines Objekts vom Typ clientSOAP, das die Verbindung zum Webservice herstellt
- wiederholte Verwendung der Methode executeFonction
- Schließen der Verbindung zum Webdienst mithilfe der Methode Close.
Betrachten wir einen ersten Client.
10.7.2. Ein Konsolen-Client
Wir greifen hier auf den Client SOAP zurück, den wir bereits untersucht haben, als die Klasse clientSOAP noch nicht existierte, und passen ihn so an, dass er nun diese Klasse verwendet:
' Namensräume
Imports System
Imports System.IO
Imports System.Text.RegularExpressions
Imports Microsoft.VisualBasic
Public Module testClientSoap
' fordert den URI vom Webdienst „operations“ an
' führt die über die Tastatur eingegebenen Befehle interaktiv aus
Public Sub Main(ByVal args() As String)
' Syntax
Const syntaxe As String = "pg URI [verbose]"
' Anzahl der Argumente
If args.Length <> 1 And args.Length <> 2 Then
erreur(syntaxe, 1)
End If
' Ausführliche Ausgabe?
Dim verbose As Boolean = False
If args.Length = 2 Then
verbose = args(1).ToLower() = "verbose"
End If
' Verbindung zum Webdienst herstellen
Dim client As clientSOAP = Nothing
Try
client = New clientSOAP(args(0), verbose)
Catch ex As Exception
' Verbindungsfehler
erreur("L'erreur suivante s'est produite lors de la connexion au service web : " + ex.Message, 2)
End Try
' Die Benutzeranfragen werden über die Tastatur eingegeben
' in der Form „Funktion a b“ – sie enden mit dem Befehl „end“
Dim commande As String = Nothing ' commande tapée au clavier
Dim champs As String() = Nothing ' champs d'une ligne de commande
' Eingabeaufforderung an den Benutzer
Console.Out.WriteLine("Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser] a b" + ControlChars.Lf)
' Fehlerbehandlung
Dim erreurCommande As Boolean
Try
' Schleife zur Erfassung der über die Tastatur eingegebenen Befehle
While True
' zunächst kein Fehler
erreurCommande = False
' Befehl auslesen
commande = Console.In.ReadLine().Trim().ToLower()
' Fertig?
If commande Is Nothing Or commande = "fin" Then
Exit While
End If
' Aufteilung des Befehls in Felder
champs = Regex.Split(commande, "\s+")
' Es sind drei Felder erforderlich
If champs.Length <> 3 Then
Console.Out.WriteLine("syntaxe : [ajouter|soustraire|multiplier|diviser] a b")
' Fehler wird vermerkt
erreurCommande = True
End If
' Anfrage an den Webdienst stellen
If Not erreurCommande Then Console.Out.WriteLine(("résultat=" + client.executeFonction(champs(0).Trim().ToLower(), champs(1).Trim(), champs(2).Trim())))
' Nächste Anfrage
End While
Catch e As Exception
Console.Out.WriteLine(("L'erreur suivante s'est produite : " + e.Message))
End Try
' Ende der Client-Server-Verbindung
Try
client.Close()
Catch
End Try
End Sub
' Anzeige der Fehler
Public Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' Fehleranzeige
System.Console.Error.WriteLine(msg)
' Beendigung mit Fehler
Environment.Exit(exitCode)
End Sub
End Module
Der Client ist nun wesentlich einfacher und enthält keinerlei Netzwerkkommunikation. Der Client akzeptiert zwei Parameter:
- die URI des Webdienstes operations
- das optionale Schlüsselwort verbose. Ist dieses vorhanden, wird der Netzwerkverkehr auf dem Bildschirm angezeigt.
Diese beiden Parameter werden verwendet, um ein Objekt clientSOAP zu erstellen, das den Datenaustausch mit dem Webdienst gewährleistet.
' Verbindung zum Webdienst wird hergestellt
Dim client As clientSOAP = Nothing
Try
client = New clientSOAP(args(0), verbose)
Catch ex As Exception
' Verbindungsfehler
erreur("L'erreur suivante s'est produite lors de la connexion au service web : " + ex.Message, 2)
End Try
Sobald die Verbindung zum Webdienst hergestellt ist, kann der Client seine Anfragen senden. Diese werden über die Tastatur eingegeben, analysiert und anschließend durch Aufruf der Methode executeFonction des Objekts clientSOAP an den Server gesendet.
' Anfrage an den Webdienst
If Not erreurCommande Then Console.Out.WriteLine(("résultat=" + client.executeFonction(champs(0).Trim().ToLower(), champs(1).Trim(), champs(2).Trim())))
Die Klasse clientSOAP wird in eine „Assembly“ kompiliert:
dos>vbc /r:clientSOAP.dll testClientSOAP.vb
dos>dir
04/03/2004 08:46 6 913 clientSOAP.vb
04/03/2004 09:07 7 168 clientSOAP.dll
Die Client-Anwendung testClientSoap wird anschließend kompiliert durch:
dos>vbc /r:clientSOAP.dll /r:system.dll testClientSOAP.vb
dos>dir
04/03/2004 09:08 2 711 testClientSOAP.vb
04/03/2004 09:08 4 608 testClientSOAP.exe
Hier ist ein Beispiel für eine nicht-verbose Ausführung:
dos>testclientsoap http://localhost/st/operations/operations.asmx
Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser] a b
ajouter 1 3
résultat=4
soustraire 6 7
résultat=-1
multiplier 4 5
résultat=20
diviser 1 2
résultat=0.5
x
syntaxe : [ajouter|soustraire|multiplier|diviser] a b
x 1 2
résultat=[fonction [x] indisponible : (ajouter, soustraire,multiplier,diviser)]
ajouter a b
résultat=[argument [a] incorrect (double)]
ajouter 1 b
résultat=[argument [b] incorrect (double)]
diviser 1 0
résultat=[division par zéro]
fin
Man kann den Netzwerkverkehr verfolgen, indem man eine „ausführliche“ Ausführung anfordert:
dos>testClientSOAP http://localhost/operations/operations.asmx ausführlich
Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser] a b
ajouter 4 8
--> POST /operations/operations.asmx HTTP/1.1
--> Host: localhost:80
--> Content-Type: text/xml; charset=utf-8
--> Content-Length: 321
--> Connection: Keep-Alive
--> SOAPAction: "st.istia.univ-angers.fr/ajouter"
-->
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Thu, 04 Mar 2004 08:15:25 GMT
<-- X-Powered-By: ASP.NET
<--
--> <?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ajouter xmlns="st.istia.univ-angers.fr">
<a>4</a>
<b>8</b>
</ajouter>
</soap:Body>
</soap:Envelope>
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Thu, 04 Mar 2004 08:15:25 GMT
<-- X-Powered-By: ASP.NET
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 346
<--
<-- <?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-inst
ance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><ajouterResponse xmlns="st.istia.univ-angers.fr"><ajouterResult>12</ajouterResult></ajouterResponse></soap:Body></soap:Envelope>
résultat=12
fin
Lassen Sie uns nun einen grafischen Client erstellen.
10.7.3. Ein grafischer Windows-Client
Wir werden nun unseren Webdienst mit einem grafischen Client abfragen, der ebenfalls die Klasse clientSOAP verwendet. Die grafische Benutzeroberfläche sieht wie folgt aus:
![]() |
Die Steuerelemente sind folgende:
Nr. | Typ | Name | Rolle |
TextBox | txtURI | URI des Webdienstes „Operations“ | |
Schaltfläche | btnOuvrir | stellt die Verbindung zum Webdienst her | |
Schaltfläche | btnFermer | schließt die Verbindung zum Webdienst | |
ComboBox | cmbFonctions | die Liste der Funktionen (addieren, subtrahieren, multiplizieren, dividieren) | |
TextBox | txtA | Die Argumente der Funktionen | |
TextBox | txtB | das Argument b der Funktionen | |
TextBox | txtRésultat | das Ergebnis der Funktion(a,b) | |
Schaltfläche | btnCalculer | startet die Berechnung von Funktion(a,b) | |
TextBox | txtErreur | Zeigt eine Statusmeldung zur Verbindung an |
Es gibt einige Einschränkungen bei der Funktionsweise:
- Die Schaltfläche btnOuvrir ist nur aktiv, wenn das Feld txtURI nicht leer ist und noch keine Verbindung besteht
- Die Schaltfläche btnFermer ist nur aktiv, wenn eine Verbindung zum Webdienst hergestellt wurde
- Die Schaltfläche btnCalculer ist nur aktiv, wenn eine Verbindung geöffnet ist und die Felder txtA und txtB nicht leer sind
- die Felder txtRésultat und txtErreur haben das Attribut ReadOnly auf „wahr“ gesetzt
Der Client stellt zunächst mithilfe der Schaltfläche [Ouvrir] eine Verbindung zum Webdienst her:

Anschließend kann der Benutzer eine Funktion sowie Werte für a und b auswählen:





Der Anwendungscode folgt. Wir haben den Code des Formulars weggelassen, da er hier nicht von Interesse ist.
'Namensräume
Imports System
Imports System.Windows.Forms
' Formularklasse
Public Class FormClientSOAP
Inherits System.Windows.Forms.Form
' Instanzattribute
Dim client As clientSOAP ' client SOAP du service web operations
#Region „Vom Windows-Form-Designer generierter Code“
Public Sub New()
MyBase.New()
'Dieser Aufruf wird vom Windows-Form-Designer benötigt.
InitializeComponent()
' Weitere Initialisierungen
myInit()
End Sub
'Die überschriebene Dispose-Methode des Formulars dient zum Bereinigen der Komponentenliste.
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
....
End Sub
...
Private Sub InitializeComponent()
....
End Sub
#Ende Region
Private Sub myInit()
' Formular initialisieren
cmbFonctions.SelectedIndex = 0
btnOuvrir.Enabled = False
btnFermer.Enabled = True
btnCalculer.Enabled = False
End Sub
Private Sub txtURI_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles txtURI.TextChanged
' Der Inhalt des Eingabefelds hat sich geändert – der Status der Schaltfläche „Öffnen“ wird festgelegt
btnOuvrir.Enabled = txtURI.Text.Trim <> ""
End Sub
Private Sub btnOuvrir_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnOuvrir.Click
' Anfrage zum Aufbau einer Verbindung mit dem Webdienst
Try
' Erstellung eines Objekts vom Typ [clientSOAP]
client = New clientSOAP(txtURI.Text.Trim, False)
' Zustände der Schaltflächen
btnOuvrir.Enabled = False
btnFermer.Enabled = True
' Das Objekt URI kann nicht mehr geändert werden
txtURI.ReadOnly = True
' Kundenzustand
txtErreur.Text = "Liaison au service web ouverte"
Catch ex As Exception
' Es ist ein Fehler aufgetreten – dieser wird angezeigt
txtErreur.Text = ex.Message
End Try
End Sub
Private Sub btnFermer_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnFermer.Click
' Die Verbindung zum Webdienst wird geschlossen
client.Close()
' Schaltflächenstatus
btnOuvrir.Enabled = True
btnFermer.Enabled = False
' URI
txtURI.ReadOnly = False
' Kundenzustand
txtErreur.Text = "Liaison au service web fermée"
End Sub
Private Sub btnCalculer_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnCalculer.Click
' Berechnung einer Funktion f(a,b)
' Das vorherige Ergebnis wird gelöscht
txtRésultat.Text = ""
Try
txtRésultat.Text = client.executeFonction(cmbFonctions.Text, txtA.Text.Trim, txtB.Text.Trim)
Catch ex As Exception
' Es ist ein Netzwerkfehler aufgetreten
txtErreur.Text = ex.Message
' Verbindung wird geschlossen
btnFermer_Click(Nothing, Nothing)
End Try
End Sub
Private Sub txtA_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles txtA.TextChanged
' Änderung des Wertes von A
btnCalculer.Enabled = txtA.Text.Trim <> "" And txtB.Text.Trim <> ""
End Sub
Private Sub txtB_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles txtB.TextChanged
' Änderung des Wertes von B
txtA_TextChanged(Nothing, Nothing)
End Sub
' Hauptmethode
Public Shared Sub main()
Application.Run(New FormClientSOAP)
End Sub
End Class
Auch hier verbirgt die Klasse clientSOAP den gesamten Netzwerkaspekt der Anwendung. Die Anwendung wurde wie folgt aufgebaut:
- Die Assembly clientSOAP.dll, die die Klasse clientSOAP enthält, wurde im Projektordner abgelegt
- Die grafische Benutzeroberfläche clientsoapgui.vb wurde mit VS.NET erstellt und anschließend in einem DOS-Fenster kompiliert:
dos>vbc /r:system.dll /r:system.windows.forms.dll /r:system.drawing.dll /r:clientSOAP.dll clientsoapgui.vb
dos>dir
04/03/2004 09:13 7 168 clientSOAP.dll
04/03/2004 16:44 9 866 clientsoapgui.vb
04/03/2004 16:44 11 264 clientsoapgui.exe
Die grafische Benutzeroberfläche wurde anschließend mit folgendem Befehl gestartet:
10.8. Ein Proxy-Client
Fassen wir zusammen, was wir gerade getan haben. Wir haben eine Zwischenklasse erstellt, die den Netzwerkverkehr zwischen einem Client und einem Webdienst gemäß dem folgenden Schema kapseln:
![]() |
Die Plattform .NET treibt diese Logik noch weiter voran. Sobald der anzusteuernde Webdienst bekannt ist, können wir automatisch die Klasse generieren, die uns als Vermittler dient, um auf die Funktionen des Webdienstes zuzugreifen, und die den gesamten Netzwerkteil verbirgt. Diese Klasse wird als Proxy für den Webdienst bezeichnet, für den sie generiert wurde.
Wie generiert man die Proxy-Klasse eines Webdienstes? Ein Webdienst wird immer von einer Beschreibungsdatei im Format XML begleitet. Wenn die URI-Datei unseres Webdienstes „operations“ http://localhost/operations/operations.asmx lautet, ist seine Beschreibungsdatei unter der Adresse http://localhost/operations/operations.asmx?wsdl verfügbar, wie der folgende Screenshot zeigt:

Hier handelt es sich um eine Datei XML, die alle Funktionen des Webdienstes genau beschreibt, einschließlich des Typs und der Anzahl der Parameter sowie des Ergebnistyps für jede einzelne Funktion. Diese Datei wird als WSDL-Datei des Dienstes bezeichnet, da sie die Sprache WSDL (Web Services Description Language) verwendet. Aus dieser Datei lässt sich mithilfe des Tools wsdl eine Proxy-Klasse generieren:
dos>wsdl http://localhost/operations/operations.asmx?wsdl /language=vb
Microsoft (R) Web Services Description Language Utility
[Microsoft (R) .NET Framework, Version 1.1.4322.573]
Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.
Écriture du fichier 'D:\data\devel\vbnet\poly\chap9\clientproxy\operations.vb'.
dos>dir
04/03/2004 17:17 6 663 operations.vb
Das Tool wsdl generiert eine Quelldatei VB.NET (Option /language=vb) mit dem Namen der Klasse, die den Webdienst implementiert, in diesem Fall operations. Sehen wir uns einen Ausschnitt aus dem generierten Code an:
'------------------------------------------------------------------------------
' <automatisch generiert>
' Dieser Code wurde von einem Tool generiert.
' Laufzeitversion: 1.1.4322.573
'
' Änderungen an dieser Datei können zu Fehlfunktionen führen und gehen verloren, wenn
' der Code neu generiert wird.
' </autogenerated>
'------------------------------------------------------------------------------
Option Strict Off
Option Explicit On
Imports System
Imports System.ComponentModel
Imports System.Diagnostics
Imports System.Web.Services
Imports System.Web.Services.Protocols
Imports System.Xml.Serialization
'
'Dieser Quellcode wurde automatisch von wsdl generiert, Version=1.1.4322.573.
'
'<remarks/>
<System.Diagnostics.DebuggerStepThroughAttribute(), _
System.ComponentModel.DesignerCategoryAttribute("code"), _
System.Web.Services.WebServiceBindingAttribute(Name:="operationsSoap", [Namespace]:="st.istia.univ-angers.fr")> _
Public Class operations
Inherits System.Web.Services.Protocols.SoapHttpClientProtocol
'<remarks/>
Public Sub New()
MyBase.New
Me.Url = "http://localhost/operations/operations.asmx"
End Sub
'<remarks/>
<System.Web.Services.Protocols.SoapDocumentMethodAttribute("st.istia.univ-angers.fr/ajouter", RequestNamespace:="st.istia.univ-angers.fr", ResponseNamespace:="st.istia.univ-angers.fr", Use:=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle:=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)> _
Public Function ajouter(ByVal a As Double, ByVal b As Double) As Double
Dim results() As Object = Me.Invoke("ajouter", New Object() {a, b})
Return CType(results(0),Double)
End Function
'<Anmerkungen/>
Public Function Beginajouter(ByVal a As Double, ByVal b As Double, ByVal callback As System.AsyncCallback, ByVal asyncState As Object) As System.IAsyncResult
Return Me.BeginInvoke("ajouter", New Object() {a, b}, callback, asyncState)
End Function
'<Anmerkungen/>
Public Function Endajouter(ByVal asyncResult As System.IAsyncResult) As Double
Dim results() As Object = Me.EndInvoke(asyncResult)
Return CType(results(0),Double)
End Function
....
Dieser Code wirkt auf den ersten Blick etwas komplex. Wir müssen die Details jedoch nicht verstehen, um ihn nutzen zu können. Schauen wir uns zunächst die Klassendeklaration an:
Die Klasse trägt den Namen operations des Webdienstes, für den sie erstellt wurde. Sie leitet sich von der Klasse SoapHttpClientProtocol ab:

Unsere Proxy-Klasse verfügt über einen einzigen Konstruktor:
Der Konstruktor weist dem Attribut url den Wert URL des mit dem Proxy verbundenen Webdienstes zu. Die oben genannte Klasse operations definiert das Attribut url nicht selbst. Dieses wird von der Klasse geerbt, von der der Proxy abgeleitet ist: System.Web.Services.Protocols.SoapHttpClientProtocol. Sehen wir uns nun an, was sich auf die Methode ajouter bezieht:
Public Function ajouter(ByVal a As Double, ByVal b As Double) As Double
Dim results() As Object = Me.Invoke("ajouter", New Object() {a, b})
Return CType(results(0),Double)
End Function
Man sieht, dass sie dieselbe Signatur aufweist wie im Webdienst operations, wo sie wie folgt definiert war:
<WebMethod> _
Function ajouter(a As Double, b As Double) As Double
Return a + b
End Function 'ajouter
Die Art und Weise, wie diese Klasse mit dem Webdienst kommuniziert, wird hier nicht dargestellt. Diese Kommunikation wird vollständig von der übergeordneten Klasse System.Web.Services.Protocols.SoapHttpClientProtocol übernommen. Im Proxy findet sich nur das, was ihn von anderen Proxys unterscheidet:
- die URL des zugehörigen Webdienstes
- die Definition der Methoden des zugehörigen Dienstes.
Um die Methoden des Web-Service operations zu nutzen, benötigt ein Client lediglich die zuvor generierte Proxy-Klasse operations. Kompilieren wir diese Klasse in eine Datei namens assembly:
Schreiben wir nun einen Konsolen-Client. Er wird ohne Parameter aufgerufen und führt die über die Tastatur eingegebenen Anfragen aus:
dos>testclientproxy
Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser|toutfaire] a b
ajouter 4 5
résultat=9
soustraire 9 8
résultat=1
multiplier 10 4
résultat=40
diviser 6 7
résultat=0,857142857142857
toutfaire 10 20
résultats=[30,-10,200,0,5]
diviser 5 0
résultat=+Infini
fin
Der Kundencode lautet wie folgt:
' Namensräume
Imports System
Imports System.IO
Imports System.Text.RegularExpressions
Imports System.Collections
Imports Microsoft.VisualBasic
Public Module testClientProxy
' führt die über die Tastatur eingegebenen Befehle interaktiv aus
' und sendet sie an den Webdienst „operations“
Public Sub Main()
' Es gibt keine Argumente mehr – die URL des Webdienstes ist fest im Proxy hinterlegt
' Erstellung eines Verzeichnisses der Funktionen des Webdienstes
Dim fonctions As String() = {"ajouter", "soustraire", "multiplier", "diviser", "toutfaire"}
Dim dicoFonctions As New Hashtable
Dim i As Integer
For i = 0 To fonctions.Length - 1
dicoFonctions.Add(fonctions(i), True)
Next i
' Es wird ein Operations-Proxy-Objekt erstellt
Dim myOperations As operations = Nothing
Try
myOperations = New operations
Catch ex As Exception
' Verbindungsfehler
erreur("L'erreur suivante s'est produite lors de la connexion au proxy dy service web : " + ex.Message, 2)
End Try
' Die Anfragen des Benutzers werden über die Tastatur eingegeben
' in der Form „Funktion a b“ – sie enden mit dem Befehl „end“
Dim commande As String = Nothing ' commande tapée au clavier
Dim champs As String() = Nothing ' champs d'une ligne de commande
' Eingabeaufforderung an den Benutzer
Console.Out.WriteLine("Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser|toutfaire] a b" + ControlChars.Lf)
' einige lokale Daten
Dim erreurCommande As Boolean
Dim fonction As String
Dim a, b As Double
' Schleife zur Erfassung der über die Tastatur eingegebenen Befehle
While True
' zunächst kein Fehler
erreurCommande = False
' Befehl auslesen
commande = Console.In.ReadLine().Trim().ToLower()
' Fertig?
If commande Is Nothing Or commande = "fin" Then
Exit While
End If
' Aufteilung des Befehls in Felder
champs = Regex.Split(commande, "\s+")
Try
' Es sind drei Felder erforderlich
If champs.Length <> 3 Then
Throw New Exception
End If
' Feld 0 muss eine erkannte Funktion sein
fonction = champs(0)
If Not dicoFonctions.ContainsKey(fonction) Then
Throw New Exception
End If
' Feld 1 muss eine gültige Zahl sein
a = Double.Parse(champs(1))
' Feld 2 muss eine gültige Zahl sein
b = Double.Parse(champs(2))
Catch
' ungültiger Befehl
Console.Out.WriteLine("syntaxe : [ajouter|soustraire|multiplier|diviser] a b")
erreurCommande = True
End Try
' Die Anfrage wird an den Webdienst gesendet
If Not erreurCommande Then
Try
Dim résultat As Double
Dim résultats() As Double
If fonction = "ajouter" Then
résultat = myOperations.ajouter(a, b)
Console.Out.WriteLine(("résultat=" + résultat.ToString))
End If
If fonction = "soustraire" Then
résultat = myOperations.soustraire(a, b)
Console.Out.WriteLine(("résultat=" + résultat.ToString))
End If
If fonction = "multiplier" Then
résultat = myOperations.multiplier(a, b)
Console.Out.WriteLine(("résultat=" + résultat.ToString))
End If
If fonction = "diviser" Then
résultat = myOperations.diviser(a, b)
Console.Out.WriteLine(("résultat=" + résultat.ToString))
End If
If fonction = "toutfaire" Then
résultats = myOperations.toutfaire(a, b)
Console.Out.WriteLine(("résultats=[" + résultats(0).ToString + "," + résultats(1).ToString + "," + _
résultats(2).ToString + "," + résultats(3).ToString + "]"))
End If
Catch e As Exception
Console.Out.WriteLine(("L'erreur suivante s'est produite : " + e.Message))
End Try
End If
End While
End Sub
' Anzeige der Fehler
Public Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' Fehleranzeige
System.Console.Error.WriteLine(msg)
' Abbruch mit Fehler
Environment.Exit(exitCode)
End Sub
End Module
Wir betrachten nur den Code, der für die Verwendung der Proxy-Klasse relevant ist. Zunächst wird ein Proxy-Objekt operations erstellt:
' Erstellung eines Proxy-Objekts für Operationen
Dim myOperations As operations = Nothing
Try
myOperations = New operations
Catch ex As Exception
' Verbindungsfehler
erreur("L'erreur suivante s'est produite lors de la connexion au proxy dy service web : " + ex.Message, 2)
End Try
Die Zeilen der Funktionen a und b werden über die Tastatur eingegeben. Anhand dieser Informationen werden die entsprechenden Methoden des Proxys aufgerufen:
' Anfrage an den Webdienst
If Not erreurCommande Then
Try
Dim résultat As Double
Dim résultats() As Double
If fonction = "ajouter" Then
résultat = myOperations.ajouter(a, b)
Console.Out.WriteLine(("résultat=" + résultat.ToString))
End If
If fonction = "soustraire" Then
résultat = myOperations.soustraire(a, b)
Console.Out.WriteLine(("résultat=" + résultat.ToString))
End If
If fonction = "multiplier" Then
résultat = myOperations.multiplier(a, b)
Console.Out.WriteLine(("résultat=" + résultat.ToString))
End If
If fonction = "diviser" Then
résultat = myOperations.diviser(a, b)
Console.Out.WriteLine(("résultat=" + résultat.ToString))
End If
If fonction = "toutfaire" Then
résultats = myOperations.toutfaire(a, b)
Console.Out.WriteLine(("résultats=[" + résultats(0).ToString + "," + résultats(1).ToString + "," + _
résultats(2).ToString + "," + résultats(3).ToString + "]"))
End If
Catch e As Exception
Console.Out.WriteLine(("L'erreur suivante s'est produite : " + e.Message))
End Try
Hier wird zum ersten Mal die Operation toutfaire verarbeitet, die die vier Operationen ausführt. Sie wurde bisher ignoriert, da sie ein Zahlenarray sendet, das in einem XML-Container gekapselt ist, der komplizierter zu handhaben ist als die einfachen XML-Antworten der anderen Funktionen, die nur ein einziges Ergebnis liefern. Man sieht, dass hier mit der Proxy-Klasse die Verwendung der Methode toutfaire nicht komplizierter ist als die Verwendung der anderen Methoden. Die Anwendung wurde in einem DOS-Fenster wie folgt kompiliert:
dos>vbc /r:operations.dll /r:system.dll /r:system.web.services.dll testClientProxy.vb
dos>dir
04/03/2004 17:17 6 663 operations.vb
04/03/2004 17:24 7 680 operations.dll
04/03/2004 17:41 4 099 testClientProxy.vb
04/03/2004 17:41 5 632 testClientProxy.exe
10.9. Einen Webdienst konfigurieren
Ein Webdienst benötigt möglicherweise Konfigurationsinformationen, um ordnungsgemäß initialisiert zu werden. Beim Server IIS können diese Informationen in einer Datei namens web.config gespeichert werden, die sich im selben Ordner wie der Webdienst befindet. Angenommen, man möchte einen Webdienst erstellen, der zur Initialisierung zwei Angaben benötigt: einen Namen und ein Alter. Diese beiden Angaben können in der Datei „web.config“ in folgender Form hinterlegt werden:
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<appSettings>
<add key="nom" value="tintin"/>
<add key="age" value="27"/>
</appSettings>
</configuration>
Die Initialisierungsparameter werden in einem Umschlag mit der Kennung XML abgelegt:
Ein Initialisierungsparameter mit dem Namen P und dem Wert V wird mit folgender Zeile deklariert:
<add key="P" value="V"/>
Wie ruft der Webdienst diese Informationen ab? Wenn IIS einen Webdienst lädt, prüft er, ob sich im selben Ordner eine Datei mit dem Namen web.config befindet. Ist dies der Fall, liest er sie ein. Der Wert V eines Parameters P wird durch die Anweisung ermittelt:
wobei ConfigurationSettings eine Klasse im Namensraum System.Configuration ist.
Testen wir diese Technik anhand des folgenden Webdienstes:
<%@ WebService language="VB" class=personne %>
Imports System.Web.Services
imports System.Configuration
<WebService([Namespace] := "st.istia.univ-angers.fr")> _
Public Class personne
Inherits WebService
' Attribute
Private nom As String
Private age As Integer
' Konstruktor
Public Sub New()
' Attribute initialisieren
nom = ConfigurationSettings.AppSettings("nom")
age = Integer.Parse(ConfigurationSettings.AppSettings("age"))
End Sub
<WebMethod> _
Function id() As String
Return "[" + nom + "," + age.ToString + "]"
End Function
End Class
Der Webdienst personne verfügt über zwei Attribute, nom und age, die in seinem Konstruktor ohne Parameter anhand der Werte initialisiert werden, die aus der Konfigurationsdatei web.config des Dienstes personne. Diese Datei lautet wie folgt:
<configuration>
<appSettings>
<add key="nom" value="tintin"/>
<add key="age" value="27"/>
</appSettings>
</configuration>
Der Webdienst verfügt außerdem über eine <WebMethod>-ID ohne Parameter, die lediglich die Attribute nom und age zurückgibt. Der Dienst ist in der Quelldatei personne.asmx registriert, die zusammen mit ihrer Konfigurationsdatei im Ordner c:\inetpub\wwwroot\st\personne abgelegt ist:
Ordnen wir dem oben genannten physischen Ordner einen virtuellen Ordner „IIS/config“ zu. Starten wir „IIS“ und rufen wir anschließend mit einem Browser die URL http://localhost/config/personne.asmx des Dienstes „personne“ auf:

Folgen wir dem Link der einzigen Methode-ID:

Die Methode id hat keine Parameter. Verwenden wir die Schaltfläche Appeler:

Wir haben die in der Datei web.config des Dienstes enthaltenen Informationen erfolgreich abgerufen.
10.10. Der Webdienst IMPOTS
Wir greifen nun auf die mittlerweile bekannte Anwendung IMPOTS zurück. Als wir das letzte Mal damit gearbeitet haben, hatten wir daraus einen Remote-Server gemacht, den man über das Internet aufrufen konnte. Jetzt machen wir daraus einen Webdienst.
10.10.1. Der Webdienst
Wir gehen von der Klasse impôt aus, die im Kapitel über Datenbanken erstellt wurde und auf den Informationen einer Datenbank namens ODBC basiert:
' Optionen
Option Strict On
Option Explicit On
' Namensräume
Imports System
Imports System.Data
Imports Microsoft.Data.Odbc
Imports System.Collections
Public Class impôt
' die für die Steuerberechnung erforderlichen Daten
' stammen aus einer externen Quelle
Private limites(), coeffR(), coeffN() As Decimal
' Hersteller
Public Sub New(ByVal LIMITES() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal)
' Es wird überprüft, ob alle drei Tabellen dieselbe Größe haben
Dim OK As Boolean = LIMITES.Length = COEFFR.Length And LIMITES.Length = COEFFN.Length
If Not OK Then
Throw New Exception("Les 3 tableaux fournis n'ont pas la même taille(" & LIMITES.Length & "," & COEFFR.Length & "," & COEFFN.Length & ")")
End If
' alles in Ordnung
Me.limites = LIMITES
Me.coeffR = COEFFR
Me.coeffN = COEFFN
End Sub
' Konstruktor 2
Public Sub New(ByVal DSNimpots As String, ByVal Timpots As String, ByVal colLimites As String, ByVal colCoeffR As String, ByVal colCoeffN As String)
' initialisiert die drei Begrenzungstabellen coeffR, coeffN anhand
' des Inhalts der Tabelle „Timpots“ aus der Datenbank ODBC DSNimpots
' colLimites, colCoeffR, colCoeffN sind die drei Spalten dieser Tabelle
' kann eine Ausnahme auslösen
Dim connectString As String = "DSN=" + DSNimpots + ";" ' chaîne de connexion à la base
Dim impotsConn As OdbcConnection = Nothing ' la connexion
Dim sqlCommand As OdbcCommand = Nothing ' la commande SQL
' die Abfrage SELECT
Dim selectCommand As String = "select " + colLimites + "," + colCoeffR + "," + colCoeffN + " from " + Timpots
' Tabellen zum Abrufen der Daten
Dim tLimites As New ArrayList
Dim tCoeffR As New ArrayList
Dim tCoeffN As New ArrayList
' Es wird versucht, auf die Datenbank zuzugreifen
impotsConn = New OdbcConnection(connectString)
impotsConn.Open()
' ein Befehlsobjekt wird erstellt
sqlCommand = New OdbcCommand(selectCommand, impotsConn)
' Die Abfrage wird ausgeführt
Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader()
' Auswertung der abgerufenen Tabelle
While myReader.Read()
' Die Daten der aktuellen Zeile werden in die Tabellen übertragen
tLimites.Add(myReader(colLimites))
tCoeffR.Add(myReader(colCoeffR))
tCoeffN.Add(myReader(colCoeffN))
End While
' Freigabe der Ressourcen
myReader.Close()
impotsConn.Close()
' Die dynamischen Arrays werden in statische Arrays umgewandelt
Me.limites = New Decimal(tLimites.Count) {}
Me.coeffR = New Decimal(tLimites.Count) {}
Me.coeffN = New Decimal(tLimites.Count) {}
Dim i As Integer
For i = 0 To tLimites.Count - 1
limites(i) = Decimal.Parse(tLimites(i).ToString())
coeffR(i) = Decimal.Parse(tCoeffR(i).ToString())
coeffN(i) = Decimal.Parse(tCoeffN(i).ToString())
Next i
End Sub
' Berechnung der Steuer
Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Integer) As Long
' Berechnung der Anzahl der Anteile
Dim nbParts As Decimal
If marié Then
nbParts = CDec(nbEnfants) / 2 + 2
Else
nbParts = CDec(nbEnfants) / 2 + 1
End If
If nbEnfants >= 3 Then
nbParts += 0.5D
End If
' Berechnung des steuerpflichtigen Einkommens und des Familienquotienten
Dim revenu As Decimal = 0.72D * salaire
Dim QF As Decimal = revenu / nbParts
' Steuerberechnung
limites((limites.Length - 1)) = QF + 1
Dim i As Integer = 0
While QF > limites(i)
i += 1
End While
' Ergebnisrückgabe
Return CLng(revenu * coeffR(i) - nbParts * coeffN(i))
End Function
End Class
Im Webdienst darf nur ein Konstruktor ohne Parameter verwendet werden. Daher wird der Konstruktor der Klasse wie folgt aussehen:
' Konstruktor
Public Sub New()
' initialisiert die drei Grenzwerttabellen coeffR und coeffN anhand
' des Inhalts der Tabelle „Timpots“ aus der Datenbank ODBC DSNimpots
' colLimites, colCoeffR, colCoeffN sind die drei Spalten dieser Tabelle
' kann eine Ausnahme auslösen
' hiermit werden die Konfigurationsparameter des Dienstes abgerufen
Dim DSNimpots As String = ConfigurationSettings.AppSettings("DSN")
Dim Timpots As String = ConfigurationSettings.AppSettings("TABLE")
Dim colLimites As String = ConfigurationSettings.AppSettings("COL_LIMITES")
Dim colCoeffR As String = ConfigurationSettings.AppSettings("COL_COEFFR")
Dim colCoeffN As String = ConfigurationSettings.AppSettings("COL_COEFFN")
' die Datenbank wird abgefragt
Dim connectString As String = "DSN=" + DSNimpots + ";" ' chaîne de connexion à la base
Die fünf Parameter des Konstruktors der vorherigen Klasse werden nun aus der Datei web.config des Webdienstes gelesen. Der Code der Quelldatei impots.asmx lautet wie folgt. Er übernimmt den Großteil des vorherigen Codes. Wir haben uns darauf beschränkt, die für den Webdienst spezifischen Codeabschnitte einzurahmen:
<%@ WebService language="VB" class=impots %>
' Erstellung eines Webdienstes für Steuern
Imports System
Imports System.Data
Imports Microsoft.Data.Odbc
Imports System.Collections
Imports System.Configuration
Imports System.Web.Services
<WebService([Namespace]:="st.istia.univ-angers.fr")> _
Public Class impôt
Inherits WebService
' Die für die Steuerberechnung erforderlichen Daten
' stammen aus einer externen Quelle
Private limites(), coeffR(), coeffN() As Decimal
Private OK As Boolean = False
Private errMessage As String = ""
' Konstruktor
Public Sub New()
' initialisiert die drei Begrenzungstabellen coeffR, coeffN anhand
' des Inhalts der Tabelle „Timpots“ aus der Datenbank ODBC DSNimpots
' colLimites, colCoeffR, colCoeffN sind die drei Spalten dieser Tabelle
' kann eine Ausnahme auslösen
' hiermit werden die Konfigurationsparameter des Dienstes abgerufen
Dim DSNimpots As String = ConfigurationSettings.AppSettings("DSN")
Dim Timpots As String = ConfigurationSettings.AppSettings("TABLE")
Dim colLimites As String = ConfigurationSettings.AppSettings("COL_LIMITES")
Dim colCoeffR As String = ConfigurationSettings.AppSettings("COL_COEFFR")
Dim colCoeffN As String = ConfigurationSettings.AppSettings("COL_COEFFN")
' die Datenbank wird abgefragt
Dim connectString As String = "DSN=" + DSNimpots + ";" ' chaîne de connexion à la base
Dim impotsConn As OdbcConnection = Nothing ' la connexion
Dim sqlCommand As OdbcCommand = Nothing ' la commande SQL
Dim myReader As OdbcDataReader ' lecteur de données Odbc
' die Abfrage SELECT
Dim selectCommand As String = "select " + colLimites + "," + colCoeffR + "," + colCoeffN + " from " + Timpots
' Tabellen zum Abrufen der Daten
Dim tLimites As New ArrayList
Dim tCoeffR As New ArrayList
Dim tCoeffN As New ArrayList
' Es wird versucht, auf die Datenbank zuzugreifen
Try
impotsConn = New OdbcConnection(connectString)
impotsConn.Open()
' ein Befehlsobjekt wird angelegt
sqlCommand = New OdbcCommand(selectCommand, impotsConn)
' Die Abfrage wird ausgeführt
myReader = sqlCommand.ExecuteReader()
' Auswertung der abgerufenen Tabelle
While myReader.Read()
' Die Daten der aktuellen Zeile werden in die Arrays übertragen
tLimites.Add(myReader(colLimites))
tCoeffR.Add(myReader(colCoeffR))
tCoeffN.Add(myReader(colCoeffN))
End While
' Freigabe der Ressourcen
myReader.Close()
impotsConn.Close()
' Die dynamischen Arrays werden in statische Arrays übertragen
Me.limites = New Decimal(tLimites.Count) {}
Me.coeffR = New Decimal(tLimites.Count) {}
Me.coeffN = New Decimal(tLimites.Count) {}
Dim i As Integer
For i = 0 To tLimites.Count - 1
limites(i) = Decimal.Parse(tLimites(i).ToString())
coeffR(i) = Decimal.Parse(tCoeffR(i).ToString())
coeffN(i) = Decimal.Parse(tCoeffN(i).ToString())
Next i
' Alles in Ordnung
OK = True
errMessage = ""
Catch ex As Exception
' Fehler
OK = False
errMessage += "[" + ex.Message + "]"
End Try
End Sub
' Steuerberechnung
<WebMethod()> _
Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Integer) As Long
' Berechnung der Anzahl der Anteile
Dim nbParts As Decimal
If marié Then
nbParts = CDec(nbEnfants) / 2 + 2
Else
nbParts = CDec(nbEnfants) / 2 + 1
End If
If nbEnfants >= 3 Then
nbParts += 0.5D
End If
' Berechnung des steuerpflichtigen Einkommens und des Familienquotienten
Dim revenu As Decimal = 0.72D * salaire
Dim QF As Decimal = revenu / nbParts
' Steuerberechnung
limites((limites.Length - 1)) = QF + 1
Dim i As Integer = 0
While QF > limites(i)
i += 1
End While
' Ergebnisrückgabe
Return CLng(revenu * coeffR(i) - nbParts * coeffN(i))
End Function
' ID
<WebMethod()> _
Function id() As String
' um zu prüfen, ob alles in Ordnung ist OK
Return "[" + OK + "," + errMessage + "]"
End Function
End Class
Lassen Sie uns die wenigen Änderungen erläutern, die an der Klasse impots vorgenommen wurden, abgesehen von denen, die für die Umwandlung in einen Webdienst erforderlich waren:
- Das Einlesen der Datenbank im Konstruktor kann fehlschlagen. Daher haben wir unserer Klasse zwei Attribute und eine Methode hinzugefügt:
- Der boolesche Wert OK ist gleich vrai, wenn die Datenbank gelesen werden konnte, andernfalls gleich faux
- Die Zeichenkette errMessage enthält eine Fehlermeldung, wenn die Datenbank nicht gelesen werden konnte.
- Mit der Methode id ohne Parameter lassen sich die Werte dieser beiden Attribute abrufen.
- Um einen möglichen Fehler beim Zugriff auf die Datenbank zu behandeln, wurde der für diesen Zugriff relevante Teil des Konstruktorkodes mit einem try-catch umschlossen.
Die Konfigurationsdatei web.config für den Dienst lautet wie folgt:
<configuration>
<appSettings>
<add key="DSN" value="mysql-impots" />
<add key="TABLE" value="timpots" />
<add key="COL_LIMITES" value="limites" />
<add key="COL_COEFFR" value="coeffr" />
<add key="COL_COEFFN" value="coeffn" />
</appSettings>
</configuration>
Bei einem ersten Versuch, den Service impots zu laden, meldete der Compiler, dass er den in der Anweisung verwendeten Namensraum Microsoft.Data.Odbc nicht finden konnte:
Nach Einsicht in die Dokumentation
- wurde in web.config eine Kompilierungsanweisung hinzugefügt, um anzugeben, dass die Assembly Microsoft.Data.odbc verwendet werden muss
- Eine Kopie der Datei „microsoft.data.odbc.dll“ wurde im Ordner „bin“ des Projekts abgelegt. Dieser Ordner wird vom Compiler eines Webdienstes systematisch durchsucht, wenn er nach einer „Assembly“ sucht.
Andere Lösungen scheinen möglich zu sein, wurden hier jedoch nicht näher untersucht. Die Konfigurationsdatei lautet nun also:
<configuration>
<appSettings>
<add key="DSN" value="mysql-impots" />
<add key="TABLE" value="timpots" />
<add key="COL_LIMITES" value="limites" />
<add key="COL_COEFFR" value="coeffr" />
<add key="COL_COEFFN" value="coeffn" />
</appSettings>
<system.web>
<compilation>
<assemblies>
<add assembly="Microsoft.Data.Odbc" />
</assemblies>
</compilation>
</system.web>
</configuration>
Inhalt des Ordners impots\bin:
Der Dienst und seine Konfigurationsdatei wurden in impots abgelegt:
Der physische Ordner des Webdienstes wurde dem virtuellen Ordner /impots von IIS zugeordnet. Die Seite des Dienstes sieht nun wie folgt aus:

Wenn man dem Link id folgt:

Wenn man die Schaltfläche „Appeler“ verwendet:

Das vorstehende Ergebnis zeigt die Werte der Attribute OK (true) und errMessage („“) an. In diesem Beispiel wurde die Datenbank korrekt geladen. Das war nicht immer der Fall, weshalb wir die Methode id hinzugefügt haben, um Zugriff auf die Fehlermeldung zu erhalten. Der Fehler bestand darin, dass der Name DSN der Datenbank als Benutzer-DSN definiert worden war, obwohl er als System-DSN hätte definiert werden müssen. Diese Unterscheidung wird im 32-Bit-Quellmanager ODBC vorgenommen:
![]() |
Kehren wir zur Seite des Dienstes zurück:

Folgen wir dem Link calculer:

Wir legen die Parameter für den Aufruf fest und führen diesen aus:

Das Ergebnis ist korrekt.
10.10.2. Proxy für den Dienst „impots“ generieren
Da wir nun über einen funktionsfähigen Webdienst impots verfügen, können wir dessen Proxy-Klasse generieren. Zur Erinnerung: Diese wird von Client-Anwendungen verwendet, um den Webdienst impots transparent anzurufen. Zunächst verwenden wir das Dienstprogramm wsdl, um die Quelldatei der Proxy-Klasse zu generieren; anschließend wird diese zu einer DLL kompiliert.
dos>wsdl /language=vb http://localhost/impots/impots.asmx
Microsoft (R) Web Services Description Language Utility
[Microsoft (R) .NET Framework, Version 1.1.4322.573]
Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.
Écriture du fichier 'D:\data\serge\devel\vbnet\poly\chap9\impots\impots.vb'.
D:\data\serge\devel\vbnet\poly\chap9\impots>dir
09/03/2004 10:20 <REP> bin
09/03/2004 10:58 4 651 impots.asmx
09/03/2004 11:05 3 364 impots.vb
09/03/2004 10:19 431 web.config
dos>vbc /t:library /r:system.dll /r:system.web.services.dll /r:system.xml.dll impots.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4
pour Microsoft (R) .NET Framework version 1.1.4322.573
Copyright (C) Microsoft Corporation 1987-2002. Tous droits réservés.
dos>dir
09/03/2004 10:20 <REP> bin
09/03/2004 10:58 4 651 impots.asmx
09/03/2004 11:09 5 120 impots.dll
09/03/2004 11:05 3 364 impots.vb
09/03/2004 10:19 431 web.config
10.10.3. Den Proxy mit einem Client verwenden
Im Kapitel über Datenbanken hatten wir eine Konsolenanwendung erstellt, mit der sich Steuern berechnen lassen:
dos>dir
27/02/2004 16:56 5 120 impots.dll
27/02/2004 17:12 3 586 impots.vb
27/02/2004 17:08 6 144 testimpots.exe
27/02/2004 17:18 3 328 testimpots.vb
dos>testimpots
pg DSNimpots tabImpots colLimites colCoeffR colCoeffN
dos>testimpots odbc-mysql-dbimpots impots limites coeffr coeffn
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 200000
impôt=22504 F
Das Programm testimpots verwendete damals die klassische Klasse impôt, die in der Datei impots.dll enthalten war. Der Programmcode von testimpots.vb lautete wie folgt:
Option Explicit On
Option Strict On
' Namensräume
Imports System
Imports Microsoft.VisualBasic
' Test-PG
Module testimpots
Sub Main(ByVal arguments() As String)
' Interaktives Programm zur Steuerberechnung
' Der Benutzer gibt drei Daten über die Tastatur ein: verheiratet nbEnfants Gehalt
' Das Programm zeigt daraufhin die zu zahlende Steuer an
Const syntaxe1 As String = "pg DSNimpots tabImpots colLimites colCoeffR colCoeffN"
Const syntaxe2 As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"
' Überprüfung der Programmparameter
If arguments.Length <> 5 Then
' Fehlermeldung
Console.Error.WriteLine(syntaxe1)
' Ende
Environment.Exit(1)
End If 'if
' Die Argumente werden abgerufen
Dim DSNimpots As String = arguments(0)
Dim tabImpots As String = arguments(1)
Dim colLimites As String = arguments(2)
Dim colCoeffR As String = arguments(3)
Dim colCoeffN As String = arguments(4)
' Erstellung eines Steuerobjekts
Dim objImpôt As impôt = Nothing
Try
objImpôt = New impôt(DSNimpots, tabImpots, colLimites, colCoeffR, colCoeffN)
Catch ex As Exception
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
Environment.Exit(2)
End Try
' Endlosschleife
While True
' zunächst keine Fehler
Dim erreur As Boolean = False
' die Parameter für die Steuerberechnung werden abgefragt
Console.Out.Write("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :")
Dim paramètres As String = Console.In.ReadLine().Trim()
' Was ist zu tun?
If paramètres Is Nothing Or paramètres = "" Then
Exit While
End If
' Überprüfung der Anzahl der Argumente in der eingegebenen Zeile
Dim args As String() = paramètres.Split(Nothing)
Dim nbParamètres As Integer = args.Length
If nbParamètres <> 3 Then
Console.Error.WriteLine(syntaxe2)
erreur = True
End If
Dim marié As String
Dim nbEnfants As Integer
Dim salaire As Integer
If Not erreur Then
' Überprüfung der Gültigkeit der Parameter
' verheiratet
marié = args(0).ToLower()
If marié <> "o" And marié <> "n" Then
Console.Error.WriteLine((syntaxe2 + ControlChars.Lf + "Argument marié incorrect : tapez o ou n"))
erreur = True
End If
' nbEnfants
nbEnfants = 0
Try
nbEnfants = Integer.Parse(args(1))
If nbEnfants < 0 Then
Throw New Exception
End If
Catch
Console.Error.WriteLine(syntaxe2 + "\nArgument nbEnfants incorrect : tapez un entier positif ou nul")
erreur = True
End Try
' Gehalt
salaire = 0
Try
salaire = Integer.Parse(args(2))
If salaire < 0 Then
Throw New Exception
End If
Catch
Console.Error.WriteLine(syntaxe2 + "\nArgument salaire incorrect : tapez un entier positif ou nul")
erreur = True
End Try
End If
If Not erreur Then
' Die Parameter sind korrekt – die Steuer wird berechnet
Console.Out.WriteLine(("impôt=" & objImpôt.calculer(marié = "o", nbEnfants, salaire).ToString + " F"))
End If
End While
End Sub
End Module
Wir greifen auf dasselbe Programm zurück, um es nun den Webdienst impots über die zuvor erstellte Proxy-Klasse impots nutzen zu lassen. Dazu müssen wir den Code geringfügig anpassen:
- Während die ursprüngliche Klasse impôt einen Konstruktor mit fünf Argumenten hatte, verfügt die Proxy-Klasse impots über einen Konstruktor ohne Parameter. Die fünf Parameter sind, wie wir gesehen haben, nun in der Konfigurationsdatei des Webdienstes festgelegt.
- Es ist daher nicht mehr erforderlich, diese fünf Parameter als Argumente an das Testprogramm zu übergeben
Der neue Code lautet wie folgt:
Imports System
Imports Microsoft.VisualBasic
' Testseite
Module testimpots
Public Sub Main(ByVal arguments() As String)
' Interaktives Programm zur Steuerberechnung
' Der Benutzer gibt drei Daten über die Tastatur ein: verheiratet nbEnfants Gehalt
' Das Programm zeigt dann die zu zahlende Steuer an
Const syntaxe2 As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"
' Erstellung eines Steuerobjekts
Dim objImpôt As impôt = Nothing
Try
objImpôt = New impôt
Catch ex As Exception
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
Environment.Exit(2)
End Try
' Endlosschleife
Dim erreur As Boolean
While True
' zunächst kein Fehler
erreur = False
' Die Parameter für die Steuerberechnung werden abgefragt
Console.Out.Write("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :")
Dim paramètres As String = Console.In.ReadLine().Trim()
' Was ist zu tun?
If paramètres Is Nothing Or paramètres = "" Then
Exit While
End If
' Überprüfung der Anzahl der Argumente in der eingegebenen Zeile
Dim args As String() = paramètres.Split(Nothing)
Dim nbParamètres As Integer = args.Length
If nbParamètres <> 3 Then
Console.Error.WriteLine(syntaxe2)
erreur = True
End If
If Not erreur Then
' Überprüfung der Gültigkeit der Parameter
' verheiratet
Dim marié As String = args(0).ToLower()
If marié <> "o" And marié <> "n" Then
Console.Error.WriteLine((syntaxe2 + ControlChars.Lf + "Argument marié incorrect : tapez o ou n"))
erreur = True
End If
' nbEnfants
Dim nbEnfants As Integer = 0
Try
nbEnfants = Integer.Parse(args(1))
If nbEnfants < 0 Then
Throw New Exception
End If
Catch
Console.Error.WriteLine((syntaxe2 + ControlChars.Lf + "Argument nbEnfants incorrect : tapez un entier positif ou nul"))
erreur = True
End Try
' Gehalt
Dim salaire As Integer = 0
Try
salaire = Integer.Parse(args(2))
If salaire < 0 Then
Throw New Exception
End If
Catch
Console.Error.WriteLine((syntaxe2 + ControlChars.Lf + "Argument salaire incorrect : tapez un entier positif ou nul"))
erreur = True
End Try
' Wenn die Parameter korrekt sind, wird die Steuer berechnet
If Not erreur Then Console.Out.WriteLine(("impôt=" + objImpôt.calculer(marié = "o", nbEnfants, salaire).ToString + " F"))
End If
End While
End Sub
End Module
Wir haben den Proxy impots.dll und die Quelle testimpots im selben Ordner.
dos>dir
09/03/2004 11:28 <REP> bin
09/03/2004 11:09 5 120 impots.dll
09/03/2004 11:34 3 396 testimpots.vb
09/03/2004 10:19 431 web.config
Wir kompilieren den Quellcode testimpots.vb:
dos>vbc /r:impots.dll /r:microsoft.visualbasic.dll /r:system.web.services.dll /r:system.dll testimpots.vb
dos>dir
09/03/2004 11:28 <REP> bin
09/03/2004 11:09 5 120 impots.dll
09/03/2004 11:05 3 364 impots.vb
09/03/2004 11:35 5 632 testimpots.exe
09/03/2004 11:34 3 396 testimpots.vb
09/03/2004 10:19 431 web.config
und führen wir ihn dann aus:
dos>testimpots
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 200000
impôt=22504 F
Wir erhalten tatsächlich das erwartete Ergebnis.





