7. Die Anwendung [SimuPaie] – Version 3 – 3-Schichten-Architektur mit NHibernate
Empfohlene Lektüre: „C# 2008, Kapitel 4: 3-Schichten-Architekturen, Tests NUnit, Spring-Framework“.
7.1. Allgemeine Architektur der Anwendung
Die Anwendung [SimuPaie] wird nun die folgende dreischichtige Struktur aufweisen:
![]() |
- Die Schicht [1-dao] (DAO = Data Access Object) ist für den Datenzugriff zuständig.
- Die Schicht [2-métier] ist für den fachlichen Aspekt der Anwendung, die Lohn- und Gehaltsabrechnung, zuständig.
- Die Schicht [3-ui] (ui = User Interface) ist für die Darstellung der Daten gegenüber dem Benutzer und die Ausführung seiner Anfragen zuständig. Die Gesamtheit der Module, die diese Funktion gewährleisten, bezeichnen wir als [Application]. Sie ist der Ansprechpartner für den Benutzer.
- Die drei Schichten werden durch die Verwendung von Schnittstellen unabhängig voneinander gestaltet.NET
- Die Integration der verschiedenen Schichten erfolgt über Spring IoC
Die Bearbeitung einer Kundenanfrage erfolgt in folgenden Schritten:
- Der Kunde sendet eine Anfrage an die Anwendung.
- Die Anwendung verarbeitet diese Anfrage. Dazu benötigt sie möglicherweise die Unterstützung der Schicht [métier], die ihrerseits die Schicht [dao] benötigen kann, falls Daten mit der Datenbank ausgetauscht werden müssen.
- Die Anwendung erhält eine Antwort von der Schicht [métier]. Entsprechend dieser Antwort sendet sie die entsprechende Ansicht (= die Antwort) an den Kunden.
Nehmen wir als Beispiel die Berechnung des Gehalts einer Tagesmutter. Diese erfordert mehrere Schritte:
![]() |
- Die Schicht [ui] muss den Benutzer fragen
- die Identität der Person, für die das Gehalt berechnet werden soll
- die Anzahl der von ihr gearbeiteten Tage
- die Anzahl der gearbeiteten Stunden
- Dazu muss sie dem Benutzer die Liste der Personen (Nachname, Vorname, SS) aus der Tabelle [EMPLOYES] anzeigen, damit der Benutzer eine davon auswählen kann. Die Schicht [ui] nutzt den Pfad [2, 3, 4, 5, 6, 7], um diese Daten abzurufen. Die Operation [2] ist die Anfrage nach der Mitarbeiterliste, die Operation [7] die Antwort auf diese Anfrage. Anschließend kann die Schicht [ui] dem Benutzer die Mitarbeiterliste über [8] anzeigen.
- Der Benutzer übermittelt der Schicht [ui] die Anzahl der Arbeitstage sowie die Anzahl der geleisteten Arbeitsstunden. Dies entspricht dem oben genannten Vorgang [1]. In diesem Schritt interagiert der Benutzer ausschließlich mit der Schicht [ui]. Diese überprüft insbesondere die Gültigkeit der eingegebenen Daten. Anschließend fordert der Benutzer die Berechnung der Lohnabrechnung an.
- Die Schicht [ui] fordert die Fachschicht auf, diese Berechnung durchzuführen. Dazu übermittelt sie ihr die Daten, die sie vom Benutzer erhalten hat. Dies ist der Vorgang [2].
- Die Schicht [metier] benötigt bestimmte Informationen, um ihre Aufgabe erfolgreich auszuführen:
- ausführlichere Informationen zur Person (Adresse, Index, …)
- die mit ihrem Index verbundenen Zulagen
- die Sätze der verschiedenen Sozialabgaben, die vom Bruttolohn abzuziehen sind
Sie fordert diese Informationen von der Ebene [dao] über den Pfad [3, 4, 5, 6] an. [3] ist die ursprüngliche Anfrage und [6] die Antwort auf diese Anfrage.
- Da nun alle benötigten Daten vorliegen, berechnet die Ebene [metier] die Abrechnung der vom Benutzer ausgewählten Person.
- Die Schicht [metier] kann nun die in (d) gestellte Anfrage der Schicht [ui] beantworten. Dies ist der Pfad [7].
- Die Schicht [ui] formatiert diese Ergebnisse, um sie dem Benutzer in geeigneter Form darzustellen, und gibt sie anschließend aus. Dies ist der Pfad [8].
- Man kann sich vorstellen, dass diese Ergebnisse in einer Datei oder einer Datenbank gespeichert werden müssen. Dies kann automatisch erfolgen. In diesem Fall fordert die Schicht [metier] nach dem Vorgang (f) die Schicht [dao] auf, die Ergebnisse zu speichern. Dies erfolgt über den Pfad [3, 4, 5, 6]. Dies kann auch auf Anfrage des Benutzers erfolgen. In diesem Fall wird der Pfad [1-8] im Anfrage-Antwort-Zyklus verwendet.
Aus dieser Beschreibung geht hervor, dass eine Schicht die Ressourcen der Schicht rechts von ihr nutzt, niemals jedoch die der Schicht links von ihr.
Unsere erste Implementierung dieser 3-Schichten-Architektur wird eine Anwendung mit dem Namen ASP.NET sein, wobei
- die Schichten [dao] und [metier] durch DLL implementiert werden
- die Schicht [ui] durch das Webformular der Version 1 implementiert wird (siehe Abschnitt 4.2.1).
Wir beginnen mit der Implementierung der Schicht [dao] mit dem Framework NHibernate.
7.2. Die Datenzugriffsebene [dao]
![]() |
7.2.1. Das Visual Studio C#-Projekt der Schicht [dao]
Das Visual Studio-Projekt der Schicht [dao] lautet wie folgt:
![]() |
- in [1], das gesamte Projekt
- in [2] die verschiedenen Klassen des Projekts
- in [3] die Referenzen des Projekts.
- in [4], ein Ordner [lib], in dem die für die verschiedenen nachfolgenden Projekte erforderlichen DLL-Dateien zusammengestellt wurden
In den Projektreferenzen [3] finden sich die folgenden DLL:
- NHibernate: für die ORM und NHibernate
- MySql.Data: der Treiber ADO.NET für SGBD MySQL
- Spring.Core: für das Spring-Framework
- log4net: eine Protokollbibliothek
- nunit.framework: eine Bibliothek für Unit-Tests
Diese Referenzen wurden aus dem Ordner [lib] [4] übernommen. Es ist darauf zu achten, dass bei allen diesen Referenzen die Eigenschaft „Lokale Kopie“ auf „True“ gesetzt ist. [5]:
![]() |
7.2.2. Die Entitäten der Ebene [dao]
![]() |
Die für die Ebene [dao] erforderlichen Entitäten (Objekte) wurden im Ordner [entites] des Projekts zusammengestellt. Einige davon sind uns bereits bekannt: [Cotisations], beschrieben in Abschnitt 6.3.2.1, [Employe], beschrieben in Abschnitt 6.3.2.3, [Indemnites], beschrieben in Abschnitt 6.3.2.2. Sie befinden sich alle im Namensraum [Pam.Dao.Entites].
Die Klasse [Employe] entwickelt sich wie folgt:
namespace Pam.Dao.Entites {
public class Employe {
// automatische Eigenschaften
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; }
// Konstrukteure
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. Die Klasse [PamException]
Die Schicht [dao] ist dafür zuständig, Daten mit einer externen Quelle auszutauschen. Dieser Austausch kann fehlschlagen. Werden beispielsweise Informationen von einem Remote-Dienst im Internet angefordert, schlägt der Abruf bei einer beliebigen Netzwerkstörung fehl. Bei dieser Art von Fehlern ist es in Java üblich, eine Ausnahme auszulösen. Wenn die Ausnahme nicht vom Typ [RunTimeException] oder einem davon abgeleiteten Typ ist, muss in der Signatur der Methode angegeben werden, dass diese eine Ausnahme auslöst (throws). In .NET sind alle Ausnahmen unkontrolliert, c.a.d. entsprechen dem Java-Typ [RunTimeException]. Es ist daher nicht erforderlich, anzugeben, dass die Methoden vom Typ [GetAllIdentitesEmployes, GetEmploye, GetCotisations] eine Ausnahme auslösen können.
Es ist jedoch sinnvoll, die Ausnahmen voneinander unterscheiden zu können, da ihre Behandlung unterschiedlich sein kann. So lässt sich der Code zur Behandlung verschiedener Ausnahmetypen wie folgt schreiben:
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{
...
}
Wir erstellen also einen Ausnahmetyp für die Schicht [dao] unserer Anwendung. Es handelt sich um den folgenden Typ [PamException]:
using System;
namespace Pam.Dao.Entites {
public class PamException : Exception {
// Fehlercode
public int Code { get; set; }
// Konstruktoren
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;
}
}
}
- Zeile 2: Die Klasse gehört zum Namensraum [Pam.Dao.Entites]
- Zeile 4: Die Klasse leitet sich von der Klasse [Exception] ab
- Zeile 7: Sie verfügt über eine öffentliche Eigenschaft [Code], bei der es sich um einen Fehlercode handelt
- Wir werden in unserer Schicht [dao] zwei Arten von Konstruktoren verwenden:
- den in den Zeilen 18–21, den wir wie unten gezeigt verwenden können:
- (Fortsetzung)
- oder den in den Zeilen 23–26, der dazu dient, eine bereits aufgetretene Ausnahme weiterzuleiten, indem er in eine Ausnahme vom Typ [PamException] gekapselt wird:
try{
....
}catch (IOException ex){
// Die Ausnahme wird gekapselt
throw new PamException("Problème d'accès aux données",ex,10);
}
Diese zweite Methode hat den Vorteil, dass die Informationen, die die erste Ausnahme enthalten kann, nicht verloren gehen.
7.2.4. Die Zuordnungstabellen <--> Klassen von NHibernate
Kommen wir zurück zur Architektur der Anwendung:
![]() |
Beim Lesen nutzt das Framework NHibernate Daten aus der Datenbank und wandelt sie in Objekte um, deren Klassen wir gerade vorgestellt haben. Beim Schreiben erfolgt der umgekehrte Vorgang: Ausgehend von Objekten erstellt, aktualisiert und löscht es Zeilen in den Datenbanktabellen. Die Dateien, die die Umwandlung zwischen Tabellen und Klassen gewährleisten, wurden bereits vorgestellt:
![]() |
- Die in Abschnitt 6.3.2.1 vorgestellte Datei [Cotisations.hbm.xml] stellt die Zuordnung zwischen der Tabelle [COTISATIONS] und der Klasse [Cotisations] her
<?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>
- Die in Abschnitt 6.3.2.3 vorgestellte Datei [Employe.hbm.xml] stellt die Zuordnung zwischen der Tabelle [EMPLOYES] und der Klasse [Employe] her
<?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>
- Die in Abschnitt 6.3.2.2 vorgestellte Datei [Indemnites.hbm.xml] stellt die Zuordnung zwischen der Tabelle [INDEMNITES] und der Klasse [Indemnites] her
<?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>
Es ist zu beachten, dass im <hibernate-mapping>-Tag dieser Dateien (Zeile 2) folgende Attribute vorhanden sind:
- namespace: Pam.Dao.Entites. Die Klassen [Cotisations], [Employe] und [Indemnites] müssen sich in diesem Namensraum befinden.
- assembly: pam-dao-nhibernate. Die Mapping-Dateien [*.hbm.xml] müssen in einer DLL namens [pam-dao-nhibernate] gekapselt werden. Um dieses Ergebnis zu erzielen, wird das C#-Projekt wie folgt konfiguriert:
![]() |
- in [1], die Assembly des Projekts trägt den Namen [pam-dao-nhibernate]
- in [2] werden die Mapping-Dateien [*.hbm.xml] in die Projekt-Assembly [3] integriert
7.2.5. Die Schnittstelle [IPamDao] der Schicht [dao]
Kommen wir zurück zur Architektur unserer Anwendung:
![]() |
In einfachen Fällen kann man von der Schicht [metier] ausgehen, um die Schnittstellen der Anwendung zu ermitteln. Für den Betrieb benötigt sie Daten:
- die bereits in Dateien, Datenbanken oder über das Netzwerk verfügbar sind. Diese werden von der Schicht [dao] bereitgestellt.
- noch nicht verfügbar sind. In diesem Fall werden sie von der Schicht [ui] bereitgestellt, die sie vom Benutzer der Anwendung erhält.
Welche Schnittstelle muss die Schicht [dao] der Schicht [metier] bereitstellen? Welche Interaktionen sind zwischen diesen beiden Schichten möglich? Die Schicht [dao] muss der Schicht [metier] folgende Daten bereitstellen:
- die Liste der Tagesmütter, damit der Benutzer eine bestimmte Tagesmutter auswählen kann
- vollständige Informationen über die ausgewählte Person (Adresse, Index, …)
- die mit dem Index der Person verbundenen Vergütungen
- die Sätze der verschiedenen Sozialbeiträge
Diese Informationen liegen bereits vor der Lohnabrechnung vor und können daher gespeichert werden. In der Richtung [metier] -> [dao] kann die Schicht [metier] die Schicht [dao] auffordern, das Ergebnis der Lohnabrechnung zu speichern. Dies werden wir hier jedoch nicht tun.
Mit diesen Informationen könnte man eine erste Definition der Schnittstelle der Schicht [dao] versuchen:
using Pam.Dao.Entites;
namespace Pam.Dao.Service {
public interface IPamDao {
// Liste aller Mitarbeiteridentitäten
Employe[] GetAllIdentitesEmployes();
// ein bestimmter Mitarbeiter mit seinen Zulagen
Employe GetEmploye(string ss);
// Liste aller Beiträge
Cotisations GetCotisations();
}
}
- Zeile 1: Der Namensraum der Entitäten der Schicht [dao] wird importiert.
- Zeile 3: Die Schicht [dao] befindet sich im Namensraum [Pam.Dao.Service]. Elemente des Namensraums [Pam.Dao.Entites] können mehrfach angelegt werden. Elemente des Namensraums [Pam.Dao.Service] werden nur einmal angelegt (Singleton). Dies war der Grund für die Wahl der Namen der Namensräume.
- Zeile 4: Die Schnittstelle heißt [IPamDao]. Sie definiert drei Methoden:
- Zeile 6: [GetAllIdentitesEmployes] gibt ein Array von Objekten des Typs [Employe] zurück, das die Liste der Tagesmütter in vereinfachter Form darstellt (Nachname, Vorname, SS).
- Zeile 8: [GetEmploye] gibt ein Objekt vom Typ [Employe] zurück: den Mitarbeiter, dessen Sozialversicherungsnummer als Parameter an die Methode übergeben wurde, zusammen mit den seinem Index zugeordneten Zulagen.
- Zeile 10: [GetCotisations] gibt das Objekt [Cotisations] zurück, das die Sätze der verschiedenen Sozialabgaben enthält, die vom Bruttolohn abzuziehen sind.
7.3. Implementierung und Tests der Schicht [dao]
7.3.1. Das Visual Studio-Projekt
Das Visual Studio-Projekt wurde bereits vorgestellt. Zur Erinnerung:
![]() |
- in [1], das gesamte Projekt
- in [2], die verschiedenen Klassen des Projekts. Der Ordner [entites] enthält die von der Schicht [dao] verarbeiteten Entitäten sowie die Mapping-Dateien NHibernate. Der Ordner [service] enthält die Schnittstelle [IPamDao] und deren Implementierung [PamDaoNHibernate]. Der Ordner [tests] enthält einen Konsolentest [Main.cs] und einen Unit-Test [NUnit.cs].
- In [3] befinden sich die Projektverweise.
7.3.2. Das Konsolentestprogramm [Main.cs]
Das Testprogramm [Main.cs] wird in der folgenden Architektur ausgeführt:
![]() |
Es dient zum Testen der Methoden der Schnittstelle [IPamDao]. Ein einfaches Beispiel könnte wie folgt aussehen:
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 {
// Instanziierung der Schicht [dao]
IPamDao pamDao = (IPamDao)ContextRegistry.GetContext().GetObject("pamdao");
// Liste der Mitarbeiteridentitäten
foreach (Employe Employe in pamDao.GetAllIdentitesEmployes()) {
Console.WriteLine(Employe.ToString());
}
// ein Mitarbeiter mit seinen Zulagen
Console.WriteLine("------------------------------------");
Console.WriteLine(pamDao.GetEmploye("254104940426058"));
Console.WriteLine("------------------------------------");
// Liste der Beiträge
Cotisations cotisations = pamDao.GetCotisations();
Console.WriteLine(cotisations.ToString());
} catch (Exception ex) {
// Anzeige einer Ausnahme
Console.WriteLine(ex.ToString());
}
//Pause
Console.ReadLine();
}
}
}
- Zeile 11: Spring wird um eine Referenz auf die Schicht [dao] gebeten.
- Zeilen 13–15: Test der Methode [GetAllIdentitesEmployes] der Schnittstelle [IPamDao]
- Zeile 18: Test der Methode [GetEmploye] der Schnittstelle [IPamDao]
- Zeile 21: Test der Methode [GetCotisations] der Schnittstelle [IPamDao]
Spring, NHibernate und log4net werden über die folgende [App.config]- -Datei konfiguriert:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<!-- Konfigurationsabschnitte -->
<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>
<!-- Spring-Konfiguration -->
<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>
<!-- Konfiguration 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>
<!-- Dieser Abschnitt enthält die log4net-Konfigurationseinstellungen -->
<!-- NOTE IMPORTANTE: Die Protokolle sind standardmäßig nicht aktiviert. Sie müssen programmgesteuert aktiviert werden
avec l'instruction log4net.Config.XmlConfigurator.Configure();
! -->
<log4net>
...
</log4net>
</configuration>
Die Konfiguration von NHibernate (Zeile 10, Zeilen 25–36) wurde in Abschnitt 6.3.1 erläutert. Beachten Sie Zeile 34, die angibt, dass sich die Mapping-Dateien in der Assembly [pam-dao-nhibernate] befinden. Dies ist die Assembly des Projekts.
Die Spring-Konfiguration erfolgt in den Zeilen 6–9 und 15–22. In Zeile 20 wird das Objekt [pamdao] definiert, das vom Konsolenprogramm [Main.cs] verwendet wird. Das Tag <object> weist hier folgende Attribute auf:
- type: Legt die zu instanziierende Klasse fest. Es handelt sich um die Klasse [PamDaoNHibernate], die die Schnittstelle [IPamDao] implementiert. Sie befindet sich im Projekt unter DLL [pam-dao-nhibernate].
- init-method: Die Methode der Klasse [PamDaoNHibernate], die nach der Instanziierung der Klasse ausgeführt werden soll
- destroy-method: Die Methode der Klasse [PamDaoNHibernate], die ausgeführt werden soll, wenn der Spring-Container am Ende der Projektausführung zerstört wird.
Die Ausführung mit der in Abschnitt 6.2 beschriebenen Datenbank liefert die folgende Konsolenausgabe:
- Zeilen 1–2: die beiden Mitarbeiter vom Typ [Employe] mit den einzigen Informationen [SS, Nom, Prenom]
- Zeile 4: der Mitarbeiter vom Typ [Employe] mit der Sozialversicherungsnummer [254104940426058]
- Zeile 5: die Beitragssätze
7.3.3. Buchung der Klasse [PamDaoNHibernate]
![]() |
Die von der Schicht [dao] implementierte Schnittstelle [IPamDao] lautet wie folgt:
using Pam.Dao.Entites;
namespace Pam.Dao.Service {
public interface IPamDao {
// Liste aller Mitarbeiteridentitäten
Employe[] GetAllIdentitesEmployes();
// Ein bestimmter Mitarbeiter mit seinen Zulagen
Employe GetEmploye(string ss);
// Liste aller Beiträge
Cotisations GetCotisations();
}
}
Aufgabe: Schreiben Sie den Code der Klasse [PamDaoNHibernate], die die oben genannte Schnittstelle [IPamDao] mithilfe des Frameworks NHibernate implementiert, das wie zuvor beschrieben konfiguriert ist. Außerdem werden die von Spring ausgeführten Methoden „init“ und „destroy“ implementiert. Die Methode init erstellt die Klasse SessionFactory, über die Objekte der Klasse Session abgerufen werden können. Die Methode destroy wird diese SessionFactory schließen. Dabei stützen wir uns auf die Beispiele aus Abschnitt 6.5.
Einschränkungen:
Es wird angenommen, dass bestimmte Daten, die von der Schicht [dao] angefordert werden, vollständig im Speicher Platz finden. Um die Leistung zu verbessern, speichert die Klasse [PamDaoNHibernate] daher:
- die Tabelle [EMPLOYES] in der Form (SS, NOM, PRENOM), die von der Methode [GetAllIdentitesEmployes] benötigt wird, in Form eines Arrays von Objekten vom Typ [Employe]
- die Tabelle [COTISATIONS] in Form eines einzelnen Objekts vom Typ [Cotisations]
Dies erfolgt in der Methode [init] der Klasse. Das Grundgerüst der Klasse [PamDaoNHibernate] könnte wie folgt aussehen:
using System;
...
namespace Pam.Dao.Service {
class PamDaoNHibernate : IPamDao {
// private Felder
private Cotisations cotisations;
private Employe[] employes;
private ISessionFactory sessionFactory = null;
// Initialisierung
public void init() {
try {
// Factory-Initialisierung
sessionFactory = new Configuration().Configure().BuildSessionFactory();
// Beitragssätze und Mitarbeiter werden abgerufen und zwischengespeichert
.......................
}
// Schließen SessionFactory
public void destroy() {
if (sessionFactory != null) {
sessionFactory.Close();
}
}
// Liste aller Mitarbeiteridentitäten
public Employe[] GetAllIdentitesEmployes() {
return employes;
}
// ein bestimmter Mitarbeiter mit seinen Zulagen
public Employe GetEmploye(string ss) {
................................
}
// Beitragsübersicht
public Cotisations GetCotisations() {
return cotisations;
}
}
}
7.3.4. Unit-Tests mit NUnit
Literaturempfehlungen: „C# 2008, Kapitel 4: 3-Schichten-Architekturen, Tests NUnit, Spring-Framework“.
Der vorherige Test war visuell: Man überprüfte am Bildschirm, ob tatsächlich die erwarteten Ergebnisse erzielt wurden. Diese Methode ist im beruflichen Umfeld unzureichend. Tests sollten stets so weit wie möglich automatisiert werden und darauf abzielen, keinerlei menschliches Eingreifen zu erfordern. Der Mensch ist nämlich anfällig für Ermüdung, und seine Fähigkeit, Tests zu überprüfen, lässt im Laufe des Tages nach. Das Tool [NUnit] hilft bei der Umsetzung dieser Automatisierung. Es ist unter URL [http://www.nunit.org/] verfügbar.
Das Visual Studio-Projekt der Schicht [dao] wird sich wie folgt weiterentwickeln:
![]() |
- zu [1], das Testprogramm [NUnit.cs]
- in [2,3], das Projekt generiert eine Datei mit dem Namen DLL, die als [pam-dao-nhibernate.dll] bezeichnet wird
- in [4], der Verweis auf DLL aus dem Framework NUnit: [nunit.framework.dll]
- in [5], die Klasse [Main.cs] wird nicht in die DLL [pam-dao-nhibernate]
- in [6] wird die Klasse [NUnit.cs] in die Klassen DLL und [pam-dao-nhibernate] aufgenommen
Die Testklasse „ “ NUnit lautet wie folgt:
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 {
// die zu testende Ebene [dao]
private IPamDao pamDao = null;
// Hersteller
public NunitPamDao() {
// Instanziierung der Schicht [dao]
pamDao = (IPamDao)ContextRegistry.GetContext().GetObject("pamdao");
}
// Initialisierung
[SetUp]
public void Init() {
}
[Test]
public void GetAllIdentitesEmployes() {
// Überprüfung der Mitarbeiterzahl
Expect(2, EqualTo(pamDao.GetAllIdentitesEmployes().Length));
}
[Test]
public void GetCotisations() {
// Prüfung des Beitragssatzes
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() {
// Überprüfung der Personen
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() {
// Prüfung nicht vorhandener Person
bool erreur = false;
try {
Employe employe1 = pamDao.GetEmploye("xx");
} catch {
erreur = true;
}
Expect(erreur, True);
}
}
}
- Zeile 11: Die Klasse verfügt über das Attribut [TestFixture], wodurch sie zu einer Testklasse [NUnit] wird.
- Zeile 12: Die Klasse leitet sich von der Hilfsklasse AssertionHelper des Frameworks NUnit (ab Version 2.4.6) ab.
- Zeile 14: Das private Feld [pamDao] ist eine Instanz der Schnittstelle für den Zugriff auf die Schicht [dao]. Es ist zu beachten, dass der Typ dieses Feldes eine Schnittstelle und keine Klasse ist. Das bedeutet, dass die Instanz [pamDao] nur Methoden zugänglich macht, nämlich die der Schnittstelle [IPamDao].
- Die in der Klasse getesteten Methoden sind diejenigen mit dem Attribut [Test]. Für alle diese Methoden läuft der Testablauf wie folgt ab:
- Zunächst wird die Methode mit dem Attribut [SetUp] ausgeführt. Sie dient dazu, die für den Test erforderlichen Ressourcen (Netzwerkverbindungen, Datenbankverbindungen usw.) vorzubereiten.
- Anschließend wird die zu testende Methode ausgeführt
- und schließlich wird die Methode mit dem Attribut [TearDown] ausgeführt. Sie dient in der Regel dazu, die von der Methode mit dem Attribut [SetUp] beanspruchten Ressourcen freizugeben.
- In unserem Test müssen vor jedem Test keine Ressourcen zugewiesen und anschließend wieder freigegeben werden. Daher benötigen wir keine Methode mit den Attributen [SetUp] und [TearDown]. Als Beispiel haben wir in den Zeilen 23–26 eine Methode mit dem Attribut [SetUp] vorgestellt.
- Zeilen 17–20: Der Konstruktor der Klasse initialisiert das private Feld [pamDao] mithilfe von Spring und [App.config].
- Zeilen 29–32: Testen der Methode [GetAllIdentitesEmployes]
- Zeilen 35–42: Testen der Methode [GetCotisations]
- Zeilen 45–53: Testen der Methode [GetEmploye]
- Zeilen 56–65: Testen der Methode [GetEmploye] bei einer Ausnahme.
Bei der Projektgenerierung werden die Dateien DLL und [pam-dao-nhibernate.dll] im Ordner [bin/Release] erstellt.
![]() |
Der Ordner „[bin/Release]“ enthält außerdem:
- die DLL, die Teil der Projektreferenzen sind und bei denen das Attribut [Copie locale] auf „wahr“ gesetzt ist: [Spring.Core, MySql.data, NHibernate, log4net]. Diesen DLL-Dateien liegen Kopien der DLL-Dateien bei, die sie selbst verwenden:
- [CastleDynamicProxy, Iesi.Collections] für das Tool NHibernate
- [antlr.runtime, Common.Logging] für das Tool Spring
- Die Datei [pam-dao-nhibernate.dll.config] ist eine Kopie der Konfigurationsdatei [App.config]. Diese Duplizierung wird von VS durchgeführt. Bei der Ausführung wird die Datei [pam-dao-nhibernate.dll.config] verwendet und nicht [App.config].
Man lädt die Dateien DLL und [pam-dao-nhibernate.dll] mit dem Tool [NUnit-Gui], Version 2.4.6, und führt die Tests durch:
![]() |
Die oben genannten Tests wurden erfolgreich durchgeführt.
Praktische Aufgabe:
Führen Sie die Tests der Klasse [PamDaoNHibernate] auf dem Rechner durch.- Verwenden Sie verschiedene Konfigurationsdateien [App.config], um unterschiedliche SGBD-Instanzen (Firebird, MySQL, Postgres, SQL-Server) zu nutzen
7.3.5. Generierung v us der DLL aus der Schicht [dao]
Nachdem die Klasse [PamDaoNHibernate] geschrieben und getestet wurde, wird die Klasse DLL aus der Schicht [dao] wie folgt generiert:
![]() |
- [1], die Testprogramme werden beim Zusammenstellen des Projekts ausgeschlossen
- [2,3], Projektkonfiguration
- [4], Projektgenerierung
- DLL wird im Ordner [bin/Release] [5] generiert. Wir fügen sie zu den bereits im Ordner [lib] vorhandenen Dateien DLL und [6] hinzu:
![]() |
7.4. Die Fachschicht
Kommen wir noch einmal auf die allgemeine Architektur der Anwendung [SimuPaie] zurück:
![]() |
Wir gehen nun davon aus, dass die Schicht [dao] fertiggestellt und in die Schichten DLL und [pam-dao-nhibernate.dll] gekapselt wurde. Wir wenden uns nun der Schicht [metier] zu. Diese implementiert die Geschäftsregeln, in diesem Fall die Regeln zur Gehaltsberechnung.
7.4.1. Das Visual-Studio- -Projekt der Schicht [metier]
Das Visual Studio-Projekt der Geschäftsebene könnte wie folgt aussehen:
![]() |
- in [1] das gesamte Projekt, das durch die Datei [App.config] konfiguriert wird
- in [2] besteht die Schicht [metier] aus den beiden Ordnern [entites, service]. Der Ordner [tests] enthält ein Konsolen-Testprogramm (Main.cs) und ein Testprogramm NUnit (NUnit.cs).
- In [3] sind die vom Projekt verwendeten Referenzen aufgeführt. Zu beachten sind dabei die Einträge DLL und [pam-dao-nhibernate] der zuvor untersuchten Ebene [dao].
7.4.2. Die Schnittstelle [IPamMetier] „ “ der Schicht [metier]
Kehren wir zur allgemeinen Architektur der Anwendung zurück:
![]() |
Welche Schnittstelle muss die Schicht [metier] der Schicht [ui] bereitstellen? Welche Interaktionen sind zwischen diesen beiden Schichten möglich? Erinnern wir uns an die Weboberfläche, die dem Benutzer präsentiert wird:
![]() |
- Bei der ersten Anzeige des Formulars muss in [1] die Liste der Mitarbeiter zu finden sein. Eine vereinfachte Liste reicht aus (Nachname, Vorname, SS). Die Nummer SS ist erforderlich, um Zugriff auf die zusätzlichen Informationen zum ausgewählten Mitarbeiter zu erhalten (Informationen 6 bis 11).
- Die Angaben 12 bis 15 sind die verschiedenen Beitragssätze.
- Die Angaben 16 bis 19 sind die an den Index des Mitarbeiters gekoppelten Zulagen.
- Die Informationen 20 bis 24 sind die Gehaltsbestandteile, die anhand der vom Benutzer vorgenommenen Eingaben 1 bis 3 berechnet wurden.
Die Schnittstelle [IPamMetier], die der Schicht [ui] von der Schicht [metier] bereitgestellt wird, muss die oben genannten Anforderungen erfüllen. Es gibt zahlreiche mögliche Schnittstellen. Wir schlagen folgende vor:
using Pam.Dao.Entites;
using Pam.Metier.Entites;
namespace Pam.Metier.Service {
public interface IPamMetier {
// Liste aller Mitarbeiteridentitäten
Employe[] GetAllIdentitesEmployes();
// ------- Lohnberechnung
FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés);
}
}
- Zeile 7: die Methode, mit der das Kombinationsfeld [1] gefüllt wird
- Zeile 10: Die Methode, mit der die Informationen 6 bis 24 abgerufen werden können. Diese wurden in einem Objekt vom Typ [FeuilleSalaire] zusammengefasst.
7.4.3. Die Entitäten der Schicht [metier]
Der Ordner [entites] des Visual Studio-Projekts enthält die von der Geschäftsklasse verarbeiteten Objekte: [FeuilleSalaire] und [ElementsSalaire].
![]() |
Die Klasse [FeuilleSalaire] fasst die Informationen 6 bis 24 des vorherigen Formulars zusammen:
using Pam.Dao.Entites;
namespace Pam.Metier.Entites {
public class FeuilleSalaire {
// Automatische Eigenschaften
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);
}
}
}
- Zeile 8: die Informationen 6 bis 11 zum Mitarbeiter, dessen Gehalt berechnet wird, sowie die Informationen 16 bis 19 zu seinen Zulagen. Dabei ist zu beachten, dass ein Objekt [Employe] ein Objekt [Indemnites] kapselt, das die Zulagen des Mitarbeiters darstellt.
- Zeile 9: die Informationen 12 bis 15
- Zeile 10: die Informationen 20 bis 24
- Zeilen 13–15: die Methode [ToString]
Die Klasse [ElementsSalaire] kapselt die Informationen 20 bis 24 des Formulars:
namespace Pam.Metier.Entites {
public class ElementsSalaire {
// automatische Eigenschaften
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);
}
}
}
- Zeilen 4–8: Die Gehaltsbestandteile, wie in den in Abschnitt 3.2 beschriebenen Geschäftsregeln erläutert.
- Zeile 4: Das Grundgehalt des Mitarbeiters, abhängig von der Anzahl der geleisteten Arbeitsstunden
- Zeile 5: Die von diesem Grundgehalt abgezogenen Beiträge
- Zeilen 6 und 7: die zum Grundgehalt hinzuzurechnenden Zulagen, abhängig vom Index des Mitarbeiters und der Anzahl der gearbeiteten Tage
- Zeile 8: Der auszuzahlende Nettolohn
- Zeilen 12–15: die Methode [ToString] der Klasse.
7.4.4. Implementierung der Schicht [metier]
![]() |
Wir werden die Schnittstelle [IPamMetier] mit zwei Klassen implementieren:
- [AbstractBasePamMetier], einer abstrakten Klasse, in der wir den Datenzugriff der Schnittstelle [IPamMetier] implementieren werden. Diese Klasse wird eine Referenz auf die Schicht [dao] enthalten.
- [PamMetier] ist eine von [AbstractBasePamMetier] abgeleitete Klasse, die ihrerseits die Geschäftsregeln der Schnittstelle [IPamMetier] implementiert. Sie hat keine Kenntnis von der Schicht [dao].
Die Klasse [AbstractBasePamMetier] sieht wie folgt aus:
using Pam.Dao.Entites;
using Pam.Dao.Service;
using Pam.Metier.Entites;
namespace Pam.Metier.Service {
public abstract class AbstractBasePamMetier : IPamMetier {
// das Objekt für den Datenzugriff
public IPamDao PamDao { get; set; }
// Liste aller Mitarbeiteridentitäten
public Employe[] GetAllIdentitesEmployes() {
return PamDao.GetAllIdentitesEmployes();
}
// ein bestimmter Mitarbeiter mit seinen Zulagen
protected Employe GetEmploye(string ss) {
return PamDao.GetEmploye(ss);
}
// die Beiträge
protected Cotisations GetCotisations() {
return PamDao.GetCotisations();
}
// die Lohnberechnung
public abstract FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés);
}
}
- Zeile 5: Die Klasse gehört zum Namensraum [Pam.Metier.Service], wie alle Klassen und Schnittstellen der Schicht [metier].
- Zeile 6: Die Klasse ist abstrakt (Attribut abstract) und implementiert die Schnittstelle [IPamMetier]
- Zeile 9: Die Klasse enthält eine Referenz auf die Schicht [dao] in Form einer öffentlichen Eigenschaft
- Zeilen 12–14: Implementierung der Methode [GetAllIdentitesEmployes] der Schnittstelle [IPamMetier] – nutzt die gleichnamige Methode der Schicht [dao]
- Zeilen 17–19: Interne (geschützte) Methode [GetEmploye], die die gleichnamige Methode der Schicht [dao] aufruft – deklariert als protected, damit abgeleitete Klassen darauf zugreifen können, ohne dass sie öffentlich ist.
- Zeilen 22–24: Interne (geschützte) Methode [GetCotisations], die die gleichnamige Methode der Schicht [dao] aufruft
- Zeile 27: abstrakte Implementierung (Attribut abstract) der Methode [GetSalaire] der Schnittstelle [IPamMetier].
Die Gehaltsberechnung wird durch die folgende Klasse [PamMetier] implementiert:
using System;
using Pam.Dao.Entites;
using Pam.Metier.Entites;
namespace Pam.Metier.Service {
public class PamMetier : AbstractBasePamMetier {
// Lohnberechnung
public override FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés) {
// SS: SS-Nummer des Mitarbeiters
// HeuresTravaillées: Anzahl der gearbeiteten Stunden
// Gearbeitete Tage: Anzahl der gearbeiteten Tage
// Hier werden der Mitarbeiter und seine Zulagen abgerufen
...
// Die verschiedenen Beitragssätze werden abgerufen
...
// Berechnung der Lohnbestandteile
...
// Die Gehaltsabrechnung wird ausgegeben
return ...;
}
}
}
- Zeile 7: Die Klasse leitet sich von [AbstractBasePamMetier] ab und implementiert daher die Schnittstelle [IPamMetier]
- Zeile 10: Die zu implementierende Methode [GetSalaire]
Aufgabe: Schreiben Sie den Code für die Methode [GetSalaire].
7.4.5. Der Konsolentest der Schicht [metier]
Zur Erinnerung: das Visual Studio-Projekt der Schicht [metier]:
![]() |
Das oben genannte Testprogramm [Main] testet die Methoden der Schnittstelle [IPamMetier]. Ein einfaches Beispiel könnte wie folgt aussehen:
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 {
// Instanzierung der Ebene [metier]
IPamMetier pamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
// Lohnabrechnungsberechnungen
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()));
}
// Pause
Console.ReadLine();
}
}
}
- Zeile 11: Instanziierung der Schicht [metier] durch Spring.
- Zeilen 13–14: Testen der Methode [GetSalaire] der Schnittstelle [IPamMetier]
- Zeilen 15–22: Test der Methode [GetSalaire], wenn eine Ausnahme auftritt
Das Testprogramm verwendet die folgende Konfigurationsdatei „ “ [App.config] :
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<!-- Konfigurationsabschnitte -->
<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>
<!-- Spring-Konfiguration -->
<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>
<!-- Konfiguration NHibernate -->
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
....
</hibernate-configuration>
<!-- Dieser Abschnitt enthält die log4net-Konfigurationseinstellungen -->
<!-- NOTE IMPORTANTE: Die Protokolle sind standardmäßig nicht aktiviert. Sie müssen programmgesteuert aktiviert werden
avec l'instruction log4net.Config.XmlConfigurator.Configure();
! -->
<log4net>
...
</log4net>
</configuration>
Diese Datei ist identisch mit der Datei [App.config], die für das Projekt der Schicht [dao] verwendet wird (siehe Abschnitt 7.3.2), mit folgenden Abweichungen:
- Zeile 20: Das Objekt mit der ID „pamdao“ hat den Typ [Pam.Dao.Service.PamDaoNHibernate] und befindet sich in der Assembly [pam-dao-nhibernate]. Die Ebene [dao] ist die zuvor untersuchte.
- Zeilen 21–23: Das Objekt mit der ID „pammetier“ hat den Typ [Pam.Metier.Service.PamMetier] und befindet sich in der Assembly [pam-metier-dao-nhibernate]. Das Projekt muss entsprechend konfiguriert werden:
![]() |
- Zeile 22: Das von Spring instanziierte Objekt [PamMetier] verfügt über eine öffentliche Eigenschaft [PamDao], die eine Referenz auf die Schicht [dao] darstellt. Diese Eigenschaft wird mit der Referenz der in Zeile 20 erstellten Ebene [dao] initialisiert.
Die Ausführung mit der in Abschnitt 6.2 beschriebenen Datenbank liefert die folgende Konsolenausgabe:
- Zeilen 1–2: die beiden angeforderten Lohnabrechnungen
- Zeile 3: Die Ausnahme vom Typ [PamException], ausgelöst durch einen nicht existierenden Mitarbeiter.
7.4.6. Unit-Tests der Geschäftslogik
Der vorherige Test war visuell: Wir haben auf dem Bildschirm überprüft, ob tatsächlich die erwarteten Ergebnisse angezeigt wurden. Wir gehen nun zu den nicht-visuellen Tests NUnit über.
Kehren wir zum Visual Studio-Projekt [metier] zurück:
![]() |
- in [1], das Testprogramm NUnit
- in [2], die Referenz auf DLL [nunit.framework]
![]() |
- in [3,4]; bei der Projektgenerierung werden die Dateien DLL und [pam-metier-dao-nhibernate.dll] erzeugt.
- Bei [5] wird die Datei [NUnit.cs] in die Assembly [pam-metier-dao-nhibernate.dll] aufgenommen, nicht jedoch in [Main.cs] und [6]
Die Testklasse „ “ NUnit lautet wie folgt:
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 {
// Die zu testende Schicht [metier]
private IPamMetier pamMetier;
// Konstruktor
public NunitTestPamMetier() {
// Instanziierung der Schicht [dao]
pamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
}
[Test]
public void GetAllIdentitesEmployes() {
// Überprüfung der Mitarbeiterzahl
Expect(2, EqualTo(pamMetier.GetAllIdentitesEmployes().Length));
}
[Test]
public void GetSalaire1() {
// Berechnung einer Gehaltsabrechnung
FeuilleSalaire feuilleSalaire = pamMetier.GetSalaire("254104940426058", 150, 20);
// Prüfungen
Expect(368.77, EqualTo(feuilleSalaire.ElementsSalaire.SalaireNet).Within(1E-06));
// Lohnabrechnung eines nicht existierenden Mitarbeiters
bool erreur = false;
try {
feuilleSalaire = pamMetier.GetSalaire("xx", 150, 20);
} catch (PamException) {
erreur = true;
}
Expect(erreur, True);
}
}
}
- Zeile 13: Das private Feld [pamMetier] ist eine Instanz der Schnittstelle für den Zugriff auf die Schicht [metier]. Es ist zu beachten, dass der Typ dieses Feldes eine Schnittstelle und keine Klasse ist. Das bedeutet, dass die Instanz [PamMetier] nur Methoden zugänglich macht, nämlich die der Schnittstelle [IPamMetier].
- Zeilen 16–19: Der Konstruktor der Klasse initialisiert das private Feld [pamMetier] mithilfe von Spring und der Konfigurationsdatei [App.config].
- Zeilen 23–26: Testen die Methode [GetAllIdentitesEmployes]
- Zeilen 29–42: Testen der Methode [GetSalaire]
Das oben genannte Projekt generiert die Dateien DLL und [pam-metier.dll] im Ordner [bin/Release].
![]() |
Der Ordner „[bin/Release]“ enthält außerdem:
- die Dateien DLL, die Teil der Projektreferenzen sind und bei denen das Attribut [Copie locale] auf „wahr“ gesetzt ist: [Spring.Core, MySql.data, NHibernate, log4net, pam-dao-nhibernate]. Diesen DLL-Dateien liegen Kopien der DLL-Dateien bei, die sie selbst verwenden:
- [CastleDynamicProxy, Iesi.Collections] für das Tool NHibernate
- [antlr.runtime, Common.Logging] für das Tool Spring
- Die Datei [pam-metier-dao-nhibernate.dll.config] ist eine Kopie der Konfigurationsdatei [App.config].
Man lädt die Datei DLL [pam-metier-dao-nhibernate.dll] mit dem Tool [NUnit-Gui, version 2.4.6] und führt die Tests durch:
![]() |
Oben wurden die Tests erfolgreich durchgeführt.
Praktische Aufgabe:
Führen Sie die Tests der Klasse [PamMetier] auf dem Rechner durch.- Verwenden Sie verschiedene Konfigurationsdateien App.config, um unterschiedliche SGBD (Firebird, MySQL, Postgres, SQL Server) zu nutzen.
7.4.7. Generierung der DLL aus der Schicht [metier]
Sobald die Klasse [PamMetier] geschrieben und getestet wurde, wird die DLL aus der Schicht [metier] gemäß der in Abschnitt 7.3 beschriebenen Methode generiert.5 Es ist darauf zu achten, dass die Testprogramme [Main.cs] und [NUnit.cs] nicht in die Klasse DLL aufgenommen werden. Anschließend wird sie in den Ordner [lib] der Ordner DLL und [1] verschoben.
![]() |
7.5. Die Ebene [web]
Kommen wir noch einmal auf die allgemeine Architektur der Anwendung [SimuPaie] zurück:
![]() |
Wir gehen davon aus, dass die Schichten [dao] und [métier] bereits fertiggestellt und in den Schichten DLL und [pam-dao-nhibernate, pam-metier-dao-nhibernate.dll] gekapselt sind. Wir beschreiben nun die Webschicht.
7.5.1. Das Visual Web Developer-Projekt der Schicht [web]
![]() |
- in [1], das gesamte Projekt:
- [Global.asax]: die Klasse, die beim Start der Webanwendung instanziiert wird und die Initialisierung der Anwendung gewährleistet
- [Default.aspx]: die Seite des Webformulars
- in [2], die für die Webanwendung erforderlichen DLL. Zu beachten sind die DLL der zuvor erstellten Schichten [dao] und [metier].
7.5.2. Konfiguration der Anwendung
Die Datei [Web.config], die die Anwendung konfiguriert, definiert dieselben Daten wie die Datei [App.config], die die zuvor untersuchte Schicht [metier] konfiguriert. Diese müssen in den vorgenerierten Code der Datei [Web.config] eingefügt werden:
<?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>
<!-- Spring-Konfiguration -->
<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>
<!-- Konfiguration 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>
<!-- Dieser Abschnitt enthält die log4net-Konfigurationseinstellungen -->
<!-- NOTE IMPORTANTE: Die Protokolle sind standardmäßig nicht aktiviert. Sie müssen programmgesteuert aktiviert werden
avec l'instruction log4net.Config.XmlConfigurator.Configure();
! -->
<log4net>
....
</log4net>
<appSettings/>
<connectionStrings/>
<system.web>
....
....
</configuration>
In den Zeilen 9–12, 18–28 und 31–44 findet sich die Spring- und NHibernate-Konfiguration, die in der Datei [App.config] der Schicht [metier] beschrieben ist (siehe Abschnitt 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
{
// --- statische Daten der Anwendung ---
public static Employe[] Employes;
public static IPamMetier PamMetier = null;
public static string Msg;
public static bool Erreur = false;
// Start der Anwendung
public void Application_Start(object sender, EventArgs e)
{
// Auswertung der Konfigurationsdatei
try
{
// Instanziierung der Schicht [metier]
PamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
// vereinfachte Mitarbeiterliste
Employes = PamMetier.GetAllIdentitesEmployes();
// Es ist gelungen
Msg = "Base chargée...";
}
catch (Exception ex)
{
// Fehler wird protokolliert
Msg = string.Format("L'erreur suivante s'est produite lors de l'accès à la base de données : {0}", ex);
Erreur = true;
}
}
}
}
Zur Erinnerung:
- die Klasse [Global.asax.cs] beim Start der Anwendung instanziiert wird und diese Instanz für alle Abfragen aller Benutzer zugänglich ist. Die statischen Felder in den Zeilen 11–14 werden somit von allen Benutzern gemeinsam genutzt.
- dass die Methode [Application_Start] nach der Instanziierung der Klasse einmalig ausgeführt wird. In dieser Methode erfolgt in der Regel die Initialisierung der Anwendung.
Die von allen Benutzern gemeinsam genutzten Daten sind folgende:
- Zeile 11: das Array von Objekten vom Typ [Employe], das die vereinfachte Liste (SS, NOM, PRENOM) aller Mitarbeiter speichert
- Zeile 12: ein Verweis auf die Ebene [metier], die in DLL und [pam-metier-dao-nhibernate.dll] gekapselt ist
- Zeile 13: Eine Meldung, die angibt, wie die Initialisierung abgeschlossen wurde (erfolgreich oder mit Fehler)
- Zeile 14: Ein boolescher Wert, der angibt, ob die Initialisierung mit einem Fehler beendet wurde oder nicht.
In [Application_Start]:
- Zeile 23: Spring instanziiert die Schichten [metier] und [dao] und gibt eine Referenz auf die Schicht [metier] zurück. Diese wird im statischen Feld [PamMetier] in Zeile 12 gespeichert.
- Zeile 25: Das Mitarbeiterarray wird von der Schicht [metier] angefordert
- Zeile 27: Die Erfolgsmeldung
- Zeile 32: Die Fehlermeldung
7.5.3. Das Formular [Default.aspx]
Das Formular entspricht der Version 2.
![]() |
Frage: Schreiben Sie in Anlehnung an den C#-Code auf der Seite [Default.aspx.cs] der Version 2 den Code [Default.aspx.cs] der Version 3. Der einzige Unterschied besteht in der Berechnung des Gehalts. Während in Version 2 die Methode API ADO.NET zum Abrufen von Informationen aus der Datenbank verwendet wurde, wird hier die Methode GetSalaire aus der Schicht [metier] verwendet.
Praktische Aufgabe:
Implementieren Sie die vorherige Webanwendung auf einem Rechner- Verwenden Sie verschiedene Konfigurationsdateien [Web.config], um unterschiedliche SGBD (Firebird, MySQL, Postgres, SQL Server) zu nutzen
































