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:
- klient wysyła żądanie do aplikacji.
- 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.
- 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:
![]() |
- 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
- 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].
- 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.
- 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].
- 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.
- Mając wszystkie potrzebne dane, warstwa [metier] oblicza wynagrodzenie osoby wybranej przez użytkownika.
- Warstwa [metier] może teraz odpowiedzieć na żądanie warstwy [ui] wysłane w punkcie (d). Jest to ścieżka [7].
- Warstwa [ui] sformatuje te wyniki, aby przedstawić je użytkownikowi w odpowiedniej formie, a następnie je wyświetli. Jest to ścieżka [8].
- 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:
- (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:
- 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:

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:
![]() |
- 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).
- Informacje od 12 do 15 dotyczą różnych stawek składek.
- Informacje od 16 do 19 dotyczą dodatków związanych z indeksem pracownika
- 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:
- 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:

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.

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)





























