9. L'applicazione [SimuPaie] – versione 5 – ASP.NET / servizio web
Letture consigliate: riferimento [2], Introduzione a C# 2008, capitolo 10 "Servizi web"
9.1. La nuova architettura dell’applicazione
L’architettura a livelli dell’applicazione Pam è attualmente la seguente:
![]() |
La modificheremo come segue:
![]() |
Mentre nell'architettura precedente i livelli [web], [metier] e [dao] venivano eseguiti nella stessa macchina virtuale,NET, nella nuova architettura il livello [web] verrà eseguito in una macchina virtuale diversa rispetto ai livelli [metier] e [dao]. Ciò avverrà in particolare se il livello [web] si trova su una macchina M1 e i livelli [metier] e [dao] su una macchina M2. In questo caso si tratta di un’architettura client/server:
- il server è costituito dai livelli [metier] e [dao]. Trattandosi di un servizio web, necessita del server web n. 2 per funzionare.
- Il client è costituito dal livello [web]. Per funzionare, necessita del server web n. 1.
- Il client e il server comunicano tramite la rete TCP/IP con il protocollo HTTP / SOAP. A tal fine, è necessario aggiungere due nuovi livelli all’architettura:
- il livello [S], che sarà un servizio web. Il servizio web riceve le richieste dai client remoti e utilizza i livelli [metier] e [dao] per soddisfarle. Esistono molti modi per realizzare un servizio TCP/IP. Il vantaggio del servizio web è duplice:
- utilizza il protocollo HTTP, che viene consentito dai firewall delle aziende e delle amministrazioni
- utilizza un sottoprotocollo HTTP / SOAP standard, implementato da numerose piattaforme di sviluppo: .Net, Java, PHP, Flex, ... In questo modo, un servizio web può essere «utilizzato» (è il termine comunemente usato) da client .Net, Java, PHP, Flex, ...
- il livello [C], che fungerà da client del servizio web remoto. Il suo ruolo sarà quello di comunicare con il servizio web [S].
- il livello [S], che sarà un servizio web. Il servizio web riceve le richieste dai client remoti e utilizza i livelli [metier] e [dao] per soddisfarle. Esistono molti modi per realizzare un servizio TCP/IP. Il vantaggio del servizio web è duplice:
Questa nuova architettura può essere ricavata da quelle precedenti senza troppa difficoltà:
- i livelli [metier] e [dao] rimangono invariati
- il livello [web] subisce una leggera evoluzione, essenzialmente per fare riferimento a entità quali Employe e FeuilleSalaire, che sono diventate entità del livello client [C]. Queste entità sono analoghe a quelle dei livelli [metier] o [dao], ma appartengono a spazi dei nomi diversi.
- Il livello server [S] è una classe che implementa l’interfaccia IPamMetier del livello [metier]. Questa implementazione si limita a richiamare i metodi corrispondenti del livello [metier]. I metodi implementati dal livello server [S] saranno «esposti» ai client remoti, che potranno richiamarli.
- Il livello client [C] verrà generato da Visual Studio.
I principi della nuova architettura sono i seguenti:
- il livello [web] continua a comunicare con il livello [metier] come se quest’ultimo fosse locale. A tal fine, il livello client [C] implementa l’interfaccia IPamMetier del livello [metier] effettivo e si presenta al livello [web] come un livello [metier] locale. A parte il problema degli spazi dei nomi menzionato in precedenza, il livello [web] rimane invariato. Questo è il vantaggio di aver lavorato a livelli. Se si fosse realizzata un’applicazione a livello singolo, sarebbe stato necessario rielaborarla in modo molto approfondito.
- Il livello client [C] trasmette, in modo trasparente per il livello [web], le richieste di quest’ultimo al servizio web remoto [S]. Si occupa di tutto l’aspetto della “comunicazione di rete”. Riceve una risposta dal servizio web remoto che formatta per restituirla al livello [web] nella forma che quest’ultimo si aspetta.
- Sul lato server, il servizio web [S] riceve i comandi dai propri clienti remoti. Li formatta in modo da richiamare i metodi dell’interfaccia IPamMetier del livello [metier]. Una volta ricevuta la risposta dal livello [metier], la formatta per trasmetterla tramite la rete al client [C]. I livelli [metier] e [dao] non devono essere modificati.
9.2. Il progetto Visual Web Developer del servizio web
Creiamo un nuovo progetto con Visual Web Developer:
![]() |
- in [1], scegliamo un progetto web in C#
- in [2], scegliamo "Applicazione di servizio web ASP.NET"
- in [3], assegniamo un nome al progetto web
- in [4], specifichiamo una posizione per questo progetto
![]() |
- in [1] il progetto generato. Si tratta di un progetto web classico, con le seguenti precisazioni:
- è stato specificato che il progetto è di tipo «servizio web». Un servizio web non invia pagine web HTML ai propri clienti, ma dati in formato XML. Pertanto, la pagina [Default.aspx] che solitamente viene generata non è stata creata.
- In [2] è stato generato un file [Service1.asmx] con il seguente contenuto:
<%@ WebService Language="C#" CodeBehind="Service1.asmx.cs" Class="pam_v5_webservice.Service1" %>
- (continua)
- - il tag WebService indica che [Service.asmx] è un servizio web
- - l'attributo CodeBehind indica la posizione del codice sorgente di questo servizio web
- - l'attributo Class indica il nome della classe che implementa il servizio web nel codice sorgente
Il codice sorgente [Service.asmx.cs] del servizio web generato per impostazione predefinita è il seguente:
using System.Web.Services;
namespace pam_v5_webservice
{
/// <summary>
/// Descrizione sintetica di Servizio1
/// </summary>
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
// Per consentire la chiamata di questo servizio Web da uno script utilizzando ASP.NET AJAX, rimuovere i simboli di commento dalla riga seguente.
// [System.Web.Script.Services.ScriptService]
public class Service1 : System.Web.Services.WebService
{
[WebMethod]
public string HelloWorld()
{
return "Hello World";
}
}
}
- riga 8: l'annotazione WebService che fa sì che la classe Service1 della riga 13 venga esposta come servizio web. Un servizio web appartiene a uno spazio dei nomi per evitare che due servizi web al mondo abbiano lo stesso nome. In seguito dovremo modificare questo spazio dei nomi.
- riga 13: la classe Service1 deriva dalla classe WebService del framework .NET.
- riga 16: l’annotazione WebMethod fa sì che il metodo così annotato venga reso disponibile ai client remoti, che potranno quindi richiamarlo.
- righe 17-20: il metodo HelloWorld è un metodo di dimostrazione. Lo elimineremo in seguito. Ci permette di effettuare i primi test e di familiarizzare con gli strumenti di Visual Studio, nonché con alcuni aspetti fondamentali relativi ai servizi web.
![]() |
- in [1], eseguiamo il servizio web [Service.asmx]
![]() |
- VS Web Developer ha avviato il proprio server web integrato e lo ha configurato per ascoltare su una porta casuale, in questo caso la 1599. L’URL [2] è stato quindi richiesto al server web. Si tratta dell’URL di una pagina di test del servizio web.
- In [3], un link che consente di visualizzare il file di descrizione del servizio web. Questo file, denominato WSDL (WebService Description Language) a causa del suo suffisso (.wsdl), è un file XML che descrive i metodi esposti dal servizio web. È a partire da questo file WSDL che i clienti possono conoscere:
- lo spazio dei nomi del servizio web
- l'elenco dei metodi esposti dal servizio web
- i parametri richiesti da ciascuno di essi
- la risposta restituita da ciascuno di essi
- in [4], l’unico metodo esposto dal servizio web .
![]() |
- in [5], il contenuto del file WSDL ottenuto tramite il link [3]. Si noti il file URL [6]. La sua conoscenza è necessaria per i clienti del servizio web.
![]() |
- in [7], la pagina ottenuta seguendo il link [4] consente di richiamare il metodo [HelloWorld] del servizio web
- in [8], il risultato ottenuto: una risposta XML. Si noti il URL [9] del metodo.
L'analisi delle pagine precedenti permette di comprendere come viene chiamato un metodo di un servizio web e quale tipo di risposta restituisce. Ciò consente di scrivere client HTTP in grado di dialogare con il servizio web. La maggior parte degli attuali IDE consente la generazione automatica di questo client HTTP, evitando così allo sviluppatore di doverlo scrivere. È il caso, in particolare, di Visual Studio Express.
Prima di proseguire con questo progetto, modificheremo lo spazio dei nomi utilizzato per impostazione predefinita durante la generazione delle classi:
![]() |
Quando selezioniamo le proprietà del progetto (clic destro sul progetto / Proprietà), in [1] troviamo il nome del progetto l'assembly e in [2] il suo spazio dei nomi predefinito.
Fatto ciò,
- in [Service1.asmx.cs], modifichiamo lo spazio dei nomi della classe:
using System.Web.Services;
namespace pam_v5
{
...
public class Service1 : System.Web.Services.WebService
{
...
}
}
- in [Service.asmx], modifichiamo anche lo spazio dei nomi utilizzato per la classe [Service1] (clic destro / visualizza markup):
<%@ WebService Language="C#" CodeBehind="Service1.asmx.cs" Class="pam_v5.Service1" %>
Torniamo all’architettura della nostra applicazione:
![]() |
- il livello [S] è il servizio web. Si limita a esporre i metodi del livello [metier] ai client remoti. È questo il livello che stiamo realizzando.
- Il livello [C] è il client HTTP del servizio web. È questo il livello che i IDE sono in grado di generare automaticamente.
- Il livello [web] vede il livello [C] come un livello [metier] locale se si fa in modo che il livello [C] implementi l’interfaccia del livello remoto [metier].
Di seguito vediamo che il nostro servizio web:
- esporre i metodi del livello [metier]
- interagire con quest’ultimo, che a sua volta interagirà con il livello [dao].
Il progetto deve quindi utilizzare i DLL dei livelli [metier] e [dao]. Si evolve nel modo seguente:
![]() |
- in [1], si aggiungono i riferimenti al progetto
- in [2], si selezionano i soliti DLL dalla cartella [lib]. Si farà attenzione che tutti abbiano la proprietà "Copia locale" impostata su True. I DLL selezionati sono quelli che implementano i livelli [metier] e [dao] con supporto NHibernate.
Un’applicazione web di tipo “servizio web ASP.NET” può avere una classe di applicazione globale “Global.asax” proprio come una classica applicazione “sito web ASP.NET”. Abbiamo visto il vantaggio di una classe di questo tipo:
- viene istanziata all’avvio dell’applicazione e rimane in memoria
- può quindi memorizzare dati condivisi da tutti i client e di sola lettura. Nella nostra applicazione memorizzerà, come nelle precedenti, l’elenco semplificato dei dipendenti. Ciò eviterà di dover recuperare tale elenco dal database ogni volta che un client lo richiederà.
![]() |
- in [1], fare clic con il tasto destro del mouse sul progetto
- in [2], selezionare l’opzione [Ajouter un nouvel élément]
- in [3], selezionare [Classe d'application globale]
- in [4], il file [Global.asax] è stato aggiunto al progetto
Il contenuto del file [Global.asax] è il seguente:
<%@ Application Codebehind="Global.asax.cs" Inherits="pam_v5.Global" Language="C#" %>
Il contenuto del file [Global.asax.cs] è il seguente:
using System;
namespace pam_v5
{
public class Global : System.Web.HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
}
...
}
}
Cosa dobbiamo fare nel metodo Application_Start? Esattamente la stessa cosa che abbiamo fatto nelle precedenti applicazioni web. Torniamo all’architettura dell’applicazione e inseriamo al suo interno la classe [Global]:
![]() |
Nello schema sopra riportato,
- la classe [Global] viene istanziata all’avvio del servizio web. Rimane in memoria finché il servizio web è attivo.
- La classe [Global] istanzia i livelli [metier] e [dao] nel proprio metodo [Application_Start]
- Per migliorare le prestazioni, la classe [Global] memorizza l'elenco semplificato dei dipendenti in un campo interno. Fornirà l'elenco dei dipendenti a partire da questo campo.
- Il servizio web viene istanziato ad ogni richiesta di un client. Scompare dopo aver evaso la richiesta. Non si rivolgerà direttamente al livello [metier], ma alla classe [Global]. Quest’ultima implementerà l’interfaccia del livello [metier].
La classe [Global] è analoga a quella già realizzata per le applicazioni precedenti:
using System;
using Pam.Dao.Entites;
using Pam.Metier.Entites;
using Pam.Metier.Service;
using Spring.Context.Support;
namespace pam_v5
{
public class Global : System.Web.HttpApplication
{
// --- dati statici dell'applicazione ---
public static Employe[] Employes;
public static IPamMetier PamMetier = null;
protected void Application_Start(object sender, EventArgs e)
{
// istanziazione del livello [metier]
PamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
// si recupera l'array semplificato dei dipendenti
Employes = PamMetier.GetAllIdentitesEmployes();
}
// elenco semplificato dei dipendenti
static public Employe[] GetAllIdentitesEmployes()
{
return Employes;
}
// stipendio di un dipendente
static public FeuilleSalaire GetSalaire(string SS, double heuresTravaillées, int joursTravailles)
{
return PamMetier.GetSalaire(SS, heuresTravaillées, joursTravailles);
}
}
}
La classe [Global] implementa l'interfaccia [IPamMetier], ma ciò non è indicato esplicitamente nella dichiarazione:
public class Global : System.Web.HttpApplication, IPamMetier
Infatti, i metodi GetAllIdentitesEmployes (riga 24) e GetSalaire (riga 30) sono statici, mentre i metodi dell'interfaccia IPamMetier non lo sono. Pertanto, la classe Global non può implementare l'interfaccia IPamMetier. Inoltre, non è possibile dichiarare i metodi GetAllIdentitesEmployes e GetSalaire come non statici. Infatti, vi si accede tramite il nome della classe e non tramite un’istanza della stessa.
- riga 15: il metodo Application_Start è analogo a quello delle classi [Global] esaminate nelle versioni precedenti. Crea un'istanza del livello [metier] (riga 18) e quindi inizializza (riga 20) l'array dei dipendenti della riga 12.
- riga 24: il metodo GetAllIdentitesEmployes si limita a restituire l’array dei dipendenti della riga 12. È proprio questo il vantaggio di averlo memorizzato all’avvio dell’applicazione.
- riga 30: il metodo GetSalaire richiama il metodo omonimo del livello [metier].
Per istanziare il livello [metier] (riga 18), la classe [Global] utilizza il framework Spring. Quest’ultimo è configurato dal file [Web.config], identico a quello del progetto precedente: configura Spring e NHibernate per istanziare i livelli [metier] e [dao] del servizio web.
Torniamo all’architettura della nostra applicazione client/server:
![]() |
Sul lato server, non resta che scrivere il servizio web [S] stesso. Se torniamo all’architettura dell’applicazione:
![]() |
vediamo che, sul lato server, tutti i livelli che precedono il livello [metier] implementano la relativa interfaccia IPamMetier. Non è obbligatorio, ma è un approccio che sembra logico. Questo ragionamento potrà essere applicato sul lato client, al client [C] del servizio web [S]. Pertanto, tutti i livelli che separano il livello [web] dal livello [metier] implementano a loro volta l’interfaccia IPamMetier. Si può quindi affermare di essere tornati a un’applicazione a tre livelli:
- il livello di presentazione [web] [1]
- il livello [metier] [2]
- il livello di accesso ai dati [3]
L’implementazione del servizio web [Service1.asmx.cs] potrebbe essere la seguente:
using System.Web.Services;
using Pam.Dao.Entites;
using Pam.Metier.Entites;
using Pam.Metier.Service;
namespace pam_v5
{
[WebService(Namespace = "http://st.istia.univ-angers.fr/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
public class Service1 : System.Web.Services.WebService, IPamMetier
{
// elenco di tutte le identità dei dipendenti
[WebMethod]
public Employe[] GetAllIdentitesEmployes()
{
return Global.GetAllIdentitesEmployes();
}
// ------- il calcolo dello stipendio
[WebMethod]
public FeuilleSalaire GetSalaire(string ss, double heuresTravaillees, int joursTravailles)
{
return Global.GetSalaire(ss, heuresTravaillees, joursTravailles);
}
}
}
- riga 8: la classe è annotata con l'attributo [WebService] e assegniamo un nome allo spazio dei nomi del servizio web
- riga 11: la classe [Service1] eredita dalla classe [WebService] e implementa l'interfaccia [IPamMetier]
- righe 15 e 22: ogni metodo della classe è annotato con l'attributo [WebMethod] per essere reso visibile ai client remoti. Per impostazione predefinita, tutti i metodi pubblici di un servizio web sono visibili. Gli attributi delle righe 15 e 22 sono quindi facoltativi in questo caso. Per implementare l'interfaccia [IPamMetier], ogni metodo si limita a chiamare il metodo omonimo della classe [Global].
Siamo pronti per l'esecuzione del servizio web:
![]() |
- in [1], il progetto viene rigenerato
- in [2], si seleziona il servizio web [Service1.asmx] e lo si visualizza nel browser [3]
- in [4], la pagina web visualizzata. Presenta i metodi del servizio web.
![]() |
- in [4], seguiamo il link [GetAllIdentitesEmployes] e otteniamo in [5] la pagina di test di questo metodo.
- in [6], il URL del metodo
- in [7], il pulsante [Appeler] che consente di testare il metodo. Questo non richiede alcun parametro.
- in [8], il risultato XML restituito dal servizio web. In esso, sono significative solo le proprietà SS, Nom, Prenom degli oggetti Employe sono rilevanti, poiché il metodo [GetAllIdentitesEmployes] richiede solo queste proprietà. Tuttavia, tale metodo restituisce un array di oggetti Employe. Si nota in [8] che le proprietà numeriche Id e Version sono presenti nel flusso XML restituito, ma non le proprietà con valore null: Adresse, Ville, CodePostal, Indemnites.
Abbiamo un servizio web attivo. Ora scriveremo un client in C# per esso. A tal fine, avremo bisogno dell’URI del file WSDL del servizio web. Lo otteniamo dalla pagina visualizzata inizialmente all’esecuzione di [Service.asmx]:
![]() |
- in [1], il URI del servizio web
- in [2], il link che rimanda al suo file WSDL
- in [3], il valore di questo link
9.3. Il progetto C# di un client NUnit del servizio web
Creiamo un progetto C# (con Visual C# e non con Visual Web Developer) per il client del servizio web. Si tratterà di un client di prova NUnit. Il progetto sarà quindi di tipo “Libreria di classi”.
![]() |
- in [1], creiamo un progetto C# di tipo "Libreria di classi"
- in [2], assegniamo un nome al progetto
- in [3], il progetto. Eliminiamo [Class1.cs].
- in [4], il nuovo progetto.
![]() |
- nelle proprietà del progetto, nella scheda [Application] [5], impostiamo lo spazio dei nomi del progetto. Ogni classe generata da IDE sarà inserita in questo spazio.
Salviamo il nostro nuovo progetto in una posizione a nostra scelta:
Fatto ciò, generiamo il client del servizio web remoto. Per comprendere ciò che stiamo per fare, è necessario tornare all’architettura client/server che stiamo realizzando:
![]() |
L'IDE genererà il livello client [C] a partire dall'URI del file WSDL del servizio web [S]. Ricordiamo che il URI di questo file è stato annotato in precedenza. Procediamo dall' e nel modo seguente:
![]() |
- in [1], fare clic con il tasto destro del mouse sul ramo References e aggiungere un riferimento al servizio
- in [2], indicando l’URL del file WSDL del servizio web annotato in precedenza. Quest’ultimo deve essere avviato in precedenza, se non lo è già.
- in [3], richiedere il rilevamento del servizio web tramite il suo file WSDL
- in [4], il servizio web individuato
- in [5], i metodi esposti dal servizio web.
- in [6], lo spazio dei nomi in cui si desidera collocare le classi e le interfacce del client che verrà generato.
- si conferma la procedura guidata
![]() |
- in [1], il client generato. Si fa doppio clic su di esso per accedere al suo contenuto.
- In [2], nell’esploratore degli oggetti, vengono visualizzate le classi e le interfacce dello spazio dei nomi Client.WsPam. Si tratta dello spazio dei nomi del client generato.
- In [3], la classe che implementa il client del servizio web.
- In [4] sono riportati i metodi implementati dal client [Service1SoapClient]. Vi si trovano i due metodi del servizio web remoto [5] e [6].
- In [2] sono presenti le immagini delle entità dei livelli:
- [metier]: FeuilleSalaire, ElementsSalaire
- [dao]: Employe, Cotisations, Indemnites
Di seguito, è importante ricordare che queste immagini delle entità remote si trovano sul lato client e nello spazio dei nomi PamV5Client.WsPam.
Esaminiamo i metodi e le proprietà esposti da una di esse:
![]() |
- in [1], si seleziona la classe locale [Employe]
- in [2], si ritrovano le proprietà dell’entità remota [Employe], nonché i campi privati utilizzati per le esigenze specifiche dell’entità locale.
Torniamo alla nostra applicazione C#. Aggiungiamo una classe di test NUnit:
![]() |
- in [1], è stata aggiunta la classe [NUnit]. La classe [NUnit] richiederà il framework NUnit e quindi un riferimento alla classe DLL contenuta in esso. Supponiamo che il framework NUnit sia stato installato sul computer (http://nunit.org/).
- In [2], si aggiunge un riferimento al progetto
- nella scheda [3] .NET, che raggruppa i file DLL salvati sul computer, selezioniamo [4], il DLL [nunit.framework] versione minima 2.4.6.
Inoltre, utilizzeremo Spring per istanziare il client locale [C] del servizio web [S]:
![]() |
Il riferimento a DLL di Spring può essere aggiunto come è stato fatto con il framework NUnit se i DLL sono stati precedentemente registrati sul computer (http://www.springframework.net/download.html).
Procediamo in modo diverso. Utilizziamo la cartella [lib] dei progetti precedenti, che conteneva i file DLL necessari a Spring, e aggiungiamo il riferimento a Spring al progetto:
![]() |
Torniamo all’architettura del client in fase di sviluppo:
![]() |
Come si vede sopra, il client di test [1] si interfaccia con un livello esteso [metier] [2]. Quest’ultimo presenta gli stessi metodi del livello remoto [metier]. Possiamo quindi utilizzare la classe di test già incontrata durante il test del livello [metier] nel progetto C# [pam-metier-dao-nhibernate]:
using NUnit.Framework;
using Pam.Dao.Entites;
using Pam.Metier.Entites;
using Pam.Metier.Service;
using Spring.Context.Support;
namespace Pam.Metier.Tests {
[TestFixture()]
public class NunitTestPamMetier : AssertionHelper {
// il livello [metier] da testare
private IPamMetier pamMetier;
// costruttore
public NunitTestPamMetier() {
// istanziazione del livello [dao]
pamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
}
[Test]
public void GetAllIdentitesEmployes() {
// verifica del numero di dipendenti
Expect(2, EqualTo(pamMetier.GetAllIdentitesEmployes().Length));
}
[Test]
public void GetSalaire1() {
// calcolo di una busta paga
FeuilleSalaire feuilleSalaire = pamMetier.GetSalaire("254104940426058", 150, 20);
// verifiche
Expect(368.77, EqualTo(feuilleSalaire.ElementsSalaire.SalaireNet).Within(1E-06));
// busta paga di un dipendente inesistente
bool erreur = false;
try {
feuilleSalaire = pamMetier.GetSalaire("xx", 150, 20);
} catch (PamException) {
erreur = true;
}
Expect(erreur, True);
}
}
}
È necessario apportare alcune modifiche:
- alla riga 18, si istanzia il livello [metier] con il framework Spring. La classe non è la stessa in entrambi i casi. Qui, il livello locale [metier] è un'istanza della classe [PamV5Client.WsPam.Service1SoapClient], la classe generata da IDE. Pertanto, Spring è configurato come segue nel file [app.config] del progetto C#:
<?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 id="pammetier" type="PamV5Client.WsPam.Service1SoapClient, pam-v5-client-csharp-webservice"/>
</objects>
</spring>
<system.serviceModel>
...
- alla riga 16 sopra riportata, l’oggetto [pammetier] è un’istanza della classe [PamV5Client.WsPam.Service1SoapClient] che si trova nell’assembly [pam-v5-client-csharp-webservice]. Per ottenere la prima informazione, è sufficiente tornare alla definizione della classe [Service1SoapClient] nell’Esplora oggetti (paragrafo 9.3):
![]() |
- in [2], la classe di implementazione del livello locale [metier] e in [1] il suo spazio dei nomi
- in [3]; nelle proprietà del progetto, il nome dell'assembly, la seconda informazione necessaria per la configurazione dell'oggetto Spring [pammetier].
Torniamo al codice di istanziazione del livello locale [metier] in [NUnit.cs]:
// il livello [metier] da testare
private IPamMetier pamMetier;
// costruttore
public NunitTestPamMetier() {
// istanza del livello [dao]
pamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
}
Riga 7: il livello remoto [metier] era di tipo IPamMetier. Qui il livello [metier] è di tipo [Service1SoapClient]:
public class Service1SoapClient : System.ServiceModel.ClientBase<Service1Soap>
Notiamo che la classe Service1SoapClient non implementa l’interfaccia IPamMetier, anche se espone metodi con lo stesso nome. Dobbiamo quindi scrivere l’istanziazione del livello locale [metier] nel modo seguente:
// il livello [metier] da testare
private Service1SoapClient pamMetier;
// costruttore
public NunitTestPamMetier() {
// istanza del livello [metier]
pamMetier = ContextRegistry.GetContext().GetObject("pammetier") as Service1SoapClient;
}
Un'altra modifica da apportare:
[Test]
public void GetSalaire1() {
...
try {
feuilleSalaire = pamMetier.GetSalaire("xx", 150, 20);
} catch (PamException) {
erreur = true;
}
Expect(erreur, True);
}
Il codice sopra riportato utilizza, alla riga 6, il tipo PamException che non esiste sul lato client. Lo sostituiremo con la sua classe padre, il tipo Exception.
[Test]
public void GetSalaire1() {
...
try {
feuilleSalaire = pamMetier.GetSalaire("xx", 150, 20);
} catch (Exception) {
erreur = true;
}
Expect(erreur, True);
}
Infine, gli spazi dei nomi importati non sono più gli stessi:
using System;
using PamV5Client.WsPam;
using NUnit.Framework;
using Spring.Context.Support;
A questo punto, è possibile generare il progetto di tipo "Libreria di classi". Viene creato il seguente DLL:
![]() |
- in [1], la cartella [bin/Release] del progetto C#
- in [2], il file DLL del progetto.
Il test NUnit viene quindi eseguito dal framework NUnit (la base MySQL dbpam_nhibernate deve essere attiva per il test):
- in [3] e [4], DLL [2] viene caricata nell’applicazione di test NUnit
![]() |
- in [5], la classe di test viene selezionata ed eseguita [6]
- in [7], i risultati di un test riuscito
Ora disponiamo di un servizio web operativo.






























