Skip to content

7. Aplikacja [SimuPaie] – wersja 3 – architektura trójwarstwowa z NHibernate


Zalecana lektura: „Język C# 2008, rozdział 4: Architektury trójwarstwowe, testy NUnit, framework Spring”.


7.1. Ogólna architektura aplikacji

Aplikacja [SimuPaie] będzie teraz miała następującą strukturę trójwarstwową:

  • warstwa [1-dao] (DAO = Data Access Object) będzie odpowiadać za dostęp do danych.
  • warstwa [2-métier] będzie odpowiadać za aspekt biznesowy aplikacji, czyli obliczanie wynagrodzeń.
  • warstwa [3-ui] (ui = User Interface) będzie odpowiedzialna za prezentację danych użytkownikowi oraz realizację jego zapytań. Zbiór modułów zapewniających tę funkcję nazywamy [Application]. Jest to interfejs użytkownika.
  • Trzy warstwy zostaną uniezależnione dzięki zastosowaniu interfejsów .NET
  • Integracja poszczególnych warstw zostanie zrealizowana przez Spring IoC

Przetwarzanie żądania klienta przebiega zgodnie z następującymi etapami:

  1. klient wysyła żądanie do aplikacji.
  2. Aplikacja przetwarza to żądanie. W tym celu może potrzebować pomocy warstwy [métier], która z kolei może potrzebować warstwy [dao], jeśli konieczna jest wymiana danych z bazą danych.
  3. aplikacja otrzymuje odpowiedź z warstwy [métier]. Na jej podstawie wysyła klientowi odpowiedni widok (= odpowiedź).

Weźmy na przykład obliczenie wynagrodzenia opiekunki do dzieci. Będzie to wymagało kilku etapów:

  1. warstwa [ui] będzie musiała zapytać użytkownika
    • o tożsamość osoby, dla której ma zostać obliczone wynagrodzenie
    • liczbę przepracowanych przez nią dni
    • liczbę przepracowanych godzin
  1. W tym celu warstwa ta będzie musiała przedstawić użytkownikowi listę osób (nazwisko, imię, SS) znajdujących się w tabeli [EMPLOYES], aby użytkownik mógł wybrać jedną z nich. Warstwa [ui] wykorzysta ścieżkę [2, 3, 4, 5, 6, 7], aby je uzyskać. Operacja [2] stanowi żądanie listy pracowników, a operacja [7] stanowi odpowiedź na to żądanie. Po wykonaniu tych czynności warstwa [ui] może przedstawić użytkownikowi listę pracowników za pośrednictwem operacji [8].
  2. Użytkownik przekaże warstwie [ui] liczbę przepracowanych dni oraz liczbę przepracowanych godzin. Jest to wspomniana powyżej operacja [1]. Na tym etapie użytkownik wchodzi w interakcję wyłącznie z warstwą [ui]. To właśnie ta warstwa sprawdzi w szczególności poprawność wprowadzonych danych. Po zakończeniu tej czynności użytkownik zleci obliczenie wynagrodzenia.
  3. Warstwa [ui] zwróci się do warstwy biznesowej o wykonanie tego obliczenia. W tym celu przekaże jej dane otrzymane od użytkownika. Jest to operacja [2].
  4. Warstwa [metier] potrzebuje pewnych informacji, aby pomyślnie wykonać swoje zadanie:
    • bardziej szczegółowe informacje o osobie (adres, wskaźnik itp.)
    • świadczenia związane z jej wskaźnikiem
    • stawki różnych składek na ubezpieczenie społeczne, które należy potrącić z wynagrodzenia brutto

Warstwa ta zwróci się o te informacje do warstwy [dao], korzystając ze ścieżki [3, 4, 5, 6]. [3] to żądanie początkowe, a [6] to odpowiedź na to żądanie.

  1. Mając wszystkie potrzebne dane, warstwa [metier] oblicza wynagrodzenie osoby wybranej przez użytkownika.
  2. Warstwa [metier] może teraz odpowiedzieć na żądanie warstwy [ui] wysłane w punkcie (d). Jest to ścieżka [7].
  3. Warstwa [ui] sformatuje te wyniki, aby przedstawić je użytkownikowi w odpowiedniej formie, a następnie je wyświetli. Jest to ścieżka [8].
  4. Można sobie wyobrazić, że wyniki te muszą zostać zapisane w pliku lub bazie danych. Można to zrobić automatycznie. W takim przypadku po operacji (f) warstwa [metier] zwróci się do warstwy [dao] o zapisanie wyników. Będzie to ścieżka [3, 4, 5, 6]. Można to również wykonać na żądanie użytkownika. W tym przypadku w cyklu żądanie–odpowiedź zostanie wykorzystana ścieżka [1-8].

Z opisu tego wynika, że dana warstwa korzysta z zasobów warstwy znajdującej się po jej prawej stronie, nigdy z zasobów warstwy po lewej stronie.

Naszą pierwszą implementacją tej architektury trójwarstwowej będzie aplikacja ASP.NET, w której

  • warstwy [dao] i [metier] zostaną zaimplementowane przez DLL
  • warstwa [ui] zostanie zaimplementowana przez formularz internetowy w wersji 1 (patrz punkt 4.2.1).

Zaczynamy od wdrożenia warstwy [dao] przy użyciu frameworka NHibernate.

7.2. Warstwa [dao] zapewniająca dostęp do danych

7.2.1. Projekt Visual Studio C# warstwy [dao]

Projekt Visual Studio warstwy [dao] wygląda następująco:

  • w [1], cały projekt
  • w [2] – poszczególne klasy projektu
  • w [3] – odniesienia projektu.
  • w pliku [4] – folder [lib], w którym zgromadzono pliki DLL niezbędne do realizacji kolejnych projektów

W odniesieniach [3] projektu znajdują się następujące pliki DLL:

  • NHibernate: dla pliku ORM NHibernate
  • MySql.Data: sterownik ADO.NET dla SGBD MySQL
  • Spring.Core: dla frameworka Spring
  • log4net: biblioteka logów
  • nunit.framework: biblioteka testów jednostkowych

Odwołania te zostały pobrane z folderu [lib] [4]. Należy zadbać o to, aby dla wszystkich tych odwołań właściwość „Kopia lokalna” miała wartość „True” [5]:

7.2.2. Elementy warstwy [dao]

   

Elementy (obiekty) niezbędne dla warstwy [dao] zostały zebrane w folderze [entites] projektu. Niektóre z nich są nam już znane: [Cotisations] opisana w paragrafie 6.3.2.1, [Employe] opisana w paragrafie 6.3.2.3, [Indemnites] opisana w paragrafie 6.3.2.2. Wszystkie znajdują się w przestrzeni nazw [Pam.Dao.Entites].

Klasa [Employe] zmienia się w następujący sposób:


namespace Pam.Dao.Entites {
    public class Employe {
        // właściwości automatyczne
        public virtual int Id { get; set; }
        public virtual int Version { get; set; }
        public virtual string SS { get; set; }
        public virtual string Nom { get; set; }
        public virtual string Prenom { get; set; }
        public virtual string Adresse { get; set; }
        public virtual string Ville { get; set; }
        public virtual string CodePostal { get; set; }
        public virtual Indemnites Indemnites { get; set; }

        // konstruktorzy
        public Employe() {
        }

        // ToString
        public override string ToString() {
            return string.Format("[{0},{1},{2},{3},{4},{5},{6}]", SS, Nom, Prenom, Adresse, Ville, CodePostal, Indemnites);
        }
    }
}

7.2.3. Klasa [PamException]

Warstwa [dao] odpowiada za wymianę danych ze źródłem zewnętrznym. Wymiana ta może zakończyć się niepowodzeniem. Na przykład, jeśli informacje są żądane od zdalnej usługi w Internecie, ich uzyskanie nie powiedzie się w przypadku jakiejkolwiek awarii sieci. W przypadku tego typu błędów w Javie standardowo rzuca się wyjątek. Jeśli wyjątek nie jest typu [RunTimeException] lub pochodnego, należy wskazać w sygnaturze metody, że wywołuje ona (throws) wyjątek. W .NET wszystkie wyjątki są niekontrolowane, c.a.d. odpowiadają typowi [RunTimeException] w Javie. Nie ma zatem potrzeby deklarowania, że metody typu [GetAllIdentitesEmployes, GetEmploye, GetCotisations] mogą wywołać wyjątek.

Warto jednak móc rozróżniać poszczególne wyjątki, ponieważ sposób ich obsługi może się różnić. W ten sposób kod obsługujący różne typy wyjątków można zapisać w następujący sposób:

try{
    ... code pouvant générer divers types d'exceptions
}catch (Exception1 ex1){
...on gère un type d'exceptions
}catch (Exception2 ex2){
...on gère un autre type d'exceptions
}finally{
...
}

Tworzymy zatem typ wyjątków dla warstwy [dao] naszej aplikacji. Jest to następujący typ [PamException]:


using System;
namespace Pam.Dao.Entites {

    public class PamException : Exception {

        // kod błędu 
        public int Code { get; set; }

        // konstruktorzy 
        public PamException() {
        }

        public PamException(int Code)
            : base() {
            this.Code = Code;
        }

        public PamException(string message, int Code)
            : base(message) {
            this.Code = Code;
        }

        public PamException(string message, Exception ex, int Code)
            : base(message, ex) {
            this.Code = Code;
        }
    }
}
  • wiersz 2: klasa należy do przestrzeni nazw [Pam.Dao.Entites]
  • wiersz 4: klasa ta wywodzi się z klasy [Exception]
  • wiersz 7: posiada ona publiczną właściwość [Code], która jest kodem błędu
  • w naszej warstwie [dao] będziemy używać dwóch rodzajów konstruktorów:
    • ten z wierszy 18–21, którego można używać w sposób pokazany poniżej:
throw new PamException("Problème d'accès aux données",5);
  • (ciąg dalszy)
    • lub ten z wierszy 23–26, służący do zgłaszania już wystąpionego wyjątku poprzez zamknięcie go w wyjątku typu [PamException]:
try{
....
}catch (IOException ex){
     // wyjątek jest enkapsulowany
    throw new PamException("Problème d'accès aux données",ex,10);
}

Zaletą tej drugiej metody jest to, że nie traci się informacji, które może zawierać pierwszy wyjątek.

7.2.4. Pliki mapowania tabel <--> klas z NHibernate

Wróćmy do architektury aplikacji:

Podczas odczytu framework NHibernate wykorzystuje dane z bazy danych i przekształca je w obiekty, których klasy właśnie przedstawiliśmy. Podczas zapisu działa odwrotnie: na podstawie obiektów tworzy, aktualizuje i usuwa wiersze w tabelach bazy danych. Pliki zapewniające przekształcenie tabele <--> klasy zostały już przedstawione:

   
  • plik [Cotisations.hbm.xml] przedstawiony w paragrafie 6.3.2.1 ustanawia powiązanie między tabelą [COTISATIONS] a klasą [Cotisations]

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="Pam.Dao.Entites" assembly="pam-dao-nhibernate">
    <class name="Cotisations" table="COTISATIONS">
        <id name="Id" column="ID">
            <generator class="native" />
        </id>
        <version name="Version" column="VERSION"/>
        <property name="CsgRds" column="CSGRDS" not-null="true"/>
        <property name="Csgd" column="CSGD" not-null="true"/>
        <property name="Retraite" column="RETRAITE" not-null="true"/>
        <property name="Secu" column="SECU" not-null="true"/>
    </class>
</hibernate-mapping>
  • plik [Employe.hbm.xml] przedstawiony w paragrafie 6.3.2.3 ustanawia powiązanie między tabelą [EMPLOYES] a klasą [Employe]

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="Pam.Dao.Entites" assembly="pam-dao-nhibernate">
    <class name="Employe" table="EMPLOYES">
        <id name="Id" column="ID">
            <generator class="native" />
        </id>
        <version name="Version" column="VERSION"/>
        <property name="SS" column="SS" length="15" not-null="true" unique="true"/>
        <property name="Nom" column="NOM" length="30" not-null="true"/>
        <property name="Prenom" column="PRENOM" length="20" not-null="true"/>
        <property name="Adresse" column="ADRESSE" length="50" not-null="true" />
        <property name="Ville" column="VILLE" length="30" not-null="true"/>
        <property name="CodePostal" column="CP" length="5" not-null="true"/>
        <many-to-one name="Indemnites" column="INDEMNITE_ID" cascade="save-update" lazy="false"/>
    </class>
</hibernate-mapping>
  • plik [Indemnites.hbm.xml] przedstawiony w paragrafie 6.3.2.2 ustanawia powiązanie między tabelą [INDEMNITES] a klasą [Indemnites]

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="Pam.Dao.Entites" assembly="pam-dao-nhibernate">
    <class name="Indemnites" table="INDEMNITES">
        <id name="Id" column="ID">
            <generator class="native" />
        </id>
        <version name="Version" column="VERSION"/>
        <property name="Indice" column="INDICE" not-null="true" unique="true"/>
        <property name="BaseHeure" column="BASE_HEURE" not-null="true"/>
        <property name="EntretienJour" column="ENTRETIEN_JOUR" not-null="true"/>
        <property name="RepasJour" column="REPAS_JOUR" not-null="true" />
        <property name="IndemnitesCp" column="INDEMNITES_CP" not-null="true"/>
    </class>
</hibernate-mapping>

Należy zauważyć, że w tagu <hibernate-mapping> tych plików (wiersz 2) występują następujące atrybuty:

  • namespace : Pam.Dao.Entites. Klasy [Cotisations], [Employe] i [Indemnites] muszą znajdować się w tej przestrzeni nazw.
  • assembly: pam-dao-nhibernate. Pliki mapowania [*.hbm.xml] muszą być zawarte w pliku DLL o nazwie [pam-dao-nhibernate]. Aby uzyskać ten wynik, projekt C# jest skonfigurowany w następujący sposób:
  • w pliku [1], nazwa zestawu projektu to [pam-dao-nhibernate]
  • w [2] pliki mapowania [*.hbm.xml] są dołączone do zestawu projektu jako [3]

7.2.5. Interfejs [IPamDao] warstwy [dao]

Wróćmy do architektury naszej aplikacji:

W prostych przypadkach można zacząć od warstwy [metier], aby odkryć interfejsy aplikacji. Do działania aplikacja potrzebuje danych:

  • już dostępne w plikach, bazach danych lub w sieci. Dane te są dostarczane przez warstwę [dao].
  • które nie są jeszcze dostępne. W takim przypadku są one dostarczane przez warstwę [ui], która pozyskuje je od użytkownika aplikacji.

Jakie interfejsy powinna udostępniać warstwa [dao] warstwie [metier]? Jakie są możliwe interakcje między tymi dwiema warstwami? Warstwa [dao] musi dostarczyć warstwie [metier] następujące dane:

  • listę opiekunek, aby umożliwić użytkownikowi wybranie konkretnej osoby
  • kompletne informacje o wybranej osobie (adres, wskaźnik itp.)
  • świadczenia związane z indeksem danej osoby
  • stawki różnych składek na ubezpieczenie społeczne

Informacje te są bowiem znane przed obliczeniem wynagrodzenia i mogą zatem zostać zapisane. W kierunku [metier] -> [dao] warstwa [metier] może zwrócić się do warstwy [dao] o zapisanie wyniku obliczeń wynagrodzenia. Nie będziemy tego tutaj robić.

Na podstawie tych informacji można by spróbować sformułować wstępną definicję interfejsu warstwy [dao]:


using Pam.Dao.Entites;

namespace Pam.Dao.Service {
    public interface IPamDao {
        // lista wszystkich tożsamości pracowników 
        Employe[] GetAllIdentitesEmployes();
        // konkretny pracownik wraz z jego świadczeniami 
        Employe GetEmploye(string ss);
        // lista wszystkich składek 
        Cotisations GetCotisations();
    }
}
  • wiersz 1: importujemy przestrzeń nazw elementów warstwy [dao].
  • wiersz 3: warstwa [dao] znajduje się w przestrzeni nazw [Pam.Dao.Service]. Elementy przestrzeni nazw [Pam.Dao.Entites] mogą być tworzone w wielu egzemplarzach. Elementy przestrzeni nazw [Pam.Dao.Service] są tworzone w jednym egzemplarzu (singleton). To właśnie uzasadniało wybór nazw przestrzeni nazw.
  • wiersz 4: interfejs nosi nazwę [IPamDao]. Definiuje on trzy metody:
    • wiersz 6: metoda [GetAllIdentitesEmployes] zwraca tablicę obiektów typu [Employe], która przedstawia listę opiekunek do dzieci w uproszczonej formie (nazwisko, imię, SS).
    • w wierszu 8 metoda [GetEmploye] zwraca obiekt typu [Employe]: pracownika o numerze ubezpieczenia społecznego przekazanym jako parametr metody wraz z dodatkami związanymi z jego wskaźnikiem.
    • w wierszu 10, [GetCotisations] zwraca obiekt [Cotisations], który zawiera stawki różnych składek na ubezpieczenie społeczne pobieranych od wynagrodzenia brutto.

7.3. Implementacja i testy warstwy [dao]

7.3.1. Projekt Visual Studio

Projekt w Visual Studio został już przedstawiony. Przypomnijmy:

  • w pliku [1], a cały projekt
  • w [2] – poszczególne klasy projektu. Folder [entites] zawiera encje przetwarzane przez warstwę [dao], a także pliki mapowania NHibernate. Folder [service] zawiera interfejs [IPamDao] oraz jego implementację [PamDaoNHibernate]. Folder [tests] zawiera test konsolowy [Main.cs] oraz test jednostkowy [NUnit.cs].
  • W pliku [3] znajdują się odniesienia do projektu.

7.3.2. Program testowy konsolowy [Main.cs]

Program testowy [Main.cs] jest uruchamiany w następującej architekturze:

Jego zadaniem jest testowanie metod interfejsu [IPamDao]. Podstawowy przykład mógłby wyglądać następująco:


using System;
using Pam.Dao.Entites;
using Pam.Dao.Service;
using Spring.Context.Support;

namespace Pam.Dao.Tests {
    public class MainPamDaoTests {
        public static void Main() {
            try {
                // instancja warstwy [dao]
                IPamDao pamDao = (IPamDao)ContextRegistry.GetContext().GetObject("pamdao");
                // lista identyfikatorów pracowników 
                foreach (Employe Employe in pamDao.GetAllIdentitesEmployes()) {
                    Console.WriteLine(Employe.ToString());
                }
                // pracownik wraz z jego dodatkami 
                Console.WriteLine("------------------------------------");
                Console.WriteLine(pamDao.GetEmploye("254104940426058"));
                Console.WriteLine("------------------------------------");
                // lista składek 
                Cotisations cotisations = pamDao.GetCotisations();
                Console.WriteLine(cotisations.ToString());
            } catch (Exception ex) {
                // wyświetlenie wyjątku 
                Console.WriteLine(ex.ToString());
            }
            //przerwa 
            Console.ReadLine();
        }
    }
}
  • wiersz 11: zwracamy się do Springa o odwołanie do warstwy [dao].
  • wiersze 13–15: test metody [GetAllIdentitesEmployes] interfejsu [IPamDao]
  • wiersz 18: test metody [GetEmploye] interfejsu [IPamDao]
  • wiersz 21: test metody [GetCotisations] interfejsu [IPamDao]

Spring, NHibernate i log4net są skonfigurowane w następującym pliku [App.config] :


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <!-- sekcje konfiguracyjne -->
    <configSections>
        <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
        <sectionGroup name="spring">
            <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
            <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
        </sectionGroup>
        <section name="hibernate-configuration" type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" />
    </configSections>


    <!-- konfiguracja Spring -->
    <spring>
        <context>
            <resource uri="config://spring/objects" />
        </context>
        <objects xmlns="http://www.springframework.net">
            <object id="pamdao" type="Pam.Dao.Service.PamDaoNHibernate, pam-dao-nhibernate" init-method="init" destroy-method="destroy"/>
        </objects>
    </spring>

    <!-- konfiguracja NHibernate -->
    <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
        <session-factory>
            <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
            <property name="connection.driver_class">NHibernate.Driver.MySqlDataDriver</property>
            <property name="dialect">NHibernate.Dialect.MySQLDialect</property>
            <property name="connection.connection_string">
                Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;
            </property>
            <property name="show_sql">false</property>
            <mapping assembly="pam-dao-nhibernate"/>
        </session-factory>
    </hibernate-configuration>

    <!-- Ta sekcja zawiera ustawienia konfiguracyjne log4net -->
    <!-- NOTE IMPORTANTE: logi nie są domyślnie aktywne. Należy je aktywować programowo
    avec l'instruction log4net.Config.XmlConfigurator.Configure();
    ! -->
    <log4net>
    ...
    </log4net>

</configuration>

Konfiguracja pliku NHibernate (wiersz 10, wiersze 25–36) została wyjaśniona w paragrafie 6.3.1. Należy zwrócić uwagę na wiersz 34, który wskazuje, że pliki mapowania znajdują się w zestawie [pam-dao-nhibernate]. Jest to zestaw projektu.

Konfiguracja Springa znajduje się w wierszach 6–9 oraz 15–22. Wiersz 20 definiuje obiekt [pamdao] używany przez program konsolowy [Main.cs]. Tag <object> posiada tutaj następujące atrybuty:

  • type: określa klasę, która ma zostać zainicjowana. To właśnie klasa [PamDaoNHibernate] implementuje interfejs [IPamDao]. Znajduje się ona w pliku DLL [pam-dao-nhibernate] projektu.
  • init-method: metoda klasy [PamDaoNHibernate], która ma zostać wykonana po utworzeniu instancji klasy
  • destroy-method: metoda klasy [PamDaoNHibernate], która ma zostać wykonana, gdy kontener Spring zostanie zniszczony po zakończeniu wykonywania projektu.

Wykonanie z wykorzystaniem bazy danych opisanej w punkcie 6.2 daje następujący wynik na konsoli:

1
2
3
4
5
6
[254104940426058,Jouveinal,Marie,,,,]
[260124402111742,Laverti,Justine,,,,]
------------------------------------
[254104940426058,Jouveinal,Marie,5 rue des oiseaux,St Corentin,49203,[2, 2,1, 2,1, 3,1, 15]]
------------------------------------
[3,49,6,15,9,39,7,88]
  • wiersze 1–2: dwaj pracownicy typu [Employe] z następującymi informacjami: [SS, Nom, Prenom]
  • wiersz 4: pracownik typu [Employe] o numerze ubezpieczenia społecznego [254104940426058]
  • wiersz 5: stawki składek

7.3.3. Zapis klasy [PamDaoNHibernate]

Interfejs [IPamDao] zaimplementowany przez warstwę [dao] wygląda następująco:


using Pam.Dao.Entites;

namespace Pam.Dao.Service {
    public interface IPamDao {
        // lista wszystkich tożsamości pracowników 
        Employe[] GetAllIdentitesEmployes();
        // konkretny pracownik wraz z jego dodatkami 
        Employe GetEmploye(string ss);
        // lista wszystkich składek 
        Cotisations GetCotisations();
    }
}

Zadanie: napisz kod klasy [PamDaoNHibernate] implementującej powyższy interfejs [IPamDao] przy użyciu frameworka NHibernate skonfigurowanego zgodnie z wcześniejszym opisem. Zaimplementujemy również metody init i destroy wykonywane przez Spring. Metoda init utworzy klasę SessionFactory, z której będziemy pobierać obiekty Session. Metoda destroy zamknie ten obiekt SessionFactory. Skorzystamy z przykładów z paragrafu 6.5.


Ograniczenia:

Zakładamy, że niektóre dane żądane z warstwy [dao] mogą w całości zmieścić się w pamięci. W związku z tym, w celu poprawy wydajności, klasa [PamDaoNHibernate] będzie przechowywać:

  • tabelę [EMPLOYES] w postaci (SS, NOM, PRENOM) wymaganą przez metodę [GetAllIdentitesEmployes] w postaci tablicy obiektów typu [Employe]
  • tabeli [COTISATIONS] w postaci pojedynczego obiektu typu [Cotisations]

Zostanie to zrealizowane w metodzie [init] tej klasy. Szkielet klasy [PamDaoNHibernate] mógłby wyglądać następująco:


using System;
...

namespace Pam.Dao.Service {
    class PamDaoNHibernate : IPamDao {
        // pola prywatne 
        private Cotisations cotisations;
        private Employe[] employes;
        private ISessionFactory sessionFactory = null;

        // inicjalizacja 
        public void init() {
            try {
                // inicjalizacja fabryczna
                sessionFactory = new Configuration().Configure().BuildSessionFactory();
                // pobieramy stawki składek i dane pracowników w celu zapisania ich w pamięci podręcznej 
.......................
        }

        // zamknięcie SessionFactory
        public void destroy() {
            if (sessionFactory != null) {
                sessionFactory.Close();
            }
        }

        // lista wszystkich tożsamości pracowników 
        public Employe[] GetAllIdentitesEmployes() {
            return employes;
        }

        // konkretny pracownik wraz z jego świadczeniami 
        public Employe GetEmploye(string ss) {
................................
        }

        // wykaz składek 
        public Cotisations GetCotisations() {
            return cotisations;
        }
    }
}

7.3.4. Testy jednostkowe z wykorzystaniem NUnit


Lektury zalecane: „Język C# 2008, Rozdział 4: Architektury trójwarstwowe, testy NUnit, framework Spring”.


Poprzedni test miał charakter wizualny: na ekranie sprawdzano, czy rzeczywiście uzyskano oczekiwane wyniki. W środowisku zawodowym jest to metoda niewystarczająca. Testy powinny być zawsze w jak największym stopniu zautomatyzowane i dążyć do całkowitego wyeliminowania konieczności interwencji człowieka. Człowiek jest bowiem podatny na zmęczenie, a jego zdolność do weryfikacji testów słabnie w miarę upływu dnia. Narzędzie [NUnit] pomaga w realizacji tej automatyzacji. Jest ono dostępne na URL [http://www.nunit.org/].

Projekt Visual Studio warstwy [dao] będzie ewoluował w następujący sposób:

  • do [1], program testowy [NUnit.cs]
  • w [2,3], projekt wygeneruje plik o nazwie DLL, nazwany [pam-dao-nhibernate.dll]
  • w [4], odwołanie do DLL w frameworku NUnit: [nunit.framework.dll]
  • w [5], klasa [Main.cs] nie zostanie uwzględniona w DLL [pam-dao-nhibernate]
  • w [6] klasa [NUnit.cs] zostanie uwzględniona w DLL [pam-dao-nhibernate]

Klasa testowa NUnit jest następująca:


using System.Collections;
using NUnit.Framework;
using Pam.Dao.Service;
using Pam.Dao.Entites;
using Spring.Objects.Factory.Xml;
using Spring.Core.IO;
using Spring.Context.Support;

namespace Pam.Dao.Tests {

    [TestFixture]
    public class NunitPamDao : AssertionHelper {
        // warstwa [dao] do przetestowania 
        private IPamDao pamDao = null;

        // producent 
        public NunitPamDao() {
            // instancja warstwy [dao]
            pamDao = (IPamDao)ContextRegistry.GetContext().GetObject("pamdao");
        }

        // inicjalizacja 
        [SetUp]
        public void Init() {

        }

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

        [Test]
        public void GetCotisations() {
            // weryfikacja stawek składek 
            Cotisations cotisations = pamDao.GetCotisations();
            Expect(3.49, EqualTo(cotisations.CsgRds).Within(1E-06));
            Expect(6.15, EqualTo(cotisations.Csgd).Within(1E-06));
            Expect(9.39, EqualTo(cotisations.Secu).Within(1E-06));
            Expect(7.88, EqualTo(cotisations.Retraite).Within(1E-06));
        }

        [Test]
        public void GetEmployeIdemnites() {
            // weryfikacja osób fizycznych 
            Employe employe1 = pamDao.GetEmploye("254104940426058");
            Employe employe2 = pamDao.GetEmploye("260124402111742");
            Expect("Jouveinal", EqualTo(employe1.Nom));
            Expect(2.1, EqualTo(employe1.Indemnites.BaseHeure).Within(1E-06));
            Expect("Laverti", EqualTo(employe2.Nom));
            Expect(1.93, EqualTo(employe2.Indemnites.BaseHeure).Within(1E-06));
        }

        [Test]
        public void GetEmployeIdemnites2() {
            // weryfikacja nieistniejącej osoby 
            bool erreur = false;
            try {
                Employe employe1 = pamDao.GetEmploye("xx");
            } catch {
                erreur = true;
            }
            Expect(erreur, True);
        }
    }
}
  • wiersz 11: klasa posiada atrybut [TestFixture], co czyni ją klasą testową [NUnit].
  • wiersz 12: klasa ta wywodzi się z klasy pomocniczej AssertionHelper z frameworka NUnit (od wersji 2.4.6).
  • wiersz 14: pole prywatne [pamDao] jest instancją interfejsu dostępu do warstwy [dao]. Należy zauważyć, że typem tego pola jest interfejs, a nie klasa. Oznacza to, że instancja [pamDao] udostępnia wyłącznie metody należące do interfejsu [IPamDao].
  • Metody testowane w tej klasie to te, które mają atrybut [Test]. W przypadku wszystkich tych metod proces testowania przebiega następująco:
    • najpierw uruchamiana jest metoda o atrybucie [SetUp]. Służy ona do przygotowania zasobów (połączeń sieciowych, połączeń z bazami danych itp.) niezbędnych do przeprowadzenia testu.
    • Następnie uruchamiana jest metoda, która ma zostać przetestowana
    • i na koniec wykonywana jest metoda o atrybucie [TearDown]. Służy ona zazwyczaj do zwolnienia zasobów wykorzystanych przez metodę o atrybucie [SetUp].
  • W naszym teście nie ma zasobów, które należałoby przydzielać przed każdym testem, a następnie zwalniać. Dlatego nie potrzebujemy metod z atrybutami [SetUp] i [TearDown]. W przykładzie przedstawiliśmy w wierszach 23–26 metodę z atrybutem [SetUp].
  • wiersze 17–20: konstruktor klasy inicjuje pole prywatne [pamDao] przy użyciu Springa i [App.config].
  • wiersze 29–32: testują metodę [GetAllIdentitesEmployes]
  • wiersze 35–42: testują metodę [GetCotisations]
  • wiersze 45–53: testują metodę [GetEmploye]
  • wiersze 56–65: testują metodę [GetEmploye] w przypadku wystąpienia wyjątku.

Podczas generowania projektu tworzone są pliki DLL i [pam-dao-nhibernate.dll] w folderze [bin/Release].

Folder [bin/Release] zawiera ponadto:

  • pliki DLL, które stanowią część odniesień projektu i mają atrybut [Copie locale] ustawiony na wartość „prawda”: [Spring.Core, MySql.data, NHibernate, log4net]. Do tych plików DLL dołączone są kopie plików DLL, z których one same korzystają:
    • [CastleDynamicProxy, Iesi.Collections] dla narzędzia NHibernate
    • [antlr.runtime, Common.Logging] dla narzędzia Spring
  • plik [pam-dao-nhibernate.dll.config] jest kopią pliku konfiguracyjnego [App.config]. To właśnie plik VS dokonuje tego powielania. Podczas wykonywania wykorzystywany jest plik [pam-dao-nhibernate.dll.config], a nie [App.config].

Wczytujemy pliki DLL i [pam-dao-nhibernate.dll] za pomocą narzędzia [NUnit-Gui] w wersji 2.4.6 i przeprowadzamy testy:

Image

Powyższe testy zakończyły się powodzeniem.

Zadanie praktyczne:


  • zrealizować na komputerze testy klasy [PamDaoNHibernate].
  • należy użyć różnych plików konfiguracyjnych [App.config], aby korzystać z różnych serwerów SGBD (Firebird, MySQL, Postgres, SQL)

7.3.5. Generacj a warstwy DLL z warstwy [dao]

Po napisaniu i przetestowaniu klasy [PamDaoNHibernate] wygenerujemy klasę DLL z warstwy [dao] w następujący sposób:

  • [1], programy testowe są wykluczone z kompilacji projektu
  • [2,3], konfiguracja projektu
  • [4], generowanie projektu
  • plik DLL jest generowany w folderze [bin/Release] [5]. Dodajemy go do plików DLL już znajdujących się w folderze [lib] [6]:

7.4. Warstwa biznesowa

Wróćmy do ogólnej architektury aplikacji [SimuPaie]:

Zakładamy teraz, że warstwa [dao] została już wdrożona i została enkapsulowana w warstwie DLL [pam-dao-nhibernate.dll]. Teraz skupiamy się na warstwie [metier]. To właśnie ona implementuje reguły biznesowe, w tym przypadku reguły obliczania wynagrodzenia.

7.4.1. Projekt Visual Studio dla warstwy [metier]

Projekt Visual Studio warstwy biznesowej mógłby wyglądać następująco:

  • w [1] cały projekt skonfigurowany przez plik [App.config]
  • w [2] warstwa [metier] składa się z dwóch folderów [entites, service]. Folder [tests] zawiera program testowy konsolowy (Main.cs) oraz program testowy NUnit (NUnit.cs).
  • W pliku [3] znajdują się odniesienia wykorzystywane przez projekt. Należy zwrócić uwagę na pliki DLL i [pam-dao-nhibernate] z warstwy [dao], którą analizowaliśmy wcześniej.

7.4.2. Interfejs [IPamMetier] warstwy [metier]

Wróćmy do ogólnej architektury aplikacji:

Jaki interfejs powinna zapewnić warstwa [metier] warstwie [ui]? Jakie są możliwe interakcje między tymi dwiema warstwami? Przypomnijmy sobie interfejs internetowy, który zostanie zaprezentowany użytkownikowi:

  1. przy pierwszym wyświetleniu formularza w warstwie [1] powinna znajdować się lista pracowników. Wystarczy uproszczona lista (nazwisko, imię, SS). Numer SS jest niezbędny, aby uzyskać dostęp do dodatkowych informacji o wybranym pracowniku (pozycje 6–11).
  2. Informacje od 12 do 15 dotyczą różnych stawek składek.
  3. Informacje od 16 do 19 dotyczą dodatków związanych z indeksem pracownika
  4. Informacje od 20 do 24 to składniki wynagrodzenia obliczone na podstawie danych wprowadzonych przez użytkownika w polach od 1 do 3.

Interfejs [IPamMetier] udostępniany warstwie [ui] przez warstwę [metier] musi spełniać powyższe wymagania. Istnieje wiele możliwych interfejsów. Proponujemy następujący:


using Pam.Dao.Entites;
using Pam.Metier.Entites;

namespace Pam.Metier.Service {
    public interface IPamMetier {
        // lista wszystkich tożsamości pracowników 
        Employe[] GetAllIdentitesEmployes();

        // ------- obliczanie wynagrodzenia 
        FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés);
    }
}
  • wiersz 7: metoda umożliwiająca wypełnienie listy rozwijanej [1]
  • wiersz 10: metoda umożliwiająca uzyskanie informacji od 6 do 24. Zostały one zebrane w obiekcie typu [FeuilleSalaire].

7.4.3. Elementy warstwy [metier]

Folder [entites] projektu Visual Studio zawiera obiekty obsługiwane przez klasę biznesową: [FeuilleSalaire] i [ElementsSalaire].

Klasa [FeuilleSalaire] zawiera informacje od 6 do 24 z poprzedniego formularza:


using Pam.Dao.Entites;

namespace Pam.Metier.Entites {

    public class FeuilleSalaire {

        // właściwości automatyczne 
        public Employe Employe { get; set; }
        public Cotisations Cotisations { get; set; }
        public ElementsSalaire ElementsSalaire { get; set; }

        // ToString 
        public override string ToString() {
            return string.Format("[{0},{1},{2}", Employe, Cotisations, ElementsSalaire);
        }
    }
}
  • wiersz 8: informacje od 6 do 11 dotyczące pracownika, dla którego obliczane jest wynagrodzenie, oraz informacje od 16 do 19 dotyczące jego dodatków. Należy pamiętać, że obiekt [Employe] zawiera obiekt [Indemnites] reprezentujący dodatki pracownika.
  • wiersz 9: informacje od 12 do 15
  • wiersz 10: informacje od 20 do 24
  • wiersze 13–15: metoda [ToString]

Klasa [ElementsSalaire] zawiera informacje z pozycji 20–24 formularza:


namespace Pam.Metier.Entites {
    public class ElementsSalaire {
        // właściwości automatyczne 
        public double SalaireBase { get; set; }
        public double CotisationsSociales { get; set; }
        public double IndemnitesEntretien { get; set; }
        public double IndemnitesRepas { get; set; }
        public double SalaireNet { get; set; }


        // ToString 
        public override string ToString() {
            return string.Format("[{0} : {1} : {2} : {3} : {4} ]", SalaireBase, CotisationsSociales, IndemnitesEntretien, IndemnitesRepas, SalaireNet);
        }
    }
}
  • wiersze 4–8: składniki wynagrodzenia zgodnie z zasadami biznesowymi opisanymi w punkcie 3.2.
  • wiersz 4: wynagrodzenie podstawowe pracownika, zależne od liczby przepracowanych godzin
  • wiersz 5: składki potrącane z tego wynagrodzenia podstawowego
  • wiersze 6 i 7: dodatki do wynagrodzenia podstawowego, zależne od indeksu pracownika i liczby przepracowanych dni
  • wiersz 8: wynagrodzenie netto do wypłaty
  • wiersze 12–15: metoda [ToString] klasy.

7.4.4. Implementacja warstwy [metier]

Zaimplementujemy interfejs [IPamMetier] za pomocą dwóch klas:

  • [AbstractBasePamMetier], która jest klasą abstrakcyjną, w której zaimplementujemy dostęp do danych z interfejsu [IPamMetier]. Klasa ta będzie odwoływać się do warstwy [dao].
  • [PamMetier] – klasa wywodząca się z [AbstractBasePamMetier], która z kolei będzie implementować reguły biznesowe interfejsu [IPamMetier]. Nie będzie ona miała świadomości istnienia warstwy [dao].

Klasa [AbstractBasePamMetier] będzie wyglądać następująco:


using Pam.Dao.Entites;
using Pam.Dao.Service;
using Pam.Metier.Entites;

namespace Pam.Metier.Service {
    public abstract class AbstractBasePamMetier : IPamMetier {

        // obiekt dostępu do danych 
        public IPamDao PamDao { get; set; }

        // lista wszystkich tożsamości pracowników 
        public Employe[] GetAllIdentitesEmployes() {
            return PamDao.GetAllIdentitesEmployes();
        }

        // konkretny pracownik wraz z jego dodatkami 
        protected Employe GetEmploye(string ss) {
            return PamDao.GetEmploye(ss);
        }

        // składki 
        protected Cotisations GetCotisations() {
            return PamDao.GetCotisations();
        }

        // obliczenie wynagrodzenia 
        public abstract FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés);
    }
}
  • wiersz 5: klasa należy do przestrzeni nazw [Pam.Metier.Service], podobnie jak wszystkie klasy i interfejsy warstwy [metier].
  • wiersz 6: klasa jest abstrakcyjna (atrybut abstract) i implementuje interfejs [IPamMetier]
  • wiersz 9: klasa posiada odwołanie do warstwy [dao] w postaci właściwości publicznej
  • wiersze 12–14: implementacja metody [GetAllIdentitesEmployes] z interfejsu [IPamMetier] – wykorzystuje metodę o tej samej nazwie z warstwy [dao]
  • wiersze 17–19: metoda wewnętrzna (chroniona) [GetEmploye], która wywołuje metodę o tej samej nazwie z warstwy [dao] – zadeklarowana jako protected, aby klasy pochodne miały do niej dostęp, mimo że nie jest ona publiczna.
  • wiersze 22–24: metoda wewnętrzna (chroniona) [GetCotisations], która wywołuje metodę o tej samej nazwie z warstwy [dao]
  • wiersz 27: abstrakcyjna implementacja (atrybut abstract) metody [GetSalaire] z interfejsu [IPamMetier].

Obliczanie wynagrodzenia jest realizowane przez następującą klasę [PamMetier]:


using System;
using Pam.Dao.Entites;
using Pam.Metier.Entites;

namespace Pam.Metier.Service {

    public class PamMetier : AbstractBasePamMetier {

        // obliczenie wynagrodzenia 
        public override FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés) {
            // SS: numer identyfikacyjny pracownika SS 
            // HeuresTravaillées: liczba przepracowanych godzin 
            // Dni przepracowane: liczba przepracowanych dni 
            // pobieramy dane pracownika wraz z jego dodatkami 
            ...
            // pobieramy różne stawki składek 
            ...
            // obliczamy składniki wynagrodzenia 
            ...
            // generowanie listy płac 
            return ...;
        }
    }
}
  • wiersz 7: klasa ta wywodzi się z klasy [AbstractBasePamMetier], a zatem implementuje interfejs [IPamMetier]
  • wiersz 10: metoda [GetSalaire], którą należy zaimplementować

Zadanie: napisz kod metody [GetSalaire].


7.4.5. Test konsolowy warstwy [metier]

Przypomnijmy projekt Visual Studio warstwy [metier]:

Powyższy program testowy [Main] testuje metody interfejsu [IPamMetier]. Podstawowy przykład mógłby wyglądać następująco:


using System;
using Pam.Dao.Entites;
using Pam.Metier.Service;
using Spring.Context.Support;

namespace Pam.Metier.Tests {
    class MainPamMetierTests {
        public static void Main() {
            try {
                // instancjonowanie warstwy [metier]
                IPamMetier pamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
                // obliczenia list płac 
                Console.WriteLine(pamMetier.GetSalaire("260124402111742", 30, 5));
                Console.WriteLine(pamMetier.GetSalaire("254104940426058", 150, 20));
                try {
                    Console.WriteLine(pamMetier.GetSalaire("xx", 150, 20));
                } catch (PamException ex) {
                    Console.WriteLine(string.Format("PamException : {0}", ex.Message));
                }
            } catch (Exception ex) {
                Console.WriteLine(string.Format("Exception : {0}", ex.ToString()));
            }
            // przerwa 
            Console.ReadLine();
        }
    }
}
  • wiersz 11: instancjonowanie warstwy [metier] przez Spring.
  • wiersze 13–14: testy metody [GetSalaire] interfejsu [IPamMetier]
  • wiersze 15–22: test metody [GetSalaire] w przypadku wystąpienia wyjątku

Program testowy korzysta z pliku konfiguracyjnego [App.config] o następującej treści:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <!-- sekcje konfiguracyjne -->
    <configSections>
        <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
        <sectionGroup name="spring">
            <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
            <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
        </sectionGroup>
        <section name="hibernate-configuration" type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" />
    </configSections>


    <!-- konfiguracja Spring -->
    <spring>
        <context>
            <resource uri="config://spring/objects" />
        </context>
        <objects xmlns="http://www.springframework.net">
            <object id="pamdao" type="Pam.Dao.Service.PamDaoNHibernate, pam-dao-nhibernate" init-method="init" destroy-method="destroy"/>
            <object id="pammetier" type="Pam.Metier.Service.PamMetier, pam-metier-dao-nhibernate" >
                <property name="PamDao" ref="pamdao"/>
            </object>
        </objects>
    </spring>

    <!-- konfiguracja NHibernate -->
    <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
....
    </hibernate-configuration>

    <!-- Ta sekcja zawiera ustawienia konfiguracyjne log4net -->
    <!-- NOTE IMPORTANTE: logi nie są domyślnie aktywne. Należy je aktywować programowo
    avec l'instruction log4net.Config.XmlConfigurator.Configure();
    ! -->
    <log4net>
...
    </log4net>

</configuration>

Plik ten jest identyczny z plikiem [App.config] używanym w projekcie warstwy [dao] (patrz punkt 7.3.2), z wyjątkiem następujących szczegółów:

  • wiersz 20: obiekt o identyfikatorze „pamdao” ma typ [Pam.Dao.Service.PamDaoNHibernate] i znajduje się w zestawie [pam-dao-nhibernate]. Warstwa [dao] to ta, którą analizowaliśmy wcześniej.
  • wiersze 21–23: obiekt o identyfikatorze „pammetier” ma typ [Pam.Metier.Service.PamMetier] i znajduje się w zestawie [pam-metier-dao-nhibernate]. Należy skonfigurować projekt w następujący sposób:
 
  • wiersz 22: obiekt [PamMetier], którego instancję utworzył Spring, posiada publiczną właściwość [PamDao], która jest odwołaniem do warstwy [dao]. Właściwość ta jest inicjowana odwołaniem do warstwy [dao] utworzonej w wierszu 20.

Wykonanie z wykorzystaniem bazy danych opisanej w paragrafie 6.2 daje następujący wynik w konsoli:

1
2
3
[[260124402111742,Laverti,Justine,La Brûlerie,St Marcel,49014,[1, 1,93, 2, 3, 12]],[3,49,6,15,9,39,7,88],[1, 1,93, 2, 3, 12],[64,85 : 17,45 : 10 : 15 : 72,4 ]
[[254104940426058,Jouveinal,Marie,5 rue des oiseaux,St Corentin,49203,[2, 2,1, 2,1, 3,1, 15]],[3,49,6,15,9,39,7,88],[2, 2,1, 2,1, 3,1, 15],[362,25 : 97,48 : 42: 62 : 368,77 ]
PamException : L'employé de n° ss [xx] n'existe pas
  • wiersze 1–2: 2 żądane arkusze płacowe
  • wiersz 3: wyjątek typu [PamException] spowodowany przez nieistniejącego pracownika.

7.4.6. Testy jednostkowe warstwy biznesowej

Poprzedni test miał charakter wizualny: na ekranie sprawdzaliśmy, czy rzeczywiście uzyskujemy oczekiwane wyniki. Przechodzimy teraz do testów niewizualnych NUnit.

Wróćmy do projektu Visual Studio o nazwie [metier]:

  • w [1], program testowy NUnit
  • na [2], odwołanie na DLL [nunit.framework]
  • w [3,4], generowanie projektu spowoduje utworzenie plików DLL i [pam-metier-dao-nhibernate.dll].
  • W przypadku pliku [5] plik [NUnit.cs] zostanie dołączony do zestawu [pam-metier-dao-nhibernate.dll], ale nie do [Main.cs] ani [6]

Klasa testowa NUnit wygląda następująco:


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

    }
}
  • wiersz 13: pole prywatne [pamMetier] jest instancją interfejsu dostępu do warstwy [metier]. Należy zauważyć, że typem tego pola jest interfejs, a nie klasa. Oznacza to, że instancja [PamMetier] udostępnia wyłącznie metody należące do interfejsu [IPamMetier].
  • wiersze 16–19: konstruktor klasy inicjuje pole prywatne [pamMetier] przy użyciu Springa i pliku konfiguracyjnego [App.config].
  • wiersze 23–26: testują metodę [GetAllIdentitesEmployes]
  • wiersze 29–42: testują metodę [GetSalaire]

Powyższy projekt generuje pliki DLL i [pam-metier.dll] w folderze [bin/Release].

Folder [bin/Release] zawiera ponadto:

  • pliki DLL, które stanowią część odniesień projektu i mają atrybut [Copie locale] ustawiony na wartość „prawda”: [Spring.Core, MySql.data, NHibernate, log4net, pam-dao-nhibernate]. Do tych plików DLL dołączone są kopie plików DLL, z których one same korzystają:
    • [CastleDynamicProxy, Iesi.Collections] dla narzędzia NHibernate
    • [antlr.runtime, Common.Logging] dla narzędzia Spring
  • plik [pam-metier-dao-nhibernate.dll.config] jest kopią pliku konfiguracyjnego [App.config].

Wczytujemy pliki DLL i [pam-metier-dao-nhibernate.dll] za pomocą narzędzia [NUnit-Gui, version 2.4.6] i uruchamiamy testy:

Image

Powyższe testy zakończyły się powodzeniem.

Zadanie praktyczne:


  • zrealizować na komputerze testy klasy [PamMetier].
  • użyj różnych plików konfiguracyjnych App.config, aby korzystać z różnych serwerów SGBD (Firebird, MySQL, Postgres, SQL Server)

7.4.7. Generowanie pliku DLL z warstwy [metier]

Po napisaniu i przetestowaniu klasy [PamMetier] wygenerujemy klasy DLL i [pam-metier-dao-nhibernate.dll] z warstwy [metier], postępując zgodnie z metodą opisaną w paragrafie 7.3.5 Należy zadbać o to, aby nie dołączać do pliku DLL programów testowych [Main.cs] i [NUnit.cs]. Następnie należy umieścić ją w folderze [lib] w ramach DLL i [1].

7.5. Warstwa [web]

Wróćmy do ogólnej architektury aplikacji [SimuPaie]:

Zakładamy, że warstwy [dao] i [métier] zostały już zrealizowane i są zawarte w warstwach DLL oraz [pam-dao-nhibernate, pam-metier-dao-nhibernate.dll]. Teraz opiszemy warstwę internetową.

7.5.1. Projekt Visual Web Developer warstwy [web]

  • w pliku [1], a cały projekt:
    • [Global.asax]: klasa instancjonowana podczas uruchamiania aplikacji internetowej, która zapewnia jej inicjalizację
    • [Default.aspx]: strona formularza internetowego
  • w [2], pliki DLL niezbędne dla aplikacji internetowej. Należy zwrócić uwagę na elementy DLL warstw [dao] i [metier] utworzonych wcześniej.

7.5.2. Konfiguracja aplikacji

Plik [Web.config], który konfiguruje aplikację, definiuje te same dane, co plik [App.config] konfigurujący warstwę [metier] omówioną wcześniej. Dane te należy umieścić w wstępnie wygenerowanym kodzie pliku [Web.config]:


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

<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>
    <sectionGroup name="spring">
      <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
      <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
    </sectionGroup>
    <section name="hibernate-configuration" type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" />
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
  </configSections>

  <!-- konfiguracja Spring -->
  <spring>
    <context>
      <resource uri="config://spring/objects" />
    </context>
    <objects xmlns="http://www.springframework.net">
      <object id="pamdao" type="Pam.Dao.Service.PamDaoNHibernate, pam-dao-nhibernate" init-method="init" destroy-method="destroy"/>
      <object id="pammetier" type="Pam.Metier.Service.PamMetier, pam-metier-dao-nhibernate" >
        <property name="PamDao" ref="pamdao"/>
      </object>
    </objects>
  </spring>

  <!-- konfiguracja NHibernate -->
  <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
    <session-factory>
      <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
      <!--
            <property name="connection.driver_class">NHibernate.Driver.MySqlDataDriver</property>
            -->
      <property name="dialect">NHibernate.Dialect.MySQLDialect</property>
      <property name="connection.connection_string">
        Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;
      </property>
      <property name="show_sql">false</property>
      <mapping assembly="pam-dao-nhibernate"/>
    </session-factory>
  </hibernate-configuration>

  <!-- Ta sekcja zawiera ustawienia konfiguracyjne log4net -->
  <!-- NOTE IMPORTANTE: logi nie są domyślnie aktywne. Należy je aktywować programowo
    avec l'instruction log4net.Config.XmlConfigurator.Configure();
    ! -->
  <log4net>
....
  </log4net>

  <appSettings/>
  <connectionStrings/>

  <system.web>
....
....

</configuration>

W wierszach 9–12, 18–28 i 31–44 znajduje się konfiguracja Spring i NHibernate opisana w pliku [App.config] warstwy [metier] (patrz punkt 7.4.5).

Global.asax.cs


using System;
using Pam.Dao.Entites;
using Pam.Metier.Service;
using Spring.Context.Support;

namespace pam_v3
{
  public class Global : System.Web.HttpApplication
  {
    // --- statyczne dane aplikacji ---
    public static Employe[] Employes;
    public static IPamMetier PamMetier = null;
    public static string Msg;
    public static bool Erreur = false;

    // uruchomienie aplikacji
    public void Application_Start(object sender, EventArgs e)
    {
      // przetwarzanie pliku konfiguracyjnego
      try
      {
        // utworzenie instancji warstwy [metier]
        PamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
        // uproszczona lista pracowników
        Employes = PamMetier.GetAllIdentitesEmployes();
        // operacja zakończyła się powodzeniem
        Msg = "Base chargée...";
      }
      catch (Exception ex)
      {
        // odnotowano błąd
        Msg = string.Format("L'erreur suivante s'est produite lors de l'accès à la base de données : {0}", ex);
        Erreur = true;
      }
    }
  }
}

Przypominamy, że:

  • klasa [Global.asax.cs] jest instancjonowana podczas uruchomienia aplikacji, a ta instancja jest dostępna dla wszystkich zapytań wszystkich użytkowników. Pola statyczne w wierszach 11–14 są zatem współdzielone przez wszystkich użytkowników.
  • metoda [Application_Start] jest wykonywana tylko raz po utworzeniu instancji klasy. Jest to metoda, w której zazwyczaj przeprowadzana jest inicjalizacja aplikacji.

Dane współdzielone przez wszystkich użytkowników to:

  • wiersz 11: tablica obiektów typu [Employe], która będzie przechowywać uproszczoną listę (SS, NOM, PRENOM) wszystkich pracowników
  • wiersz 12: odwołanie do warstwy [metier] zawartej w DLL [pam-metier-dao-nhibernate.dll]
  • wiersz 13: komunikat wskazujący, jak zakończyła się inicjalizacja (pomyślnie lub z błędem)
  • wiersz 14: wartość logiczna wskazująca, czy inicjalizacja zakończyła się błędem, czy nie.

W pliku [Application_Start]:

  • w wierszu 23: Spring tworzy instancje warstw [metier] i [dao] oraz zwraca odwołanie do warstwy [metier]. Odwołanie to jest zapisywane w polu statycznym [PamMetier] w wierszu 12.
  • wiersz 25: tablica pracowników jest pobierana z warstwy [metier]
  • wiersz 27: komunikat w przypadku powodzenia
  • wiersz 32: komunikat w przypadku błędu

7.5.3. Formularz [Default.aspx]

Formularz pochodzi z wersji 2.

Image


Pytanie: Korzystając z kodu C# ze strony [Default.aspx.cs] z wersji 2, napisz kod [Default.aspx.cs] z wersji 3. Jedyna różnica dotyczy obliczania wynagrodzenia. Podczas gdy w wersji 2 do pobierania informacji z bazy danych używano metod API i ADO.NET, tutaj zastosujemy metodę GetSalaire z warstwy [metier].


Zadanie praktyczne:


  • zainstalować na komputerze poprzednią aplikację internetową
  • wykorzystać różne pliki konfiguracyjne [Web.config] w celu korzystania z różnych baz danych (Firebird, MySQL, Postgres, SQL Server)