Skip to content

9. Aplikacja [SimuPaie] – wersja 5 – ASP.NET / usługa internetowa


Zalecana literatura: dokumentacja [2], „Wprowadzenie do języka C# 2008”, rozdział 10 „Usługi internetowe


9.1. Nowa architektura aplikacji

Architektura warstwowa aplikacji Pam wygląda obecnie następująco:

Zamierzamy ją rozbudować w następujący sposób:

Podczas gdy w poprzedniej architekturze warstwy [web], [metier], [dao] działały w tej samej maszynie wirtualnej.NET, w nowej architekturze warstwa [web] będzie działać na innej maszynie wirtualnej niż warstwy [metier] i [dao]. Będzie to miało miejsce w szczególności wtedy, gdy warstwa [web] znajduje się na maszynie M1, a warstwy [metier] i [dao] na maszynie M2. Mamy tu do czynienia z architekturą klient-serwer:

  • serwer składa się z warstw [metier] i [dao]. Ponieważ jest to usługa internetowa, do działania potrzebuje serwera internetowego nr 2.
  • Klient składa się z warstwy [web]. Do działania potrzebuje serwera WWW nr 1.
  • Klient i serwer komunikują się przez sieć TCP/IP za pomocą protokołu HTTP / SOAP. W tym celu do architektury należy dodać dwie nowe warstwy:
    • warstwa [S], która będzie stanowić usługę internetową. Usługa internetowa odbiera żądania od zdalnych klientów i wykorzystuje warstwy [metier] oraz [dao], aby je zrealizować. Istnieje wiele sposobów na zbudowanie usługi TCP/IP. Zaleta usługi internetowej jest dwojaka:
      • wykorzystuje protokół HTTP, który jest przepuszczany przez zapory sieciowe przedsiębiorstw i instytucji administracyjnych
      • wykorzystuje standardowy podprotokół HTTP / SOAP, zaimplementowany przez wiele platform programistycznych: .NET, Java, PHP, Flex itp. W ten sposób usługa internetowa może być „wykorzystywana” (tak się to potocznie nazywa) przez klientów .Net, Java, PHP, Flex itp.
    • warstwa [C], która będzie klientem zdalnej usługi internetowej. Jej rolą będzie komunikacja z usługą internetową [S].

Tę nową architekturę można bez większego wysiłku wyprowadzić z poprzednich:

  • warstwy [metier] i [dao] pozostają bez zmian
  • warstwa [web] ulega niewielkiej zmianie, głównie w celu odwołania się do encji takich jak Employe, FeuilleSalaire, które stały się encjami warstwy klienckiej [C]. Entytety te są analogiczne do tych z warstw [metier] lub [dao], ale należą do różnych przestrzeni nazw.
  • Warstwa serwerowa [S] jest klasą, która implementuje interfejs IPamMetier warstwy [metier]. Ta implementacja ogranicza się do wywoływania odpowiednich metod warstwy [metier]. Metody zaimplementowane przez warstwę serwerową [S] zostaną „udostępnione” klientom zdalnym, którzy będą mogli je wywoływać.
  • Warstwa kliencka [C] zostanie wygenerowana przez Visual Studio.

Zasady działania nowej architektury są następujące:

  • warstwa [web] nadal komunikuje się z warstwą [metier] tak, jakby ta ostatnia była lokalna. W tym celu warstwa kliencka [C] implementujeinterfejs IPamMetier rzeczywistej warstwy [metier] i prezentuje się warstwie [web] jako lokalna warstwa [metier]. Pomijając wspomniany wcześniej problem przestrzeni nazw, warstwa [web] pozostaje bez zmian. Na tym polega zaleta pracy w warstwach. Gdybyśmy stworzyli aplikację jednowarstwową, konieczna byłaby jej bardzo gruntowna przebudowa.
  • Warstwa kliencka [C] przekazuje, w sposób przezroczysty dla warstwy [web], jej żądania do zdalnej usługi internetowej [S]. Zajmuje się ona całym aspektem „komunikacji sieciowej”. Otrzymuje odpowiedź od zdalnej usługi internetowej, którą formatuje, aby przekazać ją warstwie [web] w oczekiwanej przez nią formie.
  • Po stronie serwera usługa internetowa [S] odbiera polecenia od swoich zdalnych klientów. Przetwarza je w celu wywołania metod interfejsu IPamMetier warstwy [metier]. Po otrzymaniu odpowiedzi z warstwy [metier] formatuje ją w celu przesłania przez sieć do klienta [C]. Warstwy [metier] i [dao] nie wymagają modyfikacji.

9.2. Projekt Visual Web Developer dla serwisu internetowego

Tworzymy nowy projekt w programie Visual Web Developer:

  • w [1] wybieramy projekt internetowy w języku C#
  • w [2] wybieramy „Aplikacja usługi internetowej ASP.NET”
  • w [3] nadajemy nazwę projektowi internetowemu
  • w [4] wskazujemy lokalizację dla tego projektu
  • w [1] wygenerowany projekt. Jest to klasyczny projekt internetowy z następującymi szczegółami:
    • określono, że projekt jest typu „serwis internetowy”. Serwis internetowy nie wysyła stronom internetowym HTML do swoich klientów, lecz dane w formacie XML. Dlatego strona [Default.aspx], która zazwyczaj jest generowana, nie została wygenerowana.
  • W pliku [2] wygenerowano plik [Service1.asmx] o następującej treści:

<%@ WebService Language="C#" CodeBehind="Service1.asmx.cs" Class="pam_v5_webservice.Service1" %>
  • (ciąg dalszy)
    • - tag WebService wskazuje, że [Service.asmx] jest usługą internetową
    • - atrybut CodeBehind wskazuje lokalizację kodu źródłowego tej usługi internetowej
    • - atrybut Class wskazuje nazwę klasy implementującej usługę internetową w kodzie źródłowym

Kod źródłowy [Service.asmx.cs] domyślnie wygenerowanej usługi internetowej wygląda następująco:


using System.Web.Services;

namespace pam_v5_webservice
{
  /// <summary>
  /// Skrócony opis usługi Service1
  /// </summary>
  [WebService(Namespace = "http://tempuri.org/")]
  [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
  [System.ComponentModel.ToolboxItem(false)]
  // Aby umożliwić wywołanie tej usługi internetowej ze skryptu za pomocą ASP.NET AJAX, usuń znaczniki komentarza z poniższego wiersza. 
  // [System.Web.Script.Services.ScriptService]
  public class Service1 : System.Web.Services.WebService
  {

    [WebMethod]
    public string HelloWorld()
    {
      return "Hello World";
    }
  }
}
  • wiersz 8: adnotacja WebService, która powoduje, że klasa Service1 z wiersza 13 zostanie udostępniona jako usługa internetowa. Usługa internetowa należy do przestrzeni nazw, aby uniknąć sytuacji, w której dwie usługi internetowe na świecie miałyby tę samą nazwę. W przyszłości będziemy musieli zmienić tę przestrzeń nazw.
  • wiersz 13: klasa Service1 wywodzi się z klasy WebService z frameworka .NET.
  • wiersz 16: adnotacja WebMethod powoduje, że metoda opatrzona tą adnotacją zostanie udostępniona klientom zdalnym, którzy będą mogli ją wywołać.
  • wiersze 17–20: metoda HelloWorld jest metodą demonstracyjną. Usuniemy ją później. Pozwala nam ona przeprowadzić pierwsze testy oraz zapoznać się z narzędziami programu Visual Studio, a także z niektórymi elementami, które warto znać na temat usług internetowych.
  • w [1] uruchamiamy usługę internetową [Service.asmx]
  • VS Program Web Developer uruchomił swój wbudowany serwer internetowy i skonfigurował go tak, aby nasłuchiwał na losowym porcie, w tym przypadku 1599. Następnie do serwera internetowego wysłano żądanie o adres URL [2]. Jest to adres strony testowej usługi internetowej.
  • W adresie [3] znajduje się link umożliwiający wyświetlenie pliku opisu usługi internetowej. Plik ten, nazwany WSDL (WebService Description Language) ze względu na swój rozszerzenie (.wsdl), jest plikiem XML opisującym metody udostępniane przez usługę internetową. Na podstawie tego pliku WSDL klienci mogą poznać:
    • przestrzeń nazw serwisu internetowego
    • listę metod udostępnianych przez serwis internetowy
    • parametry oczekiwane przez każdą z nich
    • odpowiedź zwracaną przez każdą z nich
  • w pliku [4] – jedyną metodę udostępnianą przez serwis internetowy .
  • w [5] – zawartość pliku WSDL uzyskanego poprzez link [3]. Należy zwrócić uwagę na pliki URL i [6]. Znajomość tych plików jest niezbędna dla użytkowników serwisu internetowego.
  • w [7] strona uzyskana po kliknięciu linku [4] umożliwia wywołanie metody [HelloWorld] serwisu internetowego
  • w [8], uzyskany wynik: odpowiedź XML. Należy zwrócić uwagę na URL [9] tej metody.

Analiza poprzednich stron pozwala zrozumieć, w jaki sposób wywoływana jest metoda serwisu internetowego i jaki typ odpowiedzi zwraca. Umożliwia to tworzenie klientów HTTP zdolnych do komunikacji z serwisem internetowym. Większość obecnych narzędzi IDE umożliwia automatyczne wygenerowanie takiego klienta HTTP, dzięki czemu programista nie musi go pisać samodzielnie. Dotyczy to w szczególności programu Visual Studio Express.

Zanim przejdziemy dalej w tym projekcie, zmienimy przestrzeń nazw używaną domyślnie podczas generowania klas:

Po wybraniu właściwości projektu (kliknięcie prawym przyciskiem myszy na projekcie / Właściwości) widzimy w polu [1] nazwę projektu l'assembly, a w polu [2] jego domyślną przestrzeń nazw.

Po wykonaniu tej czynności,

  • w polu [Service1.asmx.cs] zmieniamy przestrzeń nazw klasy:

using System.Web.Services;

namespace pam_v5
{
...
  public class Service1 : System.Web.Services.WebService
  {
...
  }
}
  • w [Service.asmx] zmieniamy również przestrzeń nazw używaną dla klasy [Service1] (kliknięcie prawym przyciskiem myszy / wyświetl kod):

<%@ WebService Language="C#" CodeBehind="Service1.asmx.cs" Class="pam_v5.Service1" %>

Wróćmy do architektury naszej aplikacji:

  • warstwa [S] stanowi usługę internetową. Jej zadaniem jest jedynie udostępnianie metod warstwy [metier] zdalnym klientom. To właśnie tę warstwę obecnie tworzymy.
  • Warstwa [C] jest klientem HTTP serwisu internetowego. To właśnie tę warstwę moduły IDE potrafią generować automatycznie.
  • Warstwa [web] postrzega warstwę [C] jako lokalną warstwę [metier], jeśli zadbamy o to, by warstwa [C] implementowałainterfejs warstwy zdalnej [metier].

Poniżej widać, że nasza usługa internetowa:

  • udostępni metody warstwy [metier]
  • komunikować się z tą warstwą, która z kolei będzie komunikować się z warstwą [dao].

Projekt musi zatem korzystać z warstw DLL, [metier] i [dao]. Rozwija się on w następujący sposób:

  • do [1], dodaje się odniesienia do projektu
  • w [2] wybiera się standardowe pliki DLL z folderu [lib]. Należy zadbać o to, aby wszystkie miały właściwość „Kopia lokalna” ustawioną na True. Wybrane pliki DLL to te, które implementują warstwy [metier] i [dao] z obsługą NHibernate.

Aplikacja internetowa typu „usługa internetowa ASP.NET” może posiadać globalną klasę aplikacji „Global.asax”, podobnie jak klasyczna aplikacja typu „strona internetowa ASP.NET”. Widzieliśmy już zalety takiej klasy:

  • jest ona instancjonowana podczas uruchamiania aplikacji i pozostaje w pamięci
  • dzięki czemu może przechowywać dane udostępniane wszystkim klientom, które są tylko do odczytu. W naszej aplikacji będzie ona przechowywać, podobnie jak w poprzednich, uproszczoną listę pracowników. Pozwoli to uniknąć pobierania tej listy z bazy danych, gdy klient o nią poprosi.
  • w [1] kliknij prawym przyciskiem myszy na projekt
  • w [2], wybierz opcję [Ajouter un nouvel élément]
  • w [3], wybierz [Classe d'application globale]
  • w pliku [4] plik [Global.asax] został dodany do projektu

Zawartość pliku [Global.asax] jest następująca:


<%@ Application Codebehind="Global.asax.cs" Inherits="pam_v5.Global" Language="C#" %>

Zawartość pliku [Global.asax.cs] jest następująca:


using System;

namespace pam_v5
{
  public class Global : System.Web.HttpApplication
  {

    protected void Application_Start(object sender, EventArgs e)
    {

    }
...
  }
}

Co powinniśmy zrobić w metodzie Application_Start? Dokładnie to samo, co w poprzednich aplikacjach internetowych. Wróćmy do architektury aplikacji i umieśćmy w niej klasę [Global]:

Na powyższym schemacie

  • klasa [Global] jest instancjonowana podczas uruchamiania serwisu internetowego. Pozostaje w pamięci tak długo, jak długo serwis internetowy jest aktywny.
  • Klasa [Global] instancjonuje warstwy [metier] i [dao] w swojej metodzie [Application_Start]
  • Aby poprawić wydajność, klasa [Global] umieszcza uproszczoną listę pracowników w polu wewnętrznym. Będzie ona generować listę pracowników na podstawie tego pola.
  • Usługa internetowa jest instancjonowana przy każdym żądaniu klienta. Znika po obsłużeniu tego żądania. Nie będzie on zwracał się bezpośrednio do warstwy [metier], lecz do klasy [Global]. Ta z kolei zaimplementuje interfejs warstwy [metier].

Klasa [Global] jest analogiczna do tej, która została już utworzona dla poprzednich aplikacji:


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
  {
    // --- statyczne dane aplikacji ---
    public static Employe[] Employes;
    public static IPamMetier PamMetier = null;

    protected void Application_Start(object sender, EventArgs e)
    {
      // instancjonowanie warstwy [metier]
      PamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
      // pobieramy uproszczoną listę pracowników 
      Employes = PamMetier.GetAllIdentitesEmployes();
    }

    // uproszczona lista pracowników
    static public Employe[] GetAllIdentitesEmployes()
    {
      return Employes;
    }

    // wynagrodzenie pracownika
    static public FeuilleSalaire GetSalaire(string SS, double heuresTravaillées, int joursTravailles)
    {
      return PamMetier.GetSalaire(SS, heuresTravaillées, joursTravailles);
    }
  }
}

Klasa [Global] implementuje interfejs [IPamMetier], ale nie jest to wyraźnie zaznaczone w deklaracji:


  public class Global : System.Web.HttpApplication, IPamMetier

W rzeczywistości metody GetAllIdentitesEmployes (wiersz 24) i GetSalaire (wiersz 30) są statyczne, podczas gdy metody interfejsu IPamMetier nie są. W związku z tym klasa Global nie może implementować interfejsu IPamMetier. Ponadto nie można zadeklarować metod GetAllIdentitesEmployes i GetSalaire jako niestacjonarnych. Dostęp do nich odbywa się bowiem poprzez nazwę klasy, a nie poprzez jej instancję.

  • wiersz 15: metoda Application_Start jest analogiczna do metod klas [Global] omówionych w poprzednich wersjach. Tworzy ona instancję warstwy [metier] (wiersz 18), a następnie inicjuje (wiersz 20) tablicę pracowników z wiersza 12.
  • wiersz 24: metoda GetAllIdentitesEmployes ogranicza się do zwracania tablicy pracowników z wiersza 12. Na tym polega sens jej zapisania już na początku działania aplikacji.
  • wiersz 30: metoda GetSalaire wywołuje metodę o tej samej nazwie z warstwy [metier].

Aby utworzyć instancję warstwy [metier] (wiersz 18), klasa [Global] wykorzystuje framework Spring. Jest on konfigurowany przez plik [Web.config], który jest identyczny jak w poprzednim projekcie: konfiguruje on Spring oraz NHibernate w celu utworzenia instancji warstw [metier] i [dao] serwisu internetowego.

Wróćmy do architektury naszej aplikacji klient-serwer:

Po stronie serwera pozostaje już tylko napisać sam serwis internetowy [S]. Jeśli wrócimy do architektury aplikacji:

widzimy, że po stronie serwera wszystkie warstwy poprzedzające warstwę [metier] implementują jej interfejs IPamMetier. Nie jest to obowiązkowe, ale wydaje się to logicznym podejściem. To rozumowanie można zastosować również po stronie klienta, w odniesieniu do klienta [C] serwisu internetowego [S]. W ten sposób wszystkie warstwy oddzielające warstwę [web] od warstwy [metier] implementują zatem interfejs IPamMetier. Można zatem stwierdzić, że powrócono do aplikacji trójwarstwowej:

  • warstwa prezentacji [web] [1]
  • warstwa [metier] [2]
  • warstwa dostępu do danych [3]

Implementacja serwisu internetowego [Service1.asmx.cs] mogłaby wyglądać następująco:


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
  {

    // lista wszystkich identyfikatorów pracowników 
    [WebMethod]
    public Employe[] GetAllIdentitesEmployes()
    {
      return Global.GetAllIdentitesEmployes();
    }

    // ------- obliczenie wynagrodzenia 
    [WebMethod]
    public FeuilleSalaire GetSalaire(string ss, double heuresTravaillees, int joursTravailles)
    {
      return Global.GetSalaire(ss, heuresTravaillees, joursTravailles);
    }
  }
}
  • wiersz 8: klasa jest opatrzona atrybutem [WebService] i nadajemy nazwę przestrzeni nazw usługi internetowej
  • wiersz 11: klasa [Service1] dziedziczy po klasie [WebService] i implementuje interfejs [IPamMetier]
  • wiersze 15 i 22: każda metoda klasy jest opatrzona atrybutem [WebMethod], aby była udostępniona klientom zdalnym. Domyślnie wszystkie metody publiczne serwisu internetowego są udostępniane. Atrybuty w wierszach 15 i 22 są zatem w tym przypadku opcjonalne. Aby zaimplementować interfejs [IPamMetier], każda metoda po prostu wywołuje metodę o tej samej nazwie z klasy [Global].

Jesteśmy gotowi do uruchomienia serwisu internetowego:

  • w [1] projekt jest ponownie generowany
  • w [2] wybieramy usługę internetową [Service1.asmx] i wyświetlamy ją w przeglądarce [3]
  • w [4] – wyświetlona strona internetowa. Przedstawia ona metody serwisu internetowego.
  • w [4] klikamy link [GetAllIdentitesEmployes] i otrzymujemy w [5] stronę testową tej metody.
  • w [6], URL metody
  • w [7] znajduje się przycisk [Appeler] umożliwiający przetestowanie tej metody. Metoda ta nie wymaga żadnych parametrów.
  • w [8] – wynik XML zwrócony przez serwis internetowy. W tym wyniku znaczące są jedynie właściwości SS, Nom, Prenom obiektów Employe są istotne, ponieważ metoda [GetAllIdentitesEmployes] wymaga tylko tych właściwości. Jednak metoda ta zwraca tablicę obiektów Employe. W [8] widać, że właściwości liczbowe Id, Version znajdują się w zwracanym strumieniu XML, ale nie ma tam właściwości o wartości null: Adresse, Ville, CodePostal, Indemnites.

Mamy już działającą usługę internetową. Teraz napiszemy dla niego klienta w języku C#. W tym celu będziemy potrzebować wartości URI z pliku WSDL serwisu internetowego. Uzyskujemy ją na stronie wyświetlanej początkowo podczas uruchamiania pliku [Service.asmx]:

  • w [1], URI z serwisu internetowego
  • w [2], link prowadzący do jego pliku WSDL
  • w [3], wartość tego linku

9.3. Projekt w języku C# dla klienta serwisu internetowego NUnit

Tworzymy projekt w języku C# (w programie Visual C#, a nie Visual Web Developer) dla klienta serwisu internetowego. Będzie to klient testowy o nazwie NUnit. Projekt będzie miał typ „Biblioteka klas”.

  • w [1] tworzymy projekt w języku C# typu „Biblioteka klas”
  • w pliku [2] nadajemy nazwę projektowi
  • w [3] – projekt. Usuwamy [Class1.cs].
  • w [4] – nowy projekt.
  • w właściwościach projektu, w zakładce [Application] [5], ustalamy przestrzeń nazw projektu. Każda klasa wygenerowana przez IDE będzie znajdować się w tej przestrzeni.

Zapisujemy nasz nowy projekt w wybranej przez nas lokalizacji:

 

Po wykonaniu tej czynności generujemy klienta zdalnej usługi internetowej. Aby zrozumieć, co zamierzamy zrobić, należy powrócić do budowanej obecnie architektury klient-serwer:

IDE wygeneruje warstwę kliencką [C] na podstawie pliku URI z pliku WSDL serwisu internetowego [S]. Przypomnijmy, że plik URI z tego pliku został wcześniej odnotowany. Postępujemy w następujący sposób w przypadku pliku :

  • na [1], klikamy prawym przyciskiem myszy na gałęzi References i dodajemy odniesienie do usługi
  • w [2], podając URL z pliku WSDL usługi internetowej odnotowanej wcześniej. Usługa ta musi zostać uruchomiona wcześniej, jeśli jeszcze nie została uruchomiona.
  • w [3] należy zlecić wykrycie usługi internetowej za pomocą jej pliku WSDL
  • w pliku [4] – wykryta usługa internetowa
  • w pliku [5] – metody udostępniane przez usługę internetową.
  • w pliku [6] – przestrzeń nazw, w której chcemy umieścić klasy i interfejsy generowanego klienta.
  • potwierdzamy działanie kreatora

  • w [1], wygenerowany k . Klikamy na niego dwukrotnie, aby uzyskać dostęp do jego zawartości.
  • W [2], w eksploratorze obiektów, wyświetlane są klasy i interfejsy z przestrzeni nazw Client.WsPam. Jest to przestrzeń nazw wygenerowanego klienta.
  • W [3] znajduje się klasa implementująca klienta serwisu internetowego.
  • W [4] znajdują się metody zaimplementowane przez klienta [Service1SoapClient]. Znajdują się tam dwie metody zdalnej usługi internetowej: [5] i [6].
  • W pliku [2] znajdują się obrazy encji warstw:
    • [metier]: FeuilleSalaire, ElementsSalaire
    • [dao]: Employe, Cotisations, Indemnites

W dalszej części należy pamiętać, że te obrazy zdalnych obiektów znajdują się po stronie klienta oraz w przestrzeni nazw PamV5Client.WsPam.

Przyjrzyjmy się metodom i właściwościom udostępnianym przez jeden z nich:

  • w [1] wybieramy lokalną klasę [Employe]
  • w [2] znajdują się właściwości zdalnej encji [Employe], a także pola prywatne wykorzystywane na potrzeby samej encji lokalnej.

Wróćmy do naszej aplikacji w języku C#. Dodajemy do niej klasę testową NUnit:

  • w [1] dodano klasę [NUnit]. Klasa [NUnit] będzie potrzebowała frameworka NUnit, a zatem odniesienia do klasy DLL zawartej w tym frameworku. Zakładamy tutaj, że framework NUnit został zainstalowany na komputerze (http://nunit.org/).
  • W [2] dodajemy odwołanie do projektu
  • w zakładce [3]. W NET, który gromadzi pliki DLL zapisane na komputerze, wybieramy [4], DLL oraz [nunit.framework] w wersji co najmniej 2.4.6.

Ponadto użyjemy Springa do utworzenia instancji lokalnego klienta [C] serwisu internetowego [S]:

Odwołanie do DLL w Springu można dodać tak samo, jak w przypadku frameworka NUnit, o ile DLL zostały wcześniej zarejestrowane na komputerze (http://www.springframework.net/download.html).

Postępujemy inaczej. Wykorzystujemy folder [lib] z poprzednich projektów, który zawierał pliki DLL niezbędne dla Springa, i dodajemy odniesienie do Springa w projekcie:

Wróćmy do architektury tworzonego obecnie klienta:

Powyżej widać, że klient testowy [1] łączy się z rozszerzoną warstwą [metier] [2]. Warstwa ta posiada te same metody, co zdalna warstwa [metier]. Możemy zatem wykorzystać klasę testową, z którą mieliśmy już do czynienia podczas testowania warstwy [metier] w projekcie 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 {

        // warstwa [metier] do przetestowania 
        private IPamMetier pamMetier;

        // konstruktor
        public NunitTestPamMetier() {
            // instancja warstwy [dao]
            pamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
        }


        [Test]
        public void GetAllIdentitesEmployes() {
            // sprawdzenie liczby pracowników 
            Expect(2, EqualTo(pamMetier.GetAllIdentitesEmployes().Length));
        }

        [Test]
        public void GetSalaire1() {
            // obliczenie listy płac 
            FeuilleSalaire feuilleSalaire = pamMetier.GetSalaire("254104940426058", 150, 20);
            // weryfikacje 
            Expect(368.77, EqualTo(feuilleSalaire.ElementsSalaire.SalaireNet).Within(1E-06));
            // arkusz płacowy nieistniejącego pracownika 
            bool erreur = false;
            try {
                feuilleSalaire = pamMetier.GetSalaire("xx", 150, 20);
            } catch (PamException) {
                erreur = true;
            }
            Expect(erreur, True);
        }

    }
}

Należy wprowadzić kilka zmian:

  • w wierszu 18 tworzymy instancję warstwy [metier] przy użyciu frameworka Spring. Klasa ta nie jest taka sama w obu przypadkach. W tym przypadku lokalna warstwa [metier] jest instancją klasy [PamV5Client.WsPam.Service1SoapClient], czyli klasy wygenerowanej przez IDE. W związku z tym Spring jest skonfigurowany w następujący sposób w pliku [app.config] projektu 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>
...
  • w wierszu 16 powyżej obiekt [pammetier] jest instancją klasy [PamV5Client.WsPam.Service1SoapClient], która znajduje się w zestawie (assembly) [pam-v5-client-csharp-webservice]. Aby uzyskać tę pierwszą informację, wystarczy przejść do definicji klasy [Service1SoapClient] w eksploratorze obiektów (punkt 9.3):
  • w [2], klasie implementującej lokalną warstwę [metier], oraz w [1] jej przestrzeni nazw
  • na [3], w właściwościach projektu, nazwa zestawu, druga informacja niezbędna do konfiguracji obiektu Spring [pammetier].

Wróćmy do kodu instancjonowania lokalnej warstwy [metier] w [NUnit.cs]:


        // warstwa [metier] do przetestowania 
        private IPamMetier pamMetier;

        // konstruktor
        public NunitTestPamMetier() {
            // instancja warstwy [dao]
            pamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
        }

W wierszu 7 zdalna warstwa [metier] była typu IPamMetier. Tutaj warstwa [metier] jest typu [Service1SoapClient]:


public class Service1SoapClient : System.ServiceModel.ClientBase<Service1Soap>

Widzimy, że klasa Service1SoapClient nie implementuje interfejsu IPamMetier, mimo że udostępnia metody o tej samej nazwie. Musimy zatem napisać instancję lokalnej warstwy [metier] w następujący sposób:


        // warstwa [metier] do przetestowania 
        private Service1SoapClient pamMetier;

        // konstruktor
        public NunitTestPamMetier() {
            // instancja warstwy [metier]
            pamMetier = ContextRegistry.GetContext().GetObject("pammetier") as Service1SoapClient;
}

Kolejna zmiana, którą należy wprowadzić:


        [Test]
        public void GetSalaire1() {
...
            try {
                feuilleSalaire = pamMetier.GetSalaire("xx", 150, 20);
            } catch (PamException) {
                erreur = true;
            }
            Expect(erreur, True);
        }

W powyższym kodzie, w wierszu 6, użyto typu PamException, który nie istnieje po stronie klienta. Należy go zastąpić jego klasą nadrzędną, czyli typem Exception.


        [Test]
        public void GetSalaire1() {
...
            try {
                feuilleSalaire = pamMetier.GetSalaire("xx", 150, 20);
            } catch (Exception) {
                erreur = true;
            }
            Expect(erreur, True);
        }

Wreszcie importowane przestrzenie nazw nie są już takie same:


using System;
using PamV5Client.WsPam;
using NUnit.Framework;
using Spring.Context.Support;

Po wykonaniu tych czynności można wygenerować projekt typu „Biblioteka klas”. Tworzony jest następujący plik DLL:

  • w pliku [1], folder [bin/Release] projektu C#
  • w plik [2], a plik DLL z projektu

Następnie test NUnit jest uruchamiany przez framework NUnit (baza MySQL dbpam_nhibernate musi być aktywna dla tego testu):

  • w [3] i [4] moduł DLL [2] jest ładowany do aplikacji testowej NUnit
  • w [5] wybierana jest klasa testowa i uruchamiana w [6]
  • w [7], wyniki pomyślnie przeprowadzonego testu

Mamy teraz działającą usługę internetową.