10. Webdienste
10.1. Einführung
Im vorigen Kapitel haben wir mehrere TCP/IP-Client-Server-Anwendungen vorgestellt. Da Clients und Server Textzeilen austauschen, können sie in jeder beliebigen Sprache geschrieben werden. Der Client muss lediglich das vom Server erwartete Kommunikationsprotokoll kennen. Webdienste sind TCP/IP-Serveranwendungen mit den folgenden Merkmalen:
- Sie werden von Webservern gehostet, und das Client-Server-Kommunikationsprotokoll ist daher HTTP (HyperText Transfer Protocol), ein Protokoll, das über TCP/IP läuft.
- Der Webdienst verfügt über ein Standard-Kommunikationsprotokoll, unabhängig vom angebotenen Dienst. Ein Webdienst bietet verschiedene Dienste S1, S2, .., Sn an. Jeder von ihnen erwartet vom Client bereitgestellte Parameter und gibt ein Ergebnis an den Client zurück. Für jeden Dienst muss der Client Folgendes wissen:
- den genauen Namen des Dienstes Si
- die Liste der bereitzustellenden Parameter und deren Typen
- den Typ des vom Dienst zurückgegebenen Ergebnisses
Sobald diese Elemente bekannt sind, folgt die Client-Server-Interaktion dem gleichen Format, unabhängig davon, welcher Webdienst abgefragt wird. Der Client-Code ist somit standardisiert.
- Aus Sicherheitsgründen im Zusammenhang mit Angriffen aus dem Internet unterhalten viele Organisationen private Netzwerke und öffnen nur bestimmte Ports ihrer Server für das Internet: in erster Linie Port 80 für den Webdienst. Alle anderen Ports sind gesperrt. Folglich werden Client-Server-Anwendungen, wie sie im vorigen Kapitel vorgestellt wurden, innerhalb des privaten Netzwerks (Intranet) aufgebaut und sind von außen in der Regel nicht zugänglich. Das Hosten eines Dienstes auf einem Webserver macht ihn für die gesamte Internet-Community zugänglich.
- Der Webdienst kann als Remote-Objekt modelliert werden. Die angebotenen Dienste werden dann zu Methoden dieses Objekts. Ein Client kann auf dieses Remote-Objekt zugreifen, als wäre es lokal. Dies verbirgt die gesamte Netzwerkkommunikationsschicht und ermöglicht die Entwicklung eines von dieser Schicht unabhängigen Clients. Wenn sich die Schicht ändert, muss der Client nicht angepasst werden. Dies ist ein enormer Vorteil und wahrscheinlich der Hauptnutzen von Webdiensten.
- Wie bei den im vorigen Kapitel vorgestellten TCP/IP-Client-Server-Anwendungen können Client und Server in jeder beliebigen Sprache geschrieben werden. Sie tauschen Textzeilen aus. Diese bestehen aus zwei Teilen:
- die vom HTTP-Protokoll geforderten Header
- dem Nachrichtentext. Bei einer Serverantwort an den Client liegt der Nachrichtentext im XML-Format (eXtensible Markup Language) vor. Bei einer Clientanfrage an den Server kann der Nachrichtentext verschiedene Formen annehmen, darunter auch XML. Die XML-Anfrage des Clients kann ein spezielles Format namens SOAP (Simple Object Access Protocol) verwenden. In diesem Fall folgt auch die Antwort des Servers dem SOAP-Format.
10.2. Browser und XML
Webdienste senden XML an ihre Clients. Browser können beim Empfang dieses XML-Stroms unterschiedlich reagieren. Internet Explorer verfügt über ein vordefiniertes Stylesheet, das die Anzeige des XML ermöglicht. Netscape Communicator verfügt jedoch nicht über dieses Stylesheet und zeigt den empfangenen XML-Code nicht an. Sie müssen den Quellcode der empfangenen Seite anzeigen, um auf das XML zuzugreifen. Hier ist 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>
Internet Explorer zeigt die folgende Seite an:

während Netscape Navigator Folgendes anzeigt:

Wenn wir uns den Quellcode der von Netscape empfangenen Seite ansehen, erhalten wir:

Netscape hat zwar denselben Inhalt wie der Internet Explorer empfangen, ihn jedoch anders dargestellt. Von nun an werden wir für die Screenshots den Internet Explorer verwenden.
10.3. Ein erster Webdienst
Wir werden Webdienste anhand eines sehr einfachen Beispiels untersuchen, das in drei Versionen verfügbar ist.
10.3.1. Version 1
Für diese erste Version verwenden wir VS.NET, was den Vorteil hat, dass damit ein sofort einsatzbereites Webdienst-Grundgerüst generiert werden kann. Sobald wir diese Architektur verstanden haben, können wir selbstständig weiterarbeiten. Das wird der Schwerpunkt der folgenden Versionen sein.
Erstellen wir mit VS.NET über die Option [Datei/Neu/Projekt] ein neues Projekt:

Beachten Sie folgende Punkte:
- Der Projekttyp ist Visual Basic (linker Bereich)
- Die Projektvorlage ist „ASP.NET-Webdienst“ (rechter Bereich)
- Der Speicherort ist flexibel. Hier wird der Webdienst von einem lokalen IIS-Webserver gehostet. Seine URL lautet daher http://localhost/[Pfad], wobei [Pfad] noch definiert werden muss. Hier wählen wir den Pfad http://localhost/polyvbnet/demo. VS.NET erstellt dann einen Ordner für dieses Projekt. Wo? Der IIS-Server verfügt über ein Stammverzeichnis für die von ihm bereitgestellte Webdokumentenstruktur. Nennen wir dieses Stammverzeichnis <IISroot>. Es entspricht der 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 daher der Ordner e:\inetpub\wwwroot\polyvbnet\demo:

Wie immer wurden zahlreiche Ordner erstellt. Diese sind nicht immer nützlich. 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 im physischen Ordner des Projekts. Am interessantesten ist für uns die Datei mit der Erweiterung .asmx. Dies ist die Erweiterung für Webdienste. Ein Webdienst wird von VS.NET als Windows-Anwendung verwaltet, d. h. als Anwendung mit einer grafischen Benutzeroberfläche und Code zu deren Verwaltung. Deshalb haben wir ein Entwurfsfenster:

Ein Webdienst verfügt normalerweise nicht über eine grafische Benutzeroberfläche. Er stellt ein Objekt dar, das remote aufgerufen werden kann. Er verfügt über Methoden, und Anwendungen rufen diese Methoden auf. Wir betrachten ihn daher als ein klassisches Objekt mit der Besonderheit, dass es remote über das Netzwerk instanziiert werden kann. Daher werden wir das von VS.NET bereitgestellte Entwurfsfenster nicht verwenden. Konzentrieren wir uns stattdessen mithilfe der Option „Ansicht/Code“ auf den Code des Dienstes:

Einige Punkte sind dabei besonders erwähnenswert:
- Die Datei heißt Service1.asmx.vb, nicht Service1.asmx. Wir werden etwas später auf den Inhalt der Datei Service1.asmx zurückkommen.
- Wir sehen ein Codefenster, das dem ähnelt, das wir beim Erstellen von Windows-Anwendungen mit VS.NET hatten
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 " Code généré par le Concepteur des services Web "
Public Sub New()
MyBase.New()
'This call is required by the Web Services Designer.
InitializeComponent()
'Add your initialization code after the InitializeComponent() call
End Sub
'Required by the Web Services Designer
Private components As System.ComponentModel.IContainer
'REMARQUE: the following procedure is required by the Web Services Designer
'It can be modified using the Web Services Designer.
'Do not modify it using the 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: this procedure is required by the Web Services Designer
'Do not modify it using the code editor.
If disposing Then
If Not (components Is Nothing) Then
components.Dispose()
End If
End If
MyBase.Dispose(disposing)
End Sub
#End Region
' EXEMPLE DE SERVICE WEB
' The HelloWorld() service example returns the string Hello World.
' To generate, leave the following lines uncommented, then save and generate the project.
' To test this Web service, make sure that the .asmx file is the start page
' and press F5.
'
'<WebMethod()> Public Function HelloWorld() As String
' HelloWorld = "Hello World
' End Function
End Class
Beachten Sie zunächst, dass wir hier eine Klasse haben, die Klasse „Service1“, die von der Klasse „WebService“ abgeleitet ist:
Dies führt dazu, dass wir den Namespace „System.Web.Services“ importieren:
Imports System.Web.Services
Der Klassendeklaration geht ein Kompilierungsattribut voraus:
<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 die folgende Klasse ein Webdienst ist. Dieses Attribut akzeptiert verschiedene Parameter, darunter einen namens NameSpace. Er wird verwendet, um den Webdienst in einem Namespace zu platzieren. Tatsächlich kann man sich vorstellen, dass es weltweit mehrere Webdienste mit dem Namen „weather“ gibt. Wir benötigen eine Möglichkeit, diese voneinander zu unterscheiden. Der Namespace macht dies möglich. Einer könnte [namespace1].weather heißen und ein anderer [namespace2].weather. Dies ist ein Konzept, das den Klassennamespaces entspricht. VS.NET hat automatisch Code generiert und ihn in einem Bereich der Quelldatei platziert:
#Region " Code généré par le Concepteur des services Web "
Wenn wir uns diesen Code ansehen, handelt es sich um denselben Code, den der Designer generiert hat, als wir Windows-Anwendungen erstellt haben. Diesen Code können wir einfach löschen, wenn wir keine grafische Benutzeroberfläche haben, was bei Webdiensten der Fall sein wird.
Die Lektion endet mit einem Beispiel dafür, wie ein Webdienst aussehen könnte:
#End Region
' EXEMPLE DE SERVICE WEB
' L'exemple de service HelloWorld() retourne la chaîne Hello World.
' Pour générer, ne commentez pas les lignes suivantes, puis enregistrez et générez le projet.
' Pour tester ce service Web, assurez-vous que le fichier .asmx est la page de démarrage
' et appuyez sur 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 haben wir ein klareres Bild.
- Ein Webdienst ist eine von der WebService-Klasse abgeleitete Klasse
- Die Klasse wird durch das Attribut <System.Web.Services.WebService(Namespace:="st.istia.univ-angers.fr")> qualifiziert. Wir platzieren unseren Dienst daher im Namespace st.istia.univ-angers.fr.
- Die Methoden der Klasse werden durch ein <WebMethod()>-Attribut gekennzeichnet, 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 daher 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 Sie den IIS-Webserver, falls Sie dies noch nicht getan haben
- Verwenden Sie die Option „Debug/Run Without Debugging“ (Debuggen/Ohne Debugging ausführen). VS.NET
VS.NET kompiliert dann die gesamte Anwendung, startet einen Browser (oft Internet Explorer, falls verfügbar) und zeigt die URL http://localhost/polyvbnet/demo/Service1.asmx an:

Warum die URL http://localhost/polyvbnet/demo/Service1.asmx? Weil es 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 soll. Dies geschieht durch einen Rechtsklick auf die entsprechende .asmx-Datei und die Auswahl der Option [Als Startseite festlegen].

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 für den IIS-Server, die angibt:
- dass es sich um einen Webdienst handelt (Schlüsselwort „WebService“)
- dass die Sprache der Klasse dieses Dienstes Visual Basic ist (Language="vb")
- dass sich der Quellcode für diese Klasse in der Datei Service1.asmx.vb befindet (Codebehind="Service1.asmx.vb")
- dass die Klasse, die den Dienst implementiert, demo.Bonjour heißt (Class="demo.Bonjour"). Beachten Sie, dass VS.NET die Bonjour-Klasse im Namespace „demo“ abgelegt hat, der auch der Name des Projekts ist.
Kehren wir nun zu der Seite zurück, auf die über die URL http://localhost/polyvbnet/demo/Service1.asmx zugegriffen wird:

Wer hat den HTML-Code für die obige Seite geschrieben? Nicht wir – das wissen wir. Es ist IIS, das Webdienste auf standardisierte Weise präsentiert. Diese Seite bietet uns zwei Links. Folgen wir dem ersten [Service Description]:

Hoppla... das ist ziemlich unverständliches XML. Beachten Sie jedoch die 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. Merken Sie sich also, dass die URL http://serviceweb?WSDL Zugriff auf die XML-Beschreibung des Webdienstes bietet. Kehren wir zur Startseite zurück und klicken wir auf den Link [Hello]. Denken Sie daran, dass „Hello“ eine Methode des Webdienstes ist. Hätte es mehrere Methoden gegeben, wären sie alle hier aufgelistet worden. Wir erhalten die folgende neue Seite:

Wir haben die angezeigte Seite absichtlich gekürzt, um unsere Demonstration übersichtlich zu halten. Beachten Sie erneut die URL:
Wenn wir diese URL direkt in einen Browser eingeben, erhalten wir das gleiche Ergebnis wie oben. Wir werden aufgefordert, die Schaltfläche [Call] zu verwenden. Machen wir das. Wir erhalten eine neue Seite:

Dies ist wieder XML. Es enthält zwei Informationen, die in unserem Webdienst vorhanden waren:
- den Namensraum st.istia.univ-angers.fr unseres Dienstes
<System.Web.Services.WebService(Namespace:="st.istia.univ-angers.fr")>
- der von der Bonjour-Methode zurückgegebene Wert:
Return "bonjour !"
Was haben wir gelernt?
- wie man einen S-Webdienst schreibt
- wie man ihn aufruft
Wir werden uns nun damit befassen, einen Webdienst ohne Verwendung von VS.NET zu schreiben.
10.3.2. Version 2
Im vorherigen Beispiel hat VS.NET vieles selbst erledigt. Ist es möglich, einen Webdienst ohne dieses Tool zu erstellen? Die Antwort lautet ja, und wir zeigen Ihnen jetzt, wie das geht. 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 befindet sich in der Datei demo2.vb, die sich wiederum in der IIS-Serververzeichnisstruktur im Ordner E:\Inetpub\wwwroot\polyvbnet\demo2 befindet. Es handelt sich um eine Standard-VB.NET-Klasse, die daher 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 Assembly „demo2.dll“ in einem Ordner namens „bin“ ab (dieser Name ist erforderlich):
Wir erstellen nun die Datei demo2.asmx. Dies ist die Datei, die von Web-Clients aufgerufen wird. Ihr Inhalt lautet wie folgt:
Diese Anweisung ist uns bereits begegnet. Sie besagt Folgendes:
- die Webdienstklasse Bonjour2 heißt und sich in der Assembly demo2.dll befindet. IIS sucht an verschiedenen Orten nach dieser Assembly, darunter auch im bin-Ordner des Webdienstes. Deshalb haben wir die Assembly demo2.dll dort abgelegt.
Nun können wir verschiedene Tests durchführen. Wir stellen sicher, dass IIS läuft, und rufen die URL http://localhost/polyvbnet/demo2/demo2.asmx in einem Browser auf:

Dann die URL http://localhost/polyvbnet/demo2/demo2.asmx?WSDL

Anschließend die URL http://localhost/polyvbnet/demo2/demo2.asmx?op=getBonjour, wobei getBonjour der Name der einzigen Methode in unserem Webdienst ist:

Wir verwenden die Schaltfläche [Call] oben:

Wir erhalten erfolgreich das Ergebnis des Aufrufs der Methode „getBonjour“ des Webdienstes. Wir wissen nun, wie man einen Webdienst ohne Visual Studio .NET erstellt. Von nun an werden wir die Einzelheiten der Erstellung des Webdienstes außer Acht lassen und uns ausschließlich auf die grundlegenden Dateien konzentrieren.
10.3.3. Version 3
Die beiden vorherigen Versionen des [Hello]-Webdienstes verwendeten zwei Dateien:
- eine .asmx-Datei, den Einstiegspunkt des Webdienstes
- eine .vb-Datei, den Quellcode des Webdienstes
Hier zeigen wir, dass eine einzige .asmx-Datei ausreicht. Der Code für den Dienst demo3.asmx lautet wie folgt:
Wir sehen, 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 Datei im Ordner <IISroot>\polyvbnet\demo3:

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

Wir stellen einen deutlichen Unterschied zur vorherigen Version fest: Wir mussten den VB-Code des Dienstes nicht kompilieren. IIS führte diese Kompilierung selbst mit dem auf demselben Rechner installierten VB.NET-Compiler durch. Anschließend lieferte es die Seite aus. Sollte ein Kompilierungsfehler auftreten, meldet IIS diesen:

10.3.4. Version 4
Hier konzentrieren wir uns auf die IIS-Serverkonfiguration. Bislang haben wir unsere Webdienste immer im Stammverzeichnis <IISroot> des IIS-Servers abgelegt, hier [e:\inetpub\wwwroot]. Wir zeigen hier, dass wir den Webdienst an einem beliebigen Ort ablegen können. Dies geschieht mithilfe virtueller Verzeichnisse in IIS. Legen wir unseren Dienst im folgenden Verzeichnis ab:

Der Ordner [D:\data\devel\vbnet\poly\chap9\demo3] befindet sich nicht in der Verzeichnisstruktur des IIS-Servers. Wir müssen dies dem IIS mitteilen, indem wir einen virtuellen IIS-Ordner erstellen. Starten wir den IIS und wählen wir unten die Option [Erweitert] aus:

Es wird eine Liste der virtuellen Verzeichnisse angezeigt. Wir werden uns nicht näher mit dieser Liste befassen. Wir erstellen ein neues virtuelles Verzeichnis über die Schaltfläche [Hinzufügen] oben:

Über die Schaltfläche [Durchsuchen] 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] über die URL [http://<machine>/virdemo3] im Netzwerk zugänglich sind. Das obige Dialogfeld enthält weitere Einstellungen, die wir unverändert lassen. Wir klicken auf OK. Der neue virtuelle Ordner erscheint in der Liste der virtuellen Ordner in IIS:

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

10.3.5. Fazit
Wir haben verschiedene Möglichkeiten zur Erstellung eines Webdienstes vorgestellt. Im weiteren Verlauf werden wir die Methode aus Version 3 zur Erstellung des Dienstes und Methode 4 für dessen Bereitstellung verwenden. Auf diese Weise benötigen wir VS.NET nicht. Dennoch sind die Vorteile der Verwendung von VS.NET hinsichtlich der Debugging-Unterstützung erwähnenswert. Es gibt kostenlose Tools für die Entwicklung von Webanwendungen, insbesondere das von Microsoft gesponserte Produkt WebMatrix, das unter der 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
Betrachten wir einen Webdienst, der fünf Funktionen bietet:
- add(a,b), das a+b zurückgibt
- subtract(a,b), das a-b zurückgibt
- multiply(a,b), der a*b zurückgibt
- divide(a,b), das a/b zurückgibt
- doAll(a,b), das das Array [a+b, a-b, a*b, a/b] zurückgibt
Der VB.NET-Code 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 wiederholen hier einige Erläuterungen, die bereits gegeben wurden, die es jedoch wert sind, noch einmal aufgegriffen oder vertieft zu werden. Die Operationsklasse ähnelt einer VB.NET-Klasse, wobei einige Punkte zu beachten sind:
- Methoden werden durch ein <WebMethod()>-Attribut eingeleitet, das dem Compiler mitteilt, welche Methoden „veröffentlicht“, d. h. dem Client zur Verfügung gestellt werden sollen. Eine Methode, der dieses Attribut nicht vorangestellt ist, wäre für Remote-Clients unsichtbar. Dies könnte eine interne Methode sein, die von anderen Methoden verwendet wird, aber nicht zur Veröffentlichung bestimmt ist.
- Die Klasse leitet sich von der WebService-Klasse ab, die im Namespace System.Web.Services definiert ist. Diese Vererbung ist nicht immer zwingend erforderlich. Insbesondere in diesem Beispiel könnten wir darauf verzichten.
- Der Klasse selbst ist ein <WebService(Namespace="st.istia.univ-angers.fr")>-Attribut vorangestellt, das einen Namespace für den Webdienst bereitstellen soll. Ein Klassenanbieter weist seinen Klassen einen Namespace zu, um ihnen einen eindeutigen Namen zu geben und so Konflikte mit Klassen anderer Anbieter zu vermeiden, die möglicherweise denselben Namen haben. Das Gleiche gilt für Webdienste. Jeder Webdienst muss durch einen eindeutigen Namen identifizierbar sein, in diesem Fall st.istia.univ-angers.fr.
- Wir haben keinen Konstruktor definiert. Daher wird implizit der Konstruktor der übergeordneten Klasse verwendet.
Der obige Quellcode ist nicht direkt für den VB.NET-Compiler bestimmt, sondern für den IIS-Webserver. Er muss die Erweiterung .asmx haben und in der Verzeichnisstruktur des Webservers gespeichert werden. Hier speichern wir ihn als operations.asmx im Ordner <IISroot>\polyvbnet\operations:

Wir verknüpfen das virtuelle IIS-Verzeichnis [operations] mit diesem physischen Verzeichnis:
![]() | ![]() |
Rufen wir den Dienst über einen Browser auf. Die anzufordernde URL lautet [http://localhost/operations/operations.asmx]:

Wir erhalten ein Webdokument mit einem Link für jede der im Operations-Webdienst definierten Methoden. Folgen wir dem Link „Hinzufügen“:

Die angezeigte Seite fordert uns auf, die Methode add zu testen, indem wir die beiden erforderlichen Argumente a und b übergeben. Erinnern wir uns an die Definition der Methode *add*:
Beachten Sie, dass die Seite die Argumentnamen a und b aus der Methodendefinition verwendet hat. Klicken Sie auf die Schaltfläche „Aufrufen“, woraufhin die folgende Antwort in einem separaten Browserfenster angezeigt wird:

Wenn Sie oben [Ansicht/Quelltext] auswählen, erhalten Sie den folgenden Code:

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

Wir erhalten die folgende Seite:

Klicken wir oben auf die Schaltfläche [Anrufen]:

In allen Fällen hat die Antwort des Servers das folgende Format:
- Die Antwort liegt im XML-Format vor
- Zeile 1 ist Standard und immer in der Antwort enthalten
- Die folgenden Zeilen hängen vom Ergebnistyp (double, ArrayOfDouble), der Anzahl der Ergebnisse und dem Webservice-Namespace (in diesem Fall st.istia.univ-angers.fr) ab.
Es gibt mehrere Methoden, um einen Webdienst abzufragen und dessen Antwort zu erhalten. Kehren wir zur URL des Dienstes zurück:

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



Diese beiden Methoden für den Zugriff auf die Funktionen eines Webdienstes heißen: HTTP-POST und SOAP. Wir werden sie nun nacheinander betrachten.
Hinweis: In früheren 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 GET-Anfragen akzeptiert. Das bedeutet jedoch nicht, dass Sie keine Webdienste schreiben können, die GET-Anfragen akzeptieren, insbesondere wenn Sie andere Tools als VS.NET verwenden oder den Code einfach von Hand schreiben.
10.5. Ein HTTP-POST-Client
Wir werden der vom Webdienst vorgeschlagenen Methode folgen:

Schauen wir uns den Text genauer an. Zunächst muss der Web-Client die folgenden HTTP-Header senden:
Der Webclient sendet eine POST-Anfrage an die URL /operations/operations.asmx/add gemäß dem HTTP-Protokoll der Version 1.1 | |
Wir geben den Zielrechner für die Anfrage an. Hier ist das localhost. Dieser Header wurde durch Version 1.1 des HTTP-Protokolls zur Pflicht gemacht | |
Dies gibt an, dass nach den HTTP-Headern zusätzliche Parameter im URL-kodierten Format gesendet werden. Bei diesem Format werden bestimmte Zeichen durch ihre Hexadezimalcodes ersetzt. | |
Dies ist die Zeichenanzahl der Parameterzeichenfolge, die nach den HTTP-Headern gesendet wird. |
Auf die HTTP-Header folgt eine Leerzeile, dann die POST-Parameterzeichenfolge mit [Content-Length] Zeichen in der Form a=XX&b=YY, wobei XX und YY die „URL-kodierten“ Zeichenfolgen der Werte der Parameter a und b sind. Wir wissen genug, um das Obige mit unserem generischen TCP-Client zu reproduzieren, der bereits im Kapitel über 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 DOS-Fenster
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 zu beachten, 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 notwendig. Wenn wir dies nicht angeben, hält der Server die Verbindung standardmäßig offen. Seine Antwort besteht jedoch aus einer Folge von Textzeilen, von denen die letzte nicht durch ein Zeilenendezeichen beendet wird. Es stellt sich heraus, dass unser generischer TCP-Client Textzeilen, die durch ein Zeilenendezeichen beendet werden, mit der ReadLine-Methode liest. Wenn der Server die Verbindung nach dem Senden der letzten Zeile nicht schließt, wird der Client blockiert, da er auf ein Zeilenendezeichen wartet, das niemals eintrifft. Wenn der Server die Verbindung schließt, wird die ReadLine-Methode des Clients abgeschlossen, und der Client wird nicht blockiert.
Unmittelbar nach dem Empfang der Leerzeile, die das Ende der HTTP-Header signalisiert, sendet der IIS-Server 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 HTTP-Headern besteht, teilt dem Client mit, dass er die 7 Zeichen senden kann, die er senden wollte. Was wir tun:
Beachten Sie, dass unser TCP-Client hier mehr als 7 Zeichen sendet, da er diese mit einem Zeilenende-Marker (WriteLine) versendet. Dies hat keine Auswirkungen auf den Server, der nur die ersten 7 der empfangenen Zeichen übernimmt, da die Verbindung anschließend geschlossen wird (Connection: close). Diese zusätzlichen 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 Elemente, um ein Client-Programm für unseren Webdienst zu schreiben. Es handelt sich um einen Konsolen-Client namens 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 die 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 „function“ die aufgerufene Webservice-Funktion ist (add, subtract, multiply, divide) und „a“ und „b“ die Werte sind, auf die diese Funktion angewendet wird. Zum Beispiel:
Von dort aus sendet der Client die erforderliche HTTP-Anfrage an den Webserver und erhält eine Antwort. Der Austausch zwischen Client und Server wird auf dem Bildschirm angezeigt, damit Sie den Vorgang besser nachvollziehen können:
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]
Der oben gezeigte Austausch entspricht dem, den wir beim generischen TCP-Client gesehen haben, mit einem Unterschied: Der HTTP-Header **Connection: Keep-Alive weist den Server an, die Verbindung nicht zu schließen. Die Verbindung bleibt daher für den nächsten Vorgang des Clients offen, sodass der Client keine neue Verbindung zum Server herstellen muss. Dies erfordert jedoch, dass der Client eine andere Methode als ReadLine() verwendet, um die Antwort des Servers zu lesen, da wir wissen, dass die Antwort aus einer Folge von Zeilen besteht, von denen die letzte nicht durch ein Zeilenumbruchzeichen beendet wird. Sobald die gesamte Serverantwort empfangen wurde, analysiert der Client sie, um das Ergebnis der angeforderten Operation zu finden und anzuzeigen:
Sehen wir uns den Code unseres Clients an:
' namespaces
Imports System
Imports System.Net.Sockets
Imports System.IO
Imports System.Text.RegularExpressions
Imports System.Collections
Imports Microsoft.VisualBasic
Imports System.Web
' web operations client
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"}
' number of arguments
If args.Length <> 1 Then
erreur(syntaxe, 1)
End If
' note the URI required
Dim URIstring As String = args(0)
' connect to the server
Dim uri As Uri = Nothing ' the URI of the web service
Dim client As TcpClient = Nothing ' the client's tcp link with the server
Dim [IN] As StreamReader = Nothing ' the customer's reading flow
Dim OUT As StreamWriter = Nothing ' the customer's writing flow
Try
' server connection
uri = New Uri(URIstring)
client = New TcpClient(uri.Host, uri.Port)
' create customer input/output flows TCP
[IN] = New StreamReader(client.GetStream())
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
Catch ex As Exception
' URI incorrect or other problem
erreur("L'erreur suivante s'est produite : " + ex.Message, 2)
End Try
' creation of a dictionary of web service functions
Dim dicoFonctions As New Hashtable
Dim i As Integer
For i = 0 To fonctions.Length - 1
dicoFonctions.Add(fonctions(i), True)
Next i
' user requests are typed on the keyboard
' as function a b
' they are terminated with the command fin
Dim commande As String = Nothing ' keyboard command
Dim champs As String() = Nothing ' command line fields
Dim fonction As String = Nothing ' name of a web service function
Dim a, b As String ' web service function arguments
' invites the user
Console.Out.WriteLine("Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser] a b")
' error management
Dim erreurCommande As Boolean
Try
' keyboard command input loop
While True
' no error at start
erreurCommande = False
' read command
commande = Console.In.ReadLine().Trim().ToLower()
' finished?
If commande Is Nothing Or commande = "fin" Then
Exit While
End If
' breaking down the order into fields
champs = Regex.Split(commande, "\s+")
Try
' three fields are required
If champs.Length <> 3 Then
Throw New Exception
End If
' field 0 must be a recognized function
fonction = champs(0)
If Not dicoFonctions.ContainsKey(fonction) Then
Throw New Exception
End If
' field 1 must be a valid number
a = champs(1)
Double.Parse(a)
' field 2 must be a valid number
b = champs(2)
Double.Parse(b)
Catch
' invalid order
Console.Out.WriteLine("syntaxe : [ajouter|soustraire|multiplier|diviser] a b")
erreurCommande = True
End Try
' request the web service
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
' end of client-server link
Try
[IN].Close()
OUT.Close()
client.Close()
Catch
End Try
End Sub
...........
' error display
Public Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' error display
System.Console.Error.WriteLine(msg)
' stop with error
Environment.Exit(exitCode)
End Sub
End Module
Diese Elemente haben wir bereits mehrfach gesehen, und sie bedürfen keiner besonderen Erläuterung. Sehen wir uns nun den Code für die Methode executeFonction an, in der sich die neuen Elemente befinden:
' 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)
' executes function(a,b) on the URI uri web service
' client-server exchanges take place via IN and OUT flows
' the result of the function is in the line
' <double xmlns="st.istia.univ-angers.fr">double</double>
' sent by the server
' query chain construction
Dim requête As String = "a=" + HttpUtility.UrlEncode(a) + "&b=" + HttpUtility.UrlEncode(b)
Dim nbChars As Integer = requête.Length
' construction of header table HTTP to be sent
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) = ""
' send HTTP headers to the server
Dim i As Integer
For i = 0 To entetes.Length - 1
' send to server
OUT.WriteLine(entetes(i))
' screen echo
Console.Out.WriteLine(("--> " + entetes(i)))
Next i
' we read the 1st web server response HTTP/1.1 100
Dim ligne As String = Nothing
' a line in the read stream
ligne = [IN].ReadLine()
While ligne <> ""
'echo
Console.Out.WriteLine(("<-- " + ligne))
' next line
ligne = [IN].ReadLine()
End While
'last line echo
Console.Out.WriteLine(("<-- " + ligne))
' send request parameters
OUT.Write(requête)
' echo
Console.Out.WriteLine(("--> " + requête))
' construction of the regular expression to find the response size XML
' in the web server response stream
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
' read the second response from the web server after sending the request
' the value of the Content-Length line is stored
ligne = [IN].ReadLine()
While ligne <> ""
' screen echo
Console.Out.WriteLine(("<-- " + ligne))
' Content-Length ?
MatchLength = RegexLength.Match(ligne)
If MatchLength.Success Then
longueur = Integer.Parse(MatchLength.Groups(1).Value)
End If
' next line
ligne = [IN].ReadLine()
End While
' last line echo
Console.Out.WriteLine("<--")
' build the regular expression to retrieve the result
' in the web server response stream
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
' we read the rest of the web server response
Dim chrRéponse(longueur) As Char
[IN].Read(chrRéponse, 0, longueur)
Dim strRéponse As String = New [String](chrRéponse)
' the answer is broken down into lines of text
Dim lignes As String() = Regex.Split(strRéponse, ControlChars.Lf)
' scroll through the lines of text looking for the result
Dim strRésultat As String = "?" ' function result
For i = 0 To lignes.Length - 1
' follow-up
Console.Out.WriteLine(("<-- " + lignes(i)))
' compare current line to model
MatchRésultat = ModèleRésultat.Match(lignes(i))
' have we found?
If MatchRésultat.Success Then
' we note the result
strRésultat = MatchRésultat.Groups(1).Value
End If
Next i
' the result is displayed
Console.Out.WriteLine(("[résultat=" + strRésultat + "]" + ControlChars.Lf))
End Sub
Zunächst sendet der HTTP-POST-Client seine Anfrage im POST-Format:
' query chain construction
Dim requête As String = "a=" + HttpUtility.UrlEncode(a) + "&b=" + HttpUtility.UrlEncode(b)
Dim nbChars As Integer = requête.Length
' construction of header table HTTP to be sent
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) = ""
' send HTTP headers to the server
Dim i As Integer
For i = 0 To entetes.Length - 1
' send to server
OUT.WriteLine(entetes(i))
' screen echo
Console.Out.WriteLine(("--> " + entetes(i)))
Next i
In der Kopfzeile
Wir müssen die Größe der Parameter angeben, die vom Client hinter den HTTP-Headern gesendet werden:
Verwenden Sie dazu den folgenden Code:
' query chain construction
Dim requête As String = "a=" + HttpUtility.UrlEncode(a) + "&b=" + HttpUtility.UrlEncode(b)
Dim nbChars As Integer = requête.Length
Die Methode HttpUtility.UrlEncode(string string) wandelt bestimmte Zeichen in der Zeichenfolge in %n1n2 um, wobei n1n2 der ASCII-Code des umgewandelten Zeichens ist. Die von dieser Konvertierung betroffenen Zeichen sind alle Zeichen, die in einer POST-Anfrage eine bestimmte Bedeutung haben (Leerzeichen, =, & usw.). Hier ist die Methode HttpUtility.UrlEncode normalerweise nicht erforderlich, da a und b Zahlen sind, die keine dieser Sonderzeichen enthalten. Sie wird hier als Beispiel verwendet. Sie erfordert den Namespace System.Web. Sobald der Client seine HTTP-Header 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 HTTP-Header „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 einfach diese erste Antwort ein und zeigt sie auf dem Bildschirm an:
' we read the 1st web server response HTTP/1.1 100
Dim ligne As String = Nothing
' a line in the read stream
ligne = [IN].ReadLine()
While ligne <> ""
'echo
Console.Out.WriteLine(("<-- " + ligne))
' next line
ligne = [IN].ReadLine()
End While
'last line echo
Console.Out.WriteLine(("<-- " + ligne))
Sobald diese erste Antwort gelesen wurde, muss der Client seine Parameter senden:
Dies geschieht mit dem folgenden Code:
' envoi paramètres de la requête
OUT.Write(requête)
' echo
Console.Out.WriteLine(("--> " + requête))
Der Server sendet daraufhin seine Antwort. Diese Antwort besteht aus zwei Teilen:
- HTTP-Header, gefolgt von einer Leerzeile
- die Antwort im XML-Format
<-- 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 HTTP-Header, um die Zeile „Content-Length“ zu finden und die Größe der XML-Antwort abzurufen (hier 90). Dies geschieht mithilfe eines regulären Ausdrucks. Wir hätten dies auch anders und wahrscheinlich effizienter lösen können.
' construction of the regular expression to find the response size XML
' in the web server response stream
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
' read the second response from the web server after sending the request
' the value of the Content-Length line is stored
ligne = [IN].ReadLine()
While ligne <> ""
' screen echo
Console.Out.WriteLine(("<-- " + ligne))
' Content-Length ?
MatchLength = RegexLength.Match(ligne)
If MatchLength.Success Then
longueur = Integer.Parse(MatchLength.Groups(1).Value)
End If
' next line
ligne = [IN].ReadLine()
End While
' last line echo
Console.Out.WriteLine("<--")
Sobald wir die Länge N der XML-Antwort haben, müssen wir lediglich N Zeichen aus dem IN-Stream der Serverantwort lesen. Diese Zeichenfolge aus N Zeichen wird für die Bildschirmüberwachung in Textzeilen aufgeteilt. Unter diesen Zeilen suchen wir nach der Zeile, die das Ergebnis enthält:
wiederum unter Verwendung eines regulären Ausdrucks. Sobald das Ergebnis gefunden wurde, wird es angezeigt.
Der Client-Code endet wie folgt:
' build the regular expression to retrieve the result
' in the web server response stream
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
' we read the rest of the web server response
Dim chrRéponse(longueur) As Char
[IN].Read(chrRéponse, 0, longueur)
Dim strRéponse As String = New [String](chrRéponse)
' the answer is broken down into lines of text
Dim lignes As String() = Regex.Split(strRéponse, ControlChars.Lf)
' scroll through the lines of text looking for the result
Dim strRésultat As String = "?" ' function result
For i = 0 To lignes.Length - 1
' follow-up
Console.Out.WriteLine(("<-- " + lignes(i)))
' compare current line to model
MatchRésultat = ModèleRésultat.Match(lignes(i))
' have we found?
If MatchRésultat.Success Then
' we note the result
strRésultat = MatchRésultat.Groups(1).Value
End If
Next i
' the result is displayed
Console.Out.WriteLine(("[résultat=" + strRésultat + "]" + ControlChars.Lf))
End Sub
10.6. Ein SOAP-Client
Hier betrachten wir einen zweiten Client, der einen SOAP-Client-Server-Dialog (Simple Object Access Protocol) verwendet. Ein Beispiel für einen solchen Dialog wird für die Funktion „add“ vorgestellt:


Die Anfrage des Clients ist eine POST-Anfrage. Wir werden daher einige der gleichen Mechanismen wie beim vorherigen Client sehen. Der Hauptunterschied besteht darin, dass der HTTP-POST-Client die Parameter a und b im Formular gesendet hat
, sendet der SOAP-Client diese in einem komplexeren XML-Format:
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 es eine XML-Antwort, 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 hier tatsächlich um denselben HTTP-Mechanismus wie beim HTTP-POST-Client. Der SOAP-Client-Code kann daher nach dem Vorbild des HTTP-POST-Clients gestaltet 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 SOAP-Client sendet die HTTP-Header für seine 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)
' executes function(a,b) on the URI uri web service
' client-server exchanges take place via IN and OUT flows
' the result of the function is in the line
' <double xmlns="st.istia.univ-angers.fr">double</double>
' sent by the server
' construction of query string 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
' construction of header table HTTP to be sent
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) = ""
' send HTTP headers to the server
Dim i As Integer
For i = 0 To entetes.Length - 1
' send to server
OUT.WriteLine(entetes(i))
' screen echo
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 Code zum Auslesen dieser ersten Antwort lautet wie folgt:
' we read the 1st web server response HTTP/1.1 100
Dim ligne As String = Nothing
' a line in the read stream
ligne = [IN].ReadLine()
While ligne <> ""
'echo
Console.Out.WriteLine(("<-- " + ligne))
' next line
ligne = [IN].ReadLine()
End While 'while
'last line echo
Console.Out.WriteLine(("<-- " + ligne))
Der Client sendet nun seine Parameter im XML-Format in einem sogenannten SOAP-Envelope:
--> <?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:
' envoi paramètres de la requête
OUT.Write(requêteSOAP)
' echo
Console.Out.WriteLine(("--> " + requêteSOAP))
Der Server sendet dann 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 Client zeigt die empfangenen HTTP-Header auf dem Bildschirm an und sucht dabei nach der Zeile „Content-Length“:
' construction of the regular expression to find the response size XML
' in the web server response stream
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
' read the second response from the web server after sending the request
' the value of the Content-Length line is stored
ligne = [IN].ReadLine()
While ligne <> ""
' screen echo
Console.Out.WriteLine(("<-- " + ligne))
' Content-Length ?
MatchLength = RegexLength.Match(ligne)
If MatchLength.Success Then
longueur = Integer.Parse(MatchLength.Groups(1).Value)
End If
' next line
ligne = [IN].ReadLine()
End While 'while
' last line echo
Console.Out.WriteLine("<--")
Sobald die Größe N der XML-Antwort bekannt ist, liest der Client N Zeichen aus dem Antwortstrom des Servers, teilt die abgerufene Zeichenfolge in Textzeilen auf, um sie auf dem Bildschirm anzuzeigen, und sucht nach dem XML-Tag des Ergebnisses: <ajouterResult>7</ajouterResult> und zeigt es an:
' build the regular expression to retrieve the result
' in the web server response stream
Dim modèle As String = "<" + fonction + "Result>(.+?)</" + fonction + "Result>"
Dim ModèleRésultat As New Regex(modèle)
Dim MatchRésultat As Match = Nothing
' we read the rest of the web server response
Dim chrRéponse(longueur) As Char
[IN].Read(chrRéponse, 0, longueur)
Dim strRéponse As String = New [String](chrRéponse)
' the answer is broken down into lines of text
Dim lignes As String() = Regex.Split(strRéponse, ControlChars.Lf)
' scroll through the lines of text looking for the result
Dim strRésultat As String = "?" ' function result
For i = 0 To lignes.Length - 1
' follow-up
Console.Out.WriteLine(("<-- " + lignes(i)))
' compare current line to model
MatchRésultat = ModèleRésultat.Match(lignes(i))
' have we found?
If MatchRésultat.Success Then
' we note the result
strRésultat = MatchRésultat.Groups(1).Value
End If
'next line
Next i
' the result is displayed
Console.Out.WriteLine(("[résultat=" + strRésultat + "]" + ControlChars.Lf))
End Sub
10.7. Kapselung der Client-Server-Kommunikation
Stellen wir uns vor, dass unsere Webdienst-Operationen von verschiedenen Anwendungen genutzt werden. Es wäre sinnvoll, diesen Anwendungen eine Klasse zur Verfügung zu stellen, die als Schnittstelle zwischen der Client-Anwendung und dem Webdienst fungiert und den Großteil der Netzwerkkommunikation verbirgt, was für die meisten Entwickler nicht trivial ist. Dies würde zu folgender Architektur führen:
![]() |
Die Client-Anwendung würde sich an die Client-Server-Schnittstelle wenden, um ihre Anfragen an den Webdienst zu stellen. Die Schnittstelle würde die gesamte erforderliche Netzwerkkommunikation mit dem Server übernehmen und das Ergebnis an die Client-Anwendung zurückgeben. Die Client-Anwendung müsste sich nicht mehr um die Kommunikation mit dem Server kümmern, was ihre Entwicklung erheblich vereinfachen würde.
10.7.1. Die Kapselungsklasse
Auf der Grundlage dessen, was wir in den vorangegangenen Abschnitten behandelt haben, verfügen wir nun über ein gutes Verständnis der Netzwerkkommunikation zwischen Client und Server. Wir haben uns sogar drei Methoden angesehen. Wir haben uns dafür entschieden, die SOAP-Methode zu kapseln. Die Klasse sieht wie folgt aus:
' namespaces
Imports System
Imports System.Net.Sockets
Imports System.IO
Imports System.Text.RegularExpressions
Imports System.Collections
Imports System.Web
Imports Microsoft.VisualBasic
' clientSOAP of the Web operations service
Public Class clientSOAP
' instance variables
Private uri As uri = Nothing ' the URI of the web service
Private client As TcpClient = Nothing ' the client's tcp link with the server
Private [IN] As StreamReader = Nothing ' the customer's reading flow
Private OUT As StreamWriter = Nothing ' the customer's writing flow
' function dictionary
Private dicoFonctions As New Hashtable
' function list
Private fonctions As String() = {"ajouter", "soustraire", "multiplier", "diviser"}
' verbose
Private verbose As Boolean = False ' to true, displays client-server exchanges on screen
' manufacturer
Public Sub New(ByVal uriString As String, ByVal verbose As Boolean)
' we note verbose
Me.verbose = verbose
' server connection
uri = New Uri(uriString)
client = New TcpClient(uri.Host, uri.Port)
' create customer input/output flows TCP
[IN] = New StreamReader(client.GetStream())
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
' create a dictionary of web service functions
Dim i As Integer
For i = 0 To fonctions.Length - 1
dicoFonctions.Add(fonctions(i), True)
Next i
End Sub
' close server connection
Public Sub Close()
' end of client-server link
[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
' executes function(a,b) on the URI uri web service
' client-server exchanges take place via IN and OUT flows
' the result of the function is in the line
' <double xmlns="st.istia.univ-angers.fr">double</double>
' sent by the server
' valid function?
fonction = fonction.Trim().ToLower()
If Not dicoFonctions.ContainsKey(fonction) Then
Return "[fonction [" + fonction + "] indisponible : (ajouter, soustraire,multiplier,diviser)]"
End If
' valid arguments a and b?
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 by zero?
If fonction = "diviser" And doubleB = 0 Then
Return "[division par zéro]"
End If
' construction of query string 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
' construction of header table HTTP to be sent
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) = ""
' send HTTP headers to the server
Dim i As Integer
For i = 0 To entetes.Length - 1
' send to server
OUT.WriteLine(entetes(i))
' screen echo
If verbose Then
Console.Out.WriteLine(("--> " + entetes(i)))
End If
Next i
' we read the 1st web server response HTTP/1.1 100
Dim ligne As String = Nothing
' a line in the read stream
ligne = [IN].ReadLine()
While ligne <> ""
'echo
If verbose Then
Console.Out.WriteLine(("<-- " + ligne))
End If
' next line
ligne = [IN].ReadLine()
End While
'last line echo
If verbose Then
Console.Out.WriteLine(("<-- " + ligne))
End If
' send request parameters
OUT.Write(requêteSOAP)
' echo
If verbose Then
Console.Out.WriteLine(("--> " + requêteSOAP))
End If
' construction of the regular expression to find the response size XML
' in the web server response stream
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
' read the second response from the web server after sending the request
' the value of the Content-Length line is stored
ligne = [IN].ReadLine()
While ligne <> ""
' screen echo
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
' next line
ligne = [IN].ReadLine()
End While
' last line echo
If verbose Then
Console.Out.WriteLine("<--")
End If
' build the regular expression to retrieve the result
' in the web server response stream
Dim modèle As String = "<" + fonction + "Result>(.+?)</" + fonction + "Result>"
Dim ModèleRésultat As New Regex(modèle)
Dim MatchRésultat As Match = Nothing
' we read the rest of the web server response
Dim chrRéponse(longueur) As Char
[IN].Read(chrRéponse, 0, longueur)
Dim strRéponse As String = New [String](chrRéponse)
' the answer is broken down into lines of text
Dim lignes As String() = Regex.Split(strRéponse, ControlChars.Lf)
' scroll through the lines of text looking for the result
Dim strRésultat As String = "?" ' function result
For i = 0 To lignes.Length - 1
' follow-up
If verbose Then
Console.Out.WriteLine(("<-- " + lignes(i)))
End If ' compare current line to model
MatchRésultat = ModèleRésultat.Match(lignes(i))
' have we found?
If MatchRésultat.Success Then
' we note the result
strRésultat = MatchRésultat.Groups(1).Value
End If
Next i
' return the result
Return strRésultat
End Function
End Class
Hier gibt es nichts Neues im Vergleich zu dem, was wir bereits gesehen haben. Wir haben einfach den Code aus dem SOAP-Client, den wir untersucht haben, übernommen und ihn leicht umgestaltet, um daraus eine Klasse zu machen. Diese Klasse verfügt über einen Konstruktor und zwei Methoden:
' manufacturer
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
' close server connection
Public Sub Close()
und verfügt über die folgenden Attribute:
' variables d'instance
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
' dictionnaire des fonctions
Private dicoFonctions As New Hashtable
' liste des fonctions
Private fonctions As String() = {"ajouter", "soustraire", "multiplier", "diviser"}
' verbose
Private verbose As Boolean = False ' à vrai, affiche à l'écran les échanges client-serveur
Wir übergeben zwei Parameter an den Konstruktor:
- die URI des Webdienstes, mit dem eine Verbindung hergestellt werden soll
- einen booleschen Parameter „verbose“, der, wenn er auf „true“ gesetzt ist, die Anzeige des Netzwerkverkehrs auf dem Bildschirm anfordert; andernfalls wird dieser nicht angezeigt.
Während der Konstruktion werden der IN-Stream für das Lesen aus dem Netzwerk, der OUT-Stream für das Schreiben ins Netzwerk und das vom Dienst verwaltete Funktionswörterbuch erstellt. Sobald das Objekt konstruiert ist, ist die Client-Server-Verbindung geöffnet und seine IN- und OUT-Streams sind einsatzbereit.
Die Methode „Close“ schließt die Verbindung zum Server.
Die Methode ExecuteFonction ist diejenige, die wir für den untersuchten SOAP-Client geschrieben haben, mit einigen geringfügigen Unterschieden:
- Die Parameter `uri`, `IN` und `OUT`, die zuvor als Parameter an die Methode übergeben wurden, müssen nicht mehr übergeben werden, da sie nun Instanzattribute sind, auf die alle Methoden der Instanz zugreifen können
- Die Methode `ExecuteFonction`, die zuvor den Typ `void` zurückgab und das Ergebnis der Funktion auf dem Bildschirm anzeigte, gibt nun dieses Ergebnis zurück – und somit den Typ `string`.
In der Regel verwendet ein Client die Klasse clientSOAP wie folgt:
- Erstellen eines clientSOAP-Objekts, das die Verbindung zum Webdienst herstellt
- wiederholtes Aufrufen der Methode „executeFonction“
- Schließen der Verbindung zum Webdienst mithilfe der Methode „Close“.
Schauen wir uns einen ersten Client an.
10.7.2. Ein Konsolen-Client
Hier greifen wir den SOAP-Client wieder auf, den wir untersucht haben, als die Klasse clientSOAP noch nicht existierte, und gestalten ihn so um, dass er nun diese Klasse verwendet:
' namespaces
Imports System
Imports System.IO
Imports System.Text.RegularExpressions
Imports Microsoft.VisualBasic
Public Module testClientSoap
' requests the URI of the operations web service
' interactively executes keyboard commands
Public Sub Main(ByVal args() As String)
' syntax
Const syntaxe As String = "pg URI [verbose]"
' number of arguments
If args.Length <> 1 And args.Length <> 2 Then
erreur(syntaxe, 1)
End If
' verbose?
Dim verbose As Boolean = False
If args.Length = 2 Then
verbose = args(1).ToLower() = "verbose"
End If
' connect to the web service
Dim client As clientSOAP = Nothing
Try
client = New clientSOAP(args(0), verbose)
Catch ex As Exception
' connection error
erreur("L'erreur suivante s'est produite lors de la connexion au service web : " + ex.Message, 2)
End Try
' user requests are typed on the keyboard
' in the form function a b - they end with the command fin
Dim commande As String = Nothing ' keyboard command
Dim champs As String() = Nothing ' command line fields
' invites the user
Console.Out.WriteLine("Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser] a b" + ControlChars.Lf)
' error management
Dim erreurCommande As Boolean
Try
' keyboard command input loop
While True
' initially no error
erreurCommande = False
' read command
commande = Console.In.ReadLine().Trim().ToLower()
' finished?
If commande Is Nothing Or commande = "fin" Then
Exit While
End If
' breaking down the order into fields
champs = Regex.Split(commande, "\s+")
' three fields are required
If champs.Length <> 3 Then
Console.Out.WriteLine("syntaxe : [ajouter|soustraire|multiplier|diviser] a b")
' we note the error
erreurCommande = True
End If
' make a request to the web service
If Not erreurCommande Then Console.Out.WriteLine(("résultat=" + client.executeFonction(champs(0).Trim().ToLower(), champs(1).Trim(), champs(2).Trim())))
' following request
End While
Catch e As Exception
Console.Out.WriteLine(("L'erreur suivante s'est produite : " + e.Message))
End Try
' end of client-server link
Try
client.Close()
Catch
End Try
End Sub
' error display
Public Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' error display
System.Console.Error.WriteLine(msg)
' stop with error
Environment.Exit(exitCode)
End Sub
End Module
Der Client ist nun wesentlich einfacher und enthält keine Netzwerkkommunikation. Der Client akzeptiert zwei Parameter:
- die URI der Webdienst-Operationen
- das optionale Schlüsselwort „verbose“. Ist dieses vorhanden, werden die Netzwerkkommunikationen auf dem Bildschirm angezeigt.
Diese beiden Parameter werden verwendet, um ein clientSOAP-Objekt zu erstellen, das die Kommunikation mit dem Webdienst übernimmt.
' connect to the web service
Dim client As clientSOAP = Nothing
Try
client = New clientSOAP(args(0), verbose)
Catch ex As Exception
' connection error
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 dann durch Aufruf der Methode executeFonction des clientSOAP-Objekts an den Server gesendet.
' on fait la demande au service web
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 zu einer „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 dann wie folgt kompiliert:
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 Ausführung ohne ausführliche Ausgabe:
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
Sie können den Netzwerkverkehr überwachen, indem Sie eine „ausführliche“ Ausführung anfordern:
dos>testClientSOAP http://localhost/operations/operations.asmx verbose
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
Erstellen wir nun einen grafischen Client.
10.7.3. Ein grafischer Windows-Client
Wir werden nun unseren Webdienst über einen grafischen Client abfragen, der ebenfalls die Klasse clientSOAP verwendet. Die grafische Oberfläche sieht wie folgt aus:
![]() |
Die Steuerelemente sind wie folgt:
Nr. | Typ | Name | Rolle |
TextBox | txtURI | die URI der Webdienst-Operationen | |
Schaltfläche | btnOpen | öffnet die Verbindung zum Webdienst | |
Schaltfläche | btnClose | schließt die Verbindung zum Webdienst | |
ComboBox | cmbFunctions | die Liste der Funktionen (addieren, subtrahieren, multiplizieren, dividieren) | |
Textfeld | txtA | das Argument für Funktionen | |
Textfeld | txtB | Argument b der Funktionen | |
TextBox | txtResult | das Ergebnis von function(a,b) | |
Schaltfläche | btnCalculate | startet die Berechnung von Funktion(a,b) | |
Textfeld | txtError | zeigt eine Meldung zum Verbindungsstatus an |
Es gibt einige betriebliche Einschränkungen:
- Die Schaltfläche „btnOpen“ ist nur aktiv, wenn das Feld „txtURI“ nicht leer ist und noch keine Verbindung besteht
- Die Schaltfläche „btnClose“ ist nur aktiv, wenn eine Verbindung zum Webdienst geöffnet wurde
- Die Schaltfläche „btnCalculate“ ist nur aktiv, wenn eine Verbindung besteht und die Felder „txtA“ und „txtB“ nicht leer sind
- Für die Felder „txtResult“ und „txtError“ ist das Attribut „ReadOnly“ auf „true“ gesetzt
Der Client beginnt damit, die Verbindung zum Webdienst über die Schaltfläche [Open] zu öffnen:

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





Es folgt der Anwendungscode. Den Formularcode haben wir weggelassen, da er hier nicht relevant ist.
'namespaces
Imports System
Imports System.Windows.Forms
' the form class
Public Class FormClientSOAP
Inherits System.Windows.Forms.Form
' instance attributes
Dim client As clientSOAP ' client SOAP of the web operations service
#Region " Code généré par le Concepteur Windows Form "
Public Sub New()
MyBase.New()
'This call is required by the Windows Form Designer.
InitializeComponent()
' other initializations
myInit()
End Sub
'The substituted method Disposes of the form to clean up the list of components.
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
....
End Sub
...
Private Sub InitializeComponent()
....
End Sub
#End Region
Private Sub myInit()
' init form
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
' the content of the input field has changed - set the state of the open button
btnOuvrir.Enabled = txtURI.Text.Trim <> ""
End Sub
Private Sub btnOuvrir_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnOuvrir.Click
' request to open a connection with the web service
Try
' creation of an object of type [clientSOAP]
client = New clientSOAP(txtURI.Text.Trim, False)
' button status
btnOuvrir.Enabled = False
btnFermer.Enabled = True
' the URI can no longer be modified
txtURI.ReadOnly = True
' customer status
txtErreur.Text = "Liaison au service web ouverte"
Catch ex As Exception
' there has been an error - it is displayed
txtErreur.Text = ex.Message
End Try
End Sub
Private Sub btnFermer_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnFermer.Click
' closing the web service connection
client.Close()
' button states
btnOuvrir.Enabled = True
btnFermer.Enabled = False
' URI
txtURI.ReadOnly = False
' customer status
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
' calculating a function f(a,b)
' delete the previous result
txtRésultat.Text = ""
Try
txtRésultat.Text = client.executeFonction(cmbFonctions.Text, txtA.Text.Trim, txtB.Text.Trim)
Catch ex As Exception
' there has been a network error
txtErreur.Text = ex.Message
' we close the link
btnFermer_Click(Nothing, Nothing)
End Try
End Sub
Private Sub txtA_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles txtA.TextChanged
' change in the value of 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
' change in the value of B
txtA_TextChanged(Nothing, Nothing)
End Sub
' main method
Public Shared Sub main()
Application.Run(New FormClientSOAP)
End Sub
End Class
Auch hier verbirgt die clientSOAP-Klasse alle netzwerkbezogenen Aspekte der Anwendung. Die Anwendung wurde wie folgt erstellt:
- Die Assembly clientSOAP.dll, die die Klasse clientSOAP enthält, wurde im Projektordner abgelegt
- Die GUI 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 dann gestartet mit:
10.8. Ein Proxy-Client
Fassen wir zusammen, was wir gerade gemacht haben. Wir haben eine Zwischenklasse erstellt, die den Datenaustausch zwischen einem Client und einem Webdienst gemäß dem folgenden Diagramm kapseln:
![]() |
Die .NET-Plattform geht bei dieser Logik noch einen Schritt weiter. Sobald wir wissen, auf welchen Webdienst wir zugreifen wollen, können wir automatisch die Klasse generieren, die als Vermittler fungiert, um auf die Funktionen des Webdienstes zuzugreifen und die gesamte Netzwerkschicht zu verbergen. Diese Klasse wird als Proxy für den Webdienst bezeichnet, für den sie generiert wurde.
Wie generiert man eine Webdienst-Proxy-Klasse? Ein Webdienst wird immer von einer Beschreibungsdatei im XML-Format begleitet. Wenn die URI unserer Webdienstoperationen http://localhost/operations/operations.asmx lautet, ist die zugehörige Beschreibungsdatei unter der URL http://localhost/operations/operations.asmx?wsdl verfügbar, wie im folgenden Screenshot zu sehen ist:

Dies ist eine XML-Datei, die alle Funktionen des Webdienstes genau beschreibt, einschließlich des Typs und der Anzahl der Parameter sowie des Typs des Ergebnisses für jede einzelne Funktion. Diese Datei wird als WSDL-Datei des Dienstes bezeichnet, da sie die WSDL (Web Services Description Language) verwendet. Aus dieser Datei kann mit dem Tool „wsdl“ eine Proxy-Klasse generiert werden:
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 WSDL-Tool generiert eine VB.NET-Quelldatei (Option /language=vb), die nach der Klasse benannt ist, die den Webdienst implementiert, in diesem Fall operations. Sehen wir uns einen Teil des generierten Codes an:
'------------------------------------------------------------------------------
' <autogenerated>
' This code was generated by a tool.
' Runtime Version: 1.1.4322.573
'
' Changes to this file may cause incorrect behavior and will be lost if
' the code is regenerated.
' </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
'
'This source code has been té automatically généré by wsdl, 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
'<remarks/>
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
'<remarks/>
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 mag auf den ersten Blick etwas komplex erscheinen. Wir müssen die Details jedoch nicht verstehen, um ihn nutzen zu können. Betrachten wir zunächst die Klassendeklaration:
Die Klasse trägt den Namen der Operationen 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“ die URL des mit dem Proxy verbundenen Webdienstes zu. Die oben gezeigte Operationsklasse definiert das Attribut „url“ nicht selbst. Es wird von der Klasse geerbt, von der der Proxy abgeleitet ist: System.Web.Services.Protocols.SoapHttpClientProtocol. Sehen wir uns nun an, was mit der Methode „add“ zusammenhängt:
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
Wir sehen, dass sie dieselbe Signatur wie im Operations-Webdienst hat, wo sie wie folgt definiert wurde:
Die Art und Weise, wie diese Klasse mit dem Webdienst kommuniziert, wird hier nicht gezeigt. Diese Kommunikation wird vollständig von der übergeordneten Klasse System.Web.Services.Protocols.SoapHttpClientProtocol abgewickelt. Der Proxy enthält 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 Operations-Webdienstes zu nutzen, benötigt ein Client lediglich die zuvor generierte Operations-Proxy-Klasse. Kompilieren wir diese Klasse in eine Assembly-Datei:
Schreiben wir nun einen Konsolen-Client. Er wird ohne Parameter aufgerufen und führt die über die Tastatur eingegebenen Befehle 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 Code des Kunden lautet wie folgt:
' namespaces
Imports System
Imports System.IO
Imports System.Text.RegularExpressions
Imports System.Collections
Imports Microsoft.VisualBasic
Public Module testClientProxy
' interactively executes keyboard commands
' and sends them to the web operations service
Public Sub Main()
' there are no more arguments - the web service's URL is hard-coded in the proxy
' creation of a dictionary of web service functions
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
' create a proxy operations object
Dim myOperations As operations = Nothing
Try
myOperations = New operations
Catch ex As Exception
' connection error
erreur("L'erreur suivante s'est produite lors de la connexion au proxy dy service web : " + ex.Message, 2)
End Try
' user requests are typed on the keyboard
' in the form function a b - they end with the command fin
Dim commande As String = Nothing ' keyboard command
Dim champs As String() = Nothing ' command line fields
' invites the user
Console.Out.WriteLine("Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser|toutfaire] a b" + ControlChars.Lf)
' some local data
Dim erreurCommande As Boolean
Dim fonction As String
Dim a, b As Double
' keyboard command input loop
While True
' initially no error
erreurCommande = False
' read command
commande = Console.In.ReadLine().Trim().ToLower()
' finished?
If commande Is Nothing Or commande = "fin" Then
Exit While
End If
' breaking down the order into fields
champs = Regex.Split(commande, "\s+")
Try
' three fields are required
If champs.Length <> 3 Then
Throw New Exception
End If
' field 0 must be a recognized function
fonction = champs(0)
If Not dicoFonctions.ContainsKey(fonction) Then
Throw New Exception
End If
' field 1 must be a valid number
a = Double.Parse(champs(1))
' field 2 must be a valid number
b = Double.Parse(champs(2))
Catch
' invalid order
Console.Out.WriteLine("syntaxe : [ajouter|soustraire|multiplier|diviser] a b")
erreurCommande = True
End Try
' make a request to the web service
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
' error display
Public Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' error display
System.Console.Error.WriteLine(msg)
' stop with error
Environment.Exit(exitCode)
End Sub
End Module
Wir betrachten hier nur den Code, der speziell für die Verwendung der Proxy-Klasse relevant ist. Zunächst wird ein Proxy-Operations-Objekt erstellt:
' create a proxy operations object
Dim myOperations As operations = Nothing
Try
myOperations = New operations
Catch ex As Exception
' connection error
erreur("L'erreur suivante s'est produite lors de la connexion au proxy dy service web : " + ex.Message, 2)
End Try
Die Zeilen a und b werden über die Tastatur eingegeben. Auf Grundlage dieser Informationen werden die entsprechenden Proxy-Methoden aufgerufen:
' make a request to the web service
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 beschäftigen wir uns zum ersten Mal mit der All-in-One-Funktion, die alle vier Operationen ausführt. Sie wurde bisher ignoriert, da sie ein Array von Zahlen zurückgibt, das in einem XML-Wrapper gekapselt ist, dessen Handhabung komplizierter ist als die einfachen XML-Antworten der anderen Funktionen, die nur ein einziges Ergebnis zurückgeben. Hier sehen wir anhand der Proxy-Klasse, dass die Verwendung der All-in-One-Methode 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. Konfigurieren eines Webdienstes
Ein Webdienst benötigt möglicherweise Konfigurationsinformationen, um korrekt initialisiert zu werden. Bei IIS können diese Informationen in einer Datei namens web.config abgelegt werden, die sich im selben Ordner wie der Webdienst befindet. Angenommen, wir möchten einen Webdienst erstellen, der zur Initialisierung zwei Informationen benötigt: einen Namen und ein Alter. Diese beiden Informationen können im folgenden Format in die Datei web.config eingefügt werden:
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<appSettings>
<add key="nom" value="tintin"/>
<add key="age" value="27"/>
</appSettings>
</configuration>
Die Initialisierungseinstellungen werden in einem XML-Container abgelegt:
Ein Initialisierungsparameter namens P mit dem Wert V wird mit der folgenden Zeile deklariert:
<add key="P" value="V"/>
Wie ruft der Webdienst diese Informationen ab? Wenn IIS einen Webdienst lädt, prüft es, ob sich im selben Ordner eine web.config-Datei befindet. Ist dies der Fall, liest es diese. Der Wert V eines Parameters P wird mithilfe der Anweisung abgerufen:
wobei ConfigurationSettings eine Klasse im Namespace 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
' attributes
Private nom As String
Private age As Integer
' manufacturer
Public Sub New()
' init attributes
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 „Person“ verfügt über zwei Attribute, „name“ und „age“, die in seinem parameterlosen Konstruktor mit Werten initialisiert werden, die aus der Konfigurationsdatei „web.config“ des Webdienstes „Person“ gelesen werden. Diese Datei sieht wie folgt aus:
<configuration>
<appSettings>
<add key="nom" value="tintin"/>
<add key="age" value="27"/>
</appSettings>
</configuration>
Der Webdienst verfügt außerdem über eine <WebMethod> ohne Parameter, die lediglich die Attribute „name“ und „age“ zurückgibt. Der Dienst ist in der Quelldatei „personne.asmx“ registriert, die sich zusammen mit ihrer Konfigurationsdatei im Ordner „c:\inetpub\wwwroot\st\personne“ befindet:
Verknüpfen wir den virtuellen IIS-Ordner /config mit dem oben genannten physischen Ordner. Starten Sie IIS und rufen Sie dann über einen Browser die URL http://localhost/config/personne.asmx für den Person-Dienst auf:

Folgen Sie dem Link für die Single-ID-Methode:

Die id-Methode hat keine Parameter. Verwenden wir die Schaltfläche „Aufrufen“:

Wir haben die in der Datei „web.config“ des Dienstes gespeicherten Informationen erfolgreich abgerufen.
10.10. Der Webdienst zur Steuerberechnung
Wir kehren zur mittlerweile vertrauten IMPOTS-Anwendung zurück. Als wir das letzte Mal damit gearbeitet haben, haben wir sie in einen Remote-Server umgewandelt, auf den über das Internet zugegriffen werden kann. Nun werden wir sie in einen Webdienst umwandeln.
10.10.1. Der Webdienst
Wir beginnen mit der Klasse impôt, die im Kapitel über Datenbanken erstellt wurde und auf Informationen aus einer ODBC-Datenbank basiert:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Imports System.Data
Imports Microsoft.Data.Odbc
Imports System.Collections
Public Class impôt
' data required for tax calculation
' come from an external source
Private limites(), coeffR(), coeffN() As Decimal
' manufacturer
Public Sub New(ByVal LIMITES() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal)
' we check that the 3 tablaeux are the same size
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
' it's good
Me.limites = LIMITES
Me.coeffR = COEFFR
Me.coeffN = COEFFN
End Sub
' builder 2
Public Sub New(ByVal DSNimpots As String, ByVal Timpots As String, ByVal colLimites As String, ByVal colCoeffR As String, ByVal colCoeffN As String)
' initializes the three limit arrays, coeffR, coeffN from
' the contents of the Timpots table in the ODBC DSNimpots database
' colLimites, colCoeffR, colCoeffN are the three columns of this table
' can throw an exception
Dim connectString As String = "DSN=" + DSNimpots + ";" ' base connection chain
Dim impotsConn As OdbcConnection = Nothing ' the connection
Dim sqlCommand As OdbcCommand = Nothing ' the SQL command
' the SELECT query
Dim selectCommand As String = "select " + colLimites + "," + colCoeffR + "," + colCoeffN + " from " + Timpots
' tables to retrieve data
Dim tLimites As New ArrayList
Dim tCoeffR As New ArrayList
Dim tCoeffN As New ArrayList
' attempt to access the database
impotsConn = New OdbcConnection(connectString)
impotsConn.Open()
' create a command object
sqlCommand = New OdbcCommand(selectCommand, impotsConn)
' execute the query
Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader()
' Using the recovered table
While myReader.Read()
' the data of the current line are put in the tables
tLimites.Add(myReader(colLimites))
tCoeffR.Add(myReader(colCoeffR))
tCoeffN.Add(myReader(colCoeffN))
End While
' freeing up resources
myReader.Close()
impotsConn.Close()
' dynamic tables are placed in static tables
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
' tAX CALCULATION
Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Integer) As Long
' calculating the number of shares
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
' calculation of taxable income & family quota
Dim revenu As Decimal = 0.72D * salaire
Dim QF As Decimal = revenu / nbParts
' tAX CALCULATION
limites((limites.Length - 1)) = QF + 1
Dim i As Integer = 0
While QF > limites(i)
i += 1
End While
' return result
Return CLng(revenu * coeffR(i) - nbParts * coeffN(i))
End Function
End Class
Im Webdienst kann nur ein parameterloser Konstruktor verwendet werden. Daher sieht der Klassenkonstruktor wie folgt aus:
' manufacturer
Public Sub New()
' initializes the three limit arrays, coeffR, coeffN from
' the contents of the Timpots table in the ODBC DSNimpots database
' colLimites, colCoeffR, colCoeffN are the three columns of this table
' can throw an exception
' retrieve the service configuration parameters
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")
' database operation
Dim connectString As String = "DSN=" + DSNimpots + ";" ' base connection chain
Die fünf Parameter des Konstruktors aus der vorherigen Klasse werden nun aus der web.config-Datei des Dienstes gelesen. Der Code in der Quelldatei impots.asmx lautet wie folgt. Er enthält den Großteil des vorherigen Codes. Wir haben lediglich die für den Webdienst spezifischen Codeabschnitte umschlossen:
<%@ WebService language="VB" class=impots %>
' creation of a tax web service
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
' data required for tax calculation
' come from an external source
Private limites(), coeffR(), coeffN() As Decimal
Private OK As Boolean = False
Private errMessage As String = ""
' manufacturer
Public Sub New()
' initializes the three limit arrays, coeffR, coeffN from
' the contents of the Timpots table in the ODBC DSNimpots database
' colLimites, colCoeffR, colCoeffN are the three columns of this table
' can throw an exception
' retrieve the service configuration parameters
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")
' database operation
Dim connectString As String = "DSN=" + DSNimpots + ";" ' base connection chain
Dim impotsConn As OdbcConnection = Nothing ' the connection
Dim sqlCommand As OdbcCommand = Nothing ' the SQL command
Dim myReader As OdbcDataReader ' odbc data reader
' the SELECT query
Dim selectCommand As String = "select " + colLimites + "," + colCoeffR + "," + colCoeffN + " from " + Timpots
' tables to retrieve data
Dim tLimites As New ArrayList
Dim tCoeffR As New ArrayList
Dim tCoeffN As New ArrayList
' attempt to access the database
Try
impotsConn = New OdbcConnection(connectString)
impotsConn.Open()
' create a command object
sqlCommand = New OdbcCommand(selectCommand, impotsConn)
' execute the query
myReader = sqlCommand.ExecuteReader()
' Using the recovered table
While myReader.Read()
' the data of the current line are put in the tables
tLimites.Add(myReader(colLimites))
tCoeffR.Add(myReader(colCoeffR))
tCoeffN.Add(myReader(colCoeffN))
End While
' freeing up resources
myReader.Close()
impotsConn.Close()
' dynamic tables are placed in static tables
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
' it's good
OK = True
errMessage = ""
Catch ex As Exception
' error
OK = False
errMessage += "[" + ex.Message + "]"
End Try
End Sub
' tAX CALCULATION
<WebMethod()> _
Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Integer) As Long
' calculating the number of shares
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
' calculation of taxable income & family quota
Dim revenu As Decimal = 0.72D * salaire
Dim QF As Decimal = revenu / nbParts
' tAX CALCULATION
limites((limites.Length - 1)) = QF + 1
Dim i As Integer = 0
While QF > limites(i)
i += 1
End While
' return result
Return CLng(revenu * coeffR(i) - nbParts * coeffN(i))
End Function
' id
<WebMethod()> _
Function id() As String
' to see if everything is OK
Return "[" + OK + "," + errMessage + "]"
End Function
End Class
Lassen Sie uns die wenigen Änderungen erläutern, die an der Klasse „imports“ vorgenommen wurden und über das hinausgehen, was notwendig ist, um sie in einen Webdienst umzuwandeln:
- Das Einlesen der Datenbank im Konstruktor kann fehlschlagen. Deshalb haben wir unserer Klasse zwei Attribute und eine Methode hinzugefügt:
- Das boolesche Attribut `OK` ist wahr, wenn die Datenbank gelesen werden konnte, andernfalls falsch
- Die Zeichenkette `errMessage` enthält eine Fehlermeldung, falls die Datenbank nicht gelesen werden konnte.
- Die parameterlose Methode `id` ruft die Werte dieser beiden Attribute ab.
- Um mögliche Fehler beim Datenbankzugriff zu behandeln, wurde der Teil des Konstruktor-Codes, der sich auf diesen Zugriff bezieht, in einen try-catch-Block eingeschlossen.
Die Datei web.config für die Dienstkonfiguration sieht wie folgt aus:
<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>
Beim ersten Versuch, den impots-Dienst zu laden, meldete der Compiler, dass er den in der Anweisung verwendeten Namespace „Microsoft.Data.Odbc“ nicht finden konnte:
Nach Durchsicht der Dokumentation
- wurde eine Kompilierungsanweisung zur Datei web.config hinzugefügt, um festzulegen, dass die Microsoft.Data.odbc-Assembly verwendet werden soll
- wurde eine Kopie der Datei microsoft.data.odbc.dll im bin-Ordner des Projekts abgelegt. Dieser Ordner wird vom Webdienst-Compiler systematisch durchsucht, wenn er nach einer „Assembly“ sucht.
Andere Lösungen scheinen möglich, wurden hier jedoch nicht untersucht. Die Konfigurationsdatei sieht nun wie folgt aus:
<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>
Der Inhalt des Ordners „imports\bin“:
Der Dienst und seine Konfigurationsdatei wurden in „impots“ abgelegt:
Der physische Ordner für den Webdienst wurde im IIS dem virtuellen Ordner /impots zugeordnet. Die Dienstseite sieht dann wie folgt aus:

Wenn Sie dem id-Link folgen:

Wenn Sie die Schaltfläche „Anrufen“ verwenden:

Das vorherige Ergebnis zeigt die Werte der Attribute „OK“ (true) und „errMessage“ („“) an. In diesem Beispiel wurde die Datenbank erfolgreich geladen. Dies war jedoch nicht immer der Fall, weshalb wir die Methode „id“ hinzugefügt haben, um auf die Fehlermeldung zuzugreifen. Der Fehler bestand darin, dass der DSN-Name 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-ODBC-Source-Manager getroffen:
![]() |
Kehren wir zur Serviceseite zurück:

Klicken wir auf den Link „Berechnen“:

Wir definieren die Aufrufparameter und führen den Aufruf aus:

Das Ergebnis ist korrekt.
10.10.2. Generieren Sie den Proxy für den impots-Dienst
Da wir nun über einen funktionierenden „impots“-Webdienst verfügen, können wir dessen Proxy-Klasse generieren. Denken Sie daran, dass diese von Client-Anwendungen verwendet wird, um transparent auf den „impots“-Webdienst zuzugreifen. Zunächst verwenden wir das Dienstprogramm „wsdl“, um die Quelldatei für die Proxy-Klasse zu generieren, die anschließend zu einer DLL kompiliert wird.
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. Verwendung des Proxys mit einem Client
Im Kapitel über Datenbanken haben wir eine Konsolenanwendung zur Berechnung von Steuern erstellt:
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 dann die Standardsteuerklasse, die in der Datei „impots.dll“ enthalten ist. Der Code für das Programm „testimpots.vb“ lautete wie folgt:
Option Explicit On
Option Strict On
' namespaces
Imports System
Imports Microsoft.VisualBasic
' test pg
Module testimpots
Sub Main(ByVal arguments() As String)
' interactive tax calculator
' the user enters three data points on the keyboard: married nbEnfants salary
' the program then displays the tax payable
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"
' checking program parameters
If arguments.Length <> 5 Then
' error msg
Console.Error.WriteLine(syntaxe1)
' end
Environment.Exit(1)
End If 'if
' retrieve the arguments
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)
' tax object creation
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
' infinite loop
While True
' initially no errors
Dim erreur As Boolean = False
' tax calculation parameters are requested
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()
' anything to do?
If paramètres Is Nothing Or paramètres = "" Then
Exit While
End If
' check the number of arguments in the input line
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
' checking the validity of parameters
' married
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
' salary
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
' parameters are correct - tax is calculated
Console.Out.WriteLine(("impôt=" & objImpôt.calculer(marié = "o", nbEnfants, salaire).ToString + " F"))
End If
End While
End Sub
End Module
Wir verwenden dasselbe Programm, um nun den impots-Webdienst über die zuvor erstellte impots-Proxy-Klasse zu nutzen. Dazu müssen wir den Code leicht anpassen:
- Während die ursprüngliche Steuerklasse einen Konstruktor mit fünf Argumenten hatte, verfügt die Steuer-Proxy-Klasse über einen Konstruktor ohne Parameter. Wie wir gesehen haben, werden die fünf Parameter nun in der Konfigurationsdatei des Webdienstes festgelegt.
- Daher ist es 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
' test pg
Module testimpots
Public Sub Main(ByVal arguments() As String)
' interactive tax calculator
' the user enters three data points on the keyboard: married nbEnfants salary
' the program then displays the tax payable
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"
' tax object creation
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
' infinite loop
Dim erreur As Boolean
While True
' initially no error
erreur = False
' tax calculation parameters are requested
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()
' anything to do?
If paramètres Is Nothing Or paramètres = "" Then
Exit While
End If
' check the number of arguments in the input line
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
' checking parameter validity
' married
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
' salary
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
' if the parameters are correct - the tax is calculated
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 die impots.dll-Proxy-Datei und den Quellcode von 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 die Quelldatei „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 Sie es 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 das erwartete Ergebnis.





