Skip to content

12. Servizi Web

12.1. Introduzione

Nel capitolo precedente abbiamo presentato diverse applicazioni client-server TCP/IP. Poiché client e server si scambiano righe di testo, possono essere scritti in qualsiasi linguaggio. Il client deve semplicemente conoscere il protocollo di dialogo previsto dal server.

Anche i servizi Web sono applicazioni server TCP/IP. Presentano le seguenti caratteristiche:

  • Sono ospitati da server Web e il protocollo di scambio client-server è HTTP (HyperText Transport Protocol), un protocollo che si sovrappone al TCP-IP.
  • Il servizio Web ha un protocollo di dialogo standard, indipendentemente dal servizio fornito. Un servizio Web offre vari servizi S1, S2, .., Sn. Ciascuno di questi si aspetta parametri forniti dal client e restituisce un risultato al client. Per ogni servizio, il client deve conoscere:
    • il nome esatto del dipartimento, se
    • l'elenco dei parametri da fornire e il loro tipo
    • il tipo di risultato restituito dal servizio

Una volta noti questi elementi, il dialogo client-server segue lo stesso formato, indipendentemente dal servizio Web interrogato. In questo modo, la scrittura del client è standardizzata.

  • Per motivi di sicurezza contro gli attacchi provenienti da Internet, molte organizzazioni dispongono di reti private e aprono verso Internet solo alcune porte dei propri server: essenzialmente la porta 80 del servizio web. Tutte le altre porte sono bloccate. Le applicazioni client-server, come quelle presentate nel capitolo precedente, sono realizzate all'interno della rete privata (intranet) e generalmente non sono accessibili dall'esterno. Ospitare un servizio su un server web lo rende accessibile all'intera comunità di Internet.
  • Il servizio Web può essere modellato come un oggetto remoto. I servizi offerti diventano quindi metodi di questo oggetto. Un client può accedere a questo oggetto remoto come se fosse locale. Ciò nasconde l'intero livello di comunicazione di rete e consente di costruire un client indipendente da questo livello. Se il livello di rete cambia, non è necessario modificare il client.
  • Come per le applicazioni client-server TCP/IP presentate nel capitolo precedente, client e server possono essere scritti in qualsiasi linguaggio. Essi si scambiano righe di testo. Queste consistono di due parti:
    • intestazioni richieste dal protocollo HTTP
    • il corpo del messaggio. Per una risposta dal server al client, questo è in formato XML (eXtensible Markup Language). Per una richiesta dal client al server, il corpo del messaggio può assumere diverse forme, incluso l'XML. La richiesta XML del client può avere un formato speciale chiamato SOAP (Simple Object Access Protocol). In questo caso, anche la risposta del server segue il formato SOAP.

L'architettura di un'applicazione client/server basata su un servizio web è la seguente:

Si tratta di un'estensione dell'architettura a 3 livelli, alla quale vengono aggiunte classi di comunicazione di rete specializzate. Abbiamo già incontrato un'architettura simile con l'applicazione client/server grafica di Windows Tcp d'impôts nel paragrafo 11.9.1.

Spieghiamo queste nozioni generali con un primo esempio.

12.2. Un primo servizio Web con Visual Web Developer

Realizzeremo una prima applicazione client/server con la seguente architettura semplificata:

12.2.1. Il lato server

Abbiamo indicato che un servizio web è ospitato da un server web. La scrittura di un servizio web rientra nel quadro generale della programmazione web lato server. Abbiamo già avuto occasione di scrivere client web, che è anch'essa programmazione web, ma questa volta sul lato client. Il termine programmazione web si riferisce molto spesso alla programmazione lato server piuttosto che a quella lato client. Per sviluppare servizi web o, più in generale, applicazioni web, Visual C# non è lo strumento giusto. Useremo Visual Developer, una delle versioni Express di Visual Studio 2008 disponibile per il download [2] all'indirizzo [1]: [http://msdn.microsoft.com/en-fr/express/future/bb421473(en-us).aspx] (maggio 2008):

  • [1]: indirizzo di download
  • [2]: scheda Download
  • [3] : scarica Visual Developer 2008

Per creare un servizio web iniziale, procedere come segue dopo aver avviato Visual Developer :

  • [1]: selezionare l'opzione File / Nuovo sito Web
  • [2]: scegli un servizio Web ASP.NET
  • [3]: scegliere il linguaggio di sviluppo: C#
  • [4]: indicare la cartella in cui creare il progetto
  • [5]: il progetto creato in Visual Web Developer
  • [6]: la cartella del progetto sul disco

In Web Developer, un'applicazione web è strutturata come segue:

  • una radice contenente i documenti del sito web (pagine web statiche Html, immagini, pagine web dinamiche .aspx, servizi web .asmx, ...). È incluso anche il file [web.config], che è il file di configurazione dell'applicazione web. Svolge lo stesso ruolo del file [App.config] per le applicazioni Windows ed è strutturato allo stesso modo.
  • una cartella [App_Code] contenente classi e interfacce del sito web da compilare.
  • una cartella [App_Data] in cui collocare i dati utilizzati dalle classi [App_Code]. Ad esempio, questa potrebbe contenere un database SQL Server *.mdf.

[Service.asmx] è il servizio web di cui abbiamo richiesto la creazione. Contiene solo la seguente riga:


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

Il codice sorgente sopra riportato è destinato al server Web che ospiterà l'applicazione. In modalità produzione, questo server è solitamente IIS (Internet Information Server), il server Web di Microsoft. Visual Web Developer incorpora un server Web leggero da utilizzare in modalità sviluppo. La direttiva precedente indica al server Web:

  • [Service.asmx] è un servizio Web (direttiva WebService)
  • scritto in C# (attributo Language)
  • che il codice C# per il servizio Web si trova in [~/App_Code/Service.cs] (attributo CodeBehind). È qui che il server Web andrà a compilarlo.
  • che la classe che implementa il servizio web si chiama Service (attributo Class)

Il codice C# [Service.cs] del servizio web generato da Visual Developer è il seguente:


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

La classe Service assomiglia a una classica classe C#, ma con alcuni punti da notare:

  • riga 7: la classe deriva dalla classe WebService definita in System.Web.Services. Questa ereditarietà non è sempre obbligatoria. In questo esempio, in particolare, se ne potrebbe fare a meno.
  • riga 3: la classe stessa è preceduta da un attributo [WebService(Namespace="http://tempuri.org/")] destinato a fornire uno spazio dei nomi al servizio web. Un fornitore di classi assegna uno spazio dei nomi alle proprie classi per conferire loro un nome univoco ed evitare conflitti con classi di altri fornitori che potrebbero avere lo stesso nome. Lo stesso vale per i servizi Web. Ogni servizio Web deve essere identificato da un nome univoco, in questo caso http://tempuri.org/. Questo nome può essere qualsiasi cosa. Non deve necessariamente essere un Uri Http.
  • riga 15: il metodo HelloWorld è preceduto da un [WebMethod] che indica al compilatore che il metodo deve essere reso visibile ai client remoti del servizio web. Un metodo non preceduto da questo attributo non è visibile ai client del servizio web. Potrebbe trattarsi di un metodo interno utilizzato da altri metodi, ma non destinato alla pubblicazione.
  • riga 9: costruttore del servizio web. È inutile nella nostra applicazione.

La classe [Service.cs] generata viene trasformata come segue:


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);
    }
 
}

Il file di configurazione [web.config] generato per l'applicazione web è il seguente:


<?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>

Il file è lungo 140 righe. È complesso e non lo commenteremo. Lo lasceremo così com'è. Sopra sono riportati i tag <configuration>, <configSections>, <sectionGroup>, <appSettings> e <connectionString> che abbiamo incontrato nel file [App.config] delle applicazioni Windows.

Abbiamo un servizio web operativo che può essere eseguito:

  • [1,2]: clicca con il tasto destro su [Service.asmx] e richiedi di visualizzare la pagina in un browser
  • [3]: Visual Web Developer avvia il proprio server web integrato e posiziona la sua icona nell'angolo in basso a destra della barra delle applicazioni. Il server web viene avviato su una porta casuale, in questo caso la 1906. L'URI visualizzato /WsHello è il nome del sito web [4].

Visual Web Developer ha inoltre avviato un browser per visualizzare la pagina richiesta, ovvero [Service.asmx] :

  • in [1], l'URI della pagina. Troviamo l'URI del sito [http://localhost:1906/WsHello] seguito da quello della pagina /Service.asmx.
  • in [2], il suffisso .asmx ha indicato al server web che non si trattava di una normale pagina web (suffisso .aspx) che dà origine a una pagina HTML, ma di una pagina di servizio web. Genera quindi automaticamente una pagina web con un collegamento per ciascun metodo del servizio web con l'attributo [WebMethod]. Questi collegamenti consentono di testare i metodi.

Fare clic sul collegamento [2] sopra per passare alla pagina seguente:

  • in [1], si noti l'URI [http://localhost:1906/WsHello/Service.asmx?op=DisBonjourALaDame] della nuova pagina. Questo è l'URI del servizio web, con un parametro op=M, dove M è il nome di uno dei metodi del servizio web.
  • Ricorda la firma del metodo [DisBonjourALaDame]:

    public string DisBonjourALaDame(string nomDeLaDame) ;

Il metodo accetta un parametro di tipo stringa e restituisce anch'esso una stringa. La pagina ci permette di eseguire il metodo [DisBonjourALaDame]: in [2] impostiamo il valore del parametro nomDeLaDame e in [3] richiediamo l'esecuzione del metodo. Otteniamo il seguente risultato:

  • in [1], si noti che l'URI nella risposta non è identico a quello nella richiesta. È cambiato.
  • in [2], la risposta del server web. Si prega di notare i seguenti punti:
    • si tratta di una risposta XML, non HTML
    • il risultato del metodo [DisBonjourALaDame] è incapsulato in un tag <string> che ne rappresenta il tipo.
    • Il tag <string> ha un attributo xmlns (spazio dei nomi XML), che è lo spazio dei nomi che abbiamo assegnato al nostro servizio web (riga 1 qui sotto).

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

Per scoprire come il browser Web ha effettuato la richiesta, osserva il codice HTML nel modulo di test:

...
<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>
...
  • riga 11: i valori del modulo (tag form) verranno inviati (attributo method) all'URL [ http://localhost:1906/WsHello/Service.asmx/DisBonjourALaDame] (attributo action).
  • riga 19: il campo di input si chiama nomDeLaDame (attributo name).

La richiesta di esecuzione del servizio web [/Service.asmx] ci ha permesso di testarne i metodi e di acquisire una conoscenza di base degli scambi client-server.

12.2.2. La sezione clienti

È possibile implementare il client del servizio web remoto sopra descritto con un client TCP/IP di base. Ad esempio, ecco la finestra di dialogo client/server creata con un client PuTTY connesso al servizio web remoto (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>
  • righe 1-5: messaggi inviati dal cliente putty
  • riga 1: comando POST
  • righe 6-10: risposta del server. Ciò significa che il client può inviare i valori POST.
  • riga 11: valori inviati nel modulo param1=val1&param2=val2& .... Alcuni caratteri devono essere caratteri accettabili in un URL. Questo è ciò che in precedenza chiamavamo URL codificato. Qui il modulo ha un unico parametro denominato nomDeLaDame. Il valore inviato ha un totale di 23 caratteri. Questa dimensione deve essere dichiarata nell'intestazione Http alla riga 4.
  • righe 12-22: risposta del server
  • riga 22: il risultato del metodo web [DisBonjourALaDame].

Con Visual C#, è possibile utilizzare una procedura guidata per generare il client di un servizio web remoto. Questo è ciò che vedremo ora.

Il livello [1] sopra riportato è implementato da un progetto Visual Studio C# di tipo applicazione Windows denominato ClientWsHello :

  • in [1], ClientWsHello in Visual C#
  • in [2], lo spazio dei nomi predefinito del progetto sarà Customer (clic destro sul progetto / Proprietà / Applicazione). Questo spazio dei nomi verrà utilizzato per costruire lo spazio dei nomi client che verrà generato.
  • in [3], clicca con il tasto destro del mouse sul progetto per aggiungere un riferimento a un servizio web remoto
  • in [4], impostare l'URI del servizio web creato in precedenza
  • in [4b], connetti Visual C# al servizio web indicato in [4]. Visual C# recupererà la descrizione del servizio web e la utilizzerà per generare un client.
  • in [5], una volta recuperata la descrizione del servizio web, Visual C# può visualizzarne i metodi pubblici
  • in [6], specificare uno spazio dei nomi per il client da generare. Questo verrà aggiunto allo spazio dei nomi definito in [2]. In questo modo, lo spazio dei nomi del client sarà Client.WsHello.
  • in [6b] per confermare la procedura guidata.
  • in [7], il riferimento al servizio web WsHello appare nel progetto. Inoltre, è stato creato un file di configurazione [app.config].
  • in [8], visualizza tutti i file di progetto.
  • in [9], il riferimento al servizio web WsHello contiene vari file che non approfondiremo in questa sede. Daremo tuttavia un'occhiata al file [Reference.cs], che contiene il codice C# per il client generato:

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;
        }
    }
}
  • riga 1: lo spazio dei nomi generato è Client.WsHello. Se si desidera modificare questo spazio dei nomi, è qui che bisogna farlo.
  • riga 3: la classe ServiceSoapClient è la classe client generata. Si tratta di una classe proxy nel senso che nasconderà all'applicazione Windows il fatto che si sta utilizzando un servizio web remoto. L'applicazione Windows utilizzerà WsHello tramite la classe locale Client.WsHello.ServiceSoapClient. Per creare un'istanza del client, utilizzare il costruttore alla riga 5:
Client.WsHello.ServiceSoapClient client=new Client.WsHello.ServiceSoapClient();
  • riga 8: il metodo DisBonjourALaDame è la controparte lato client del servizio web DisBonjourALaDame. L'applicazione Windows utilizzerà il metodo remoto DisBonjourALaDame tramite il metodo locale Client.WsHello.ServiceSoapClient.DisBonjourALaDame nella forma seguente:
string bonjour=client.DisBonjourALaDame("Carla Bruni");

Il file [app.config] generato è il seguente:


<?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>

Da questo file, manterremo solo la riga 8, che contiene l'URI del servizio web. Se l'URI cambia, non è necessario ricompilare il client Windows. È sufficiente modificare l'URI nel file [app.config].

Torniamo all'architettura dell'applicazione Windows che vogliamo creare:

Abbiamo realizzato il livello [client] del servizio web. Il livello [ui] sarà il seguente:

tipo
nome
ruolo
1
Casella di testo
textBoxNomDame
nome della signora
2
Pulsante
pulsanteSaluti
per connettersi al servizio web remoto WsHello e interrogare il metodo DisBonjourALaDame.
3
Etichetta
labelBonjour
il risultato restituito dal servizio web

Il codice del modulo [Form1.cs] è il seguente:


using System;
using System.Windows.Forms;
using Client.WsHello;
 
namespace ClientSalutations {
    public partial class Form1 : Form {
        public Form1() {
            InitializeComponent();
        }
 
        private void buttonSalutations_Click(object sender, EventArgs e) {
             // hourglass
            Cursor=Cursors.WaitCursor;
             // web query service
            labelBonjour.Text = new ServiceSoapClient().DisBonjourALaDame(textBoxNomDame.Text.Trim());
             // normal slider
            Cursor = Cursors.Arrow;
        }
    }
}
  • riga 15: viene istanziato il client del servizio web. È di tipo Client.WsHello.ServiceSoapClient. Lo spazio dei nomi Client.WsHello è dichiarato alla riga 3. Viene chiamato il metodo locale ServiceSoapClient().DisBonjourALaDame. Sappiamo che esso interroga il metodo remoto con lo stesso nome nel servizio web.

12.3. Un servizio Web di operazioni aritmetiche

Realizzeremo una seconda applicazione client/server con la seguente architettura semplificata:

Il servizio Web precedente offriva un unico metodo. Prendiamo in considerazione un servizio Web che offrirà 4 operazioni aritmetiche:

  1. ajouter(a,b) che restituirà a+b
  2. soustraire(a,b) che restituirà a-b
  3. multiplier(a,b) che restituirà a*b
  4. divider(a,b) che restituirà a/b

che saranno interrogati dalla seguente interfaccia grafica:

  • in [1], l'operazione da eseguire
  • in [2,3]: gli operandi
  • in [4], il pulsante di chiamata del servizio web
  • in [5], il risultato del servizio web

12.3.1. Il lato server

Creiamo un progetto di servizio web con Visual Web Developer:

  • in [1], l'applicazione web WsOperations generata
  • in [2], l'applicazione web WsOperations è stata riprogettata come segue:
  • la pagina web [Service.asmx] è stata rinominata [Operations.asmx]
  • la classe [Service.cs] è stata rinominata [Operations.cs]
  • il file [web.config] è stato rimosso per dimostrare che non è essenziale.

La pagina web [Service.asmx] contiene la seguente riga:


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

Il servizio web è fornito dalla seguente classe [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;
    }
 
}

Per rendere operativo il servizio web, procediamo come descritto in [3]. Otteniamo quindi la pagina di test per i 4 metodi del servizio web WsOperations :

Image

I lettori sono invitati a provare tutti e 4 i metodi.

12.3.2. La sezione clienti

Con Visual C# creiamo un'applicazione Windows ClientWsOperations :

  • in [1], ClientWsOperations in Visual C#
  • in [2], lo spazio dei nomi predefinito del progetto sarà Customer (clic destro sul progetto / Proprietà / Applicazione). Questo spazio dei nomi verrà utilizzato per costruire lo spazio dei nomi client che verrà generato.
  • in [3], clicca con il tasto destro del mouse sul progetto per aggiungere un riferimento a un servizio web esistente
  • in [4], impostare l'URI del servizio web creato in precedenza. Per farlo, osservare ciò che viene visualizzato nel campo dell'indirizzo del browser che mostra la pagina di test del servizio web.
  • in [4b], connetti Visual C# al servizio web indicato in [4]. Visual C# recupererà la descrizione del servizio web e la utilizzerà per generare un client.
  • in [5], una volta recuperata la descrizione del servizio web, Visual C# può visualizzarne i metodi pubblici
  • in [6], specificare uno spazio dei nomi per il client da generare. Questo verrà aggiunto allo spazio dei nomi definito in [2]. In questo modo, lo spazio dei nomi del client sarà Client.WsOperations.
  • in [6b] per confermare la procedura guidata.
  • in [7], il riferimento al servizio web WsOperations appare nel progetto. Inoltre, è stato creato un file di configurazione [app.config].

Ricordare che il client generato è di tipo Client.WsOperations.OperationsSoapClient dove

  • Client.WsOperations è lo spazio dei nomi del client del servizio web
  • Operations è la classe del servizio web remoto.

Sebbene esista un modo logico per ricostruire questo nome, spesso è più facile trovarlo nel file [Reference.cs], che per impostazione predefinita è un file nascosto. Il suo contenuto è il seguente:


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) {
            ...
        }
    }
}

Si accederà ai metodi Add, Subtract, Multiply, Divide del servizio web remoto tramite i metodi proxy con lo stesso nome (righe 8, 12, 16, 20) del client di tipo Client.WsOperations.OperationsSoapClient (riga 3).

Non resta che costruire l'interfaccia grafica:

tipo
nome
ruolo
1
Casella combinata
comboBoxOperazioni
elenco delle operazioni aritmetiche
2
Casella di testo
textBoxA
numero a
3
Casella di testo
casella di testo B
numero b
4
Pulsante
buttonExecute
interroga il servizio web remoto
5
Etichetta
labelResult
il risultato dell'operazione

Il codice per [Form1.cs] è il seguente:


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

        private void Form1_Load(object sender, EventArgs e) {
             // combo filling of operations
            comboBoxOperations.Items.AddRange(opérations);
            comboBoxOperations.SelectedIndex = 0;
        }
 
        private void buttonExécuter_Click(object sender, EventArgs e) {
            // checking operation parameters a and b
            textBoxMessage.Text = "";
            bool erreur = false;
            Double a = 0;
            if (!Double.TryParse(textBoxA.Text, out a)) {
                textBoxMessage.Text += "Nombre a erroné...";
            }
            Double b = 0;
            if (!Double.TryParse(textBoxB.Text, out b)) {
                textBoxMessage.Text += String.Format("{0}Nombre b erroné...", Environment.NewLine);
            }
            if (erreur) {
                return;
            }
             // operation execution
            Double c=0;
            try {
                switch (comboBoxOperations.SelectedItem.ToString()) {
                    case "Ajouter":
                        c=opérateur.Ajouter(a, b);
                        break;
                    case "Soustraire":
                        c=opérateur.Soustraire(a, b);
                        break;
                    case "Multiplier":
                        c=opérateur.Multiplier(a, b);
                        break;
                    case "Diviser":
                        c=opérateur.Diviser(a, b);
                        break;
                }
                 // result display
                labelRésultat.Text = c.ToString();
            } catch (Exception ex) {
                textBoxMessage.Text = ex.Message;
            }
        }
    }
}
  • riga 3: namespace del client del servizio web remoto
  • riga 10: il client del servizio web remoto viene istanziato contemporaneamente al modulo
  • righe 17-21: il menu a tendina delle operazioni viene compilato al primo caricamento del modulo
  • riga 23: esecuzione dell'operazione richiesta dall'utente
  • righe 25-37: verifica che le voci a e b siano numeri reali
  • righe 41-54: uno switch per eseguire l'operazione remota richiesta dall'utente
  • righe 43, 46, 49, 52: viene interrogato il client locale. In modo trasparente, esso interroga il servizio web remoto.

12.4. Un servizio web di calcolo delle imposte

Torniamo alla ormai nota applicazione per il calcolo delle imposte. L'ultima volta che l'abbiamo utilizzata, l'abbiamo trasformata in un server TCP remoto che poteva essere richiamato su Internet. Ora l'abbiamo trasformata in un servizio web.

L'architettura della versione 8 era la seguente:

L'architettura della versione 9 sarà simile:

Questa architettura è simile a quella della versione 8, descritta nel paragrafo 11.9.1, ma dove il server e il client TCP sono sostituiti da un web service e dal suo client proxy. Riprenderemo integralmente i livelli [ui], [metier] e [dao] dalla versione 8.

12.4.1. Il lato server

Creiamo un progetto di servizio web con Visual Web Developer:

  • in [1], l'applicazione web WsImpot generata
  • in [2], l'applicazione web WsImpot è stata riprogettata come segue:
    • la pagina web [Service.asmx] è stata rinominata [ServiceImpot.asmx]
    • la classe [Service.cs] è stata rinominata [ServiceImpot.cs]

La pagina web [ServiceImpot.asmx] contiene la seguente riga:


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

Il servizio web è fornito dalla seguente classe [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;
    }
 
}

Il servizio web esporrà solo CalculerImpot alla riga 9.

Torniamo all'architettura client/server della versione 8:

Il progetto Visual Studio sul server [1] era il seguente:

  • in [1], il progetto. Comprendeva i seguenti elementi:
    • [ServeurImpot.cs]: il server di calcolo delle imposte Tcp/Ip sotto forma di applicazione console.
    • [dbimpots.sdf]: il database compatto di SQL Server dalla versione 7 descritto nel paragrafo 9.8.5.
    • [App.config]: file di configurazione dell'applicazione.
  • in [2], la cartella [lib] contiene le DLL necessarie per il progetto:
    • [ImpotsV7-dao]: il livello [dao] nella versione 7
    • [ImpotsV7-metier]: il livello [metier] nella versione 7
    • [antlr.runtime, CommonLogging, Spring.Core] per Spring
  • in [3], il progetto fa riferimento a

I livelli [metier] e [dao] della versione esistono già: sono quelli utilizzati nelle versioni 7 e 8. Sono in formato DLL, che integriamo nel progetto come segue:

  • in [1] la cartella [lib] del server della versione 8 è stata copiata nel progetto del servizio web della versione 9.
  • in [2] modifichiamo le proprietà della pagina per aggiungere la DLL dalla cartella [lib] [4] ai riferimenti del progetto [3].

Dopo questa operazione, disponiamo di tutti i livelli necessari per il server [1] riportato di seguito:

Mentre gli elementi del server [1], [server], [metier], [dao], [entities], [spring] sono tutti presenti nel progetto Visual Studio, ci manca l'elemento che li istanzierà all'avvio dell'applicazione web. Nella versione 8, una classe principale con il metodo statico [Main] svolgeva il compito di istanziare i livelli con l'aiuto di Spring. In un'applicazione web, la classe in grado di svolgere un compito simile è quella associata al file [ Global.asax]:

  • in [1], viene aggiunto un nuovo elemento al progetto web
  • in [2], scegliamo la classe dell'applicazione globale
  • in [3], il nome predefinito proposto per questo elemento
  • in [4], convalidiamo l'aggiunta di
  • in [5], il nuovo elemento è stato integrato nel

Diamo un'occhiata al contenuto del file [Global.asax]:


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

Il file è un mix di tag del server web (righe 1, 3, 30) e codice C#. Questo era l'unico metodo utilizzato con ASP, l'antenato di ASP.NET, l'attuale tecnologia di Microsoft per la programmazione web. Con ASP.NET, questo metodo può ancora essere utilizzato, ma non è quello predefinito. Il metodo predefinito è il metodo "CodeBehind", che abbiamo visto nelle pagine dei servizi web, ad esempio qui in [ServiceImpot.asmx]:


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

Il CodeBehind specifica la posizione del codice sorgente per la pagina [ServiceImpot.asmx]. Senza questo attributo, il codice sorgente si troverebbe nella pagina [ServiceImpot.asmx] con una sintassi simile a quella che si trova in [Global.asax]. Non manterremo il file [Global.asax] così come è stato generato, ma il suo codice ci permette di capire a cosa serve:

  • la classe associata a Global.asax viene istanziata all'avvio dell'applicazione. La sua durata è quella dell'intera applicazione. In termini concreti, scompare solo quando il server web viene arrestato.
  • Successivamente viene eseguito il metodo Application_Start. Questa è l'unica volta in cui verrà eseguito. Viene quindi utilizzato per istanziare oggetti condivisi da tutti gli utenti. Questi oggetti vengono collocati:
    • nei campi statici della classe associata a Global.asax. Poiché questa classe è sempre presente, qualsiasi richiesta da parte di qualsiasi utente può leggere le informazioni in essa contenute.
    • oppure nel contenitore Application. Anche questo contenitore viene creato all'avvio dell'applicazione e la sua durata è quella dell'applicazione.
      • Per inserire dati in questo contenitore, si scrive Application["key"]=value;
      • per recuperarli, si scrive T value=(T)Application["key"]; dove T è il tipo di valore.
  • il metodo Session_Start viene eseguito ogni volta che un nuovo utente effettua una richiesta. Come si riconosce un nuovo utente? Ogni utente (di solito un browser) riceve un token di sessione, che è una stringa di caratteri unica per ogni utente. Ogni volta che viene effettuata una nuova richiesta, l'utente rimanda il token di sessione che ha ricevuto. Questo permette al server web di riconoscere l'utente. Nel corso delle varie richieste di un utente, i dati specifici di quell'utente possono essere memorizzati nella Session:
    • per inserire dati in questo contenitore, scriviamo Session["key"]=value;
    • per recuperarli, scriviamo T value=(T)Session["key"]; dove T è il tipo di valore.

Per impostazione predefinita, la durata di una sessione è limitata a 20 minuti di inattività dell'utente (ovvero l'utente non ha rinviato il proprio token di sessione per 20 minuti).

  • Il metodo Application_Error viene eseguito quando un'eccezione non gestita dall'applicazione web viene inviata al server web.
  • Gli altri metodi sono utilizzati più raramente.

Dopo queste generalità, cosa possiamo fare per voi? Global.asax? Useremo il suo metodo Application_Start per inizializzare i livelli [metier], [dao] e [entites] contenuti nelle DLL [ImpotsV7-metier, ImpotsV7-dao]. Useremo Spring per istanziarli. I riferimenti ai livelli così creati saranno poi memorizzati in campi statici nella classe associata a Global.asax.

Come primo passo, trasferiamo il codice C# da Global.asax in una classe a sé stante. Il progetto si evolve come segue:

In [1], il file [Global.asax] verrà associato alla classe [Global.cs] [2] con la seguente singola riga:


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

Il parametro Inherits="WsImpot.Global" indica che la classe associata a Global.asax eredita da WsImpot.Global. Questa classe è definita in [Global.cs] come segue:


using System;
using Metier;
using Spring.Context.Support;
namespace WsImpot
{
    public class Global : System.Web.HttpApplication
    {
         // business layer
        public static IImpotMetier Metier;
 
         // method executed at application startup
        private void Application_Start(object sender, EventArgs e)
        {
            // instantiations [metier] and [dao] layers
            Metier = ContextRegistry.GetContext().GetObject("metier") as IImpotMetier;
        }
    }
}
  • riga 4: namespace della classe
  • riga 6: la classe Global. È possibile assegnarle qualsiasi nome si desideri. L'importante è che derivi da System.Web.HttpApplication.
  • riga 9: un campo statico pubblico contenente un riferimento al livello [metier].
  • riga 12: il metodo Application_Start che verrà eseguito all'avvio dell'applicazione.
  • riga 15: Spring viene utilizzato per sfruttare il file [web.config], nel quale troverà gli oggetti da istanziare per creare i livelli [metier] e [dao]. Non c'è alcuna differenza tra l'utilizzo di Spring con [App.config] in un'applicazione Windows e l'utilizzo di Spring con [web.config] in un'applicazione web. Anche [web.config] e [App.config] hanno la stessa struttura. La riga 15 memorizza il riferimento al livello [metier] nel campo statico della riga 9, in modo che questo riferimento sia disponibile per tutte le query di tutti gli utenti.

Il file [web.config] sarà il seguente:


<?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>

Questo è il file [App.config] utilizzato nella versione 7 dell'applicazione e studiato nel paragrafo 9.8.4.

  • righe 16-20: definiscono un livello [dao] che opera con un database MySQL5. Questo database è stato descritto nel paragrafo 9.8.1.
  • righe 21-23: definiscono il livello [metier]

Torniamo al puzzle del server:

All'avvio dell'applicazione, i livelli [metier] e [dao] sono stati istanziati. La durata di questi livelli è quella dell'applicazione stessa. Quando viene istanziato il servizio web? Ogni volta che viene effettuata una richiesta. Al termine della richiesta, l'oggetto che l'ha servita viene eliminato. Quindi, a prima vista, un servizio web è stateless. Non può memorizzare informazioni tra due richieste nei campi che gli appartengono. Può memorizzare informazioni nella sessione dell'utente. Per farlo, i metodi che espone devono essere contrassegnati con uno speciale :


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

Nella riga 1 sopra riportata, si autorizza CalculerImpot ad accedere al contenitore Session menzionato in precedenza. Non sarà necessario utilizzare questo attributo nella nostra applicazione. Il servizio web WsImpot verrà quindi istanziato ad ogni richiesta e sarà stateless.

Ora possiamo scrivere il codice [ServiceImpot.cs] che implementa il servizio web:


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);
    }
 
}
  • riga 10: l'unico metodo del servizio web
  • riga 12: utilizziamo CalculerImpot del livello [metier]. Un riferimento a questo livello si trova nel campo statico della classe Trade Global. Questo appartiene a WsImpot (riga 2).

Ora siamo pronti per avviare il servizio web. Per prima cosa dobbiamo eseguire SGBD MySQL5 in modo che il database bdimpots sia accessibile. Una volta fatto ciò, avviamo [1] il servizio web:

Il browser visualizza quindi la pagina [2]. Seguiamo il link :

Assegniamo un valore a ciascuno dei tre parametri del metodo CalculerImpot e richiediamo l'esecuzione del metodo. Otteniamo il seguente risultato, che è corretto:

Image

12.4.2. Un client grafico Windows per il servizio web remoto

Ora che il servizio web è stato scritto, passiamo al client. Rivediamo l'architettura dell'applicazione client/server:

Ora dobbiamo scrivere il client [2]. L'interfaccia grafica sarà identica a quella della versione 8:

Per scrivere la parte [client] della versione 9, partiremo dalla parte [client] della versione 8, quindi apporteremo le modifiche necessarie. Duplichiamo il progetto Visual Studio studiato nel paragrafo 11.9.4.1, lo rinominiamo ClientWsImpot e lo carichiamo in Visual Studio :

La soluzione Visual Studio per la versione 8 era composta da 2 progetti:

  • il progetto [metier] [1], che era un client Tcp del server di calcolo delle imposte Tcp
  • il progetto GUI [ui] [2].

Le modifiche da apportare sono le seguenti:

  • il progetto [metier] deve ora essere il cliente di un servizio web
  • il progetto [ui] deve fare riferimento alla DLL del nuovo livello [metier]
  • la configurazione del livello [metier] in [App.config] deve essere modificata.

12.4.2.1. Il nuovo livello [metier]

  • in [1], IImpotMetier è l'interfaccia del livello [metier] e ImpotMetierTcp la sua implementazione tramite un client Tcp
  • in [2], rimuoviamo ImpotMetierTcp. Dobbiamo creare un'altra implementazione di IImpotMetier che fungerà da client di un servizio web.
  • in [3], chiamiamo Customer lo spazio dei nomi predefinito del progetto [metier]. La DLL che verrà generata si chiamerà [ImpotsV9-metier.dll].
  • in [4], creiamo un riferimento al servizio web WsImpot.
  • in [5], lo configuriamo e lo convalidiamo.
  • in [6], è stato creato il riferimento al file del servizio web WsImpot ed è stato generato un file [app.config].

Nel file nascosto [Reference.cs]:

  • lo spazio dei nomi è Client.WsImpot
  • la classe client si chiama ServiceImpotSoapClient
  • ha un metodo di firma unico:

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

Non resta che implementare l'interfaccia IImpotMetier:


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

Lo implementiamo con ImpotMetierWs come segue:


using System.Net.Sockets;
using System.IO;
using Client.WsImpot;
 
namespace Metier {
    public class ImpotMetierWs : IImpotMetier {
 
         // remote web customer service
        private ServiceImpotSoapClient client = new ServiceImpotSoapClient();
 
         // tAX CALCULATION
        public int CalculerImpot(bool marié, int nbEnfants, int salaire) {
            return client.CalculerImpot(marié, nbEnfants, salaire);
        }
 
    }
}
  • riga 6: la classe ImpotMetierWs implementa l'interfaccia IImpotMetier.
  • riga 9: alla creazione di un'istanza ImpotMetierWs, il campo customer viene inizializzato con un'istanza client del servizio di calcolo delle imposte web.
  • riga 12: l'unica interfaccia da implementare è IImpotMetier.
  • riga 13: utilizziamo il metodo CalculerImpot del client del servizio web remoto di calcolo delle imposte. In definitiva, è il metodo CalculerImpot del servizio web remoto che deve essere interrogato.

È possibile generare la DLL del progetto:

  • in [1], il progetto [cliente] nella sua fase finale
  • in [2], generazione della DLL del progetto
  • in [3], la DLL ImpotsV9-metier.dll si trova nella cartella del progetto /bin/Release.

12.4.2.2. Il nuovo livello [ui]

Il livello [client] del client è stato scritto. Ora dobbiamo scrivere il livello [ui]. Torniamo al progetto Visual Studio:

  • in [1], il progetto [ui] della versione 8
  • in [2], la DLL ImpotsV8-metier del vecchio livello [metier] viene sostituita dalla DLL ImpotsV9-metier del nuovo livello
  • in [3], la DLL ImpotsV9-metier viene aggiunta ai riferimenti del progetto.

La seconda modifica riguarda il file [App.config]. Ricordiamo che questo file viene utilizzato da Spring per istanziare il livello [metier]. Poiché questo è cambiato, la configurazione di [App.config] deve cambiare. D'altra parte, [App.config] deve essere configurato per raggiungere il servizio web remoto di calcolo delle imposte. Questa configurazione è stata generata nel file [app.config] del progetto [metier] quando è stato aggiunto il riferimento al servizio web remoto.

Il file [App.config] diventa quindi il seguente:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
 
    <configSections>
        <sectionGroup name="spring">
            <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
            <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
        </sectionGroup>
    </configSections>
 
    <spring>
        <context>
            <resource uri="config://spring/objects" />
        </context>
        <objects xmlns="http://www.springframework.net">
            <object name="metier" type="Metier.ImpotMetierWs, ImpotsV9-metier">
            </object>
        </objects>
    </spring>
 
     <!-- web service -->
    <system.serviceModel>
        <bindings>
            <basicHttpBinding>
                <binding name="ServiceImpotSoap" closeTimeout="00:01:00" openTimeout="00:01:00"
                        receiveTimeout="00:10:00" sendTimeout="00:01:00" allowCookies="false"
                        bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
                        maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
                        messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
                        useDefaultWebProxy="true">
                    <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
                            maxBytesPerRead="4096" maxNameTableCharCount="16384" />
                    <security mode="None">
                        <transport clientCredentialType="None" proxyCredentialType="None"
                                realm="" />
                        <message clientCredentialType="UserName" algorithmSuite="Default" />
                    </security>
                </binding>
            </basicHttpBinding>
        </bindings>
        <client>
            <endpoint address="http://localhost:2172/WsImpot/ServiceImpot.asmx"
                    binding="basicHttpBinding" bindingConfiguration="ServiceImpotSoap"
                    contract="WsImpot.ServiceImpotSoap" name="ServiceImpotSoap" />
        </client>
    </system.serviceModel>
</configuration>
  • righe 15-18: Spring istanzia un solo oggetto, il livello [metier]
  • riga 16: il livello [metier] viene istanziato dalla classe [Metier.ImportMetierWs], che si trova nella DLL ImpotsV9-metier.
  • righe 22-46: configurazione client per il servizio web remoto. Si tratta di un copia/incolla del contenuto del file [app.config] nel progetto [metier].

Siamo pronti per partire. Eseguire l'applicazione con Ctrl-F5 (il servizio web deve essere avviato, l'SGBD MySQL5 deve essere avviato, la porta alla riga 42 sopra deve essere corretta):

  

12.5. Un client web per il servizio di calcolo delle imposte online

Torniamo all'architettura dell'applicazione client/server che abbiamo appena scritto:

Il livello [ui] sopra riportato era stato implementato da un client grafico Windows. Ora lo implementiamo con un'interfaccia web:

 

Si tratta di un cambiamento importante per gli utenti. Attualmente, la nostra applicazione client/server, versione 9, è in grado di servire più client contemporaneamente. Si tratta di un miglioramento rispetto alla versione 8, che serviva un solo client alla volta. Il vincolo è che gli utenti che desiderano utilizzare il servizio di calcolo delle imposte via web devono avere il client Windows che abbiamo sviluppato installato sulle loro workstation. In questa nuova versione, che chiameremo versione 10, gli utenti potranno utilizzare il proprio browser per accedere al servizio di calcolo delle imposte via web.

Nell'architettura sopra descritta:

  • il lato server rimane invariato. Rimane come nella versione 9.
  • sul lato client, il livello [service client web] rimane invariato. È stato incapsulato nella DLL [ImpotsV9-metier]. Riutilizzeremo questa DLL.
  • alla fine, l'unico cambiamento consiste nel sostituire una GUI Windows con un'interfaccia web.

Esamineremo più da vicino la programmazione lato server web. Poiché lo scopo di questo documento non è insegnare la programmazione web, cercheremo di spiegare l'approccio che adotteremo, ma senza entrare troppo nei dettagli. Ci sarà quindi un po' di "magia" in questa sezione. Tuttavia, riteniamo che valga la pena compiere questo passo per mostrare un nuovo esempio di architettura multistrato in cui uno degli strati viene modificato.

L'architettura della versione 10 è la seguente:

Abbiamo già tutti i livelli, tranne [web]. Per capire meglio cosa verrà fatto, dobbiamo essere più precisi riguardo all'architettura client. Sarà la seguente:

  • l'utente web ha un modulo web nel proprio browser
  • questo modulo viene inviato al server web 1, che lo elabora attraverso il livello [web]
  • il livello [web] avrà bisogno dei servizi client del servizio web remoto, incapsulati in [ImpotsV9-metier.dll].
  • il client del servizio web remoto comunicherà con il server web 2, che ospita il servizio web remoto.
  • la risposta dal servizio web remoto viene trasmessa al livello web del client, che la formatta in una pagina e la invia all'utente.

Il nostro lavoro qui consiste quindi nel:

  • costruire il modulo web che l'utente vedrà nel suo browser
  • scrivere l'applicazione web, che elaborerà la richiesta dell'utente e invierà una risposta sotto forma di una nuova pagina web. Questa sarà infatti identica al modulo, al quale abbiamo aggiunto l'importo dell'imposta da pagare
  • scrivere il "collante" che fa funzionare il tutto insieme.

Tutto questo verrà realizzato utilizzando un nuovo sito web creato con Visual Web Developer:

  • [1]: selezionare l'opzione File / Nuovo sito Web
  • [2]: scegliere un sito Web ASP.NET
  • [3]: scegliere il linguaggio di sviluppo: C#
  • [4]: indicare la cartella in cui creare il progetto
  • [5]: il progetto creato in Visual Web Developer
    • [Default.aspx] è una pagina web denominata pagina predefinita. È quella che verrà visualizzata se si richiede l'URL http://.../ClientAspImpot senza specificare un documento. Questa è la pagina che conterrà il modulo di calcolo delle imposte che l'utente vedrà nel proprio browser.
    • [Default.aspx.cs] è la classe associata alla pagina, che genererà il modulo inviato all'utente e lo elaborerà una volta compilato e convalidato.
    • [web.config] è il file di configurazione dell'applicazione. A differenza delle volte precedenti, lo manterremo.

Se torniamo all'architettura che dobbiamo costruire:

  • [1] sarà implementato da [Default.aspx]
  • [2] sarà implementato da [Default.aspx.cs]
  • [3] sarà implementato dalla DLL [ImpotV9-metier]

Iniziamo implementando il livello [3]. Ci sono diversi passaggi:

  • in [1], la cartella [lib] del client grafico di Windows 9 viene copiata nella cartella del progetto web [ClientAspWsImpot]. Questa operazione viene eseguita utilizzando Windows Explorer. Per visualizzare questa cartella nella soluzione Web Developer, aggiornare la soluzione con il pulsante [2].
  • Quindi aggiungerli ai riferimenti del progetto [3,4,5]. Le DLL di riferimento vengono copiate automaticamente nella cartella /bin del progetto [6].

Ora disponiamo delle DLL necessarie per l'esecuzione di Spring ed è stato implementato anche il livello client per il servizio web remoto. Sebbene il codice per quest'ultimo sia presente, la sua configurazione deve ancora essere effettuata. Nella versione 9, era configurato dal seguente file [App.config]:


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

Prendiamo questa configurazione e la integriamo nel file [web.config] come segue:


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

Si noti che la riga 37 fa riferimento alla porta del servizio web remoto. Questa porta potrebbe cambiare, poiché Visual Developer avvia il servizio web su una porta casuale.

Torniamo all'architettura del client web che dobbiamo realizzare:

  • [1] sarà implementato da [Default.aspx]
  • [2] sarà implementato da [Default.aspx.cs]
  • [3] è stato implementato dalla DLL [ImpotV9-metier]

Abbiamo appena implementato il livello [3]. Passiamo ora all'interfaccia web [1] implementata dalla pagina [Default.aspx]. Fare doppio clic sulla pagina [Default.aspx] per passare alla modalità di progettazione.

Esistono due modi per creare una pagina web:

  • graficamente come in [2]. È quindi necessario selezionare la modalità [Design] in [1]. Questa barra dei pulsanti si trova nella parte inferiore della barra di stato dell'editor della pagina web.
  • con un linguaggio di tag come in [3]. È quindi necessario selezionare la modalità [Source] in [1].

Le modalità [Design] e [Source] sono bidirezionali: una modifica apportata in modalità [Design] si traduce in una modifica in modalità [Source] e viceversa. Ricordate che il modulo web da presentare nel browser è il seguente:

  • in [1], il modulo visualizzato in un browser
  • in [2], i componenti utilizzati per costruirlo
  • in [3], la pagina di progettazione del modulo. Include i seguenti elementi:
    • riga A, due pulsanti di opzione denominati RadioButtonOui e RadioButtonNon
    • riga B, un campo di immissione denominato TextBoxEnfants e un'etichetta denominata LabelErreurEnfants
    • riga C, un campo di immissione denominato TextBoxSalaire e un'etichetta denominata LabelErreurSalaire
    • riga D, un'etichetta chiamata LabelImpot
    • riga e, due pulsanti denominati ButtonCalculer e ButtonEffacer

Una volta che un componente è stato posizionato sulla superficie di progettazione, è possibile accedere alle sue proprietà:

  • in [1], accesso alle proprietà del componente
  • in [2], la scheda delle proprietà del componente [LabelErreurEnfants ]
  • in [3], (ID) è il nome del componente
  • in [4], abbiamo assegnato ai caratteri dell'etichetta il colore rosso.

Non basta semplicemente posizionare i componenti sul modulo e poi impostare le loro proprietà. È necessario anche organizzare il loro layout. In un'interfaccia grafica Windows, questo layout è assoluto. Basta trascinare il componente dove si desidera che sia. In una pagina web, è diverso, più complesso ma anche più potente. Questo aspetto non verrà trattato qui.

Il codice sorgente [Default.aspx] generato da questo progetto è il seguente:


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

I componenti del modulo sono identificati dalle righe 23, 24, 33, 36, 44, 47, 55, 63 e 66. Il resto è essenzialmente formattazione.

Torniamo all'architettura che dobbiamo costruire:

  • [1] è stato implementato da [Default.aspx]
  • [2] sarà implementato da [Default.aspx.cs]
  • [3] è stato implementato dalla DLL [ImpotV9-metier]

I livelli [1] e [3] sono stati implementati. Dobbiamo ancora scrivere il livello [2], che genera il modulo, lo invia all'utente, lo elabora quando l'utente restituisce il modulo compilato, utilizza il livello [3] per calcolare l'imposta, genera la pagina di risposta web e la rinvia all'utente. Il codice [Default.aspx.cs] svolge tutto questo lavoro:


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)
    {
...
    }
}

Il codice è molto simile a quello di un classico modulo Windows. Questo è il vantaggio principale della tecnologia ASP.NET: non c'è alcuna discontinuità tra il modello di programmazione Windows e il modello di programmazione web ASP.NET. Basta ricordare il seguente diagramma:

Quando, in [1], l'utente fa clic sul pulsante [Calcola], la procedura viene ripetuta: verrà eseguita la procedura ButtonCalculer_Click alla riga 6 di [Default.aspx.cs]. Ma nel frattempo:

  • i valori del modulo compilato saranno trasmessi dal browser al server web tramite il protocollo Http
  • il server ASP.NET analizzerà la richiesta e la trasferirà alla pagina [Default.aspx]
  • la pagina [Default.aspx] verrà istanziata.
  • i suoi componenti (RadioButtonOui, RadioButtonNon, TextBoxEnfants, TextBoxSalaire, LabelErreurEnfants, LabelErreurSalaire, LabelImpot) verranno inizializzati con il valore che avevano quando il modulo è stato inviato inizialmente al browser, grazie a un meccanismo chiamato "ViewState".
  • I valori inviati saranno assegnati ai rispettivi componenti (RadioButtonOui, RadioButtonNon, TextBoxEnfants, TextBoxSalaire). Ad esempio, se l'utente ha impostato il numero di figli su 2, otterremo TextBoxEnfants.Text="2".
  • Se la pagina [Default.aspx] dispone di un metodo [Page_Load], questo verrà eseguito
  • il metodo [ButtonCalculer_Click] alla riga 6 verrà eseguito se si fa clic sul pulsante [Calculate]
  • il metodo [ButtonEffacer_Click] alla riga 10 verrà eseguito se si clicca sul pulsante [Delete]

Tra il momento in cui l'utente genera un evento nel proprio browser e il momento in cui viene elaborato in [Default.aspx.cs], c'è una grande complessità. Questa complessità è nascosta e possiamo fingere che non esista quando scriviamo i gestori di eventi sulla pagina web. Ma non dobbiamo mai dimenticare che esiste una rete tra l'evento e il suo gestore, e che è fuori discussione gestire eventi del mouse come Mouse_Move che causerebbero costosi round-trip client/server ...

Il codice per la gestione dei clic sui pulsanti [Calculate] e [Delete] è quello che sarebbe stato scritto per un'applicazione Windows convenzionale:


protected void ButtonCalculer_Click(object sender, EventArgs e)
    {
         // data verification
        int nbEnfants;
        bool erreur = false;
        if (!int.TryParse(TextBoxEnfants.Text.Trim(), out nbEnfants) || nbEnfants < 0)
        {
            LabelErreurEnfants.Text = "Valeur incorrecte...";
            erreur = true;
        }
        int salaire;
        if (!int.TryParse(TextBoxSalaire.Text.Trim(), out salaire) || salaire < 0)
        {
            LabelErreurSalaire.Text = "Valeur incorrecte...";
            erreur = true;
        }
         // mistake?
        if (erreur) return;
         // erase any errors
        LabelErreurEnfants.Text = "";
        LabelErreurSalaire.Text = "";
         // marital status
        bool marié = RadioButtonOui.Checked;
         // tAX CALCULATION
        try
        {
            LabelImpot.Text = String.Format("{0} euros",Global.Metier.CalculerImpot(marié, nbEnfants, salaire));
        }
        catch (Exception ex)
        {
            LabelImpot.Text = ex.Message;
        }
    }
  • Per comprendere questo codice, è necessario sapere
    • all'inizio della sua esecuzione, il modulo [Default.aspx] è così come l'utente lo ha compilato. Ciò significa che i campi (RadioButtonOui, RadioButtonNon, TextBoxEnfants, TextBoxSalaire) contengono i valori inseriti dall'utente.
    • Al termine dell'esecuzione, la stessa pagina [Default.aspx] verrà restituita all'utente. Ciò avviene automaticamente.

La procedura ButtonCalculer_Click deve quindi basarsi sui valori correnti dei campi (RadioButtonOui, RadioButtonNon, TextBoxEnfants, TextBoxSalaire) e impostare il valore di tutti i campi (RadioButtonOui, RadioButtonNon, TextBoxEnfants, TextBoxSalaire, LabelErreurEnfants, LabelErreurSalaire, LabelImpot) della nuova pagina [Default.aspx] che verrà restituita all’utente.

Non ci sono particolari difficoltà con questo codice. Solo la riga 27 necessita di una spiegazione. Essa utilizza il campo Global.Metier, che non è stato ancora definito. Torneremo su questo punto tra poco.

Il metodo ButtonEffacer_Click è il seguente:


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

Torniamo all'architettura che dobbiamo costruire:

  • [1] è stato implementato da [Default.aspx]
  • [2] è stato implementato da [Default.aspx.cs]
  • [3] è stato implementato dalla DLL [ImpotV9-metier]

Non resta che mettere la "collante" attorno a questi tre livelli. Si tratta essenzialmente di:

  • istanziare il livello [3] all'avvio dell'applicazione
  • inserire un riferimento ad esso in un punto in cui la pagina web [Default.aspx.cs] possa recuperarlo ogni volta che viene istanziato e gli viene richiesto di calcolare l'imposta.

Questo non è un problema nuovo. È già stato affrontato nella realizzazione del servizio web remoto e studiato nel paragrafo 12.4.1. Sappiamo che la soluzione consiste nel:

  • creare un file [Global.asax] associato a una classe [Global.cs]
  • istanziare il livello [3] nel metodo Application_Start da [Global.cs]
  • inserire il riferimento del livello [3] in un campo statico della classe [Global.cs], poiché la durata di questa classe è quella dell'applicazione.

Di conseguenza, il nostro progetto web si evolve come segue:

  • in [1], file [Global.asax].
  • in [2], il codice associato [Global.cs]. La cartella [App_Code] in cui si trova questo file non è presente di default nella soluzione web. Utilizzare [3] per crearla.

Il file Global.asax è il seguente:


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

Il codice [Global.cs] è il seguente:


using System;
using Metier;
using Spring.Context.Support;
namespace WsImpot
{
    public class Global : System.Web.HttpApplication
    {
         // business layer
        public static IImpotMetier Metier;
 
         // method executed at application startup
        private void Application_Start(object sender, EventArgs e)
        {
            // instantiations [metier] and [dao] layers
            Metier = ContextRegistry.GetContext().GetObject("metier") as IImpotMetier;
        }
    }
}
  • riga 6: la classe si chiama Global e fa parte di WsImpot (riga 4). Il suo nome completo è WsImpot.Global ed è questo nome che deve essere incluso in Inherits da Global.asax.
  • riga 6: sappiamo che la classe associata a Global.asax deve derivare da System.Web.HttpApplication.
  • riga 12: il metodo Application_Start viene eseguito all'avvio dell'applicazione web.
  • riga 15: integriamo il livello [metier] (livello [3] dell'applicazione in fase di realizzazione) utilizzando Spring e la seguente configurazione in [web.config]:

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

La classe [Metier.ImpotMetierWS] nella riga (g) sopra riportata si trova in [ImpotsV9-metier.dll].

Il riferimento al livello [metier] creato viene inserito nel campo statico alla riga 9. Questo campo viene utilizzato alla riga 27 di ButtonCalculer_Click :


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

Siamo pronti per un test. Dobbiamo avviare il SGBD MySQL5, il servizio web remoto e prendere nota della porta su cui opera:

  

Una volta fatto ciò, verificare che nel file [web.config] del client web la porta del servizio web remoto sia corretta:

Una volta fatto ciò, il client web del servizio web remoto può essere avviato premendo Ctrl-F5 :

  

12.6. Un client console Java per il servizio di calcolo delle imposte online

Per dimostrare che i servizi web sono accessibili da client scritti in qualsiasi linguaggio, stiamo realizzando un client Java da console di base. L'architettura dell'applicazione client/server sarà la seguente:

  • il client [1] sarà scritto in Java
  • il server [2] è quello scritto in C#

Per prima cosa, modificheremo un dettaglio nel nostro servizio web di calcolo delle imposte. La sua definizione attuale in [ServiceImpot.cs] è la seguente:


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

I test hanno dimostrato che l'enfasi del parametro married alle righe 6 e 8 potrebbe rappresentare un problema per l'interoperabilità Java / C#. Adottiamo la seguente nuova definizione:


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

Questo servizio verrà inserito in un nuovo progetto Web Developer denominato WsImpotsSansAccents. Il servizio web avrà quindi l'URL [/WsImpotSansAccents/ServiceImpot.asmx].

Image

Per scrivere il client Java, useremo l'IDE Netbeans [http://www.netbeans.org/] :

  • in [1], creare un nuovo progetto
  • in [2,3], selezionare il tipo di progetto Java "Java Application".
  • in [4], passare al passaggio successivo
  • in [5], assegnare un nome al progetto
  • in [6], indicare la cartella in cui verrà creata una sottocartella con il nome del progetto
  • in [7], assegnare un nome alla classe che conterrà il codice eseguito all'avvio dell'applicazione
  • in [8], completare il
  • in [9]: il progetto Java generato
  • in [10]: clicca con il tasto destro del mouse sul progetto per generare il client del servizio web di calcolo delle imposte
  • in [11], l'URL del file che descrive il servizio web di calcolo delle imposte:

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

Questo URL è quello del servizio [ServiceImpot.asmx], al quale aggiungiamo il parametro ?WSDL. Il documento che si trova a questo URL descrive in linguaggio XML cosa può fare il servizio [15]. Si tratta di un elemento standard di un servizio web.

  • in [12], il pacchetto (equivalente al namespace C#) in cui inserire le classi da generare
  • in [13], lasciare il valore predefinito
  • in [14], completare il
  • in [16], il servizio web importato è stato integrato nel progetto Java. Supporta due protocolli di comunicazione: Soap e Soap12.
  • in [17], la classe [Main] in cui useremo il client generato
  • in [18], inseriremo del codice nel metodo [main]. Posizionare il cursore nel punto in cui si desidera inserire il codice, fare clic con il tasto destro del mouse e selezionare l'opzione [19]
  • in [20], indicare che si desidera generare il codice di chiamata per CalculerImpot del servizio di calcolo delle imposte remoto, quindi premere Ok.

Il codice generato in [Main] è il seguente:

public class Main {

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

Il codice generato mostra come chiamare CalculerImpot del servizio remoto di calcolo delle imposte. Se facciamo un parallelo con quanto visto in C#, la variabile port alla riga 7 è l'equivalente del client utilizzato in C#. Non commenteremo ulteriormente questo codice. Lo riorganizzeremo come segue:

import wsimpot.ServiceImpot;
public class Main {
    public static void main(String[] args) {
      try {
        // call the CalculerImpot function of the web service
        System.out.println(String.format("Montant à payer : %d euros", new ServiceImpot().getServiceImpotSoap().calculerImpot(true, 2, 60000)));
      } catch (Exception ex) {
        System.out.println(String.format("L'erreur suivante s'est produite %s",ex.getMessage()));
      }
    }
}
  • riga 1: importiamo ServiceImpot, che rappresenta il client generato dalla procedura guidata.
  • riga 6: chiamiamo il metodo remoto CalculerImpot seguendo la procedura indicata nel codice generato in main.

I risultati ottenuti nella console in fase di esecuzione (F6) sono i seguenti:

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)