10. Webservices
10.1. Inleiding
In het vorige hoofdstuk hebben we verschillende TCP/IP-client-servertoepassingen besproken. Aangezien de clients en de server tekstregels uitwisselen, kunnen ze in elke willekeurige programmeertaal worden geschreven. De client moet alleen het communicatieprotocol kennen dat de server verwacht. Webservices zijn TCP/IP-servertoepassingen met de volgende kenmerken:
- Ze worden gehost door webservers en het protocol voor de communicatie tussen client en server is daarom HTTP (HyperText Transport Protocol), een protocol dat bovenop TCP-IP is gebouwd.
- De webservice heeft een standaardcommunicatieprotocol, ongeacht de aangeboden dienst. Een webservice biedt diverse diensten aan: S1, S2, …, Sn. Elk daarvan verwacht parameters die door de client worden aangeleverd en levert een resultaat terug aan de client. Voor elke dienst moet de client het volgende weten:
- de exacte naam van de dienst, indien
- de lijst met parameters die moeten worden doorgegeven en het type daarvan
- het type resultaat dat door de service wordt teruggestuurd
Zodra deze elementen bekend zijn, verloopt de dialoog tussen client en server volgens hetzelfde patroon, ongeacht de opgevraagde webservice. Het schrijven van de clients is hierdoor gestandaardiseerd.
- Om veiligheidsredenen tegen aanvallen via het internet beschikken veel organisaties over privénetwerken en stellen ze slechts bepaalde poorten van hun servers open voor het internet: voornamelijk poort 80 van de webservice. Alle andere poorten zijn geblokkeerd. Daarom worden client-servertoepassingen zoals beschreven in het vorige hoofdstuk binnen het privénetwerk (intranet) gebouwd en zijn ze over het algemeen niet van buitenaf toegankelijk. Door een dienst op een webserver te hosten, wordt deze toegankelijk voor de hele internetgemeenschap.
- De webservice kan worden gemodelleerd als een object op afstand. De aangeboden diensten worden dan methoden van dit object. Een client kan toegang krijgen tot dit object op afstand alsof het lokaal is. Hierdoor wordt het gehele netwerkcommunicatiegedeelte verborgen en kan een client worden gebouwd die onafhankelijk is van deze laag. Als deze laag verandert, hoeft de client niet te worden aangepast. Dit is een enorm voordeel en waarschijnlijk het belangrijkste pluspunt van webservices.
- Net als bij de TCP/IP-client-servertoepassingen die in het vorige hoofdstuk zijn besproken, kunnen de client en de server in elke willekeurige programmeertaal worden geschreven. Ze wisselen tekstregels uit. Deze bestaan uit twee delen:
- de headers die nodig zijn voor het protocol HTTP
- de hoofdtekst van het bericht. Bij een antwoord van de server aan de client heeft dit de indeling XML (eXtensible Markup Language). Bij een verzoek van de client aan de server kan de hoofdtekst van het bericht verschillende vormen aannemen, waaronder XML. Het verzoek XML van de client kan een specifieke indeling hebben, namelijk SOAP (Simple Object Access Protocol). In dat geval volgt het antwoord van de server eveneens de indeling SOAP.
10.2. Browsers en XML
Webservices sturen XML naar hun klanten. Browsers kunnen verschillend reageren op de ontvangst van deze XML-stream. Internet Explorer heeft een vooraf gedefinieerd stylesheet waarmee dit kan worden weergegeven. Netscape Communicator beschikt niet over dit stylesheet en geeft de ontvangen XML-code niet weer. Je moet de broncode van de ontvangen pagina bekijken om toegang te krijgen tot de XML-code. Hier volgt een voorbeeld voor de volgende XML-code:
<?xml version="1.0" encoding="utf-8"?>
<string xmlns="st.istia.univ-angers.fr">bonjour de nouveau !</string>
Internet Explorer geeft de volgende pagina weer:

terwijl Netscape Navigator het volgende weergeeft:

Als we de broncode van de door Netscape ontvangen pagina bekijken, krijgen we:

Netscape heeft wel degelijk hetzelfde ontvangen als Internet Explorer, maar heeft het anders weergegeven. In het vervolg zullen we Internet Explorer gebruiken voor de schermafbeeldingen.
10.3. Een eerste webservice
We gaan webservices ontdekken aan de hand van een heel eenvoudig voorbeeld dat in drie versies wordt uitgewerkt.
10.3.1. Versie 1
Voor deze eerste versie gebruiken we VS.NET, dat het voordeel biedt dat het een direct werkend raamwerk voor een webservice kan genereren. Zodra we deze architectuur begrijpen, kunnen we op eigen kracht verdergaan. Dat is het doel van de volgende versies.
Laten we met VS.NET een nieuw project opzetten met de optie [Fichier/Nouveau/Projet]:

Let op de volgende punten:
- het projecttype is Visual Basic (linkervenster)
- het projectmodel is Webservice ASP.NET (rechtervenster)
- de locatie is vrij te kiezen. In dit geval wordt de webservice gehost door een lokale webserver IIS. Het adres wordt dan http://localhost/[chemin], waarbij [chemin] nog moet worden gedefinieerd. Hier kiezen we het pad http://localhost/polyvbnet/demo. VS.NET maakt dan een map aan voor dit project. Waar? De server IIS heeft een root voor de boomstructuur van de webdocumenten die hij levert. Laten we deze root <IISroot> noemen. Deze komt overeen met de URL http://localhost. Hieruit volgt dat de http://localhost/polyvbnet/demo van URL gekoppeld zal worden aan de map <IISroot>/polyvbnet/demo. <IISroot> is normaal gesproken de map \inetpub\wwwroot op de schijf waarop IIS is geïnstalleerd. In ons voorbeeld is dat schijf E. De map die door VS.NET is aangemaakt, is dus de map e:\inetpub\wwwroot\polyvbnet\demo:

Zoals altijd zijn er weer veel te veel mappen aangemaakt. Deze zijn niet altijd even nuttig. We zullen alleen die mappen toelichten die we op een bepaald moment nodig hebben. VS.NET heeft een project aangemaakt:

We vinden hier enkele bestanden terug die ook in de fysieke projectmap aanwezig zijn. Het meest interessant voor ons is het bestand met de extensie asmx. Dit is de extensie van webservices. Een webservice wordt door VS.NET beheerd als een Windows-toepassing, c.a.d: een toepassing met een grafische gebruikersinterface en code om deze te beheren. Daarom hebben we een ontwerpvenster:

Een webservice heeft normaal gesproken geen grafische interface. Het is een object dat op afstand kan worden aangeroepen. Het beschikt over methoden die door applicaties worden aangeroepen. We zullen het dus beschouwen als een klassiek object, met als bijzonderheid dat het op afstand via het netwerk kan worden geïnstantieerd. Daarom zullen we het ontwerpvenster dat wordt weergegeven door VS.NET niet gebruiken. Laten we ons in plaats daarvan richten op de code van de service met behulp van de optie Weergave/Code:

Er zijn een aantal punten die de aandacht verdienen:
- het bestand heet Service1.asmx.vb en niet Service1.asmx. We komen straks terug op de inhoud van het bestand Service1.asmx.
- we zien een codevenster dat vergelijkbaar is met het venster dat we hadden toen we Windows-toepassingen bouwden met VS.NET
De door VS.NET gegenereerde code is als volgt:
Imports System.Web.Services
<System.Web.Services.WebService(Namespace := "http://tempuri.org/demo/Service1")> _
Public Class Service1
Inherits System.Web.Services.WebService
#Regio "Code gegenereerd door de Web Services Designer"
Public Sub New()
MyBase.New()
'Deze aanroep is vereist door de Web Services Designer.
InitializeComponent()
'Voeg uw initialisatiecode toe na de aanroep InitializeComponent()
End Sub
'Vereist door de Web Services Designer
Private components As System.ComponentModel.IContainer
'REMARQUE: de volgende procedure is vereist door de Web Services Designer
'Deze kan worden gewijzigd met behulp van de Web Services Designer.
'Wijzig deze niet met behulp van de 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: deze procedure is vereist door de Web Services Designer
'Wijzig dit niet met behulp van de code-editor.
If disposing Then
If Not (components Is Nothing) Then
components.Dispose()
End If
End If
MyBase.Dispose(disposing)
End Sub
#Einde regio
' EXEMPLE DE SERVICE WEB
' Het voorbeeld van de service HelloWorld() retourneert de tekenreeks Hello World.
' Om het project te genereren, mag u de volgende regels niet uitcommentariëren; sla het project vervolgens op en genereer het.
' Om deze webservice te testen, moet je ervoor zorgen dat het .asmx-bestand de startpagina is
' en druk op F5.
'
'<WebMethod()> Public Function HelloWorld() As String
' HelloWorld = "Hello World"
' End Function
End Class
Allereerst merken we op dat we hier een klasse hebben, de klasse Service1, die is afgeleid van de klasse WebService:
Dit betekent dat we de naamruimte System.Web.Services moeten importeren:
Imports System.Web.Services
De declaratie van de klasse wordt voorafgegaan door een compilatieattribuut:
<System.Web.Services.WebService(Namespace := "http://tempuri.org/demo/Service1")> _
Public Class Service1
Inherits System.Web.Services.WebService
Het attribuut System.Web.Services.WebService() geeft aan dat de daaropvolgende klasse een webservice is. Dit attribuut ondersteunt verschillende parameters, waaronder een parameter met de naam NameSpace. Deze dient om de webservice in een naamruimte te plaatsen. Het is namelijk denkbaar dat er wereldwijd meerdere webservices met de naam meteo bestaan. We hebben een manier nodig om ze van elkaar te onderscheiden. Dat is mogelijk dankzij de naamruimte. De ene kan bijvoorbeeld [espacenom1].meteo heten en de andere [espacenom2].meteo. Dit is een concept dat vergelijkbaar is met de naamruimten van klassen. VS.NET heeft automatisch code gegenereerd en deze in een deel van de broncode geplaatst:
Als we deze code bekijken, zien we dezelfde code die de ontwerper genereerde bij het bouwen van Windows-toepassingen. Dit is code die we simpelweg kunnen verwijderen als we geen grafische interface hebben, wat bij webservices het geval is.
De les wordt afgesloten met een voorbeeld van hoe een webservice eruit zou kunnen zien:
#Einde Regio
' EXEMPLE DE SERVICE WEB
' Het voorbeeld van de service HelloWorld() retourneert de tekenreeks Hello World.
' Om het project te genereren, mag u de volgende regels niet uitcommentariëren; sla het project vervolgens op en genereer het.
' Om deze webservice te testen, moet je ervoor zorgen dat het .asmx-bestand de startpagina is
' en druk op F5.
'
'<WebMethod()> Public Function HelloWorld() As String
' HelloWorld = "Hello World"
' End Function
Op basis van het bovenstaande maken we de code opschoner, zodat deze er als volgt uitziet:
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
Nu wordt het wat duidelijker.
- Een webservice is een klasse die afstamt van de klasse WebService
- De klasse wordt gekwalificeerd door het attribuut <System.Web.Services.WebService(Namespace:="st.istia.univ-angers.fr")>. We plaatsen onze service dus in de naamruimte st.istia.univ-angers.fr.
- De methoden van de klasse worden gekwalificeerd door een attribuut <WebMethod()>, wat aangeeft dat het gaat om een methode die op afstand via het netwerk kan worden aangeroepen
De klasse die onze webservice verzorgt, heet dus Bonjour en heeft één enkele methode die ook Bonjour heet en een tekenreeks retourneert. We zijn klaar voor een eerste test.
- Laten we de webserver IIS starten, als dat nog niet is gebeurd
- gebruiken we de optie Debuggen/Uitvoeren zonder debuggen. VS.NET
VS.NET zal vervolgens de gehele applicatie compileren, een browser starten (vaak Internet Explorer, indien aanwezig) en de URL http://localhost/polyvbnet/demo/Service1.asmx weergeven:

Waarom de URL http://localhost/polyvbnet/demo/Service1.asmx? Omdat dit het enige .asmx-bestand in het project was:

Als er meerdere .asmx-bestanden waren geweest, hadden we moeten aangeven welk bestand als eerste moest worden uitgevoerd. Dit doe je door met de rechtermuisknop op het betreffende .asmx-bestand te klikken en de optie [Définir comme page de démarrage] te selecteren.

Misschien is het interessant om te weten wat er in het bestand service1.asmx staat. Met VS.NET hebben we namelijk aan het bestand service1.asmx.vb gewerkt en niet aan het bestand service1.asmx. Dit bestand bevindt zich in de projectmap:

Laten we het openen met een teksteditor (Kladblok of iets dergelijks). We krijgen dan de volgende inhoud te zien:
Het bestand bevat een eenvoudige instructie voor de server IIS, waarin staat:
- dat het om een webservice gaat (trefwoord WebService)
- dat de programmeertaal van deze webservice Visual Basic is (Language="vb")
- dat de broncode van deze klasse te vinden is in het bestand Service1.asmx.vb (Codebehind="Service1.asmx.vb")
- dat de klasse die de service implementeert demo.Bonjour heet (Class="demo.Bonjour"). Merk op dat VS.NET de klasse Bonjour in de naamruimte demo heeft geplaatst, wat ook de naam van het project is.
Laten we teruggaan naar de pagina die te vinden is via de URL http://localhost/polyvbnet/demo/Service1.asmx:

Wie heeft de code HTML van de bovenstaande pagina geschreven? Wij niet, dat weten we zeker. Het is IIS, dat de webservices op een standaardmanier presenteert. Deze pagina biedt ons twee links. Laten we de eerste volgen, [Description du service]:

Oeps... dat is nogal cryptisch XML. Laten we toch even letten op de URL
http://localhost/polyvbnet/demo/Service1.asmx?WSDL. Open een browser en typ deze URL rechtstreeks in. Je krijgt hetzelfde resultaat als eerder. We onthouden dus dat de URL http://serviceweb?WSDL toegang geeft tot de beschrijving XML van de webservice. Laten we teruggaan naar de startpagina en de link [Bonjour] volgen. Laten we niet vergeten dat Bonjour een methode van de webservice is. Als er meerdere methoden waren geweest, zouden ze hier allemaal zijn weergegeven. We krijgen de volgende nieuwe pagina te zien:

We hebben de verkregen pagina bewust ingekort om onze demonstratie overzichtelijk te houden. Laten we nogmaals kijken naar de verkregen URL:
Als we deze URL rechtstreeks in een browser invoeren, krijgen we hetzelfde resultaat als hierboven. We worden aangemoedigd om de knop [Appeler] te gebruiken. Laten we dat doen. We krijgen een nieuwe pagina te zien:

Dit is weer XML. We vinden hier twee gegevens terug die in onze webservice aanwezig waren:
- de naamruimte st.istia.univ-angers.fr van onze service
<System.Web.Services.WebService(Namespace:="st.istia.univ-angers.fr")>
- de waarde die door de Bonjour-methode wordt geretourneerd:
Return "bonjour !"
Wat hebben we geleerd?
- hoe je een S-webservice schrijft
- hoe we deze kunnen aanroepen
We gaan nu kijken hoe we een webservice kunnen schrijven zonder gebruik te maken van VS.NET.
10.3.2. Versie 2
In het vorige voorbeeld heeft VS.NET veel dingen helemaal zelf gedaan. Is het mogelijk om een webservice te bouwen zonder deze tool? Het antwoord is ja, en dat laten we nu zien. Met een teksteditor bouwen we de volgende webservice:
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
De klasse heet Bonjour2 en heeft een methode die getBonjour heet. Deze is geplaatst in het bestand demo2.vb, dat zich op zijn beurt in de boomstructuur van de server IIS bevindt, in de map E:\Inetpub\wwwroot\polyvbnet\demo2. Het is een klassieke VB.NET-klasse die dus gecompileerd kan worden:
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
We plaatsen de assemblage demo2.dll in een map met de naam 'bin' (deze naam is verplicht):
We maken nu het bestand demo2.asmx aan. Dit bestand wordt door de webclients aangeroepen. De inhoud ervan is als volgt:
We zijn deze richtlijn al eens tegengekomen. Deze geeft aan dat:
- de klasse van de webservice Bonjour2 heet en zich in de assembly demo2.dll bevindt. IIS zal op verschillende locaties naar deze assembly zoeken, met name in de map bin van de webservice. Daarom hebben we de assembly demo2.dll daar geplaatst.
Nu kunnen we verschillende tests uitvoeren. We controleren of IIS actief is en roepen met een browser de URL http://localhost/polyvbnet/demo2/demo2.asmx op:

Vervolgens de URL http://localhost/polyvbnet/demo2/demo2.asmx?WSDL

Vervolgens de URL http://localhost/polyvbnet/demo2/demo2.asmx?op=getBonjour, waarbij getBonjour de naam is van de enige methode van onze webservice:

We gebruiken de bovenstaande knop [Appeler]:

We krijgen inderdaad het resultaat van de aanroep van de methode getBonjour van de webservice. We weten nu hoe we een webservice kunnen bouwen zonder vs.net. We zullen voortaan abstractie maken van de manier waarop de webservice is opgebouwd en ons alleen richten op de fundamentele bestanden.
10.3.3. Versie 3
De twee voorgaande versies van de webservice [Bonjour] maakten gebruik van twee bestanden:
- een .asmx-bestand, het toegangspunt van de webservice
- een .vb-bestand, de broncode van de webservice
We laten hier zien dat het .asmx-bestand op zichzelf volstaat. De code van de service demo3.asmx is als volgt:
We zien dat de broncode van de webservice nu rechtstreeks in het bronbestand van demo3.asmx staat. De instructie
verwijst niet langer naar een klasse in een externe assembly, maar naar een klasse die zich in hetzelfde bronbestand bevindt. Laten we dit bestand in de map <IISroot>\polyvbnet\demo3 plaatsen:

Laten we IIS starten en de URL http://localhost/polyvbnet/demo3/demo3.asmx opvragen:

We zien een belangrijk verschil ten opzichte van de vorige versie: we hoefden de code VB van de service niet te compileren. IIS heeft deze compilatie zelf uitgevoerd via de compiler VB.NET die op dezelfde machine is geïnstalleerd. Vervolgens heeft het de pagina weergegeven. Als er een compilatiefout optreedt, wordt deze gemeld door IIS:

10.3.4. Versie 4
We richten ons hier op de configuratie van de server IIS. Tot nu toe hebben we onze webservices altijd in de hoofdmap <IISroot> van de server IIS geplaatst, in dit geval [e:\inetpub\wwwroot]. We laten hier zien dat we de webservice overal kunnen plaatsen. Dit doen we met behulp van de virtuele mappen van IIS. Laten we onze service in de volgende map plaatsen:

De map [D:\data\devel\vbnet\poly\chap9\demo3] bevindt zich niet in de mappenstructuur van de server IIS. We moeten dit aan de server doorgeven door een virtuele map IIS aan te maken. Laten we IIS starten en de onderstaande optie [Avancé] selecteren:

Er wordt een lijst met virtuele mappen weergegeven. We zullen hier niet verder op ingaan. We maken een nieuwe virtuele map aan met de knop [Ajouter] hierboven:

Met behulp van de knop [Parcourir] wijzen we de fysieke map aan die de webservice bevat, in dit geval de map [D:\data\devel\vbnet\poly\chap9\demo3]. We geven deze map een logische (virtuele) naam: [virdemo3]. Dit betekent dat de documenten in de fysieke map [D:\data\devel\vbnet\poly\chap9\demo3] via de URL [http://<machine>/virdemo3] op het netwerk toegankelijk zullen zijn. Het bovenstaande dialoogvenster bevat nog andere instellingen die we ongewijzigd laten. We bevestigen het dialoogvenster. De nieuwe virtuele map verschijnt in de lijst met virtuele mappen van IIS:

Nu openen we een browser en roepen we de URL [http://localhost/virdemo3/demo3.asmx] op. We krijgen hetzelfde resultaat als eerder:

10.3.5. Conclusie
We hebben verschillende manieren laten zien om een webservice te maken. Vervolgens zullen we de methode uit versie 3 gebruiken voor het aanmaken van de service en methode 4 voor de lokalisatie ervan. We hebben VS.NET dus niet nodig. Toch is het de moeite waard om VS.NET te gebruiken vanwege de hulp die het biedt bij het debuggen. Er bestaan gratis tools voor het ontwikkelen van webapplicaties, met name het door Microsoft gesponsorde product WebMatrix, dat te vinden is op URL [http://www.asp.net/webmatrix]. Dit is een uitstekende tool om zonder voorafgaande investering aan de slag te gaan met webprogrammering.
10.4. Een webservice voor bewerkingen
We bekijken een webservice die vijf functies biedt:
- toevoegen(a,b), die a+b oplevert
- aftrekken(a,b), wat a-b oplevert
- vermenigvuldigen(a,b), wat a*b oplevert
- delen(a,b), wat a/b oplevert
- allesdoen(a,b), wat de array [a+b,a-b,a*b,a/b] oplevert
De code VB.NET van deze dienst is als volgt:
<%@ 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
We herhalen hier enkele uitleg die al is gegeven, maar die het waard is om nog eens te worden vermeld of aangevuld. De klasse operations lijkt op een klasse VB.NET, maar er zijn enkele punten waar je op moet letten:
- de methoden worden voorafgegaan door een attribuut <WebMethod()> dat de compiler aangeeft welke methoden moeten worden "gepubliceerd" c.a.d. en beschikbaar moeten worden gesteld aan de client. Een methode die niet wordt voorafgegaan door dit attribuut, zou onzichtbaar zijn voor externe clients. Dit zou een interne methode kunnen zijn die door andere methoden wordt gebruikt, maar niet bedoeld is om te worden gepubliceerd.
- De klasse is afgeleid van de klasse WebService, gedefinieerd in de naamruimte System.Web.Services. Deze overerving is niet altijd verplicht. In dit voorbeeld zou men er bijvoorbeeld zonder kunnen.
- De klasse zelf wordt voorafgegaan door een attribuut <WebService(Namespace="st.istia.univ-angers.fr")> dat bedoeld is om de webservice een naamruimte te geven. Een leverancier van klassen kent een naamruimte toe aan zijn klassen om ze een unieke naam te geven en zo conflicten te voorkomen met klassen van andere leveranciers die dezelfde naam zouden kunnen dragen. Voor webservices geldt hetzelfde. Elke webservice moet kunnen worden geïdentificeerd aan de hand van een unieke naam, in dit geval st.istia.univ-angers.fr.
- We hebben geen constructor gedefinieerd. Daarom wordt impliciet de constructor van de bovenliggende klasse gebruikt.
De bovenstaande broncode is niet rechtstreeks bedoeld voor de compiler VB.NET, maar voor de webserver IIS. De code moet het achtervoegsel .asmx dragen en worden opgeslagen in de mapstructuur van de webserver. Hier slaan we het op onder de naam operations.asmx in de map <IISroot>\polyvbnet\operations:

Aan deze fysieke map koppelen we de virtuele map IIS [operations]:
![]() | ![]() |
Laten we de dienst openen met een browser. Het op te vragen URl is [http://localhost/operations/operations.asmx]:

We krijgen een webpagina te zien met een link voor elk van de methoden die zijn gedefinieerd in de webservice operations. Laten we de link ajouter volgen:

Op de pagina die we te zien krijgen, wordt ons voorgesteld om de methode ajouter te testen door de twee benodigde argumenten a en b in te voeren. Laten we de definitie van de methode ajouter nog eens bekijken:
Merk op dat de pagina de namen van de argumenten a en b heeft overgenomen die in de definitie van de methode worden gebruikt. We klikken op de knop Appeler en krijgen het volgende antwoord in een apart browservenster:

Als we hierboven [Affichage/Source] invoeren, krijgen we de volgende code:

Laten we de handeling herhalen voor de methode [toutfaire]:

We krijgen de volgende pagina:

Laten we de bovenstaande knop [Appeler] gebruiken:

In alle gevallen heeft het antwoord van de server de volgende vorm:
- het antwoord heeft de indeling XML
- regel 1 is standaard en komt altijd voor in het antwoord
- de volgende regels zijn afhankelijk van het type resultaat (double, ArrayOfDouble), het aantal resultaten en de naamruimte van de webservice (hier st.istia.univ-angers.fr).
Er zijn verschillende manieren om een webservice te benaderen en een antwoord te ontvangen. Laten we teruggaan naar de URL van de service:

en volgen we de link [ajouter]. Op de weergegeven pagina worden twee methoden beschreven om de functie [ajouter] van de webservice op te roepen:



Deze twee methoden voor toegang tot de functies van een webservice heten respectievelijk: HTTP-POST en SOAP. We zullen ze nu een voor een bekijken.
Opmerking: in de eerste versies van VS.NET bestond er een derde methode, genaamd HTTP-GET. Op het moment van schrijven van dit document (maart 2004) lijkt deze methode niet meer beschikbaar te zijn. Dit betekent dat de door VS.NET gegenereerde webservice geen GET-verzoeken accepteert. Dit betekent niet dat er geen webservices kunnen worden geschreven die GET-verzoeken accepteren, met name met andere tools dan VS.NET of gewoon met de hand.
10.5. Een HTTP-POST-client
We volgen de door de webservice voorgestelde methode:

Laten we het beschreven proces toelichten. Allereerst moet de webclient de volgende HTTP-headers verzenden:
De webclient doet een verzoek POST aan de URL /operations/operations.asmx/toevoegen volgens het protocol HTTP versie 1.1 | |
Hier wordt de doelmachine van het verzoek gespecificeerd. In dit geval is dat localhost. Deze header is verplicht gesteld door versie 1.1 van het protocol HTTP | |
Hier wordt aangegeven dat er na de headers HTTP extra parameters in urlencoded-formaat worden verzonden. In dit formaat worden bepaalde tekens vervangen door hun hexadecimale code. | |
Dit is het aantal tekens van de parameterreeks die na de headers HTTP wordt verzonden. |
De kopteksten HTTP worden gevolgd door een lege regel en vervolgens door de parameterreeks van POST, bestaande uit [Content-Length] tekens in de vorm a=XX&b=YY, waarbij XX en YY de "url-gecodeerde" tekenreeksen zijn van de waarden van de parameters a en b. We weten nu genoeg om het bovenstaande na te bootsen met onze generieke TCP-client die we al hebben gebruikt in het hoofdstuk over TCP/IP-programmering:
- we starten IIS
- de service is beschikbaar via de URL [http://localhost/operations/operations.asmx]
- we gebruiken de generieke TCP-client in een venster DOS
dos>clttcpgenerique localhost 80
Commandes :
POST /operations/operations.asmx/ajouter HTTP/1.1
HOST: localhost
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-length: 7
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 13:55:17 GMT
<-- X-Powered-By: ASP.NET
<--
a=2&b=3
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 13:55:26 GMT
<-- X-Powered-By: ASP.NET
<-- Connection: close
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 90
<--
<-- <?xml version="1.0" encoding="utf-8"?>
<-- <double xmlns="st.istia.univ-angers.fr">5</double>
[fin du thread de lecture des réponses du serveur]
fin
[fin du thread d'envoi des commandes au serveur]
Laten we allereerst opmerken dat we de header [Connection: close] hebben toegevoegd om de server te vragen de verbinding te verbreken na het verzenden van het antwoord. Dit is hier noodzakelijk. Als we dit niet aangeven, houdt de server de verbinding standaard open. Het antwoord van de server bestaat echter uit een reeks tekstregels waarvan de laatste niet wordt afgesloten met een regeleinde. Onze generieke client TCP leest tekstregels die worden afgesloten met een regeleinde met behulp van de methode ReadLine. Als de server de verbinding niet verbreekt na het verzenden van de laatste regel, loopt de client vast omdat hij wacht op een regeleinde-teken dat niet komt. Als de server de verbinding verbreekt, wordt de methode ReadLine van de client beëindigd en loopt de client niet vast.
Onmiddellijk na ontvangst van de lege regel die het einde van de headers aangeeft (HTTP), verstuurt de server (IIS) een eerste antwoord:
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 13:55:17 GMT
<-- X-Powered-By: ASP.NET
<--
Dit antwoord, dat uitsluitend bestaat uit de headers HTTP, geeft de klant aan dat hij de 7 tekens mag verzenden die hij zei te willen verzenden. Wat we doen:
Hier moet worden opgemerkt dat onze TCP-client meer dan 7 tekens verstuurt, aangezien hij deze samen met een regeleinde (WriteLine) verstuurt. Dit vormt geen probleem voor de server, die van de ontvangen tekens alleen de eerste 7 zal overnemen en omdat de verbinding daarna wordt gesloten (Connection: close). Deze overtollige tekens zouden wel een probleem zijn geweest als de verbinding open was gebleven, omdat ze dan zouden zijn geïnterpreteerd als afkomstig van het volgende commando van de client. Zodra de parameters zijn ontvangen, stuurt de server zijn antwoord:
<-- 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>
We beschikken nu over de benodigde elementen om een client te schrijven die is geprogrammeerd voor onze webservice. Dit wordt een console-client met de naam httpPost2, die als volgt wordt gebruikt:
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>
De client wordt aangeroepen door de URL van de webservice door te geven:
Vervolgens leest de client de via het toetsenbord ingevoerde opdrachten en voert deze uit. Deze hebben de volgende indeling:
waarbij ‘functie’ de aangeroepen webservicefunctie is (optellen, aftrekken, vermenigvuldigen, delen) en ‘a’ en ‘b’ de waarden zijn waarop deze functie wordt toegepast. Bijvoorbeeld:
Vanaf dat moment stuurt de client het benodigde verzoek HTTP naar de webserver en ontvangt een antwoord. De communicatie tussen client en server wordt op het scherm weergegeven om het proces beter te begrijpen:
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]
Hierboven zien we dezelfde uitwisseling als bij de generieke TCP-client, met één verschil: de header HTTP Connection: Keep-Alive vraagt de server om de verbinding niet te verbreken. De verbinding blijft dus open voor de volgende bewerking van de client, die daardoor niet opnieuw verbinding hoeft te maken met de server. Dit dwingt de client echter wel om een andere methode dan ReadLine() te gebruiken om het antwoord van de server te lezen, aangezien we weten dat dit een reeks regels is waarvan de laatste niet wordt afgesloten met een regeleinde. Zodra het volledige antwoord van de server is ontvangen, analyseert de client dit om het resultaat van de gevraagde bewerking te vinden en weer te geven:
Laten we de code van onze client eens bekijken:
' naamruimten
Imports System
Imports System.Net.Sockets
Imports System.IO
Imports System.Text.RegularExpressions
Imports System.Collections
Imports Microsoft.VisualBasic
Imports System.Web
' client van een webservice operations
Public Module clientPOST
Public Sub Main(ByVal args() As String)
' syntaxis
Const syntaxe As String = "pg URI"
Dim fonctions As String() = {"ajouter", "soustraire", "multiplier", "diviser"}
' aantal argumenten
If args.Length <> 1 Then
erreur(syntaxe, 1)
End If
' let op de gevraagde URI
Dim URIstring As String = args(0)
' er wordt verbinding gemaakt met de server
Dim uri As Uri = Nothing ' l'URI du service web
Dim client As TcpClient = Nothing ' la liaison tcp du client avec le serveur
Dim [IN] As StreamReader = Nothing ' le flux de lecture du client
Dim OUT As StreamWriter = Nothing ' le flux d'écriture du client
Try
' verbinding met de server
uri = New Uri(URIstring)
client = New TcpClient(uri.Host, uri.Port)
' de in- en uitgaande stromen van de client TCP worden aangemaakt
[IN] = New StreamReader(client.GetStream())
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
Catch ex As Exception
' URI onjuist of ander probleem
erreur("L'erreur suivante s'est produite : " + ex.Message, 2)
End Try
' een woordenboek met de functies van de webservice aanmaken
Dim dicoFonctions As New Hashtable
Dim i As Integer
For i = 0 To fonctions.Length - 1
dicoFonctions.Add(fonctions(i), True)
Next i
' de verzoeken van de gebruiker worden via het toetsenbord ingevoerd
' in de vorm functie a b
' ze eindigen met het commando 'fin'
Dim commande As String = Nothing ' commande tapée au clavier
Dim champs As String() = Nothing ' champs d'une ligne de commande
Dim fonction As String = Nothing ' nom d'une fonction du service web
Dim a, b As String ' les arguments des fonctions du service web
' vraagt de gebruiker
Console.Out.WriteLine("Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser] a b")
' foutbeheer
Dim erreurCommande As Boolean
Try
' lus voor het invoeren van via het toetsenbord ingevoerde opdrachten
While True
' geen fout bij het begin
erreurCommande = False
' opdracht lezen
commande = Console.In.ReadLine().Trim().ToLower()
' klaar?
If commande Is Nothing Or commande = "fin" Then
Exit While
End If
' opdeling van de opdracht in velden
champs = Regex.Split(commande, "\s+")
Try
' er zijn drie velden nodig
If champs.Length <> 3 Then
Throw New Exception
End If
' veld 0 moet een erkende functie zijn
fonction = champs(0)
If Not dicoFonctions.ContainsKey(fonction) Then
Throw New Exception
End If
' veld 1 moet een geldig getal zijn
a = champs(1)
Double.Parse(a)
' veld 2 moet een geldig getal zijn
b = champs(2)
Double.Parse(b)
Catch
' ongeldig commando
Console.Out.WriteLine("syntaxe : [ajouter|soustraire|multiplier|diviser] a b")
erreurCommande = True
End Try
' de aanvraag wordt naar de webservice verzonden
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
' einde van de verbinding tussen client en server
Try
[IN].Close()
OUT.Close()
client.Close()
Catch
End Try
End Sub
...........
' foutmeldingen weergeven
Public Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' fout weergeven
System.Console.Error.WriteLine(msg)
' afgesloten met fout
Environment.Exit(exitCode)
End Sub
End Module
Dit zijn zaken die we al meerdere keren zijn tegengekomen en die geen speciale toelichting behoeven. Laten we nu de code van de methode executeFonction bekijken, waar de nieuwe elementen zitten:
' 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)
' voert functie(a,b) uit op de webservice met URI URI
' de communicatie tussen client en server verloopt via de stromen IN en OUT
' het resultaat van de functie staat in de regel
' <double xmlns="st.istia.univ-angers.fr">double</double>
' verzonden door de server
' opbouw van de queryreeks
Dim requête As String = "a=" + HttpUtility.UrlEncode(a) + "&b=" + HttpUtility.UrlEncode(b)
Dim nbChars As Integer = requête.Length
' opbouw van de tabel met te verzenden headers HTTP
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) = ""
' de headers HTTP worden naar de server verzonden
Dim i As Integer
For i = 0 To entetes.Length - 1
' verzending naar de server
OUT.WriteLine(entetes(i))
' schermweergave
Console.Out.WriteLine(("--> " + entetes(i)))
Next i
' het eerste antwoord van de webserver wordt gelezen: HTTP/1.1 100
Dim ligne As String = Nothing
' een regel uit de leesstroom
ligne = [IN].ReadLine()
While ligne <> ""
'echo
Console.Out.WriteLine(("<-- " + ligne))
' volgende regel
ligne = [IN].ReadLine()
End While
'echo laatste regel
Console.Out.WriteLine(("<-- " + ligne))
' verzoekparameters verzenden
OUT.Write(requête)
' echo
Console.Out.WriteLine(("--> " + requête))
' opstellen van de reguliere expressie om de grootte van het antwoord te bepalen XML
' in de responsstroom van de webserver
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
' het tweede antwoord van de webserver lezen na het verzenden van het verzoek
' de waarde van de regel Content-Length wordt opgeslagen
ligne = [IN].ReadLine()
While ligne <> ""
' schermuitvoer
Console.Out.WriteLine(("<-- " + ligne))
' Content-Length?
MatchLength = RegexLength.Match(ligne)
If MatchLength.Success Then
longueur = Integer.Parse(MatchLength.Groups(1).Value)
End If
' volgende regel
ligne = [IN].ReadLine()
End While
' laatste regel weergeven
Console.Out.WriteLine("<--")
' opstellen van de reguliere expressie om het resultaat te vinden
' in de responsstroom van de webserver
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
' de rest van het antwoord van de webserver wordt gelezen
Dim chrRéponse(longueur) As Char
[IN].Read(chrRéponse, 0, longueur)
Dim strRéponse As String = New [String](chrRéponse)
' het antwoord wordt opgesplitst in tekstregels
Dim lignes As String() = Regex.Split(strRéponse, ControlChars.Lf)
' de tekstregels worden doorlopen op zoek naar het resultaat
Dim strRésultat As String = "?" ' résultat de la fonction
For i = 0 To lignes.Length - 1
' vervolg
Console.Out.WriteLine(("<-- " + lignes(i)))
' de huidige regel wordt vergeleken met het sjabloon
MatchRésultat = ModèleRésultat.Match(lignes(i))
' is er iets gevonden?
If MatchRésultat.Success Then
' het resultaat wordt genoteerd
strRésultat = MatchRésultat.Groups(1).Value
End If
Next i
' het resultaat wordt weergegeven
Console.Out.WriteLine(("[résultat=" + strRésultat + "]" + ControlChars.Lf))
End Sub
Allereerst stuurt de client HTTP-POST zijn verzoek in het formaat POST:
' opbouw van de zoekopdracht
Dim requête As String = "a=" + HttpUtility.UrlEncode(a) + "&b=" + HttpUtility.UrlEncode(b)
Dim nbChars As Integer = requête.Length
' opbouw van de tabel met headers HTTP die moeten worden verzonden
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) = ""
' de headers HTTP worden naar de server verzonden
Dim i As Integer
For i = 0 To entetes.Length - 1
' verzending naar de server
OUT.WriteLine(entetes(i))
' schermuitvoer
Console.Out.WriteLine(("--> " + entetes(i)))
Next i
In de header
moet de grootte worden aangegeven van de parameters die door de client achter de headers HTTP worden verzonden:
Hiervoor wordt de volgende code gebruikt:
' opbouw van de queryreeks
Dim requête As String = "a=" + HttpUtility.UrlEncode(a) + "&b=" + HttpUtility.UrlEncode(b)
Dim nbChars As Integer = requête.Length
De methode HttpUtility.UrlEncode(string reeks) zet bepaalde tekens van chaîne om in %n1n2, waarbij n1n2 de code ASCII is van het omgezette teken. De tekens waarop deze transformatie betrekking heeft, zijn alle tekens die een specifieke betekenis hebben in een zoekopdracht POST (de spatie, het =-teken, het &-teken, ...). In dit geval is de methode HttpUtility.UrlEncode normaal gesproken overbodig, aangezien a en b getallen zijn die geen van deze speciale tekens bevatten. Ze wordt hier ter illustratie gebruikt. Deze methode heeft de naamruimte System.Web nodig. Zodra de client zijn headers HTTP heeft verzonden:
--> POST /operations/operations.asmx/ajouter HTTP/1.1
--> Host: localhost:80
--> Content-Type: application/x-www-form-urlencoded
--> Content-Length: 7
--> Connection: Keep-Alive
-->
de server antwoordt met de header HTTP 100 Continue:
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:47 GMT
<-- X-Powered-By: ASP.NET
<--
De code leest alleen dit eerste antwoord en geeft het weer op het scherm:
' het eerste antwoord van de webserver wordt gelezen HTTP/1.1 100
Dim ligne As String = Nothing
' een regel uit de leesstroom
ligne = [IN].ReadLine()
While ligne <> ""
'echo
Console.Out.WriteLine(("<-- " + ligne))
' volgende regel
ligne = [IN].ReadLine()
End While
'echo laatste regel
Console.Out.WriteLine(("<-- " + ligne))
Nadat deze eerste reactie is gelezen, moet de klant zijn parameters versturen:
Dat doet hij met de volgende code:
De server stuurt vervolgens zijn antwoord. Dit bestaat uit twee delen:
- de headers HTTP, afgesloten met een lege regel
- het antwoord in het formaat XML
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:38 GMT
<-- X-Powered-By: ASP.NET
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 91
<--
<-- <?xml version="1.0" encoding="utf-8"?>
<-- <double xmlns="st.istia.univ-angers.fr">13</double>
In eerste instantie leest de client de headers HTTP om daar de regel Content-Length te vinden en de grootte van het antwoord XML (hier 90) op te halen. Deze wordt opgehaald met behulp van een reguliere expressie. Dit had ook anders gekund, en waarschijnlijk op een efficiëntere manier.
' opstellen van de reguliere expressie om de grootte van het antwoord te bepalen XML
' in de responsstroom van de webserver
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
' het tweede antwoord van de webserver lezen na het verzenden van het verzoek
' de waarde van de regel Content-Length wordt opgeslagen
ligne = [IN].ReadLine()
While ligne <> ""
' schermuitvoer
Console.Out.WriteLine(("<-- " + ligne))
' Content-Length?
MatchLength = RegexLength.Match(ligne)
If MatchLength.Success Then
longueur = Integer.Parse(MatchLength.Groups(1).Value)
End If
' volgende regel
ligne = [IN].ReadLine()
End While
' laatste regel weergeven
Console.Out.WriteLine("<--")
Zodra we de lengte N van het antwoord XML hebben, hoeven we alleen nog maar N tekens te lezen uit de stream IN van het serverantwoord. Deze reeks van N tekens wordt voor de schermmonitoring opgesplitst in tekstregels. Onder deze regels zoeken we de regel met het resultaat:
, opnieuw met behulp van een reguliere expressie. Zodra het resultaat is gevonden, wordt het weergegeven.
Het einde van de clientcode is als volgt:
' opstellen van de reguliere expressie om het resultaat te vinden
' in de responsstroom van de webserver
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
' de rest van het antwoord van de webserver wordt gelezen
Dim chrRéponse(longueur) As Char
[IN].Read(chrRéponse, 0, longueur)
Dim strRéponse As String = New [String](chrRéponse)
' het antwoord wordt opgesplitst in tekstregels
Dim lignes As String() = Regex.Split(strRéponse, ControlChars.Lf)
' de tekstregels worden doorlopen op zoek naar het resultaat
Dim strRésultat As String = "?" ' résultat de la fonction
For i = 0 To lignes.Length - 1
' vervolg
Console.Out.WriteLine(("<-- " + lignes(i)))
' de huidige regel wordt vergeleken met het sjabloon
MatchRésultat = ModèleRésultat.Match(lignes(i))
' is er iets gevonden?
If MatchRésultat.Success Then
' het resultaat wordt genoteerd
strRésultat = MatchRésultat.Groups(1).Value
End If
Next i
' het resultaat wordt weergegeven
Console.Out.WriteLine(("[résultat=" + strRésultat + "]" + ControlChars.Lf))
End Sub
10.6. Een client SOAP
We bekijken hier een tweede client die gebruikmaakt van een client-serverdialoog van het type SOAP (Simple Object Access Protocol). Er wordt een voorbeeld van een dialoog getoond voor de functie ajouter:


Het verzoek van de client is een POST-verzoek. We zullen dus enkele mechanismen van de vorige client terugzien. Het belangrijkste verschil is dat, terwijl de client HTTP-POST de parameters a en b verstuurde in de vorm
de client SOAP deze in een complexer formaat XML verstuurt:
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>
Hij ontvangt als antwoord een XML, dat eveneens complexer is dan de eerder bekeken antwoorden:
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>
Hoewel het verzoek en het antwoord complexer zijn, gaat het wel degelijk om hetzelfde mechanisme HTTP als voor de client HTTP-POST. De code voor de client SOAP kan dus worden gekopieerd van die voor de client HTTP-POST. Hier volgt een uitvoervoorbeeld:
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]
Alleen de methode executeFonction verandert. De client SOAP verstuurt de headers HTTP van zijn verzoek. Deze zijn slechts iets complexer dan die van 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"
-->
De code waarmee ze worden gegenereerd:
' 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)
' voert de functie(a,b) uit op de webservice met URI URI
' de communicatie tussen client en server verloopt via de stromen IN en OUT
' het resultaat van de functie staat in de regel
' <double xmlns="st.istia.univ-angers.fr">double</double>
' verzonden door de server
' opbouw van de queryreeks 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
' opbouw van de tabel met te verzenden headers HTTP
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) = ""
' de headers HTTP worden naar de server verzonden
Dim i As Integer
For i = 0 To entetes.Length - 1
' verzending naar de server
OUT.WriteLine(entetes(i))
' schermweergave
Console.Out.WriteLine(("--> " + entetes(i)))
Next i
Bij ontvangst van dit verzoek stuurt de server zijn eerste antwoord, dat door de client wordt weergegeven:
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Thu, 04 Mar 2004 07:28:29 GMT
<-- X-Powered-By: ASP.NET
<--
De leescode van dit eerste antwoord is als volgt:
' het eerste antwoord van de webserver wordt gelezen: HTTP/1.1 100
Dim ligne As String = Nothing
' een regel uit de leesstroom
ligne = [IN].ReadLine()
While ligne <> ""
'echo
Console.Out.WriteLine(("<-- " + ligne))
' volgende regel
ligne = [IN].ReadLine()
End While 'while
'echo laatste regel
Console.Out.WriteLine(("<-- " + ligne))
De client zal nu zijn parameters in het formaat XML verzenden in wat een SOAP-envelop wordt genoemd:
--> <?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>
De code:
De server stuurt vervolgens zijn definitieve antwoord:
<-- 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>
De klant geeft de ontvangen kopteksten HTTP op het scherm weer en zoekt daarbij naar de regel Content-Length:
' opstellen van de reguliere expressie waarmee de grootte van het antwoord kan worden bepaald XML
' in de responsstroom van de webserver
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
' uitlezing van het tweede antwoord van de webserver na het verzenden van het verzoek
' de waarde van de regel Content-Length wordt opgeslagen
ligne = [IN].ReadLine()
While ligne <> ""
' schermuitvoer
Console.Out.WriteLine(("<-- " + ligne))
' Content-Length?
MatchLength = RegexLength.Match(ligne)
If MatchLength.Success Then
longueur = Integer.Parse(MatchLength.Groups(1).Value)
End If
' volgende regel
ligne = [IN].ReadLine()
End While 'while
' laatste regel weergeven
Console.Out.WriteLine("<--")
Zodra de grootte N van het antwoord XML bekend is, leest de client N tekens uit de responsstroom van de server, splitst de opgehaalde tekenreeks op in tekstregels om deze op het scherm weer te geven en zoekt daarin naar de tag XML van het resultaat: <ajouterResult>7</ajouterResult> en deze weer te geven:
' opstellen van de reguliere expressie om het resultaat te vinden
' in de responsstroom van de webserver
Dim modèle As String = "<" + fonction + "Result>(.+?)</" + fonction + "Result>"
Dim ModèleRésultat As New Regex(modèle)
Dim MatchRésultat As Match = Nothing
' de rest van het antwoord van de webserver wordt gelezen
Dim chrRéponse(longueur) As Char
[IN].Read(chrRéponse, 0, longueur)
Dim strRéponse As String = New [String](chrRéponse)
' het antwoord wordt opgesplitst in tekstregels
Dim lignes As String() = Regex.Split(strRéponse, ControlChars.Lf)
' de tekstregels worden doorlopen op zoek naar het resultaat
Dim strRésultat As String = "?" ' résultat de la fonction
For i = 0 To lignes.Length - 1
' vervolg
Console.Out.WriteLine(("<-- " + lignes(i)))
' de huidige regel wordt vergeleken met het sjabloon
MatchRésultat = ModèleRésultat.Match(lignes(i))
' is er iets gevonden?
If MatchRésultat.Success Then
' het resultaat wordt genoteerd
strRésultat = MatchRésultat.Groups(1).Value
End If
'volgende regel
Next i
' het resultaat wordt weergegeven
Console.Out.WriteLine(("[résultat=" + strRésultat + "]" + ControlChars.Lf))
End Sub
10.7. Inkapseling van client-server-uitwisselingen
Stel dat onze webservice operations door verschillende applicaties wordt gebruikt. Het zou interessant zijn om voor deze applicaties een klasse beschikbaar te stellen die als interface fungeert tussen de clientapplicatie en de webservice en die het grootste deel van de netwerkcommunicatie verbergt, wat voor de meeste ontwikkelaars geen eenvoudige zaak is. We zouden dan het volgende schema hebben:
![]() |
De clienttoepassing zou zich tot de client-server-interface wenden om verzoeken aan de webservice te doen. Deze zou alle benodigde netwerkcommunicatie met de server afhandelen en het verkregen resultaat teruggeven aan de clienttoepassing. De clienttoepassing zou zich dan niet meer bezig hoeven te houden met de communicatie met de server, wat het schrijven ervan aanzienlijk zou vergemakkelijken.
10.7.1. De inkapselingsklasse
Na wat we in de voorgaande paragrafen hebben gezien, zijn we nu goed op de hoogte van de netwerkcommunicatie tussen de client en de server. We hebben zelfs drie methoden bekeken. We kiezen ervoor om de methode SOAP te inkapselen. De klasse ziet er als volgt uit:
' naamruimten
Imports System
Imports System.Net.Sockets
Imports System.IO
Imports System.Text.RegularExpressions
Imports System.Collections
Imports System.Web
Imports Microsoft.VisualBasic
' clientSOAP van de webservice operations
Public Class clientSOAP
' instantievariabelen
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
' functiewoordenboek
Private dicoFonctions As New Hashtable
' lijst met functies
Private fonctions As String() = {"ajouter", "soustraire", "multiplier", "diviser"}
' uitgebreide weergave
Private verbose As Boolean = False ' à vrai, affiche à l'écran les échanges client-serveur
' constructor
Public Sub New(ByVal uriString As String, ByVal verbose As Boolean)
' uitgebreide uitvoer
Me.verbose = verbose
' verbinding met de server
uri = New Uri(uriString)
client = New TcpClient(uri.Host, uri.Port)
' invoer- en uitvoerstromen van de client aanmaken TCP
[IN] = New StreamReader(client.GetStream())
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
' het functiewordboek van de webservice wordt aangemaakt
Dim i As Integer
For i = 0 To fonctions.Length - 1
dicoFonctions.Add(fonctions(i), True)
Next i
End Sub
' verbinding met de server wordt verbroken
Public Sub Close()
' einde van de verbinding tussen client en server
[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
' voert functie(a,b) uit op de webservice met URI URI
' de communicatie tussen client en server verloopt via de stromen IN en OUT
' het resultaat van de functie staat in de regel
' <double xmlns="st.istia.univ-angers.fr">double</double>
' verzonden door de server
' geldige functie?
fonction = fonction.Trim().ToLower()
If Not dicoFonctions.ContainsKey(fonction) Then
Return "[fonction [" + fonction + "] indisponible : (ajouter, soustraire,multiplier,diviser)]"
End If
' geldige argumenten a en 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
' delen door nul?
If fonction = "diviser" And doubleB = 0 Then
Return "[division par zéro]"
End If
' opbouw van de queryreeks 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
' opbouw van de tabel met te verzenden headers HTTP
Dim entetes(6) As String
entetes(0) = "POST " + uri.AbsolutePath + " HTTP/1.1"
entetes(1) = "Host: " + uri.Host + ":" + uri.Port.ToString
entetes(2) = "Content-Type: text/xml; charset=utf-8"
entetes(3) = "Content-Length: " + nbCharsSOAP.ToString
entetes(4) = "Connection: Keep-Alive"
entetes(5) = "SOAPAction: ""st.istia.univ-angers.fr/" + fonction + """"
entetes(6) = ""
' de headers HTTP worden naar de server verzonden
Dim i As Integer
For i = 0 To entetes.Length - 1
' verzending naar de server
OUT.WriteLine(entetes(i))
' schermweergave
If verbose Then
Console.Out.WriteLine(("--> " + entetes(i)))
End If
Next i
' het eerste antwoord van de webserver wordt gelezen: HTTP/1.1 100
Dim ligne As String = Nothing
' een regel uit de leesstroom
ligne = [IN].ReadLine()
While ligne <> ""
'echo
If verbose Then
Console.Out.WriteLine(("<-- " + ligne))
End If
' volgende regel
ligne = [IN].ReadLine()
End While
'echo laatste regel
If verbose Then
Console.Out.WriteLine(("<-- " + ligne))
End If
' verzoekparameters verzenden
OUT.Write(requêteSOAP)
' echo
If verbose Then
Console.Out.WriteLine(("--> " + requêteSOAP))
End If
' opstellen van de reguliere expressie om de grootte van het antwoord te bepalen XML
' in de responsstroom van de webserver
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
' het tweede antwoord van de webserver lezen na het verzenden van de verzoek
' de waarde van de regel Content-Length wordt opgeslagen
ligne = [IN].ReadLine()
While ligne <> ""
' schermuitvoer
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
' volgende regel
ligne = [IN].ReadLine()
End While
' uitvoer van de laatste regel
If verbose Then
Console.Out.WriteLine("<--")
End If
' opstellen van de reguliere expressie om het resultaat te vinden
' in de responsstroom van de webserver
Dim modèle As String = "<" + fonction + "Result>(.+?)</" + fonction + "Result>"
Dim ModèleRésultat As New Regex(modèle)
Dim MatchRésultat As Match = Nothing
' de rest van het antwoord van de webserver wordt gelezen
Dim chrRéponse(longueur) As Char
[IN].Read(chrRéponse, 0, longueur)
Dim strRéponse As String = New [String](chrRéponse)
' het antwoord wordt opgesplitst in tekstregels
Dim lignes As String() = Regex.Split(strRéponse, ControlChars.Lf)
' de tekstregels worden doorlopen op zoek naar het resultaat
Dim strRésultat As String = "?" ' résultat de la fonction
For i = 0 To lignes.Length - 1
' vervolg
If verbose Then
Console.Out.WriteLine(("<-- " + lignes(i)))
End If ' comparaison ligne courante au modèle
MatchRésultat = ModèleRésultat.Match(lignes(i))
' is het gevonden?
If MatchRésultat.Success Then
' het resultaat wordt genoteerd
strRésultat = MatchRésultat.Groups(1).Value
End If
Next i
' het resultaat wordt teruggestuurd
Return strRésultat
End Function
End Class
We zien hier niets nieuws ten opzichte van wat we al hebben gezien. We hebben simpelweg de code van de client SOAP die we hebben bestudeerd, overgenomen en enigszins aangepast om er een klasse van te maken. Deze heeft een constructor en twee methoden:
' maker
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
' de verbinding met de server wordt verbroken
Public Sub Close()
en heeft de volgende kenmerken:
' instantievariabelen
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
' functie-woordenboek
Private dicoFonctions As New Hashtable
' lijst met functies
Private fonctions As String() = {"ajouter", "soustraire", "multiplier", "diviser"}
' uitgebreide weergave
Private verbose As Boolean = False ' à vrai, affiche à l'écran les échanges client-serveur
Er worden twee parameters doorgegeven aan de constructor:
- de URI van de webservice waarmee hij verbinding moet maken
- een booleaanse waarde verbose die, als deze op 'waar' staat, aangeeft dat de netwerkverkeer op het scherm moet worden weergegeven; anders gebeurt dit niet.
Tijdens het aanmaken worden de stromen IN voor het lezen van het netwerk, OUT voor het schrijven naar het netwerk, evenals het woordenboek met de door de service beheerde functies aangemaakt. Zodra het object is aangemaakt, wordt de client-serververbinding geopend en zijn de stromen IN en OUT bruikbaar.
Met de methode Close kan de verbinding met de server worden verbroken.
De methode ExecuteFonction is dezelfde die we hebben geschreven voor de besproken client SOAP, op enkele details na:
- De parameters `uri`, `IN` en `OUT`, die voorheen als parameters aan de methode werden doorgegeven, hoeven dat nu niet meer te zijn, aangezien het nu instantie-attributen zijn die toegankelijk zijn voor alle methoden van de instantie
- De methode ExecuteFonction, die voorheen een type void retourneerde en het resultaat van de functie op het scherm weergeeft, retourneert nu dit resultaat en dus een type string.
Doorgaans zal een klant de klasse clientSOAP als volgt gebruiken:
- een object van het type clientSOAP aanmaken, dat de verbinding met de webservice tot stand brengt
- herhaaldelijk gebruik van de methode executeFonction
- het verbreken van de verbinding met de webservice via de methode Close.
Laten we een eerste client bekijken.
10.7.2. Een console-client
We nemen hier de client SOAP die we eerder hebben bekeken toen de klasse clientSOAP nog niet bestond, en passen deze aan zodat hij nu gebruikmaakt van deze klasse:
' naamruimten
Imports System
Imports System.IO
Imports System.Text.RegularExpressions
Imports Microsoft.VisualBasic
Public Module testClientSoap
' vraagt de URI van de webservice operations op
' voert de via het toetsenbord ingevoerde opdrachten interactief uit
Public Sub Main(ByVal args() As String)
' syntaxis
Const syntaxe As String = "pg URI [verbose]"
' aantal argumenten
If args.Length <> 1 And args.Length <> 2 Then
erreur(syntaxe, 1)
End If
' uitgebreide uitvoer?
Dim verbose As Boolean = False
If args.Length = 2 Then
verbose = args(1).ToLower() = "verbose"
End If
' verbinding maken met de webservice
Dim client As clientSOAP = Nothing
Try
client = New clientSOAP(args(0), verbose)
Catch ex As Exception
' verbindingsfout
erreur("L'erreur suivante s'est produite lors de la connexion au service web : " + ex.Message, 2)
End Try
' de verzoeken van de gebruiker worden via het toetsenbord ingevoerd
' in de vorm functie a b – ze worden afgesloten met het commando 'einde'
Dim commande As String = Nothing ' commande tapée au clavier
Dim champs As String() = Nothing ' champs d'une ligne de commande
' prompt voor de gebruiker
Console.Out.WriteLine("Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser] a b" + ControlChars.Lf)
' foutbeheer
Dim erreurCommande As Boolean
Try
' lus voor het invoeren van via het toetsenbord ingevoerde opdrachten
While True
' in eerste instantie geen fout
erreurCommande = False
' opdracht lezen
commande = Console.In.ReadLine().Trim().ToLower()
' klaar?
If commande Is Nothing Or commande = "fin" Then
Exit While
End If
' opdeling van de opdracht in velden
champs = Regex.Split(commande, "\s+")
' er zijn drie velden nodig
If champs.Length <> 3 Then
Console.Out.WriteLine("syntaxe : [ajouter|soustraire|multiplier|diviser] a b")
' de fout wordt genoteerd
erreurCommande = True
End If
' verzoek wordt naar de webservice gestuurd
If Not erreurCommande Then Console.Out.WriteLine(("résultat=" + client.executeFonction(champs(0).Trim().ToLower(), champs(1).Trim(), champs(2).Trim())))
' volgende verzoek
End While
Catch e As Exception
Console.Out.WriteLine(("L'erreur suivante s'est produite : " + e.Message))
End Try
' einde van de verbinding tussen client en server
Try
client.Close()
Catch
End Try
End Sub
' fouten weergeven
Public Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' fout weergeven
System.Console.Error.WriteLine(msg)
' afsluiten met fout
Environment.Exit(exitCode)
End Sub
End Module
De client is nu aanzienlijk eenvoudiger en bevat geen netwerkcommunicatie meer. De client accepteert twee parameters:
- de URI van de webservice operations
- het optionele trefwoord verbose. Als dit aanwezig is, wordt de netwerkcommunicatie op het scherm weergegeven.
Deze twee parameters worden gebruikt om een object clientSOAP te construeren dat de communicatie met de webservice verzorgt.
' verbinding maken met de webservice
Dim client As clientSOAP = Nothing
Try
client = New clientSOAP(args(0), verbose)
Catch ex As Exception
' verbindingsfout
erreur("L'erreur suivante s'est produite lors de la connexion au service web : " + ex.Message, 2)
End Try
Zodra de verbinding met de webservice tot stand is gebracht, kan de client zijn verzoeken verzenden. Deze worden via het toetsenbord ingevoerd, geanalyseerd en vervolgens naar de server verzonden door de methode executeFonction van het object clientSOAP aan te roepen.
' verzoek wordt verzonden naar de webservice
If Not erreurCommande Then Console.Out.WriteLine(("résultat=" + client.executeFonction(champs(0).Trim().ToLower(), champs(1).Trim(), champs(2).Trim())))
De klasse clientSOAP wordt gecompileerd in een „assembly”:
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
De clienttoepassing testClientSoap wordt vervolgens gecompileerd door:
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 volgt een voorbeeld van een uitvoering zonder logboekuitvoer:
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
Je kunt de netwerkverkeer volgen door een "uitgebreide" uitvoering aan te vragen:
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
Laten we nu een grafische client bouwen.
10.7.3. Een grafische Windows-client
We gaan nu onze webservice opvragen met een grafische client die ook gebruikmaakt van de klasse clientSOAP. De grafische interface ziet er als volgt uit:
![]() |
De besturingselementen zijn als volgt:
nr. | type | naam | rol |
TextBox | txtURI | de URI van de afdeling Web Operations | |
Knop | btnOuvrir | opent de verbinding met de webservice | |
Knop | btnFermer | sluit de verbinding met de webservice | |
ComboBox | cmbFonctions | de lijst met bewerkingen (optellen, aftrekken, vermenigvuldigen, delen) | |
TextBox | txtA | de argumenten van de bewerkingen | |
TextBox | txtB | argument b van de functies | |
TextBox | txtRésultat | het resultaat van de functie (a, b) | |
Knop | btnCalculer | start de berekening van de functie(a,b) | |
TextBox | txtErreur | geeft een statusbericht van de verbinding weer |
Er zijn enkele gebruiksbeperkingen:
- de knop btnOuvrir is alleen actief als het veld txtURI niet leeg is en er nog geen verbinding is geopend
- de knop btnFermer is alleen actief wanneer er een verbinding met de webservice is geopend
- de knop btnCalculer is alleen actief wanneer er een verbinding is geopend en de velden txtA en txtB niet leeg zijn
- de velden txtRésultat en txtErreur hebben het attribuut ReadOnly ingesteld op ‘waar’
De client begint met het openen van de verbinding met de webservice met behulp van de knop [Ouvrir]:

Vervolgens kan de gebruiker een functie en waarden voor a en b kiezen:





Hieronder volgt de code van de applicatie. We hebben de code van het formulier weggelaten, aangezien die hier niet van belang is.
'naamruimten
Imports System
Imports System.Windows.Forms
' de formulierklasse
Public Class FormClientSOAP
Inherits System.Windows.Forms.Form
' instantie-attributen
Dim client As clientSOAP ' client SOAP du service web operations
#Regio "Code gegenereerd door de Windows Form Designer"
Public Sub New()
MyBase.New()
'Deze aanroep is vereist door de Windows Form Designer.
InitializeComponent()
' overige initialisaties
myInit()
End Sub
'De overschreven methode Dispose van het formulier om de lijst met componenten op te schonen.
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
....
End Sub
...
Private Sub InitializeComponent()
....
End Sub
#Einde regio
Private Sub myInit()
' formulier initialiseren
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
' de inhoud van het invoerveld is gewijzigd – de status van de knop ‘Openen’ wordt ingesteld
btnOuvrir.Enabled = txtURI.Text.Trim <> ""
End Sub
Private Sub btnOuvrir_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnOuvrir.Click
' verzoek om een verbinding met de webservice tot stand te brengen
Try
' aanmaken van een object van het type [clientSOAP]
client = New clientSOAP(txtURI.Text.Trim, False)
' status van de knoppen
btnOuvrir.Enabled = False
btnFermer.Enabled = True
' URI kan niet meer worden gewijzigd
txtURI.ReadOnly = True
' klantstatus
txtErreur.Text = "Liaison au service web ouverte"
Catch ex As Exception
' er is een fout opgetreden – deze wordt weergegeven
txtErreur.Text = ex.Message
End Try
End Sub
Private Sub btnFermer_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnFermer.Click
' de verbinding met de webservice wordt verbroken
client.Close()
' status van de knoppen
btnOuvrir.Enabled = True
btnFermer.Enabled = False
' URI
txtURI.ReadOnly = False
' status van de klant
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
' berekening van een functie f(a,b)
' het vorige resultaat wordt gewist
txtRésultat.Text = ""
Try
txtRésultat.Text = client.executeFonction(cmbFonctions.Text, txtA.Text.Trim, txtB.Text.Trim)
Catch ex As Exception
' er is een netwerkfout opgetreden
txtErreur.Text = ex.Message
' de verbinding wordt verbroken
btnFermer_Click(Nothing, Nothing)
End Try
End Sub
Private Sub txtA_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles txtA.TextChanged
' de waarde van A wordt gewijzigd
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
' de waarde van B is gewijzigd
txtA_TextChanged(Nothing, Nothing)
End Sub
' hoofdmethode
Public Shared Sub main()
Application.Run(New FormClientSOAP)
End Sub
End Class
Ook hier verbergt de klasse clientSOAP het volledige netwerkaspect van de applicatie. De applicatie is als volgt opgebouwd:
- het assembly-bestand clientSOAP.dll, dat de klasse clientSOAP bevat, is in de projectmap geplaatst
- de grafische interface clientsoapgui.vb is gebouwd met VS.NET en vervolgens gecompileerd in een DOS-venster:
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
De grafische interface werd vervolgens gestart met:
10.8. Een proxy-client
Laten we even terugblikken op wat er zojuist is gedaan. We hebben een tussenklasse gemaakt die de netwerkcommunicatie tussen een client en een webservice inkapselen volgens het onderstaande schema:
![]() |
Het .NET-platform gaat nog een stap verder met deze logica. Zodra bekend is welke webservice moet worden benaderd, kunnen we automatisch de klasse genereren die als tussenlaag zal dienen om toegang te krijgen tot de functies van de webservice en die het gehele netwerkgedeelte verbergt. Deze klasse wordt een proxy genoemd voor de webservice waarvoor deze is gegenereerd.
Hoe genereer je de proxyklasse van een webservice? Een webservice gaat altijd vergezeld van een beschrijvingsbestand in het formaat XML. Als het URI-bestand van onze webservice ‘operations’ http://localhost/operations/operations.asmx is, dan is het beschrijvingsbestand beschikbaar op http://localhost/operations/operations.asmx?wsdl, zoals te zien is op de volgende schermafbeelding:

Dit is een XML-bestand waarin alle functies van de webservice nauwkeurig worden beschreven, met voor elke functie het type en het aantal parameters, en het type van het resultaat. Dit bestand wordt het WSDL-bestand van de service genoemd, omdat het gebruikmaakt van de taal WSDL (Web Services Description Language). Op basis van dit bestand kan met behulp van de tool wsdl een proxyklasse worden gegenereerd:
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
De tool wsdl genereert een bronbestand VB.NET (optie /language=vb) met de naam van de klasse die de webservice implementeert, in dit geval operations. Laten we een deel van de gegenereerde code bekijken:
'------------------------------------------------------------------------------
' <automatisch gegenereerd>
' Deze code is gegenereerd door een tool.
' Runtime-versie: 1.1.4322.573
'
' Wijzigingen aan dit bestand kunnen tot onjuist gedrag leiden en gaan verloren als
' de code opnieuw wordt gegenereerd.
' </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
'
'Deze broncode is automatisch gegenereerd door wsdl, Versie=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
'<opmerkingen/>
Public Sub New()
MyBase.New
Me.Url = "http://localhost/operations/operations.asmx"
End Sub
'<opmerkingen/>
<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
'<opmerkingen/>
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
'<opmerkingen/>
Public Function Endajouter(ByVal asyncResult As System.IAsyncResult) As Double
Dim results() As Object = Me.EndInvoke(asyncResult)
Return CType(results(0),Double)
End Function
....
Deze code lijkt op het eerste gezicht wat ingewikkeld. We hoeven de details ervan niet te begrijpen om ermee te kunnen werken. Laten we eerst eens kijken naar de klasseverklaring:
De klasse draagt de naam operations van de webservice waarvoor deze is ontwikkeld. Deze is afgeleid van de klasse SoapHttpClientProtocol:

Onze proxyklasse heeft één constructor:
De constructor wijst aan het attribuut url de waarde URL toe van de webservice die aan de proxy is gekoppeld. De hierboven genoemde klasse operations definieert het attribuut url zelf niet. Dit wordt overgenomen van de klasse waarvan de proxy is afgeleid: System.Web.Services.Protocols.SoapHttpClientProtocol. Laten we nu eens kijken wat er betrekking heeft op de methode ajouter:
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
We zien dat deze dezelfde handtekening heeft als in de webservice operations, waar deze als volgt was gedefinieerd:
<WebMethod> _
Function ajouter(a As Double, b As Double) As Double
Return a + b
End Function 'ajouter
De manier waarop deze klasse met de webservice communiceert, wordt hier niet weergegeven. Deze communicatie wordt volledig afgehandeld door de bovenliggende klasse System.Web.Services.Protocols.SoapHttpClientProtocol. In de proxy staat alleen wat deze onderscheidt van andere proxy’s:
- de URL van de bijbehorende webservice
- de definitie van de methoden van de bijbehorende service.
Om de methoden van de webservice operations te gebruiken, heeft een client alleen de eerder gegenereerde proxyklasse operations nodig. Laten we deze klasse compileren in een bestand assembly:
Laten we nu een console-client schrijven. Deze wordt zonder parameters aangeroepen en voert de via het toetsenbord ingevoerde verzoeken uit:
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
De code van de klant is als volgt:
' naamruimten
Imports System
Imports System.IO
Imports System.Text.RegularExpressions
Imports System.Collections
Imports Microsoft.VisualBasic
Public Module testClientProxy
' voert de via het toetsenbord ingevoerde opdrachten interactief uit
' en stuurt deze naar de webservice operations
Public Sub Main()
' er zijn geen argumenten meer – de URL van de webservice is hard gecodeerd in de proxy
' aanmaken van een woordenboek met de functies van de webservice
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
' er wordt een proxy-object ‘operations’ aangemaakt
Dim myOperations As operations = Nothing
Try
myOperations = New operations
Catch ex As Exception
' verbindingsfout
erreur("L'erreur suivante s'est produite lors de la connexion au proxy dy service web : " + ex.Message, 2)
End Try
' de verzoeken van de gebruiker worden via het toetsenbord ingevoerd
' in de vorm functie a b – ze eindigen met het commando 'fin'
Dim commande As String = Nothing ' commande tapée au clavier
Dim champs As String() = Nothing ' champs d'une ligne de commande
' prompt voor de gebruiker
Console.Out.WriteLine("Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser|toutfaire] a b" + ControlChars.Lf)
' enkele lokale gegevens
Dim erreurCommande As Boolean
Dim fonction As String
Dim a, b As Double
' lus voor het invoeren van via het toetsenbord ingevoerde opdrachten
While True
' in het begin geen fout
erreurCommande = False
' opdracht lezen
commande = Console.In.ReadLine().Trim().ToLower()
' klaar?
If commande Is Nothing Or commande = "fin" Then
Exit While
End If
' opdeling van de opdracht in velden
champs = Regex.Split(commande, "\s+")
Try
' er zijn drie velden nodig
If champs.Length <> 3 Then
Throw New Exception
End If
' veld 0 moet een erkende functie zijn
fonction = champs(0)
If Not dicoFonctions.ContainsKey(fonction) Then
Throw New Exception
End If
' veld 1 moet een geldig getal zijn
a = Double.Parse(champs(1))
' veld 2 moet een geldig getal zijn
b = Double.Parse(champs(2))
Catch
' ongeldig commando
Console.Out.WriteLine("syntaxe : [ajouter|soustraire|multiplier|diviser] a b")
erreurCommande = True
End Try
' de aanvraag wordt naar de webservice verzonden
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
' foutmeldingen weergeven
Public Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' foutmelding
System.Console.Error.WriteLine(msg)
' afgebroken met fout
Environment.Exit(exitCode)
End Sub
End Module
We bekijken alleen de code die specifiek betrekking heeft op het gebruik van de proxyklasse. Eerst wordt een proxyobject operations aangemaakt:
' een proxy-object voor bewerkingen wordt aangemaakt
Dim myOperations As operations = Nothing
Try
myOperations = New operations
Catch ex As Exception
' verbindingsfout
erreur("L'erreur suivante s'est produite lors de la connexion au proxy dy service web : " + ex.Message, 2)
End Try
De regels van functie a en b worden via het toetsenbord ingevoerd. Op basis van deze informatie worden de juiste methoden van de proxy aangeroepen:
' verzoek wordt verzonden naar de webservice
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 wordt voor het eerst de bewerking toutfaire verwerkt, die de vier bewerkingen uitvoert. Deze was tot nu toe genegeerd omdat deze een array met getallen verstuurt, ingekapseld in een XML-envelop, die ingewikkelder is te verwerken dan de eenvoudige XML-antwoorden van de andere functies, die slechts één enkel resultaat opleveren. We zien dat het gebruik van de methode toutfaire hier, met de proxyklasse, niet ingewikkelder is dan het gebruik van de andere methoden. De toepassing is als volgt in een DOS-venster gecompileerd:
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. Een webservice configureren
Een webservice heeft mogelijk configuratiegegevens nodig om correct te kunnen opstarten. Bij de server IIS kunnen deze gegevens worden opgeslagen in een bestand met de naam web.config, dat zich in dezelfde map bevindt als de webservice. Stel dat we een webservice willen maken die twee gegevens nodig heeft om te worden geïnitialiseerd: een naam en een leeftijd. Deze twee gegevens kunnen in het bestand web.config in de volgende vorm worden geplaatst:
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<appSettings>
<add key="nom" value="tintin"/>
<add key="age" value="27"/>
</appSettings>
</configuration>
De initialisatieparameters worden in een envelop XML geplaatst:
Een initialisatieparameter met de naam P en de waarde V wordt gedeclareerd met de regel:
<add key="P" value="V"/>
Hoe haalt de webservice deze informatie op? Wanneer IIS een webservice laadt, controleert het of er in dezelfde map een bestand met de naam web.config aanwezig is. Zo ja, dan leest het dit bestand. De waarde V van een parameter P wordt verkregen via de instructie:
waarbij ConfigurationSettings een klasse is in de naamruimte System.Configuration.
Laten we deze techniek eens testen op de volgende webservice:
<%@ WebService language="VB" class=personne %>
Imports System.Web.Services
imports System.Configuration
<WebService([Namespace] := "st.istia.univ-angers.fr")> _
Public Class personne
Inherits WebService
' attributen
Private nom As String
Private age As Integer
' constructor
Public Sub New()
' attributen initialiseren
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
De webservice personne heeft twee attributen, nom en age, die in de constructor zonder parameters worden geïnitialiseerd op basis van de waarden die worden gelezen uit het configuratiebestand web.config van de service personne. Dit bestand ziet er als volgt uit:
<configuration>
<appSettings>
<add key="nom" value="tintin"/>
<add key="age" value="27"/>
</appSettings>
</configuration>
De webservice heeft bovendien een <WebMethod>-id zonder parameters, die zich beperkt tot het weergeven van de attributen nom en age. De service is opgeslagen in het bronbestand personne.asmx, dat samen met het configuratiebestand in de map c:\inetpub\wwwroot\st\personne staat:
Laten we een virtuele map IIS/config koppelen aan de voorgaande fysieke map. Laten we IIS starten en vervolgens met een browser de URL http://localhost/config/personne.asmx van de service personne opvragen:

Laten we de link van de enige methode-id volgen:

De methode id heeft geen parameters. Laten we de knop Appeler gebruiken:

We hebben de informatie uit het bestand web.config van de service inderdaad opgehaald.
10.10. De webservice IMPOTS
We pakken de inmiddels bekende applicatie IMPOTS weer op. De laatste keer dat we ermee werkten, hadden we er een externe server van gemaakt die via internet kon worden aangeroepen. Nu maken we er een webservice van.
10.10.1. De webservice
We gaan uit van de klasse impôt die in het hoofdstuk over databases is aangemaakt en die is opgebouwd op basis van de informatie in een database ODBC:
' opties
Option Strict On
Option Explicit On
' naamruimten
Imports System
Imports System.Data
Imports Microsoft.Data.Odbc
Imports System.Collections
Public Class impôt
' de gegevens die nodig zijn voor de berekening van de belasting
' is afkomstig van een externe bron
Private limites(), coeffR(), coeffN() As Decimal
' maker
Public Sub New(ByVal LIMITES() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal)
' er wordt gecontroleerd of de drie tabellen dezelfde grootte hebben
Dim OK As Boolean = LIMITES.Length = COEFFR.Length And LIMITES.Length = COEFFN.Length
If Not OK Then
Throw New Exception("Les 3 tableaux fournis n'ont pas la même taille(" & LIMITES.Length & "," & COEFFR.Length & "," & COEFFN.Length & ")")
End If
' alles is in orde
Me.limites = LIMITES
Me.coeffR = COEFFR
Me.coeffN = COEFFN
End Sub
' constructor 2
Public Sub New(ByVal DSNimpots As String, ByVal Timpots As String, ByVal colLimites As String, ByVal colCoeffR As String, ByVal colCoeffN As String)
' initialiseert de drie grensarrays, coeffR, coeffN, op basis van
' de inhoud van de tabel Timpots uit de database ODBC DSNimpots
' colLimites, colCoeffR, colCoeffN zijn de drie kolommen van deze tabel
' kan een uitzondering veroorzaken
Dim connectString As String = "DSN=" + DSNimpots + ";" ' chaîne de connexion à la base
Dim impotsConn As OdbcConnection = Nothing ' la connexion
Dim sqlCommand As OdbcCommand = Nothing ' la commande SQL
' de query SELECT
Dim selectCommand As String = "select " + colLimites + "," + colCoeffR + "," + colCoeffN + " from " + Timpots
' tabellen om de gegevens op te halen
Dim tLimites As New ArrayList
Dim tCoeffR As New ArrayList
Dim tCoeffN As New ArrayList
' er wordt geprobeerd toegang te krijgen tot de database
impotsConn = New OdbcConnection(connectString)
impotsConn.Open()
' er wordt een command-object aangemaakt
sqlCommand = New OdbcCommand(selectCommand, impotsConn)
' de query wordt uitgevoerd
Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader()
' De opgehaalde tabel wordt verwerkt
While myReader.Read()
' de gegevens van de huidige rij worden in de tabellen geplaatst
tLimites.Add(myReader(colLimites))
tCoeffR.Add(myReader(colCoeffR))
tCoeffN.Add(myReader(colCoeffN))
End While
' vrijgeven van de bronnen
myReader.Close()
impotsConn.Close()
' de dynamische tabellen worden omgezet in statische tabellen
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
' Berekening van de belasting
Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Integer) As Long
' berekening van het aantal aandelen
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
' berekening van het belastbaar inkomen en de gezinsquotiënt
Dim revenu As Decimal = 0.72D * salaire
Dim QF As Decimal = revenu / nbParts
' berekening van de belasting
limites((limites.Length - 1)) = QF + 1
Dim i As Integer = 0
While QF > limites(i)
i += 1
End While
' weergave van het resultaat
Return CLng(revenu * coeffR(i) - nbParts * coeffN(i))
End Function
End Class
In de webservice mag alleen een constructor zonder parameters worden gebruikt. Daarom wordt de constructor van de klasse als volgt:
' constructor
Public Sub New()
' initialiseert de drie limiettabellen, coeffR, coeffN, op basis van
' de inhoud van de tabel Timpots uit de database ODBC DSNimpots
' colLimites, colCoeffR, colCoeffN zijn de drie kolommen van deze tabel
' kan een uitzondering veroorzaken
' hiermee worden de configuratieparameters van de service opgehaald
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")
' de database wordt geraadpleegd
Dim connectString As String = "DSN=" + DSNimpots + ";" ' chaîne de connexion à la base
De vijf parameters van de constructor van de vorige klasse worden nu ingelezen uit het bestand web.config van de webservice. De code van het bronbestand impots.asmx is als volgt. Deze bevat het grootste deel van de vorige code. We hebben alleen de codegedeelten die specifiek zijn voor de webservice gemarkeerd:
<%@ WebService language="VB" class=impots %>
' aanmaken van een webdienst voor belastingen
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
' de gegevens die nodig zijn voor de berekening van de belasting
' zijn afkomstig van een externe bron
Private limites(), coeffR(), coeffN() As Decimal
Private OK As Boolean = False
Private errMessage As String = ""
' constructor
Public Sub New()
' initialiseert de drie limiettabellen, coeffR, coeffN, op basis van
' de inhoud van de tabel Timpots uit de database ODBC DSNimpots
' colLimites, colCoeffR, colCoeffN zijn de drie kolommen van deze tabel
' kan een uitzondering veroorzaken
' hiermee worden de configuratieparameters van de service opgehaald
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")
' de database wordt geraadpleegd
Dim connectString As String = "DSN=" + DSNimpots + ";" ' chaîne de connexion à la base
Dim impotsConn As OdbcConnection = Nothing ' la connexion
Dim sqlCommand As OdbcCommand = Nothing ' la commande SQL
Dim myReader As OdbcDataReader ' lecteur de données Odbc
' de query SELECT
Dim selectCommand As String = "select " + colLimites + "," + colCoeffR + "," + colCoeffN + " from " + Timpots
' tabellen om de gegevens op te halen
Dim tLimites As New ArrayList
Dim tCoeffR As New ArrayList
Dim tCoeffN As New ArrayList
' er wordt geprobeerd toegang te krijgen tot de database
Try
impotsConn = New OdbcConnection(connectString)
impotsConn.Open()
' er wordt een command-object aangemaakt
sqlCommand = New OdbcCommand(selectCommand, impotsConn)
' de query wordt uitgevoerd
myReader = sqlCommand.ExecuteReader()
' De opgehaalde tabel wordt verwerkt
While myReader.Read()
' de gegevens van de huidige rij worden in de tabellen geplaatst
tLimites.Add(myReader(colLimites))
tCoeffR.Add(myReader(colCoeffR))
tCoeffN.Add(myReader(colCoeffN))
End While
' vrijgeven van de bronnen
myReader.Close()
impotsConn.Close()
' de dynamische tabellen worden in statische tabellen geplaatst
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
' Klaar
OK = True
errMessage = ""
Catch ex As Exception
' fout
OK = False
errMessage += "[" + ex.Message + "]"
End Try
End Sub
' berekening van de belasting
<WebMethod()> _
Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Integer) As Long
' berekening van het aantal aandelen
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
' berekening van het belastbaar inkomen en het gezinsquotiënt
Dim revenu As Decimal = 0.72D * salaire
Dim QF As Decimal = revenu / nbParts
' berekening van de belasting
limites((limites.Length - 1)) = QF + 1
Dim i As Integer = 0
While QF > limites(i)
i += 1
End While
' resultaat weergeven
Return CLng(revenu * coeffR(i) - nbParts * coeffN(i))
End Function
' ID
<WebMethod()> _
Function id() As String
' om te controleren of alles in orde is OK
Return "[" + OK + "," + errMessage + "]"
End Function
End Class
Laten we de enkele wijzigingen toelichten die in de klasse impots zijn aangebracht, afgezien van de wijzigingen die nodig waren om er een webservice van te maken:
- het inlezen van de database in de constructor kan mislukken. Daarom hebben we twee attributen en een methode aan onze klasse toegevoegd:
- de booleaanse waarde OK is gelijk aan vrai als de database kon worden gelezen, en aan faux in het tegenovergestelde geval
- de tekenreeks errMessage bevat een foutmelding als de database niet kon worden gelezen.
- Met de methode id zonder parameters kun je de waarde van deze twee attributen opvragen.
- Om een eventuele fout bij de toegang tot de database af te vangen, is het deel van de code van de constructor dat betrekking heeft op deze toegang omgeven door een try-catch.
Het configuratiebestand web.config van de service ziet er als volgt uit:
<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>
Bij een eerste poging om de service impots te laden, meldde de compiler dat hij de naamruimte Microsoft.Data.Odbc, die in de richtlijn wordt gebruikt, niet kon vinden:
Na raadpleging van de documentatie
- werd er een compilatierichtlijn toegevoegd aan web.config om aan te geven dat de assembly Microsoft.Data.odbc moest worden gebruikt
- is een kopie van het bestand microsoft.data.odbc.dll in de map bin van het project geplaatst. Deze map wordt systematisch doorzocht door de compiler van een webservice wanneer deze op zoek is naar een "assembly".
Andere oplossingen lijken mogelijk, maar zijn hier niet verder onderzocht. Het configuratiebestand is dus als volgt geworden:
<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>
De inhoud van het dossier impots\bin:
De service en het bijbehorende configuratiebestand zijn geplaatst in impots:
De fysieke map van de webservice is gekoppeld aan de virtuele map /impots van IIS. De pagina van de service ziet er dan als volgt uit:

Als we de link id volgen:

Als je de knop Appeler gebruikt:

Het vorige resultaat toont de waarden van de attributen OK (true) en errMessage (""). In dit voorbeeld is de database correct geladen. Dat was niet altijd het geval en daarom hebben we de methode id toegevoegd om toegang te krijgen tot de foutmelding. De fout was dat de naam DSN van de database was gedefinieerd als DSN (gebruiker), terwijl deze had moeten worden gedefinieerd als DSN (systeem). Dit onderscheid wordt gemaakt in de 32-bits bronbeheerder ODBC:
![]() |
Laten we teruggaan naar de servicepagina:

Laten we de link calculer volgen:

We stellen de parameters van de aanroep in en voeren deze uit:

Het resultaat is correct.
10.10.2. De proxy voor de dienst impots genereren
Nu we een operationele webservice impots hebben, kunnen we de bijbehorende proxyklasse genereren. Ter herinnering: deze zal door clienttoepassingen worden gebruikt om op transparante wijze toegang te krijgen tot de webservice impots. Eerst gebruiken we het hulpprogramma wsdl om het bronbestand van de proxyklasse te genereren, waarna dit wordt gecompileerd tot een DLL.
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. De proxy gebruiken met een client
In het hoofdstuk over databases hebben we een console-applicatie gemaakt waarmee de belasting kan worden berekend:
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
Het programma testimpots maakte toen gebruik van de klassieke klasse impôt, die in het bestand impots.dll stond. De programmacode van testimpots.vb was als volgt:
Option Explicit On
Option Strict On
' naamruimten
Imports System
Imports Microsoft.VisualBasic
' testpagina
Module testimpots
Sub Main(ByVal arguments() As String)
' interactief programma voor belastingberekening
' de gebruiker voert drie gegevens in via het toetsenbord: getrouwd nbEnfants salaris
' het programma geeft vervolgens de te betalen belasting weer
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"
' controle van de programma-instellingen
If arguments.Length <> 5 Then
' foutmelding
Console.Error.WriteLine(syntaxe1)
' einde
Environment.Exit(1)
End If 'if
' de argumenten worden opgehaald
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)
' een belastingobject aanmaken
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
' oneindige lus
While True
' in het begin geen fouten
Dim erreur As Boolean = False
' de parameters voor de belastingberekening worden opgevraagd
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()
' moet er iets gebeuren?
If paramètres Is Nothing Or paramètres = "" Then
Exit While
End If
' controle van het aantal argumenten in de ingevoerde regel
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
' controle van de geldigheid van de parameters
' getrouwd
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
' salaris
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
' de parameters zijn correct – de belasting wordt berekend
Console.Out.WriteLine(("impôt=" & objImpôt.calculer(marié = "o", nbEnfants, salaire).ToString + " F"))
End If
End While
End Sub
End Module
We nemen hetzelfde programma opnieuw ter hand om het nu de webservice impots te laten gebruiken via de eerder aangemaakte proxyklasse impots. We moeten de code enigszins aanpassen:
- terwijl de oorspronkelijke klasse impôt een constructor met vijf argumenten had, heeft de proxyklasse impots een constructor zonder parameters. De vijf parameters zijn, zoals we hebben gezien, nu vastgelegd in het configuratiebestand van de webservice.
- Het is dus niet meer nodig om deze vijf parameters als argumenten door te geven aan het testprogramma
De nieuwe code is als volgt:
Imports System
Imports Microsoft.VisualBasic
' testpagina
Module testimpots
Public Sub Main(ByVal arguments() As String)
' interactief programma voor belastingberekening
' de gebruiker voert drie gegevens in via het toetsenbord: gehuwd nbEnfants salaris
' het programma geeft vervolgens de te betalen belasting weer
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"
' aanmaken van een belastingobject
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
' oneindige lus
Dim erreur As Boolean
While True
' in eerste instantie geen fout
erreur = False
' er wordt gevraagd om de parameters voor de belastingberekening
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()
' moet er iets gebeuren?
If paramètres Is Nothing Or paramètres = "" Then
Exit While
End If
' controle van het aantal argumenten in de ingevoerde regel
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
' controle van de geldigheid van de parameters
' getrouwd
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
' salaris
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
' als de parameters correct zijn, wordt de belasting berekend
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
We hebben de proxy impots.dll en de broncode testimpots in dezelfde map.
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
We compileren de broncode 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
en voeren we het vervolgens uit:
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
We krijgen inderdaad het verwachte resultaat.





