Skip to content

12. Webservices

12.1. Introduction

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 eveneens TCP/IP-servertoepassingen. Ze hebben de volgende kenmerken:

  • Ze worden gehost door webservers en het protocol voor de uitwisseling tussen client en server is 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 van deze diensten 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 clients wordt zo 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. Dit verbergt het gehele netwerkcommunicatiegedeelte en maakt het mogelijk om een client te bouwen die onafhankelijk is van deze laag. Als deze laag verandert, hoeft de client niet te worden aangepast.
  • 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 voor het protocol HTTP benodigde headers
    • de hoofdtekst van het bericht. Een antwoord van de server aan de client heeft het formaat 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.

De architectuur van een op een webservice gebaseerde client-servertoepassing is als volgt:

Dit is een uitbreiding van de drielaagse architectuur waaraan gespecialiseerde netwerkkommunicatieklassen zijn toegevoegd. We zijn al een vergelijkbare architectuur tegengekomen bij de grafische Windows-client/TCP-server-toepassing voor belastingen in paragraaf 11.9.1.

Laten we deze algemene principes toelichten aan de hand van een eerste voorbeeld.

12.2. Een eerste webservice met Visual Web Developer

We gaan een eerste client-servertoepassing bouwen met de volgende vereenvoudigde architectuur:

12.2.1. Het servergedeelte

We hebben aangegeven dat een webservice door een webserver wordt gehost. Het schrijven van een webservice valt onder de algemene noemer van server-side webprogrammering. Eerder hebben we al webclients geschreven, wat ook webprogrammering is, maar dan client-side. De term webprogrammering verwijst meestal naar server-side programmering in plaats van client-side programmering. Voor het ontwikkelen van webservices of, meer in het algemeen, webapplicaties is Visual C# niet het geschikte hulpmiddel. We gaan Visual Developer gebruiken, een van de Express-versies van Visual Studio 2008 die te downloaden is via [2] op het adres [1]: [http://msdn.microsoft.com/fr-fr/express/future/bb421473(en-us).aspx] (mei 2008):

  • [1]: het downloadadres
  • [2]: het tabblad ‘Downloads’
  • [3]: Visual Developer 2008 downloaden

Om een eerste webservice te maken, kunt u als volgt te werk gaan nadat u Visual Developer hebt gestart:

  • [1]: kies de optie Bestand / Nieuwe website
  • [2]: kies het type toepassing ASP.NET Web Service
  • [3]: kies de ontwikkeltaal: C#
  • [4]: de map aangeven waarin het project moet worden aangemaakt
  • [5]: het project dat in Visual Web Developer is aangemaakt
  • [6]: de projectmap op de schijf

Een webapplicatie is in Web Developer als volgt gestructureerd:

  • een hoofdmap waarin de documenten van de website staan (statische HTML-webpagina’s, afbeeldingen, dynamische .aspx-webpagina’s, .asmx-webservices, ...). Daarin bevindt zich ook het bestand [web.config], het configuratiebestand van de webapplicatie. Dit bestand vervult dezelfde rol als het bestand [App.config] bij Windows-applicaties en is op dezelfde manier gestructureerd.
  • een map [App_Code] waarin de klassen en interfaces van de website staan die moeten worden gecompileerd.
  • een map [App_Data] waarin gegevens worden geplaatst die door de klassen van [App_Code] worden gebruikt. Hierin kan bijvoorbeeld een SQL Server *.mdf-database staan.

[Service.asmx] is de webservice waarvan we de aanmaak hebben aangevraagd. Deze bevat alleen de volgende regel:


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

De bovenstaande broncode is bedoeld voor de webserver waarop de applicatie wordt gehost. In de productiemodus is deze server doorgaans IIS (Internet Information Server), de webserver van Microsoft. Visual Web Developer bevat een lichte webserver die in de ontwikkelingsmodus wordt gebruikt. De voorgaande richtlijn geeft de webserver de volgende instructie:

  • [Service.asmx] is een webservice (richtlijn WebService)
  • geschreven in C# (attribuut Language)
  • dat de C#-code van de webservice zich in het bestand [~/App_Code/Service.cs] bevindt (attribuut CodeBehind). Daar zal de webserver de code ophalen om deze te compileren.
  • dat de klasse die de webservice implementeert Service heet (attribuut Class)

De C#-code [Service.cs] van de door Visual Developer gegenereerde webservice is als volgt:


using System.Web.Services;

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
// Om deze webservice vanuit een script aan te roepen met behulp van ASP.NET AJAX, moet u de volgende regel uitcommentariëren. 
// [System.Web.Script.Services.ScriptService]
public class Service : System.Web.Services.WebService
{
    public Service () {

        //Verwijder het commentaar uit de volgende regel als u ontworpen componenten gebruikt 
        //InitializeComponent(); 
    }

    [WebMethod]
    public string HelloWorld() {
        return "Hello World";
    }
    
}

De klasse Service lijkt op een klassieke C#-klasse, maar er zijn enkele punten waar je op moet letten:

  • regel 7: 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.
  • regel 3: de klasse zelf wordt voorafgegaan door een attribuut [WebService(Namespace="http://tempuri.org/")] dat bedoeld is om de webservice een naamruimte te geven. Een leverancier van klassen kent zijn klassen een naamruimte toe 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 http://tempuri.org/. Deze naam kan willekeurig zijn. Hij hoeft niet per se de vorm van een HTTP-URI te hebben.
  • regel 15: de methode HelloWorld wordt voorafgegaan door een attribuut [WebMethod] dat de compiler aangeeft dat de methode zichtbaar moet worden gemaakt voor externe clients van de webservice. Een methode die niet wordt voorafgegaan door dit attribuut, is niet zichtbaar voor de clients van de webservice. Dit zou een interne methode kunnen zijn die door andere methoden wordt gebruikt, maar niet bedoeld is om te worden gepubliceerd.
  • regel 9: de constructor van de webservice. Deze is in onze toepassing overbodig.

De gegenereerde klasse [Service.cs] wordt als volgt omgezet:


using System.Web.Services;

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

Het voor de webapplicatie gegenereerde configuratiebestand [web.config] is als volgt:


<?xml version="1.0"?>
<!-- 
    Note: As an alternative to hand editing this file you can use the 
    web admin tool to configure settings for your application. Use
    the Website->Asp.Net Configuration option in Visual Studio.
    A full list of settings and comments can be found in 
    machine.config.comments usually located in 
    \Windows\Microsoft.Net\Framework\v2.x\Config 
-->
<configuration>


    <configSections>
      <sectionGroup name="system.web.extensions" type="System.Web.Configuration.SystemWebExtensionsSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
...
      </sectionGroup>
    </configSections>  


    <appSettings/>
    <connectionStrings/>
...
</configuration>

Het bestand telt 140 regels. Het is complex en we zullen er geen toelichting bij geven. We laten het ongewijzigd. Hierboven zien we de tags <configuration>, <configSections>, <sectionGroup>, <appSettings> en <connectionString> die we ook al tegenkwamen in het bestand [App.config] van de Windows-toepassingen.

We hebben een werkende webservice die kan worden uitgevoerd:

  • [1,2]: klik met de rechtermuisknop op [Service.asmx] en vraag om de pagina in een browser te bekijken
  • [3]: Visual Web Developer start de ingebouwde webserver en plaatst het pictogram daarvan rechtsonder in de taakbalk. De webserver wordt gestart op een willekeurige poort, in dit geval 1906. De weergegeven URI /WsHello is de naam van de website [4].

Visual Web Developer heeft ook een browser gestart om de opgevraagde pagina weer te geven, namelijk [Service.asmx]:

  • in [1], de URI van de pagina. We zien de URI van de website [http://localhost:1906/WsHello], gevolgd door die van de pagina /Service.asmx.
  • In [2] geeft het achtervoegsel .asmx aan de webserver aan dat het niet om een normale webpagina (achtervoegsel .aspx) gaat die een HTML-pagina genereert, maar om de pagina van een webservice. Vervolgens genereert de server automatisch een webpagina met een link voor elke methode van de webservice met het attribuut [WebMethod]. Deze links zijn bedoeld om de methoden te testen.

Als we op de bovenstaande link [2] klikken, komen we op de volgende pagina terecht:

  • in [1]; let op de Uri [http://localhost:1906/WsHello/Service.asmx?op=DisBonjourALaDame] van de nieuwe pagina. Dit is de Uri van de webservice met de parameter op=M, waarbij M de naam is van een van de methoden van de webservice.
  • Laten we de signatuur van de methode [DisBonjourALaDame] nog eens bekijken:

    public string DisBonjourALaDame(string nomDeLaDame) ;

De methode accepteert een parameter van het type string en retourneert eveneens een resultaat van het type string. Op de pagina kunnen we de methode [DisBonjourALaDame] uitvoeren: in [2] voeren we de waarde van de parameter nomDeLaDame in en in [3] vragen we om de methode uit te voeren. We krijgen het volgende resultaat:

  • in [1] valt op dat de Uri van het antwoord niet identiek is aan die van het verzoek. Deze is gewijzigd.
  • In [2], het antwoord van de webserver. Let op de volgende punten:
    • het is een antwoord XML en niet HTML
    • het resultaat van de methode [DisBonjourALaDame] is ingekapseld in een <string>-tag die het type ervan aangeeft.
    • de <string>-tag heeft een attribuut xmlns (xml name space), wat de naamruimte is die we aan onze webservice hebben toegekend (regel 1 hieronder).

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

Om te zien hoe de webbrowser zijn verzoek heeft gedaan, moet je de HTML-code van het testformulier bekijken:

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

  <h3>Test</h3>

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

   <form action='http://localhost:1906/WsHello/Service.asmx/DisBonjourALaDame' method="POST">                                      
      <table>
              <tr>
                    <td>Parameter</td>
                    <td>Value</td>
                </tr>
                <tr>
               <td>nomDeLaDame:</td>
               <td><input type="text" size="50" name="nomDeLaDame"></td>
           </tr>
                <tr>
                   <td></td>
                  <td align="right"> <input type="submit" value="Invoke" class="button"></td>
           </tr>
      </table>
    </form>
<span>
...
  • regel 11: de waarden van het formulier (form-tag) worden verzonden (attribuut method) naar de URL [ http://localhost:1906/WsHello/Service.asmx/DisBonjourALaDame] (attribuut action).
  • regel 19: het invoerveld heet nomDeLaDame (attribuut name).

Door de webservice [/Service.asmx] aan te roepen, konden we de methoden ervan testen en een basiskennis opdoen van de communicatie tussen client en server.

12.2.2. De clientzijde

Het is mogelijk om de client voor de bovenstaande externe webservice te implementeren met een eenvoudige TCP/IP-client. Hieronder volgt bijvoorbeeld de client/server-dialoog die is gerealiseerd met een client putty die is verbonden met de externe webservice (localhost,1906):

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

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

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

<?xml version="1.0" encoding="utf-8"?>
<string xmlns="http://st.istia.univ-angers.fr">Bonjour Mme Carla Bruni</string>
  • regels 1-5: berichten verzonden door de client putty
  • regel 1: commando POST
  • regels 6-10: antwoord van de server. Dit betekent dat de client de waarden van POST kan verzenden.
  • regel 11: de verzonden waarden in de vorm param1=val1&param2=val2& .... Bepaalde tekens moeten bestaan uit tekens die in een URL zijn toegestaan. Dit wordt ook wel een gecodeerde URL genoemd. Hier heeft het formulier slechts één parameter met de naam nomDeLaDame. De verzonden waarde bestaat in totaal uit 23 tekens. Deze lengte moet worden opgegeven in de HTTP-header op regel 4.
  • regels 12-22: het antwoord van de server
  • regel 22: het resultaat van de webmethode [DisBonjourALaDame].

Met Visual C# is het mogelijk om met behulp van een wizard de client voor een externe webservice te genereren. Dat gaan we nu bekijken.

De hierboven genoemde laag [1] wordt geïmplementeerd door een Visual Studio C#-project van het type Windows-toepassing met de naam ClientWsHello:

  • in [1], het project ClientWsHello in Visual C#
  • in [2], is de standaardnaamruimte van het project Client (rechtsklik op het project / Properties / Application). Deze naamruimte wordt gebruikt om de naamruimte van de te genereren client samen te stellen.
  • in [3], klik met de rechtermuisknop op het project om er een verwijzing naar een externe webservice aan toe te voegen
  • in [4], voer de URI in van de eerder gecreëerde webservice
  • in [4b]: verbind Visual C# met de webservice die wordt aangeduid door [4]. Visual C# haalt de beschrijving van de webservice op en kan op basis van deze beschrijving een client genereren.
  • in [5]: zodra de beschrijving van de webservice is opgehaald, kan Visual C# de openbare methoden ervan weergeven
  • Geef in [6] een naamruimte op voor de klant die zal worden gegenereerd. Deze wordt toegevoegd aan de naamruimte die is gedefinieerd in [2]. De naamruimte van de klant wordt dus Client.WsHello.
  • Bevestig de wizard in [6b].
  • In [7] verschijnt de verwijzing naar de webservice WsHello in het project. Daarnaast is er een configuratiebestand [app.config] aangemaakt.
  • in [8]: bekijk alle bestanden van het project.
  • In [9] bevat de verwijzing naar de webservice WsHello diverse bestanden die we hier niet nader zullen toelichten. We zullen echter wel even kijken naar het bestand [Reference.cs], dat de gegenereerde C#-code van de client bevat:

namespace Client.WsHello {
...
    public partial class ServiceSoapClient : System.ServiceModel.ClientBase<Client.WsHello.ServiceSoap>, Client.WsHello.ServiceSoap {
        
        public ServiceSoapClient() {
        }
...        
        public string DisBonjourALaDame(string nomDeLaDame) {
            Client.WsHello.DisBonjourALaDameRequest inValue = new Client.WsHello.DisBonjourALaDameRequest();
            inValue.Body = new Client.WsHello.DisBonjourALaDameRequestBody();
            inValue.Body.nomDeLaDame = nomDeLaDame;
            Client.WsHello.DisBonjourALaDameResponse retVal = ((Client.WsHello.ServiceSoap)(this)).DisBonjourALaDame(inValue);
            return retVal.Body.DisBonjourALaDameResult;
        }
    }
}
  • regel 1: de naamruimte van de gegenereerde client is Client.WsHello. Als je deze naamruimte wilt wijzigen, moet je dat hier doen.
  • regel 3: de klasse ServiceSoapClient is de klasse van de gegenereerde client. Dit is een proxyklasse in die zin dat deze voor de Windows-toepassing verbergt dat er gebruik wordt gemaakt van een externe webservice. De Windows-toepassing zal de externe klasse WsHello gebruiken via de lokale klasse Client.WsHello.ServiceSoapClient. Om een instantie van de client aan te maken, gebruiken we de constructor op regel 5:
Client.WsHello.ServiceSoapClient client=new Client.WsHello.ServiceSoapClient();
  • regel 8: de methode DisBonjourALaDame is de tegenhanger aan de clientzijde van de methode DisBonjourALaDame van de webservice. De Windows-toepassing gebruikt de externe methode DisBonjourALaDame via de lokale methode Client.WsHello.ServiceSoapClient.DisBonjourALaDame in de volgende vorm:
string bonjour=client.DisBonjourALaDame("Carla Bruni");

Het gegenereerde bestand [app.config] is als volgt:


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

Uit dit bestand onthouden we alleen regel 8, die de Uri van de webservice bevat. Als de Uri verandert, hoeft de Windows-client niet opnieuw te worden opgebouwd. Het volstaat om de Uri in het bestand [app.config] te wijzigen.

Laten we terugkeren naar de architectuur van de Windows-applicatie die we willen bouwen:

We hebben de laag [client] van de webservice gebouwd. De laag [ui] wordt de volgende:

nr.
type
naam
rol
1
TextBox
textBoxNomDame
naam van de dame
2
Button
buttonSalutations
om verbinding te maken met de externe webservice WsHello en de methode DisBonjourALaDame op te roepen.
3
Label
labelBonjour
het door de webservice geretourneerde resultaat

De code van het formulier [Form1.cs] is als volgt:


using System;
using System.Windows.Forms;
using Client.WsHello;

namespace ClientSalutations {
    public partial class Form1 : Form {
        public Form1() {
            InitializeComponent();
        }

        private void buttonSalutations_Click(object sender, EventArgs e) {
            // zandloper
            Cursor=Cursors.WaitCursor;
            // webservice-aanvraag
            labelBonjour.Text = new ServiceSoapClient().DisBonjourALaDame(textBoxNomDame.Text.Trim());
            // normale cursor
            Cursor = Cursors.Arrow;
        }
    }
}
  • regel 15: de webdienstclient wordt geïnstantieerd. Deze is van het type Client.WsHello.ServiceSoapClient. De naamruimte Client.WsHello wordt in regel 3 gedeclareerd. De lokale methode ServiceSoapClient().DisBonjourALaDame wordt aangeroepen. We weten dat deze op haar beurt de gelijknamige externe methode van de webservice aanroept.

12.3. Een webservice voor rekenkundige bewerkingen

We gaan een tweede client/server-toepassing bouwen met opnieuw de volgende vereenvoudigde architectuur:

De vorige webservice bood één enkele methode aan. We bekijken nu een webservice die de vier rekenkundige bewerkingen aanbiedt:

  1. toevoegen(a,b), die a+b retourneert
  2. aftrekken(a,b), die a-b retourneert
  3. vermenigvuldigen(a,b), wat a*b oplevert
  4. delen(a,b), wat a/b oplevert

en die via de volgende grafische interface kan worden aangeroepen:

  • in [1], de uit te voeren bewerking
  • in [2,3]: de operanden
  • in [4], de knop om de webservice aan te roepen
  • in [5], het door de webservice geretourneerde resultaat

12.3.1. Het servergedeelte

We bouwen een webdienstproject met Visual Web Developer:

  • in [1], de gegenereerde webapplicatie WsOperations
  • in [2] is de webapplicatie WsOperations als volgt aangepast:
  • de webpagina [Service.asmx] is hernoemd naar [Operations.asmx]
  • de klasse [Service.cs] is hernoemd naar [Operations.cs]
  • het bestand [web.config] is verwijderd om aan te tonen dat het niet onmisbaar is.

De webpagina [Service.asmx] bevat de volgende regel:


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

De webservice wordt verzorgd door de volgende klasse [Operations.cs]:


using System.Web.Services;

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

    [WebMethod]
    public double Soustraire(double a, double b)
    {
        return a - b;
    }

    [WebMethod]
    public double Multiplier(double a, double b)
    {
        return a * b;
    }

    [WebMethod]
    public double Diviser(double a, double b)
    {
        return a / b;
    }

}

Om de webservice online te zetten, gaan we te werk zoals aangegeven in [3]. We krijgen dan de testpagina met de 4 methoden van de webservice WsOperations:

Image

De lezer wordt uitgenodigd om de vier methoden te testen.

12.3.2. Het clientgedeelte

Met Visual C# maken we een Windows-toepassing ClientWsOperations:

  • in [1], het project ClientWsOperations in Visual C#
  • in [2], de standaardnaamruimte van het project is Client (rechtsklik op het project / Properties / Application). Deze naamruimte wordt gebruikt om de naamruimte van de client op te bouwen die zal worden gegenereerd.
  • in [3], klik met de rechtermuisknop op het project om er een verwijzing naar een bestaande webservice aan toe te voegen
  • in [4]: voer de URI in van de eerder gebouwde webservice. Kijk hiervoor naar wat er wordt weergegeven in het adresveld van de browser die de testpagina van de webservice weergeeft.
  • in [4b], Visual C# verbinden met de webservice die wordt aangeduid door [4]. Visual C# haalt de beschrijving van de webservice op en kan op basis van deze beschrijving een client genereren.
  • In [5] kan Visual C#, zodra de beschrijving van de webservice is opgehaald, de openbare methoden ervan weergeven
  • in [6]: geef een naamruimte op voor de client die zal worden gegenereerd. Deze wordt toegevoegd aan de naamruimte die is gedefinieerd in [2]. De naamruimte van de client wordt dus Client.WsOperations.
  • Bevestig de wizard in [6b].
  • In [7] verschijnt de verwijzing naar de webservice WsOperations in het project. Daarnaast is er een configuratiebestand [app.config] aangemaakt.

Ter herinnering: de gegenereerde client is van het type Client.WsOperations.OperationsSoapClient, waarbij

  • Client.WsOperations de naamruimte is van de webserviceclient
  • Operations de klasse van de externe webservice is.

Hoewel er een logische manier is om deze naam samen te stellen, is het vaak eenvoudiger om deze terug te vinden in het bestand [Reference.cs], dat standaard verborgen is. De inhoud ervan is als volgt:


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

De methoden Ajouter, Soustraire, Multiplier, Diviser van de externe webservice worden benaderd via de proxy-methoden met dezelfde naam (regels 8, 12, 16, 20) van de client van het type Client.WsOperations.OperationsSoapClient (regel 3).

Nu moeten we nog de grafische interface bouwen:

nr.
type
naam
rol
1
ComboBox
comboBoxOperations
lijst met rekenkundige bewerkingen
2
TextBox
textBoxA
getal a
3
TextBox
textBoxB
getal b
4
Knop
buttonExécuter
vraagt de externe webservice op
5
Label
labelRésultat
het resultaat van de bewerking

De code van [Form1.cs] is als volgt:


using System;
using System.Windows.Forms;
using Client.WsOperations;

namespace ClientWsOperations {
    public partial class Form1 : Form {
        // bewerkingstabel
        private string[] opérations = { "Ajouter", "Soustraire", "Multiplier", "Diviser" };
        // te contacteren webservice
        private OperationsSoapClient opérateur = new OperationsSoapClient();

        // constructeur
        public Form1() {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e) {
            // combo-veld voor bewerkingen invullen
            comboBoxOperations.Items.AddRange(opérations);
            comboBoxOperations.SelectedIndex = 0;
        }

        private void buttonExécuter_Click(object sender, EventArgs e) {
            // controle van de parameters a en b van de bewerking
            textBoxMessage.Text = "";
            bool erreur = false;
            Double a = 0;
            if (!Double.TryParse(textBoxA.Text, out a)) {
                textBoxMessage.Text += "Nombre a erroné...";
            }
            Double b = 0;
            if (!Double.TryParse(textBoxB.Text, out b)) {
                textBoxMessage.Text += String.Format("{0}Nombre b erroné...", Environment.NewLine);
            }
            if (erreur) {
                return;
            }
            // uitvoering van de bewerking
            Double c=0;
            try {
                switch (comboBoxOperations.SelectedItem.ToString()) {
                    case "Ajouter":
                        c=opérateur.Ajouter(a, b);
                        break;
                    case "Soustraire":
                        c=opérateur.Soustraire(a, b);
                        break;
                    case "Multiplier":
                        c=opérateur.Multiplier(a, b);
                        break;
                    case "Diviser":
                        c=opérateur.Diviser(a, b);
                        break;
                }
                // weergave van het resultaat
                labelRésultat.Text = c.ToString();
            } catch (Exception ex) {
                textBoxMessage.Text = ex.Message;
            }
        }
    }
}
  • regel 3: de naamruimte van de client van de externe webservice
  • regel 10: de client van de externe webservice wordt tegelijk met het formulier geïnstantieerd
  • regels 17-21: de keuzelijst met bewerkingen wordt ingevuld bij het eerste laden van het formulier
  • regel 23: uitvoering van de door de gebruiker gevraagde bewerking
  • regels 25-37: er wordt gecontroleerd of de invoerwaarden a en b inderdaad reële getallen zijn
  • regels 41-54: een switch om de door de gebruiker gevraagde externe bewerking uit te voeren
  • regels 43, 46, 49, 52: de lokale client wordt geraadpleegd. Deze raadpleegt op transparante wijze de externe webservice.

12.4. Een webservice voor belastingberekening

We pakken de inmiddels bekende applicatie voor belastingberekening weer op. De laatste keer dat we ermee werkten, hadden we er een externe TCP-server van gemaakt die via internet kon worden aangeroepen. Nu maken we er een webservice van.

De architectuur van versie 8 zag er als volgt uit:

De architectuur van versie 9 zal vergelijkbaar zijn:

Dit is een architectuur die vergelijkbaar is met die van versie 8, die in paragraaf 11.9.1 is besproken, maar waarbij de TCP-server en -client zijn vervangen door een webservice en de bijbehorende proxyclient. We zullen de lagen [ui], [metier] en [dao] uit versie 8 volledig overnemen.

12.4.1. Het servergedeelte

We bouwen een webdienstproject met Visual Web Developer:

  • in [1], de gegenereerde webapplicatie WsImpot
  • in [2], de webapplicatie WsImpot is als volgt aangepast:
    • de webpagina [Service.asmx] is hernoemd naar [ServiceImpot.asmx]
    • de klasse [Service.cs] is hernoemd naar [ServiceImpot .cs]

De webpagina [ServiceImpot.asmx] bevat de volgende regel:


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

De webservice wordt verzorgd door de volgende klasse [ServiceImpot.cs]:


using System.Web.Services;

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

    [WebMethod]
    public int CalculerImpot(bool marié, int nbEnfants, int salaire)
    {
        return 0;
    }

}

De webservice zal alleen de methode CalculerImpot uit regel 9 beschikbaar stellen.

Laten we terugkeren naar de client/server-architectuur van versie 8:

Het Visual Studio-project van de server [1] zag er als volgt uit:

  • in [1], het project. Daarin bevonden zich de volgende elementen:
    • [ServeurImpot.cs]: de TCP/IP-server voor belastingberekening in de vorm van een console-applicatie.
    • [dbimpots.sdf]: de compacte SQL Server-database van versie 7, zoals beschreven in paragraaf 9.8.5.
    • [App.config]: het configuratiebestand van de applicatie.
  • Naast [2] bevat de map [lib] de bestanden DLL die nodig zijn voor het project:
    • [ImpotsV7-dao]: de laag [dao] van versie 7
    • [ImpotsV7-metier]: de laag [metier] van versie 7
    • [antlr.runtime, CommonLogging, Spring.Core] voor Spring
  • in [3], de projectreferenties

De lagen [metier] en [dao] van deze versie bestaan al: dit zijn de lagen die in versie 7 en 8 worden gebruikt. Ze hebben de vorm DLL, die we als volgt in het project integreren:

  • in [1] is de map [lib] van de server van versie 8 gekopieerd naar het webdienstproject van versie 9.
  • In [2] wijzigen we de eigenschappen van de pagina om de bestanden DLL, [lib] en [4] uit de map [3] toe te voegen aan de verwijzingen van het project [3].

Na deze bewerking beschikken we over alle lagen die nodig zijn voor de onderstaande server [1]:

Als de elementen van de server [1], [serveur], [metier], [dao], [entites] en [spring] allemaal aanwezig zijn in het Visual Studio-project, ontbreekt het element dat deze bij het opstarten van de webapplicatie instantiëert. In versie 8 zorgde een hoofdklasse met de statische methode [Main] ervoor dat de lagen met behulp van Spring werden geïnstantieerd. In een webapplicatie is de klasse die een soortgelijke taak kan uitvoeren de klasse die gekoppeld is aan het bestand [Global.asax] :

  • in [1] voegen we een nieuw element toe aan het webproject
  • in [2] kiest men het type Global Application Class
  • in [3] wordt de standaard voorgestelde naam voor dit element
  • in [4]: de toevoeging bevestigen
  • in [5] is het nieuwe element in het project opgenomen

Laten we de inhoud van het bestand [Global.asax] bekijken:


<%@ Application Language="C#" %>

<script runat="server">

    void Application_Start(object sender, EventArgs e) 
    {
        // Code die wordt uitgevoerd bij het opstarten van de applicatie
    }
    
    void Application_End(object sender, EventArgs e) 
    {
        //  Code die wordt uitgevoerd bij het afsluiten van de applicatie
    }
        
    void Application_Error(object sender, EventArgs e) 
    { 
        // Code die wordt uitgevoerd wanneer zich een onbehandelde fout voordoet
    }

    void Session_Start(object sender, EventArgs e) 
    {
        // Code die wordt uitgevoerd wanneer een nieuwe sessie wordt gestart
    }

    void Session_End(object sender, EventArgs e) 
    {
        // Code die wordt uitgevoerd wanneer een sessie wordt beëindigd. 
    }
       
</script>

Het bestand is een mix van tags voor de webserver (regels 1, 3, 30) en C#-code. Deze methode was de enige die werd gebruikt met ASP, de voorloper van ASP.NET, de huidige technologie van Microsoft voor webprogrammering. Bij ASP.NET is deze methode nog steeds bruikbaar, maar niet de standaardmethode. De standaardmethode is de zogenaamde „CodeBehind”-methode, die we zijn tegengekomen op de pagina’s van de webservices, bijvoorbeeld hier in [ServiceImpot.asmx]:


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

Het attribuut CodeBehind geeft aan waar de broncode van de pagina [ServiceImpot.asmx] zich bevindt. Zonder dit attribuut zou de broncode zich in de pagina [ServiceImpot.asmx] bevinden, met een syntaxis die vergelijkbaar is met die in [Global.asax]. We zullen het bestand [Global.asax] niet behouden zoals het is gegenereerd, maar aan de hand van de code kunnen we wel begrijpen waarvoor het dient:

  • de klasse die bij Global.asax hoort, wordt bij het opstarten van de applicatie geïnstantieerd. De levensduur ervan is gelijk aan die van de gehele applicatie. Concreet verdwijnt deze pas wanneer de webserver wordt afgesloten.
  • Vervolgens wordt de methode Application_Start uitgevoerd. Dit is de enige keer dat dit gebeurt. Daarom wordt deze methode gebruikt om objecten te instantiëren die door alle gebruikers worden gedeeld. Deze objecten worden geplaatst:
    • ofwel in statische velden van de klasse die gekoppeld is aan Global.asax. Aangezien deze klasse permanent aanwezig is, kan elke gebruiker bij elke opvraging informatie daaruit ophalen.
    • ofwel de container Application. Ook deze container wordt bij het opstarten van de applicatie aangemaakt en bestaat zolang de applicatie actief is.
      • Om gegevens in deze container op te slaan, schrijft men Application["clé"]=waarde;
      • om de waarde op te halen, schrijft men T waarde=(T)Application["clé"]; waarbij T het type is van valeur.
  • De methode Session_Start wordt telkens uitgevoerd wanneer een nieuwe gebruiker een verzoek indient. Hoe herkennen we een nieuwe gebruiker? Elke gebruiker (meestal een browser) ontvangt na zijn eerste verzoek een sessietoken, een tekenreeks die uniek is voor elke gebruiker. Vervolgens stuurt de gebruiker bij elk nieuw verzoek dat hij doet het ontvangen sessietoken terug. Hierdoor kan de webserver hem herkennen. Naarmate dezelfde gebruiker verschillende verzoeken doet, kunnen gegevens die specifiek voor hem zijn, worden opgeslagen in de Session-container:
    • om gegevens in deze container op te slaan, schrijft men Session["clé"]=waarde;
    • om deze op te halen, schrijf je T waarde=(T)Session["clé"]; waarbij T het type is van valeur.

De levensduur van een sessie is standaard beperkt tot 20 minuten inactiviteit van de gebruiker (c.a.d. dat hij zijn sessietoken al 20 minuten niet heeft teruggestuurd).

  • De methode Application_Error wordt uitgevoerd wanneer een uitzondering die niet door de webapplicatie wordt afgehandeld, wordt doorgegeven aan de webserver.
  • De overige methoden worden minder vaak gebruikt.

Wat kunnen we, na deze algemene uitleg, met Global.asax doen? We gaan de methode Application_Start gebruiken om de lagen [metier], [dao] en [entites] die zijn opgenomen in de DLL en [ImpotsV7-metier, ImpotsV7-dao]. We gebruiken Spring om deze te instantiëren. De verwijzingen naar de zo gecreëerde lagen worden vervolgens opgeslagen in statische velden van de klasse die gekoppeld is aan Global.asax.

Als eerste stap verplaatsen we de C#-code van Global.asax naar een aparte klasse. Het project ziet er nu als volgt uit:

In [1] wordt het bestand [Global.asax] gekoppeld aan de klasse [Global.cs] [2] met de volgende enkele regel:


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

Het attribuut Inherits="WsImpot.Global" geeft aan dat de klasse die aan Global.asax is gekoppeld, de klasse WsImpot.Global erft. Deze klasse is in [Global.cs] als volgt gedefinieerd:


using System;
using Metier;
using Spring.Context.Support;
namespace WsImpot
{
    public class Global : System.Web.HttpApplication
    {
        // bedrijfslaag
        public static IImpotMetier Metier;

        // methode die wordt uitgevoerd bij het opstarten van de applicatie
        private void Application_Start(object sender, EventArgs e)
        {
            // instanties van de lagen [metier] en [dao]
            Metier = ContextRegistry.GetContext().GetObject("metier") as IImpotMetier;
        }
    }
}
  • regel 4: de naamruimte van de klasse
  • regel 6: de klasse Global. Je kunt deze klasse elke gewenste naam geven. Het belangrijkste is dat deze afgeleid is van de klasse System.Web.HttpApplication.
  • regel 9: een openbaar statisch veld dat een verwijzing naar de laag [metier] zal bevatten.
  • regel 12: de methode Application_Start die bij het opstarten van de applicatie wordt uitgevoerd.
  • regel 15: Spring wordt gebruikt om het bestand [web.config] te verwerken, waarin de objecten worden gevonden die moeten worden geïnstantieerd om de lagen [metier] en [dao] aan te maken. Er is geen verschil tussen het gebruik van Spring met [App.config] in een Windows-toepassing en het gebruik van Spring met [web.config] in een webtoepassing. [web.config] en [App.config] hebben overigens dezelfde structuur. Regel 15 slaat de referentie van de laag [metier] op in het statische veld van regel 9, zodat deze referentie beschikbaar is voor alle verzoeken van alle gebruikers.

Het bestand [web.config] ziet er als volgt uit:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>

  <configSections>
    <sectionGroup name="spring">
      <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
      <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
    </sectionGroup>
  </configSections>

  <spring>
    <context>
      <resource uri="config://spring/objects" />
    </context>
    <objects xmlns="http://www.springframework.net">
      <object name="dao" type="Dao.DataBaseImpot, ImpotsV7-dao">
        <constructor-arg index="0" value="MySql.Data.MySqlClient"/>
        <constructor-arg index="1" value="Server=localhost;Database=bdimpots;Uid=admimpots;Pwd=mdpimpots;"/>
        <constructor-arg index="2" value="select limite, coeffr, coeffn from tranches"/>
      </object>
      <object name="metier" type="Metier.ImpotMetier, ImpotsV7-metier">
        <constructor-arg index="0" ref="dao"/>
      </object>
    </objects>
  </spring>
</configuration>

Dit is het bestand [App.config] dat in versie 7 van de applicatie wordt gebruikt en in paragraaf 9.8.4 wordt besproken.

  • regels 16-20: definiëren een laag [dao] die werkt met een database MySQL5. Deze database is beschreven in paragraaf 9.8.1.
  • regels 21-23: definiëren de laag [metier]

Laten we teruggaan naar de serverpuzzel:

Bij het opstarten van de applicatie zijn de lagen [metier] en [dao] geïnstantieerd. De levensduur van de lagen is gelijk aan die van de applicatie zelf. Wanneer wordt de webservice geïnstantieerd? In feite bij elk verzoek dat eraan wordt gedaan. Aan het einde van het verzoek wordt het object dat het verzoek heeft afgehandeld, verwijderd. Een webservice is dus op het eerste gezicht stateloze. Hij kan geen informatie tussen twee verzoeken opslaan in velden die hem toebehoren. Hij kan deze wel opslaan in de sessie van de gebruiker. Hiervoor moeten de methoden die hij blootstelt, worden gemarkeerd met een speciaal attribuut:


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

Hierboven geeft regel 1 de methode CalculerImpot toegang tot de container Session waarover we eerder hebben gesproken. We hoeven dit attribuut niet te gebruiken in onze applicatie. De webservice WsImpot wordt dus bij elk verzoek geïnstantieerd en is stateless.

We kunnen nu de code [ServiceImpot.cs] schrijven die de webservice implementeert:


using System.Web.Services;
using WsImpot;

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

    [WebMethod]
    public int CalculerImpot(bool marié, int nbEnfants, int salaire)
    {
        return Global.Metier.CalculerImpot(marié, nbEnfants, salaire);
    }

}
  • regel 10: de enige methode van de webservice
  • regel 12: er wordt gebruikgemaakt van de methode CalculerImpot van de laag [metier]. Een verwijzing naar deze laag is te vinden in het statische veld Metier van de klasse Global. Deze behoort tot de naamruimte WsImpot (regel 2).

We zijn klaar om de webservice te starten. Eerst moeten we SGBD en MySQL5 starten, zodat de database bdimpots toegankelijk is. Zodra dit is gebeurd, starten we [1], de webservice:

De browser geeft vervolgens de pagina [2] weer. We volgen de link:

We geven een waarde aan elk van de drie parameters van de methode CalculerImpot en vragen om de methode uit te voeren. We krijgen het volgende resultaat, dat correct is:

Image

12.4.2. Een grafische Windows-client voor de externe webservice

Nu de webservice is geschreven, gaan we verder met de client. Laten we nog eens kijken naar de architectuur van de client/server-toepassing:

We moeten de client [2] schrijven. De grafische interface zal identiek zijn aan die van versie 8:

Om het gedeelte [client] van versie 9 te schrijven, gaan we uit van het gedeelte [client] van versie 8 en brengen we vervolgens de nodige wijzigingen aan. We dupliceren het Visual Studio-project dat in paragraaf 11.9.4.1 is besproken, hernoemen het naar ClientWsImpot en laden het in Visual Studio:

De Visual Studio-oplossing uit versie 8 bestond uit twee projecten:

  • het project [metier] [1], dat een TCP-client was van de TCP-server voor belastingberekening
  • het project [ui] [2] voor de grafische gebruikersinterface.

De volgende wijzigingen moeten worden aangebracht:

  • het project [metier] moet voortaan de client van een webservice zijn
  • het project [ui] moet verwijzen naar DLL van de nieuwe laag [metier]
  • de configuratie van de laag [metier] in [App.config] moet worden aangepast.

12.4.2.1. De nieuwe laag [metier]

  • in [1] is IImpotMetier de interface van de laag [metier] en ImpotMetierTcp de implementatie ervan door een TCP-client
  • in [2] verwijderen we de implementatie ImpotMetierTcp. We moeten een andere implementatie van de interface IImpotMetier maken, die als client van een webservice zal fungeren.
  • In [3] noemen we Client de standaardnaamruimte van het project [metier]. De DLL die wordt gegenereerd, krijgt de naam [ImpotsV9-metier.dll].
  • in [4] maken we een verwijzing naar de webservice WsImpot.
  • in [5] configureren en valideren we deze.
  • In [6] is de verwijzing naar de webservice WsImpot aangemaakt en is een bestand [app.config] gegenereerd.

In het verborgen bestand [Reference.cs]:

  • is de naamruimte Client.WsImpot
  • de clientklasse heet ServiceImpotSoapClient
  • heeft deze één enkele ondertekeningsmethode:

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

Nu moeten we nog de interface IImpotMetier implementeren:


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

We implementeren deze met de volgende klasse ImpotMetierWs:


using System.Net.Sockets;
using System.IO;
using Client.WsImpot;

namespace Metier {
    public class ImpotMetierWs : IImpotMetier {

        // client van de externe webservice
        private ServiceImpotSoapClient client = new ServiceImpotSoapClient();

        // belastingberekening
        public int CalculerImpot(bool marié, int nbEnfants, int salaire) {
            return client.CalculerImpot(marié, nbEnfants, salaire);
        }

    }
}
  • regel 6: de klasse ImpotMetierWs implementeert de interface IImpotMetier.
  • regel 9: bij het aanmaken van een instantie van ImpotMetierWs wordt het veld client geïnitialiseerd met een instantie van een client van de webservice voor belastingberekening.
  • regel 12: de enige methode van de interface IImpotMetier die moet worden geïmplementeerd.
  • regel 13: er wordt gebruikgemaakt van de methode CalculerImpot van de client van de externe webservice voor belastingberekening. Uiteindelijk wordt de methode CalculerImpot van de externe webservice aangeroepen.

We kunnen de DLL van het project genereren:

  • naar [1], het project [client] in zijn eindstatus
  • in [2], het genereren van het project DLL
  • in [3], het bestand DLL en ImpotsV9-metier.dll bevinden zich in de map /bin/Release van het project.

12.4.2.2. De nieuwe laag [ui]

De clientlaag [client] is geschreven. We moeten nu nog de laag [ui] schrijven. Laten we teruggaan naar het Visual Studio-project:

  • in [1], het project [ui] uit versie 8
  • naar [2], het project DLL en ImpotsV8-metier uit de oude laag [metier] wordt vervangen door het project DLL ImpotsV9-metier uit de nieuwe laag
  • in [3], de DLL en ImpotsV9-metier worden toegevoegd aan de projectreferenties.

De tweede wijziging betreft [App.config]. Houd er rekening mee dat dit bestand door Spring wordt gebruikt om de laag [metier] te instantiëren. Aangezien deze is gewijzigd, moet de configuratie van [App.config] worden aangepast. Daarnaast moet [App.config] de configuratie bevatten waarmee verbinding kan worden gemaakt met de externe webservice voor belastingberekening. Deze configuratie is gegenereerd in het bestand [app.config] van het project [metier] toen de verwijzing naar de externe webservice daar werd toegevoegd.

Het bestand [App.config] ziet er dan als volgt uit:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>

    <configSections>
        <sectionGroup name="spring">
            <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
            <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
        </sectionGroup>
    </configSections>

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

    <!-- webservice -->
    <system.serviceModel>
        <bindings>
            <basicHttpBinding>
                <binding name="ServiceImpotSoap" closeTimeout="00:01:00" openTimeout="00:01:00"
                        receiveTimeout="00:10:00" sendTimeout="00:01:00" allowCookies="false"
                        bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
                        maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
                        messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
                        useDefaultWebProxy="true">
                    <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
                            maxBytesPerRead="4096" maxNameTableCharCount="16384" />
                    <security mode="None">
                        <transport clientCredentialType="None" proxyCredentialType="None"
                                realm="" />
                        <message clientCredentialType="UserName" algorithmSuite="Default" />
                    </security>
                </binding>
            </basicHttpBinding>
        </bindings>
        <client>
            <endpoint address="http://localhost:2172/WsImpot/ServiceImpot.asmx"
                    binding="basicHttpBinding" bindingConfiguration="ServiceImpotSoap"
                    contract="WsImpot.ServiceImpotSoap" name="ServiceImpotSoap" />
        </client>
    </system.serviceModel>
</configuration>
  • regels 15-18: Spring instantiëert slechts één object, de laag [metier]
  • regel 16: de laag [metier] wordt geïnstantieerd door de klasse [Metier.ImpotMetierWs], die zich bevindt in de DLL ImpotsV9-metier.
  • regels 22-46: de configuratie van de client voor de externe webservice. Dit is een kopie van de inhoud van het bestand [app.config] uit het project [metier].

We zijn klaar. We starten de applicatie met Ctrl-F5 (de webservice moet gestart zijn, de bestanden SGBD en MySQL5 moeten gestart zijn, en de poort op regel 42 hierboven moet de juiste zijn):

  

12.5. Een webclient voor de webservice voor belastingberekening

Laten we nog eens kijken naar de architectuur van de zojuist geschreven client/server-applicatie:

De bovenstaande laag [ui] werd geïmplementeerd door een grafische Windows-client. We implementeren deze nu met een webinterface:

 

Dit is een belangrijke verandering voor de gebruikers. Momenteel kan onze client/server-applicatie, versie 9, meerdere clients tegelijk bedienen. Dit is een verbetering ten opzichte van versie 8, waarin slechts één client tegelijk werd bediend. De beperking is dat gebruikers die gebruik willen maken van de webservice voor belastingberekening, de door ons geschreven Windows-client op hun computer moeten hebben geïnstalleerd. In deze nieuwe versie, die we versie 10 zullen noemen, zullen gebruikers via hun browser toegang hebben tot de webservice voor belastingberekening.

In de bovenstaande architectuur:

  • blijft de serverzijde ongewijzigd. Deze blijft zoals in versie 9.
  • Aan de clientzijde verandert de laag [client du service web] niet. Deze is ingekapseld in de DLL en [ImpotsV9-metier]. We gaan deze DLL hergebruiken.
  • Uiteindelijk bestaat de enige wijziging uit het vervangen van een Windows-grafische interface door een webinterface.

We gaan nieuwe concepten van server-side webprogrammering behandelen. Aangezien het doel van dit document niet is om webprogrammering te onderwijzen, zullen we proberen de te volgen aanpak uit te leggen zonder al te veel in detail te treden. Dit gedeelte zal dus een beetje ‘magisch’ aanvoelen. We vinden het echter interessant om deze aanpak te volgen om een nieuw voorbeeld te laten zien van een meerlaagse architectuur waarbij één van de lagen wordt gewijzigd.

De architectuur van versie 10 is dus als volgt:

We hebben alle lagen al, behalve de laag [web]. Om beter te begrijpen wat er gaat gebeuren, moeten we wat preciezer zijn over de architectuur van de client. Die ziet er als volgt uit:

  • de webgebruiker heeft in zijn browser een webformulier
  • dit formulier wordt verzonden naar webserver 1, die het laat verwerken door de laag [web]
  • de laag [web] heeft de diensten nodig van de client van de externe webservice, ingekapseld in [ImpotsV9-metier.dll].
  • de client van de externe webservice communiceert met webserver 2, waarop de externe webservice wordt gehost.
  • Het antwoord van de externe webservice wordt doorgestuurd naar de weblaag van de client, die het opmaakt tot een pagina die vervolgens naar de gebruiker wordt verzonden.

Onze taak hier is dus:

  • het webformulier bouwen dat de gebruiker in zijn browser te zien krijgt
  • de webapplicatie te schrijven die het verzoek van de gebruiker verwerkt en hem een antwoord stuurt in de vorm van een nieuwe webpagina. Deze zal in feite hetzelfde zijn als het formulier, waaraan het te betalen belastingbedrag is toegevoegd
  • de "glue" te schrijven die ervoor zorgt dat dit alles samen werkt.

Dit alles wordt gedaan met behulp van een nieuwe website die is gemaakt met Visual Web Developer:

  • [1]: kies de optie Bestand / Nieuwe website
  • [2]: kies een applicatietype van het type ASP.NET Web Site
  • [3]: kies de ontwikkeltaal: C#
  • [4]: geef de map op waarin het project moet worden aangemaakt
  • [5]: het project is aangemaakt in Visual Web Developer
    • [Default.aspx] is een webpagina die de standaardpagina wordt genoemd. Dit is de pagina die wordt weergegeven wanneer de URL http://.../ClientAspImpot wordt opgevraagd zonder een specifiek document op te geven. Op deze pagina staat het formulier voor de belastingberekening dat de gebruiker in zijn browser te zien krijgt.
    • [Default.aspx.cs] is de klasse die aan de pagina is gekoppeld; deze genereert het formulier dat naar de gebruiker wordt verzonden en verwerkt het vervolgens wanneer de gebruiker het heeft ingevuld en verzonden.
    • [web.config] is het configuratiebestand van de applicatie. In tegenstelling tot de vorige keren zullen we dit behouden.

Als we terugkeren naar de architectuur die we moeten bouwen:

  • [1] wordt geïmplementeerd door [Default.aspx]
  • [2] wordt geïmplementeerd door [Default.aspx.cs]
  • [3] wordt geïmplementeerd door DLL en [ImpotV9-metier]

Laten we beginnen met het implementeren van de laag [3]. Dit verloopt in verschillende stappen:

  • in [1] wordt de map [lib] van de Windows-grafische client versie 9 gekopieerd naar de map van het webproject [ClientAspWsImpot]. Dit gebeurt met Windows Verkenner. Om deze map in de Web Developer-oplossing te laten verschijnen, moet de oplossing worden vernieuwd met de knop [2].
  • Vervolgens moet u ze toevoegen aan de verwijzingen van het project [3,4,5]. De DLL’s waarnaar wordt verwezen, worden automatisch opnieuw gekopieerd naar de map /bin van het project [6].

We beschikken nu over de DLL’s die nodig zijn voor de werking van Spring en de clientlaag van de externe webservice is eveneens geïmplementeerd. Hoewel de code hiervan wel aanwezig is, moet de configuratie nog worden uitgevoerd. In versie 9 werd deze geconfigureerd via het volgende bestand [App.config]:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>

    <configSections>
        <sectionGroup name="spring">
            <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
            <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
        </sectionGroup>
    </configSections>

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

    <!-- webservice -->
    <system.serviceModel>
        <bindings>
            <basicHttpBinding>
...
            </basicHttpBinding>
        </bindings>
        <client>
            <endpoint address="http://localhost:2172/WsImpot/ServiceImpot.asmx"
                    binding="basicHttpBinding" bindingConfiguration="ServiceImpotSoap"
                    contract="WsImpot.ServiceImpotSoap" name="ServiceImpotSoap" />
        </client>
    </system.serviceModel>
</configuration>

We nemen deze configuratie ongewijzigd over en integreren deze als volgt in het bestand [web.config]:


<?xml version="1.0"?>
<configuration>


    <configSections>
      <sectionGroup name="system.web.extensions"...>
...
      </sectionGroup>
      <!-- begin sectie Spring -->
        <sectionGroup name="spring">
          <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
          <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
        </sectionGroup>
      <!-- einde Spring-sectie -->
    </configSections>

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

  <!-- begin configuratie client van de externe webservice -->
  <system.serviceModel>
    <bindings>
      <basicHttpBinding>
...
      </basicHttpBinding>
    </bindings>
    <client>
      <endpoint address="http://localhost:2172/WsImpot/ServiceImpot.asmx"
                    binding="basicHttpBinding" bindingConfiguration="ServiceImpotSoap"
                    contract="WsImpot.ServiceImpotSoap" name="ServiceImpotSoap" />
    </client>
  </system.serviceModel>
  <!-- einde configuratie van de client voor de externe webservice -->

   <!-- andere configuraties die al aanwezig zijn in het gegenereerde web.config-bestand -->
...
</configuration>

Merk op dat regel 37 verwijst naar de poort van de externe webservice. Deze poort kan veranderen, aangezien Visual Developer de webservice op een willekeurige poort start.

Laten we terugkeren naar de architectuur van de webclient die we moeten bouwen:

  • [1] wordt geïmplementeerd door [Default.aspx]
  • [2] wordt geïmplementeerd door [Default.aspx.cs]
  • [3] is geïmplementeerd door DLL [ImpotV9-metier]

We hebben zojuist de laag [3] geïmplementeerd. We gaan nu naar de webinterface [1], geïmplementeerd door de pagina [Default.aspx]. Dubbelklik op de pagina [Default.aspx] om naar de ontwerpmodus te gaan.

Er zijn twee manieren om een webpagina te bouwen:

  • grafisch, zoals in [2]. Kies dan de modus [Design] in [1]. Deze knoppenbalk vind je onderaan in de statusbalk van de webpagina-editor.
  • met een opmaaktaal zoals in [3]. Je moet dan de modus [Source] of [1] kiezen.

De modi [Design] en [Source] zijn bidirectioneel: een wijziging die in de modus [Design] wordt aangebracht, resulteert in een wijziging in de modus [Source] en omgekeerd. Ter herinnering: het webformulier dat in de browser moet worden weergegeven, is het volgende:

  • in [1], het formulier dat in een browser wordt weergegeven
  • in [2], de componenten die worden gebruikt om het te bouwen
  • in [3], de ontwerppagina van het formulier. Deze bevat de volgende elementen:
    • regel A: twee keuzerondjes met de namen RadioButtonOui en RadioButtonNon
    • regel B: een invoerveld met de naam TextBoxEnfants en een label met de naam LabelErreurEnfants
    • regel C: een invoerveld met de naam TextBoxSalaire en een label met de naam LabelErreurSalaire
    • regel D, een label met de naam LabelImpot
    • regel E: twee knoppen met de namen ButtonCalculer en ButtonEffacer

Zodra een component op het ontwerpvlak is geplaatst, heeft men toegang tot de eigenschappen ervan:

  • in [1], toegang tot de eigenschappen van een component
  • in [2], het eigenschappenvenster van de component [LabelErreurEnfants ]
  • in [3], (ID) is de naam van de component
  • in [4] hebben we de letters van het label de kleur rood gegeven.

Het is niet voldoende om componenten op het formulier te plaatsen en vervolgens hun eigenschappen in te stellen. Je moet ook hun indeling regelen. In een grafische Windows-interface is deze indeling absoluut. Je sleept de component naar de plek waar je hem wilt hebben. Op een webpagina is het anders, complexer maar ook krachtiger. Dit aspect komt hier niet aan bod.

De broncode [Default.aspx] die door dit ontwerp is gegenereerd, is als volgt:


<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>

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

De formuliercomponenten zijn te herkennen op de regels 23, 24, 33, 36, 44, 47, 55, 63 en 66. De rest bestaat voornamelijk uit opmaak.

Laten we teruggaan naar de architectuur die we moeten bouwen:

  • [1] is geïmplementeerd door [Default.aspx]
  • [2] zal worden geïmplementeerd door [Default.aspx.cs]
  • [3] is geïmplementeerd door DLL [ImpotV9-metier]

De lagen [1] en [3] zijn nu geïmplementeerd. We moeten nu nog de laag [2] schrijven, die het formulier genereert, het naar de gebruiker verstuurt, het verwerkt wanneer de gebruiker het ingevuld terugstuurt, de laag [3] gebruikt voor de belastingberekening, de webpagina met het antwoord voor de gebruiker genereert en deze naar hem terugstuurt. De code [Default.aspx.cs] voert al dit werk uit:


using System;
using WsImpot;

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

Deze code lijkt sterk op die van een klassiek Windows-formulier. Dat is het belangrijkste voordeel van de ASP.NET-technologie: er is geen breuk tussen het Windows-programmeringsmodel en dat van de webprogrammering ASP.NET. Je moet alleen altijd het volgende schema in gedachten houden:

Wanneer de gebruiker in [1] op de knop [Calculer] klikt, wordt de procedure ButtonCalculer_Click op regel 6 van [Default.aspx.cs] uitgevoerd. Maar ondertussen:

  • worden de waarden van het ingevulde formulier via het HTTP-protocol van de browser naar de webserver verzonden
  • de server ASP.NET analyseert het verzoek en stuurt het door naar de pagina [Default.aspx]
  • de pagina [Default.aspx] wordt geïnstantieerd.
  • de componenten ervan (RadioButtonOui, RadioButtonNon, TextBoxEnfants, TextBoxSalaire, LabelErreurEnfants, LabelErreurSalaire, LabelImpot) worden geïnitialiseerd met de waarde die ze hadden toen het formulier aanvankelijk naar de browser werd verzonden via een mechanisme dat "ViewState" heet.
  • De verzonden waarden worden toegewezen aan hun componenten (RadioButtonOui, RadioButtonNon, TextBoxEnfants, TextBoxSalaire). Als de gebruiker dus 2 als aantal kinderen heeft ingevoerd, krijgen we TextBoxEnfants.Text="2".
  • Als de pagina [Default.aspx] een methode [Page_Load] bevat, wordt deze uitgevoerd
  • de methode [ButtonCalculer_Click] op regel 6 wordt uitgevoerd als er op de knop [Calculer] is geklikt
  • de methode [ButtonEffacer_Click] op regel 10 wordt uitgevoerd als op de knop [Effacer] is geklikt

Tussen het moment waarop de gebruiker een gebeurtenis in zijn browser creëert en het moment waarop deze wordt verwerkt in [Default.aspx.cs], schuilt een grote complexiteit. Deze complexiteit blijft verborgen en men kan doen alsof ze niet bestaat bij het schrijven van de gebeurtenishandlers voor de webpagina. Maar men mag nooit vergeten dat er een netwerk zit tussen de gebeurtenis en de bijbehorende handler en dat het dus geen optie is om muisgebeurtenissen zoals Mouse_Move te verwerken die kostbare heen-en-weer-verkeer tussen client en server zouden veroorzaken ...

De code voor de handlers van de klikken op de knoppen [Calculer] en [Effacer] is dezelfde die men voor een klassieke Windows-toepassing zou hebben geschreven:


protected void ButtonCalculer_Click(object sender, EventArgs e)
    {
        // gegevenscontrole
        int nbEnfants;
        bool erreur = false;
        if (!int.TryParse(TextBoxEnfants.Text.Trim(), out nbEnfants) || nbEnfants < 0)
        {
            LabelErreurEnfants.Text = "Valeur incorrecte...";
            erreur = true;
        }
        int salaire;
        if (!int.TryParse(TextBoxSalaire.Text.Trim(), out salaire) || salaire < 0)
        {
            LabelErreurSalaire.Text = "Valeur incorrecte...";
            erreur = true;
        }
        // fout?
        if (erreur) return;
        // eventuele fouten worden gewist
        LabelErreurEnfants.Text = "";
        LabelErreurSalaire.Text = "";
        // burgerlijke staat
        bool marié = RadioButtonOui.Checked;
        // belastingberekening
        try
        {
            LabelImpot.Text = String.Format("{0} euros",Global.Metier.CalculerImpot(marié, nbEnfants, salaire));
        }
        catch (Exception ex)
        {
            LabelImpot.Text = ex.Message;
        }
    }
  • Om deze code te begrijpen, moet men weten
    • dat het formulier [Default.aspx] bij het begin van de uitvoering er precies zo uitziet als de gebruiker het heeft ingevuld. De velden (RadioButtonOui, RadioButtonNon, TextBoxEnfants, TextBoxSalaire) bevatten dus de door de gebruiker ingevoerde waarden.
    • dat na afloop van de uitvoering dezelfde pagina [Default.aspx] aan de gebruiker wordt teruggestuurd. Dit gebeurt automatisch.

De procedure ButtonCalculer_Click moet dus op basis van de huidige waarden van de velden (RadioButtonOui, RadioButtonNon, TextBoxEnfants, TextBoxSalaire) de waarde van alle velden (RadioButtonOui, RadioButtonNon, TextBoxEnfants, TextBoxSalaire, LabelErreurEnfants, LabelErreurSalaire, LabelImpot) van de nieuwe pagina [Default.aspx] die naar de gebruiker wordt teruggestuurd.

Deze code bevat geen bijzondere moeilijkheden. Alleen regel 27 verdient enige uitleg. Deze maakt gebruik van de methode CalculerImpot van een veld Global.Metier dat nog niet is aangetroffen. We komen hier straks op terug.

De methode ButtonEffacer_Click is als volgt:


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

Laten we terugkeren naar de architectuur die we moeten bouwen:

  • [1] is geïmplementeerd door [Default.aspx]
  • [2] is geïmplementeerd door [Default.aspx.cs]
  • [3] is geïmplementeerd door DLL [ImpotV9-metier]

Nu moeten we nog de "lijm" rond deze drie lagen aanbrengen. Het gaat in wezen om:

  • het instantiëren van de laag [3] bij het opstarten van de applicatie
  • een verwijzing naar deze laag op een plek te plaatsen waar de webpagina [Default.aspx.cs] deze kan ophalen telkens wanneer deze wordt geïnstantieerd en gevraagd wordt de belasting te berekenen.

Dit is geen nieuw probleem. Het is al tegengekomen bij de ontwikkeling van de externe webservice en besproken in paragraaf 12.4.1. We weten dat de oplossing bestaat uit:

  • een bestand [Global.asax] aan te maken dat gekoppeld is aan een klasse [Global.cs]
  • om de laag [3] te instantiëren in de methode Application_Start van [Global.cs]
  • de referentie van de laag [3] in een statisch veld van de klasse [Global.cs] te plaatsen, aangezien de levensduur van deze klasse gelijk is aan die van de applicatie.

Ons webproject ontwikkelt zich dus als volgt:

  • naar [1], het bestand [Global.asax].
  • naar [2], de bijbehorende code [Global.cs]. De map [App_Code] waarin dit bestand zich bevindt, is standaard niet aanwezig in de weboplossing. Gebruik [3] om deze aan te maken.

Het bestand Global.asax is als volgt:


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

De code [Global.cs] is als volgt:


using System;
using Metier;
using Spring.Context.Support;
namespace WsImpot
{
    public class Global : System.Web.HttpApplication
    {
        // bedrijfslaag
        public static IImpotMetier Metier;

        // methode die bij het opstarten van de applicatie wordt uitgevoerd
        private void Application_Start(object sender, EventArgs e)
        {
            // instanties van de lagen [metier] en [dao]
            Metier = ContextRegistry.GetContext().GetObject("metier") as IImpotMetier;
        }
    }
}
  • regel 6: de klasse heet Global en maakt deel uit van de naamruimte WsImpot (regel 4). De volledige naam is dus WsImpot.Global en deze naam moet worden ingevuld in het attribuut Inherits van Global.asax.
  • regel 6: we weten dat de klasse die aan Global.asax is gekoppeld, verplicht moet zijn afgeleid van de klasse System.Web.HttpApplication.
  • regel 12: de methode Application_Start die bij het opstarten van de webapplicatie wordt uitgevoerd.
  • regel 15: de laag [metier] (laag [3] van de applicatie die momenteel wordt ontwikkeld) wordt geïnstantieerd met behulp van Spring en de volgende configuratie in [web.config]:

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

De klasse [Metier.ImpotMetierWS] uit regel (g) hierboven bevindt zich in [ImpotsV9-metier.dll].

De referentie van de aangemaakte laag [metier] wordt in het statische veld van regel 9 geplaatst. Dit veld wordt gebruikt in regel 27 van de procedure ButtonCalculer_Click:


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

We zijn klaar voor een test. Start de SGBD MySQL5, de externe webservice, en noteer de poort waarop deze draait:

  

Zodra dit is gebeurd, moet je controleren of in het bestand [web.config] van de webclient de poort van de externe webservice correct is:

Zodra dit is gebeurd, kan de webclient van de externe webservice worden gestart met Ctrl-F5:

  

12.6. Een Java-consoleclient voor de webservice voor belastingberekening

Om aan te tonen dat webservices toegankelijk zijn voor clients die in elke willekeurige programmeertaal zijn geschreven, schrijven we een eenvoudige Java-consoleclient. De architectuur van de client/server-toepassing ziet er als volgt uit:

  • de client [1] wordt geschreven in Java
  • de server [2] is geschreven in C#

Allereerst gaan we een detail in onze webservice voor belastingberekening aanpassen. De huidige definitie ervan in [ServiceImpot.cs] is als volgt:


...
public class ServiceImpot : System.Web.Services.WebService
{

    [WebMethod]
    public int CalculerImpot(bool marié, int nbEnfants, int salaire)
    {
        return Global.Metier.CalculerImpot(marié, nbEnfants, salaire);
    }

}

Uit tests is gebleken dat het accent in de parameter marié op regel 6 en 8 een probleem kan vormen voor de interoperabiliteit tussen Java en C#. We zullen de volgende nieuwe definitie hanteren:


...
public class ServiceImpot : System.Web.Services.WebService
{

    [WebMethod]
    public int CalculerImpot(bool marie, int nbEnfants, int salaire)
    {
        return Global.Metier.CalculerImpot(marie, nbEnfants, salaire);
    }

}

Deze service wordt ondergebracht in een nieuw Web Developer-project met de naam WsImpotsSansAccents. De webservice krijgt dan de URL [/WsImpotSansAccents/ServiceImpot.asmx].

Image

Voor het schrijven van de Java-client gebruiken we NetBeans IDE en [http://www.netbeans.org/]:

  • in [1], maak een nieuw project aan
  • in [2,3], kies een project van het type Java-toepassing in Java.
  • in [4], ga naar de volgende stap
  • in [5], geef het project een naam
  • in [6], de map aangeven waarin een submap met de naam van het project voor het project wordt aangemaakt
  • in [7], geef een naam aan de klasse die de methode main zal bevatten, die bij het opstarten van de applicatie wordt uitgevoerd
  • in [8], de wizard afsluiten
  • in [9]: het gegenereerde Java-project
  • in [10]: klik met de rechtermuisknop op het project om de client voor de webservice voor belastingberekening te genereren
  • in [11], de URL van het bestand dat de webservice voor belastingberekening beschrijft:

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

Deze URL is die van de service [ServiceImpot.asmx], waaraan de parameter ?WSDL wordt toegevoegd. Het document op deze URL beschrijft in XML-taal wat de service [15] kan doen. Dit is een standaardonderdeel van een webservice.

  • in [12], het pakket (het equivalent van de naamruimte in C#) waarin de klassen moeten worden geplaatst die zullen worden gegenereerd
  • in [13]: laat de standaardwaarde staan
  • in [14]: de wizard voltooien
  • In [16] is de geïmporteerde webservice in het Java-project geïntegreerd. Deze ondersteunt twee communicatieprotocollen: Soap en Soap12.
  • In [17], de klasse [Main] waarin we de gegenereerde client gaan gebruiken
  • in [18] gaan we code invoegen in de methode [main]. Plaats de cursor op de plek waar de code moet worden ingevoegd, klik met de rechtermuisknop en kies de optie [19]
  • in [20], geef aan dat u de aanroepcode wilt genereren voor de functie CalculerImpot van de externe dienst voor belastingberekening en klik vervolgens op OK.

De code die in [Main] wordt gegenereerd, is als volgt:

public class Main {

    public static void main(String[] args) {
         // TODO applicatielogica hier
      try { // Webservice-bewerking aanroepen
        wsimpot.ServiceImpot service = new wsimpot.ServiceImpot();
        wsimpot.ServiceImpotSoap port = service.getServiceImpotSoap();
         // TODO initialiseer hier de WS-bewerkingsargumenten
        boolean marie = false;
        int nbEnfants = 0;
        int salaire = 0;
         // TODO verwerk het resultaat hier
        int result = port.calculerImpot(marie, nbEnfants, salaire);
        System.out.println("Result = "+result);
      } catch (Exception ex) {
         // TODO hier aangepaste uitzonderingen afhandelen
      }
    }
}

De gegenereerde code laat zien hoe de functie CalculerImpot van de externe dienst voor belastingberekening moet worden aangeroepen. Als we een vergelijking maken met wat we in C# hebben gezien, is de variabele port op regel 7 het equivalent van de client die in C# wordt gebruikt. We zullen verder geen commentaar geven op deze code. We herschikken deze als volgt:

import wsimpot.ServiceImpot;
public class Main {
    public static void main(String[] args) {
      try {
         // de functie CalculerImpot van de webservice wordt aangeroepen
        System.out.println(String.format("Montant à payer : %d euros", new ServiceImpot().getServiceImpotSoap().calculerImpot(true, 2, 60000)));
      } catch (Exception ex) {
        System.out.println(String.format("L'erreur suivante s'est produite %s",ex.getMessage()));
      }
    }
}
  • regel 1: we importeren de klasse ServiceImpot, die de door de wizard gegenereerde client vertegenwoordigt.
  • regel 6: we roepen de externe methode CalculerImpot aan volgens de procedure die is aangegeven in de gegenereerde code in main.

De resultaten die bij uitvoering in de console worden weergegeven (F6) zijn als volgt:

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