Skip to content

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 evolveremo come segue:

Mentre nell'architettura precedente i livelli [web], [business] e [DAO] giravano nella stessa macchina virtuale .NET, nella nuova architettura il livello [web] girerà in una macchina virtuale diversa rispetto ai livelli [business] e [DAO]. Questo accadrà, ad esempio, se il livello [web] si trova sulla macchina M1 e i livelli [business] e [DAO] si trovano sulla macchina M2. In questo caso abbiamo un'architettura client/server:

  • il server è costituito dai livelli [business] e [DAO]. Trattandosi di un servizio web, richiede il server web n. 2 per funzionare.
  • Il client è costituito dal livello [web]. Per funzionare, richiede il server web n. 1.
  • Il client e il server comunicano sulla rete TCP/IP utilizzando il protocollo HTTP/SOAP. Per farlo, è 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 [business] e [DAO] per soddisfarle. Esistono molti modi per creare un servizio TCP/IP. Il vantaggio del servizio web è duplice:
      • utilizza il protocollo HTTP, che è consentito attraverso i firewall aziendali e governativi
      • utilizza un sottoprotocollo HTTP/SOAP standard, implementato da molte piattaforme di sviluppo: .NET, Java, PHP, Flex, ecc. Pertanto, un servizio web può essere "consumato" (termine standard) da client .NET, Java, PHP, Flex, ecc.
    • Livello [C], che fungerà da client per il servizio web remoto. Il suo ruolo sarà quello di comunicare con il servizio web [S].

Questa nuova architettura può essere ricavata da quelle precedenti senza troppa fatica:

  • i livelli [business] e [DAO] rimangono invariati
  • il livello [web] si evolve leggermente, principalmente per fare riferimento a entità come Employee e Payroll, che sono diventate entità del livello client [C]. Queste entità sono analoghe a quelle dei livelli [business] o [DAO] ma appartengono a spazi dei nomi diversi.
  • il livello server [S] è una classe che implementa l'interfaccia IPamMetier del livello [business]. Questa implementazione chiama semplicemente i metodi corrispondenti del livello [business]. I metodi implementati dal livello server [S] saranno "esposti" ai client remoti, che potranno chiamarli.
  • 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 [business] come se fosse locale. Per ottenere questo risultato, il livello client [C] implementa l'interfaccia IPamMetier del livello [business] effettivo e si presenta al livello [web] come un livello [business] locale. A parte la questione dello spazio dei nomi menzionata in precedenza, il livello [web] rimane invariato. Questo è il vantaggio di lavorare a livelli. Se avessimo realizzato un'applicazione a livello singolo, sarebbe necessaria una revisione completa.
  • Il livello client [C] inoltra in modo trasparente le richieste del livello [web] al servizio web remoto [S]. Gestisce tutti gli aspetti 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 suoi client remoti. Li formatta per chiamare i metodi dell'interfaccia IPamMetier nel livello [business]. Una volta ricevuta la risposta dal livello [business], la formatta per trasmetterla in rete al client [C]. I livelli [business] e [DAO] non devono essere modificati.

9.2. Il progetto Visual Web Developer per il servizio web

Stiamo creando un nuovo progetto con Visual Web Developer:

  • in [1], selezioniamo un progetto web C#
  • in [2], selezioniamo "Applicazione di servizi 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 standard con le seguenti caratteristiche:
    • Abbiamo specificato che il progetto è un "Servizio Web". Un servizio Web non invia pagine web HTML ai propri clienti, ma piuttosto 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 specifica la posizione del codice sorgente per questo servizio web
    • - L'attributo Class specifica il nome della classe che implementa il servizio web nel codice sorgente

Il codice sorgente [Service.asmx.cs] per il servizio Web predefinito è il seguente:


using System.Web.Services;
 
namespace pam_v5_webservice
{
  /// <summary>
  /// Service summary description1
  /// </summary>
  [WebService(Namespace = "http://tempuri.org/")]
  [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
  [System.ComponentModel.ToolboxItem(false)]
  // To allow this Web service to be called from a script using ASP.NET AJAX, remove the comment marks from the following line. 
  // [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 alla riga 13 venga esposta come servizio web. Un servizio web appartiene a uno spazio dei nomi per impedire che due servizi web nel mondo abbiano lo stesso nome. Dovremo modificare questo spazio dei nomi in seguito.
  • riga 13: la classe Service1 deriva dalla classe WebService del framework .NET.
  • Riga 16: l'annotazione WebMethod garantisce che il metodo così annotato venga reso disponibile ai client remoti, i quali potranno quindi richiamarlo.
  • Righe 17–20: il metodo HelloWorld è un metodo dimostrativo. Lo rimuoveremo in seguito. Ci permette di eseguire i test iniziali e di esplorare gli strumenti di Visual Studio, nonché alcuni concetti chiave relativi ai servizi web.
  • In [1], eseguiamo il servizio web [Service.asmx]
  • VS Web Developer ha avviato il proprio server web integrato ed è in ascolto su una porta casuale, in questo caso la 1599. L'URL [2] è stato quindi richiesto al server web. Questo è l'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 (Web Service Description Language) per via del suo suffisso (.wsdl), è un file XML che descrive i metodi esposti dal servizio web. È da questo file WSDL che i client possono apprendere:
    • il namespace del servizio web
    • l'elenco dei metodi esposti dal servizio web
    • i parametri previsti 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 l'URL [6]. La conoscenza di questo URL è necessaria per i client del servizio web.
  • In [7], la pagina a cui si accede seguendo il link [4] consente di chiamare il metodo [HelloWorld] del servizio web
  • In [8], il risultato ottenuto: una risposta XML. Prendere nota dell'URL del metodo [9].

L'analisi delle pagine precedenti ci aiuta a comprendere come viene richiamato un metodo del servizio web e quale tipo di risposta restituisce. Ciò ci consente di scrivere client HTTP in grado di comunicare con il servizio web. La maggior parte degli IDE moderni consente la generazione automatica di questo client HTTP, evitando così allo sviluppatore di doverlo scrivere. Ciò vale in particolare per Visual Studio Express.

Prima di proseguire con questo progetto, modificheremo lo spazio dei nomi predefinito utilizzato durante la generazione delle classi:

Quando selezioniamo le proprietà del progetto (clic destro sul progetto / Proprietà), vediamo [1] il nome dell'assembly del progetto e [2] il suo namespace predefinito.

Una volta fatto questo,

  • 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 sorgente):

<%@ 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 [business] ai client remoti. Questo è il livello che stiamo attualmente sviluppando.
  • Il livello [C] è il client HTTP del servizio web. Questo è il livello che gli IDE possono generare automaticamente.
  • Il livello [web] tratta il livello [C] come un livello [business] locale se ci assicuriamo che il livello [C] implementi l'interfaccia del livello [business] remoto.

Come si può vedere di seguito, il nostro servizio web:

  • esporrà i metodi del livello [business]
  • comunicare con il livello [business], che a sua volta comunicherà con il livello [DAO].

Il progetto deve quindi utilizzare le DLL per i livelli [business] e [DAO]. Si evolve come segue:

  • in [1], aggiungiamo i riferimenti al progetto
  • in [2], selezioniamo le solite DLL dalla cartella [lib]. Ci assicureremo che la loro proprietà "Copia in locale" sia impostata su True. Le DLL selezionate sono quelle che implementano i livelli [business] e [DAO] con il supporto di 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 i vantaggi 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 che sono di sola lettura. Nella nostra applicazione, memorizzerà, come nelle precedenti, l'elenco semplificato dei dipendenti. Ciò eviterà di dover recuperare questo elenco dal database quando un client lo richiede.
  • In [1], fare clic con il tasto destro del mouse sul progetto
  • in [2], selezionare l'opzione [Aggiungi nuovo elemento]
  • In [3], selezionare [Classe applicazione 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 delle precedenti applicazioni web. Torniamo all'architettura dell'applicazione e inseriamo lì la classe [Global]:

Nel diagramma sopra,

  • la classe [Global] viene istanziata all'avvio del servizio web. Rimane in memoria finché il servizio web è attivo.
  • La classe [Global] istanzia i livelli [business] e [DAO] nel suo metodo [Application_Start]
  • Per migliorare le prestazioni, la classe [Global] memorizza l'elenco semplificato dei dipendenti in un campo interno. Recupererà l'elenco dei dipendenti da questo campo.
  • Il servizio web viene istanziato ad ogni richiesta del client. Scompare dopo aver servito quella richiesta. Non comunicherà direttamente con il livello [business] ma con la classe [Global]. Quest'ultima implementerà l'interfaccia del livello [business].

La classe [Global] è simile 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
  {
    // --- static application data ---
    public static Employe[] Employes;
    public static IPamMetier PamMetier = null;
 
    protected void Application_Start(object sender, EventArgs e)
    {
      // instantiation layer [metier]
      PamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
      // retrieve the simplified employee table 
      Employes = PamMetier.GetAllIdentitesEmployes();
    }
 
    // simplified list of employees
    static public Employe[] GetAllIdentitesEmployes()
    {
      return Employes;
    }
 
    // employee salary
    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 è esplicitamente indicato nella dichiarazione:


  public class Global : System.Web.HttpApplication, IPamMetier

Infatti, i metodi GetAllEmployeeIDs (riga 24) e GetSalary (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 GetAllEmployeeIDs e GetSalary come non statici. Questo perché vi si accede tramite il nome della classe e non tramite un'istanza della classe.

  • Riga 15: Il metodo Application_Start è simile a quello delle classi [Global] studiate nelle versioni precedenti. Istanzia il livello [business] (riga 18) e quindi inizializza (riga 20) l'array dei dipendenti della riga 12.
  • Riga 24: Il metodo GetAllEmployeeIDs restituisce semplicemente l'array di dipendenti della riga 12. Questo è il vantaggio di averlo memorizzato all'avvio dell'applicazione.
  • Riga 30: Il metodo GetSalaire chiama il metodo con lo stesso nome nel livello [business].

Per istanziare il livello [business] (riga 18), la classe [Global] utilizza il framework Spring. Questo è configurato dal file [Web.config], che è identico a quello del progetto precedente: configura Spring e NHibernate per istanziare i livelli [business] 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 [business] implementano la sua interfaccia, IPamMetier. Questo non è obbligatorio, ma è un approccio logico. Questo ragionamento può essere applicato sul lato client, al client [C] del servizio web [S]. Pertanto, tutti i livelli che separano il livello [web] dal livello [business] implementano l'interfaccia IPamMetier. Possiamo quindi affermare di essere tornati a un'applicazione a tre livelli:

  • il livello di presentazione [web] [1]
  • il livello [business] [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
  {
 
    // list of all employee identities 
    [WebMethod]
    public Employe[] GetAllIdentitesEmployes()
    {
      return Global.GetAllIdentitesEmployes();
    }
 
    // ------- salary calculation 
    [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 esposto ai client remoti. Per impostazione predefinita, tutti i metodi pubblici di un servizio web sono esposti. Gli attributi nelle righe 15 e 22 sono quindi facoltativi in questo caso. Per implementare l'interfaccia [IPamMetier], ogni metodo chiama semplicemente il metodo con lo stesso nome nella classe [Global].

Siamo pronti per eseguire il servizio web:

  • in [1], il progetto viene rigenerato
  • in [2], selezioniamo il servizio web [Service1.asmx] e lo visualizziamo nel browser [3]
  • in [4], la pagina web visualizzata. Mostra i metodi del servizio web.
  • in [4], seguiamo il link [GetAllIdentitesEmployes] e in [5] otteniamo la pagina di test per questo metodo.
  • in [6], l'URL del metodo
  • In [7], il pulsante [Call] utilizzato per testare il metodo. Questo metodo non richiede alcun parametro.
  • In [8], il risultato XML restituito dal servizio web. In questo risultato, solo le proprietà SS, LastName e FirstName degli oggetti Employee sono significative perché il metodo [GetAllEmployeeIDs] richiede solo queste proprietà. Tuttavia, questo metodo restituisce un array di oggetti Employee. Possiamo vedere in [8] che le proprietà numeriche Id e Version sono incluse nel flusso XML restituito, ma non le proprietà con valore nullo: Address, City, ZipCode, Allowances.

Abbiamo un servizio web attivo. Ora scriveremo un client C# per esso. Per farlo, avremo bisogno dell'URI del file WSDL del servizio web. Lo otteniamo dalla pagina visualizzata inizialmente all'esecuzione di [Service.asmx]:

  • in [1], l'URI del servizio web
  • in [2], il link al suo file WSDL
  • in [3], il valore di questo link

9.3. Il progetto C# per un client NUnit del servizio web

Creiamo un progetto C# (utilizzando Visual C#, non Visual Web Developer) per il client del servizio web. Si tratterà di un client di test NUnit. Pertanto, il progetto sarà di tipo "Class Library".

  • 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 [Applicazione] [5], impostiamo lo spazio dei nomi del progetto. Tutte le classi generate dall'IDE saranno in questo spazio dei nomi.

Salviamo il nostro nuovo progetto in una posizione a nostra scelta:

 

Una volta fatto ciò, generiamo il client del servizio web remoto. Per capire cosa stiamo per fare, è necessario rivedere l'architettura client/server attualmente in fase di sviluppo:

L'IDE genererà il livello client [C] dall'URI del file WSDL del servizio web [S]. Ricordiamo che l'URI di questo file è stato annotato in precedenza. Procediamo dall' l web come segue:

  • In [1], fare clic con il tasto destro del mouse sul ramo Riferimenti e aggiungere un riferimento al servizio
  • In [2], inserisci l'URL del file WSDL del servizio web annotato in precedenza. Il servizio deve essere in esecuzione, 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.
  • Confermare la procedura guidata

  • In [1], il file client generato ( ). Fare doppio clic su di esso per visualizzarne il contenuto.
  • in [2], in Esplora oggetti, vengono visualizzate le classi e le interfacce dello spazio dei nomi Client.WsPam. Questo è lo spazio dei nomi del client generato.
  • In [3], la classe che implementa il client del servizio web.
  • In [4], i metodi implementati dal client [Service1SoapClient]. Qui troverai i due metodi del servizio web remoto [5] e [6].
  • In [2], è possibile visualizzare i diagrammi delle entità per i livelli:
    • [business]: Payroll, PayrollItems
    • [DAO]: Dipendente, Contributi, Indennità

Proseguendo, tieni presente che queste rappresentazioni delle entità remote si trovano sul lato client e nello spazio dei nomi PamV5Client.WsPam.

Esaminiamo i metodi e le proprietà esposti da uno di essi:

  • In [1], selezioniamo la classe locale [Employee]
  • In [2], vediamo le proprietà dell'entità remota [Employee] e i campi privati utilizzati per le esigenze specifiche dell'entità locale.

Torniamo alla nostra applicazione C#. Aggiungiamo una classe di test NUnit:

  • in [1], la classe [NUnit] aggiunta. La classe [NUnit] richiederà il framework NUnit e quindi un riferimento alla sua DLL. Supponiamo qui che il framework NUnit sia stato installato sul computer (http://nunit.org/).
  • In [2], aggiungiamo un riferimento al progetto
  • nella scheda .NET [3], che elenca le DLL registrate sul computer, selezioniamo [4], la DLL [nunit.framework], versione 2.4.6 o successive.

Inoltre, useremo Spring per istanziare il client locale [C] del servizio web [S]:

Il riferimento alla DLL di Spring può essere aggiunto allo stesso modo del framework NUnit se le DLL sono già state registrate sul computer (http://www.springframework.net/download.html).

Procediamo in modo diverso. Utilizziamo la cartella [lib] dei progetti precedenti, che conteneva le DLL richieste da Spring, e aggiungiamo il riferimento a Spring al progetto:

Rivediamo l'architettura del client attualmente in fase di sviluppo:

Sopra, vediamo che il client di test [1] si interfaccia con un livello [business] esteso [2]. Questo livello ha gli stessi metodi del livello [business] remoto. Possiamo quindi utilizzare la classe di test già incontrata durante il test del livello [business] 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 {
 
        // the [metier] layer to test 
        private IPamMetier pamMetier;
 
        // manufacturer
        public NunitTestPamMetier() {
            // layer instantiation [dao]
            pamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
        }
 
 
        [Test]
        public void GetAllIdentitesEmployes() {
            // audit no. of employees 
            Expect(2, EqualTo(pamMetier.GetAllIdentitesEmployes().Length));
        }
 
        [Test]
        public void GetSalaire1() {
            // wage sheet calculation 
            FeuilleSalaire feuilleSalaire = pamMetier.GetSalaire("254104940426058", 150, 20);
            // checks 
            Expect(368.77, EqualTo(feuilleSalaire.ElementsSalaire.SalaireNet).Within(1E-06));
            // non-existent employee payslip 
            bool erreur = false;
            try {
                feuilleSalaire = pamMetier.GetSalaire("xx", 150, 20);
            } catch (PamException) {
                erreur = true;
            }
            Expect(erreur, True);
        }
 
    }
}

Ci sono alcune modifiche da apportare:

  • Alla riga 18, istanziamo il livello [business] utilizzando il framework Spring. La classe non è la stessa in entrambi i casi. Qui, il livello [business] locale è un'istanza della classe [PamV5Client.WsPam.Service1SoapClient], la classe generata dall'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, l'oggetto [pammetier] è un'istanza della classe [PamV5Client.WsPam.Service1SoapClient] situata nell'assembly [pam-v5-client-csharp-webservice]. Per trovare la prima informazione, basta tornare alla definizione della classe [Service1SoapClient] in Esplora oggetti (Sezione 9.3):
  • in [2], la classe di implementazione del livello [business] locale, e in [1] il suo namespace
  • in [3], nelle proprietà del progetto, il nome dell'assembly, la seconda informazione necessaria per configurare l'oggetto Spring [pammetier].

Torniamo al codice per l'istanziazione del livello [business] locale in [NUnit.cs]:


        // the [metier] layer to test 
        private IPamMetier pamMetier;
 
        // manufacturer
        public NunitTestPamMetier() {
            // layer instantiation [dao]
            pamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
        }
 

Riga 7: Il livello [business] remoto era di tipo IPamMetier. Qui, il livello [business] è 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 gli stessi nomi. Dobbiamo quindi scrivere l'istanziazione del livello [business] locale come segue:


        // the [metier] layer to test 
        private Service1SoapClient pamMetier;
 
        // manufacturer
        public NunitTestPamMetier() {
            // instantiation layer [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;

Una volta fatto ciò, è possibile compilare il progetto "Class Library". Viene creata la seguente DLL:

  • in [1], la cartella [bin/Release] del progetto C#
  • in [2], la DLL del progetto.

Il test NUnit viene quindi eseguito dal framework NUnit (il database MySQL dbpam_nhibernate deve essere attivo per il test):

  • in [3] e [4], la 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 funzionante.