7. The [SimuPaie] application – version 3 – 3-tier architecture with NHibernate
Recommended reading: "C# 2008, Chapter 4: 3-tier architectures, NUnit tests, Spring framework".
7.1. General Application Architecture
The [SimuPaie] application will now have the following three-tier structure:
![]() |
- The [1-dao] layer (dao=Data Access Object) will handle data access.
- The [2-métier] layer will handle the business logic of the application, specifically payroll calculation.
- The [3-ui] layer (ui=User Interface) will handle the presentation of data to the user and the execution of user requests. We refer to the set of modules performing this function as [Application]. It serves as the user interface.
- The three layers will be made independent through the use of interfaces. NET
- The integration of the different layers will be handled by Spring IoC
The processing of a client request follows these steps:
- The client makes a request to the application.
- The application processes this request. To do so, it may need assistance from the [métier] layer, which itself may need the [dao] layer if data needs to be exchanged with the database.
- The application receives a response from the [métier] layer. Based on this response, it sends the appropriate view (= the response) to the client.
Let’s take the example of calculating a childminder’s pay. This will require several steps:
![]() |
- The [ui] layer will need to ask the user
- the identity of the person whose payroll is to be calculated
- the number of days worked by that person
- the number of hours worked
- To do this, it will need to present the user with a list of people (last name, first name, SS) from the [EMPLOYES] table so that the user can select one of them. The [ui] layer will use the [2, 3, 4, 5, 6, 7] path to retrieve them. The [2] operation is the request for the list of employees, and the [7] operation is the response to that request. Once this is done, the [ui] layer can present the list of employees to the user via [8].
- The user will transmit the number of days worked and the number of hours worked to the [ui] layer. This is the [1] operation described above. During this step, the user interacts only with the [ui] layer. This layer will, in particular, verify the validity of the entered data. Once this is done, the user will request the payroll calculation.
- The [ui] layer will request that the business layer perform this calculation. To do so, it will transmit the data it received from the user to the business layer. This is operation [2].
- The [metier] layer requires certain information to perform its task:
- more complete information about the person (address, index, etc.)
- the benefits associated with their index
- the rates of the various social security contributions to be deducted from the gross salary
It will request this information from the [dao] layer using the path [3, 4, 5, 6]. [3] is the initial request and [6] is the response to that request.
- Having all the data it needed, layer [metier] calculates the pay for the person selected by the user.
- The [metier] layer can now respond to the request from the [ui] layer made in (d). This is the [7] path.
- Layer [ui] will format these results to present them to the user in an appropriate form and then display them. This is the path [8].
- One can imagine that these results need to be stored in a file or a database. This can be done automatically. In this case, after operation (f), the [metier] layer will ask the [dao] layer to save the results. This will be the path [3, 4, 5, 6]. This can also be done at the user’s request. The path [1-8] will be used by the request-response cycle.
We can see from this description that a layer uses the resources of the layer to its right, never those of the layer to its left.
Our first implementation of this 3-layer architecture will be an application ASP.NET where
- the [dao] and [metier] layers will be implemented by DLL
- the [ui] layer will be implemented by the web form of version 1 (see section 4.2.1).
We begin by implementing the [dao] layer using the NHibernate framework.
7.2. The [dao] data access layer
![]() |
7.2.1. The Visual Studio C# project for the [dao] layer
The Visual Studio project for the [dao] layer is as follows:
![]() |
- in [1], the project as a whole
- in [2], the various classes of the project
- in [3], the project references
- in [4], a folder named [lib] containing the DLL files required for the various projects that follow
In the project references [3], the following DLL files are found:
- NHibernate: for ORM and NHibernate
- MySql.Data: the ADO driver for NET of SGBD MySQL
- Spring.Core: for the Spring framework
- log4net: a logging library
- nunit.framework: a unit testing library
These references were taken from the [lib] [4] folder. Ensure that the "Local Copy" property for all these references is set to "True" [5]:
![]() |
7.2.2. Entities in the [dao] layer
![]() |
The entities (objects) required for the [dao] layer have been gathered in the [entites] folder of the project. Some are already known to us: [Cotisations] described in section 6.3.2.1, [Employe] described in section 6.3.2.3, [Indemnites] described in section 6.3.2.2. They are all in the [Pam.Dao.Entites] namespace.
The class [Employe] evolves as follows:
namespace Pam.Dao.Entites {
public class Employe {
// automatic properties
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; }
// manufacturers
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. The [PamException] class
The [dao] layer is responsible for exchanging data with an external source. This exchange may fail. For example, if information is requested from a remote service on the Internet, retrieving it will fail due to any network outage. For this type of error, it is standard practice in Java to throw an exception. If the exception is not of type [RunTimeException] or a derived type, the method signature must indicate that the method throws an exception. In .NET, all exceptions are unhandled, c.a.d. equivalent to the Java type [RunTimeException]. There is therefore no need to declare that [GetAllIdentitesEmployes, GetEmploye, GetCotisations] methods are likely to throw an exception.
However, it is useful to be able to distinguish between exceptions because their handling may differ. Thus, code that handles various types of exceptions can be written as follows:
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{
...
}
We therefore create an exception type for the [dao] layer of our application. This is the following [PamException] type:
using System;
namespace Pam.Dao.Entites {
public class PamException : Exception {
// the error code
public int Code { get; set; }
// manufacturers
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;
}
}
}
- line 2: the class belongs to the [Pam.Dao.Entites] namespace
- line 4: the class derives from the [Exception] class
- line 7: it has a public property [Code], which is an error code
- In our [dao] layer, we will use two types of constructors:
- the one in lines 18–21, which can be used as shown below:
- (continued)
- or the one in lines 23–26, designed to propagate an exception that has already occurred by wrapping it in a [PamException] exception:
try{
....
}catch (IOException ex){
// encapsulate the exception
throw new PamException("Problème d'accès aux données",ex,10);
}
This second method has the advantage of not losing the information contained in the first exception.
7.2.4. The mapping files for tables <--> classes in NHibernate
Let’s return to the application architecture:
![]() |
During read operations, the NHibernate framework retrieves data from the database and transforms it into objects whose classes we have just presented. During write operations, it does the opposite: starting from objects, it creates, updates, and deletes rows in the database tables. The files handling the table <--> class transformation have already been presented:
![]() |
- the file [Cotisations.hbm.xml] presented in section 6.3.2.1 maps the table [COTISATIONS] to the class [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>
- The [Employe.hbm.xml] file described in section 6.3.2.3 maps the [EMPLOYES] table to the [Employe] class
<?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>
- The [Indemnites.hbm.xml] file presented in section 6.3.2.2 maps the [INDEMNITES] table to the [Indemnites] class
<?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>
Note that in the <hibernate-mapping> tag of these files (line 2), we have the following attributes:
- namespace: Pam.Dao.Entities. The classes [Cotisations], [Employe], and [Indemnites] must be located in this namespace.
- assembly: pam-dao-nhibernate. The [*.hbm.xml] mapping files must be encapsulated in a DLL named [pam-dao-nhibernate]. To achieve this, the C# project is configured as follows:
![]() |
- in [1], the project assembly is named [pam-dao-nhibernate]
- In [2], the mapping files [*.hbm.xml] are integrated into the project assembly
7.2.5. The [IPamDao] interface of the [dao] layer
Let’s return to the architecture of our application:
![]() |
In simple cases, we can start from the [metier] layer to discover the application’s interfaces. To function, it requires data:
- already available in files, databases, or via the network. This data is provided by the [dao] layer.
- not yet available. In that case, it is provided by the [ui] layer, which obtains it from the application user.
What interface must the [dao] layer provide to the [metier] layer? What interactions are possible between these two layers? The [dao] layer must provide the following data to the [metier] layer:
- the list of child care providers to allow the user to select a specific one
- complete information about the selected person (address, index, etc.)
- the benefits associated with the person’s index
- the rates for the various social security contributions
This information is known prior to payroll calculation and can therefore be stored. In the direction [metier] -> [dao], the [metier] layer can ask the [dao] layer to save the result of the payroll calculation. We will not do that here.
With this information, we could attempt an initial definition of the interface for the [dao] layer:
using Pam.Dao.Entites;
namespace Pam.Dao.Service {
public interface IPamDao {
// list of all employee identities
Employe[] GetAllIdentitesEmployes();
// an individual employee with benefits
Employe GetEmploye(string ss);
// list of all cotisations
Cotisations GetCotisations();
}
}
- Line 1: Import the namespace of the entities from the [dao] layer.
- Line 3: The layer [dao] is in the namespace [Pam.Dao.Service]. Elements in the [Pam.Dao.Entites] namespace can be created in multiple instances. Elements in the [Pam.Dao.Service] namespace are created as a single instance (singleton). This is what justified the choice of namespace names.
- Line 4: The interface is named [IPamDao]. It defines three methods:
- Line 6: [GetAllIdentitesEmployes] returns an array of objects of type [Employe], which represents the list of child care providers in a simplified form (last name, first name, SS).
- line 8, [GetEmploye] returns a [Employe] object: the employee with the social security number passed as a parameter to the method, along with the allowances associated with their index.
- Line 10, [GetCotisations] returns the object [Cotisations], which encapsulates the rates of the various social security contributions to be deducted from the gross salary.
7.3. Implementation and testing of the [dao] layer
7.3.1. The Visual Studio project
The Visual Studio project has already been presented. As a reminder:
![]() |
- in [1], the project as a whole
- in [2], the various classes of the project. The [entites] folder contains the entities handled by the [dao] layer as well as the NHibernate mapping files. The [service] folder contains the [IPamDao] interface and its implementation [PamDaoNHibernate]. The [tests] folder contains a console test [Main.cs] and a unit test [NUnit.cs].
- In [3], the project references.
7.3.2. The console test program [Main.cs]
The test program [Main.cs] runs in the following architecture:
![]() |
It is responsible for testing the methods of the [IPamDao] interface. A basic example might be the following:
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 {
// instantiation layer [dao]
IPamDao pamDao = (IPamDao)ContextRegistry.GetContext().GetObject("pamdao");
// list of employee identities
foreach (Employe Employe in pamDao.GetAllIdentitesEmployes()) {
Console.WriteLine(Employe.ToString());
}
// an employee with benefits
Console.WriteLine("------------------------------------");
Console.WriteLine(pamDao.GetEmploye("254104940426058"));
Console.WriteLine("------------------------------------");
// cotisations list
Cotisations cotisations = pamDao.GetCotisations();
Console.WriteLine(cotisations.ToString());
} catch (Exception ex) {
// exception display
Console.WriteLine(ex.ToString());
}
//break
Console.ReadLine();
}
}
}
- line 11: Spring is asked for a reference to the [dao] layer.
- lines 13–15: testing the [GetAllIdentitesEmployes] method of the [IPamDao] interface
- line 18: testing the [GetEmploye] method of the [IPamDao] interface
- line 21: test of the [GetCotisations] method of the [IPamDao] interface
Spring, NHibernate, and log4net are configured by the following [App.config] :
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<!-- configuration sections -->
<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 configuration -->
<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>
<!-- configuration 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>
<!-- This section contains the log4net configuration settings -->
<!-- NOTE IMPORTANTE: logs are not active by default. They must be activated by program
avec l'instruction log4net.Config.XmlConfigurator.Configure();
! -->
<log4net>
...
</log4net>
</configuration>
The configuration of NHibernate (line 10, lines 25–36) was explained in section 6.3.1. Note line 34, which indicates that the mapping files are located in the [pam-dao-nhibernate] assembly. This is the project assembly.
The Spring configuration is defined in lines 6–9 and 15–22. Line 20 defines the [pamdao] object used by the [Main.cs] console program. The <object> tag has the following attributes here:
- type: specifies the class to instantiate. This is the [PamDaoNHibernate] class, which implements the [IPamDao] interface. It can be found in the DLL and [pam-dao-nhibernate] classes of the project.
- init-method: the method of the [PamDaoNHibernate] class to be executed after the class is instantiated
- destroy-method: the method of the [PamDaoNHibernate] class to be executed when the Spring container is destroyed at the end of the project’s execution.
Execution using the database described in Section 6.2 produces the following console output:
- lines 1-2: the 2 employees of type [Employe] with only the information [SS, Nom, Prenom]
- line 4: the employee of type [Employe] with social security number [254104940426058]
- line 5: the rates for cotisations
7.3.3. Writing the [PamDaoNHibernate] class
![]() |
The [IPamDao] interface implemented by the [dao] layer is as follows:
using Pam.Dao.Entites;
namespace Pam.Dao.Service {
public interface IPamDao {
// list of all employee identities
Employe[] GetAllIdentitesEmployes();
// an individual employee with benefits
Employe GetEmploye(string ss);
// list of all cotisations
Cotisations GetCotisations();
}
}
Question: Write the code for the [PamDaoNHibernate] class that implements the [IPamDao] interface above using the NHibernate framework configured as described previously. We will also implement the init and destroy methods executed by Spring. The init method will create the SessionFactory from which we will obtain Session objects. The destroy method will close this SessionFactory. We will use the examples from Section 6.5.
Constraints:
We will assume that certain data requested from the [dao] layer can fit entirely in memory. Thus, to improve performance, the [PamDaoNHibernate] class will store:
- the table [EMPLOYES] in the form (SS, NOM, PRENOM) required by the [GetAllIdentitesEmployes] method in the form of an array of objects of type [Employe]
- the table [COTISATIONS] in the form of a single object of type [Cotisations]
This will be done in the [init] method of the class. The skeleton of the [PamDaoNHibernate] class could be as follows:
using System;
...
namespace Pam.Dao.Service {
class PamDaoNHibernate : IPamDao {
// private fields
private Cotisations cotisations;
private Employe[] employes;
private ISessionFactory sessionFactory = null;
// init
public void init() {
try {
// factory initialization
sessionFactory = new Configuration().Configure().BuildSessionFactory();
// retrieve cotisations rates and employees for caching
.......................
}
// closure SessionFactory
public void destroy() {
if (sessionFactory != null) {
sessionFactory.Close();
}
}
// list of all employee identities
public Employe[] GetAllIdentitesEmployes() {
return employes;
}
// an individual employee with benefits
public Employe GetEmploye(string ss) {
................................
}
// cotisations list
public Cotisations GetCotisations() {
return cotisations;
}
}
}
7.3.4. Unit tests with NUnit
Recommended reading: "C# 2008, Chapter 4: Three-Tier Architectures, NUnit Tests, Spring Framework".
The previous test was visual: we checked on the screen to ensure we were getting the expected results. This method is insufficient in a professional setting. Tests should always be automated as much as possible and aim to require no human intervention. Humans are indeed prone to fatigue, and their ability to verify tests diminishes as the day goes on. The [NUnit] tool helps achieve this automation. It is available at URL [http://www.nunit.org/].
The Visual Studio project for the [dao] layer will evolve as follows:
![]() |
- in [1], the test program [NUnit.cs]
- to [2,3], the project will generate a DLL named [pam-dao-nhibernate.dll]
- in [4], the reference to DLL from the NUnit framework: [nunit.framework.dll]
- In [5], class [Main.cs] will not be included in DLL [pam-dao-nhibernate]
- In [6], class [NUnit.cs] will be included in DLL [pam-dao-nhibernate]
The test class NUnit is as follows:
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 {
// the [dao] layer to be tested
private IPamDao pamDao = null;
// manufacturer
public NunitPamDao() {
// instantiation layer [dao]
pamDao = (IPamDao)ContextRegistry.GetContext().GetObject("pamdao");
}
// init
[SetUp]
public void Init() {
}
[Test]
public void GetAllIdentitesEmployes() {
// audit no. of employees
Expect(2, EqualTo(pamDao.GetAllIdentitesEmployes().Length));
}
[Test]
public void GetCotisations() {
// check cotisations rate
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() {
// individual verification
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() {
// non-existent individual verification
bool erreur = false;
try {
Employe employe1 = pamDao.GetEmploye("xx");
} catch {
erreur = true;
}
Expect(erreur, True);
}
}
}
- line 11: the class has the attribute [TestFixture], which makes it a test class [NUnit].
- line 12: the class derives from the utility class AssertionHelper of the NUnit framework (starting with version 2.4.6).
- Line 14: The private field [pamDao] is an instance of the [dao] layer access interface. Note that the type of this field is an interface, not a class. This means that the [pamDao] instance makes only methods accessible—specifically, those of the [IPamDao] interface.
- The methods tested in the class are those with the [Test] attribute. For all these methods, the testing process is as follows:
- The method with the [SetUp] attribute is executed first. It is used to prepare the resources (network connections, database connections, etc.) required for the test.
- Then the method to be tested is executed
- and finally, the method with the attribute [TearDown] is executed. It is generally used to release the resources allocated by the method with the attribute [SetUp].
- In our test, there are no resources to allocate before each test and then deallocate afterward. Therefore, we do not need methods with the attributes [SetUp] and [TearDown]. For the example, we have presented, in lines 23–26, a method with the attribute [SetUp].
- Lines 17–20: The class constructor initializes the private field [pamDao] using Spring and [App.config].
- Lines 29–32: Test the method [GetAllIdentitesEmployes]
- lines 35-42: test the [GetCotisations] method
- lines 45-53: test the [GetEmploye] method
- lines 56-65: test the [GetEmploye] method when an exception occurs.
Project generation creates DLL and [pam-dao-nhibernate.dll] in the [bin/Release] folder.
![]() |
The [bin/Release] file also contains:
- the DLL files that are part of the project references and have the [Copie locale] attribute set to true: [Spring.Core, MySql.data, NHibernate, log4net]. These DLL files are accompanied by copies of the DLL files that they themselves use:
- [CastleDynamicProxy, Iesi.Collections] for the NHibernate tool
- [antlr.runtime, Common.Logging] for the Spring tool
- The file [pam-dao-nhibernate.dll.config] is a copy of the configuration file [App.config]. It is VS that performs this duplication. At runtime, the file [pam-dao-nhibernate.dll.config] is used, not [App.config].
We load DLL and [pam-dao-nhibernate.dll] using the [NUnit-Gui] and version 2.4.6 tools, and run the tests:

The tests above were successful.
Practical exercise:
Implement the tests for the [PamDaoNHibernate] class on the machine.- Use different [App.config] configuration files to use different SGBD databases (Firebird, MySQL, Postgres, SQL Server)
7.3.5. Generating the DLL from the [dao] layer
Once the [PamDaoNHibernate] class has been written and tested, we will generate DLL from the [dao] layer as follows:
![]() |
- [1], test programs are excluded from the project build
- [2,3], project configuration
- [4], project generation
- DLL is generated in the [bin/Release] [5] folder. We add it to the DLL files already present in the [lib] [6] folder:
![]() |
7.4. The business layer
Let’s revisit the general architecture of the [SimuPaie] application:
![]() |
We now assume that the [dao] layer is complete and has been encapsulated within DLL and [pam-dao-nhibernate.dll]. We are now focusing on the [metier] layer. This is the layer that implements the business rules, in this case the rules for calculating a salary.
7.4.1. The Visual Studio " " project for the [metier] layer
The Visual Studio project for the business layer might look like the following:
![]() |
- in [1], the entire project configured by the file [App.config]
- In [2], the [metier] layer consists of the two [entites, service] folders. The [tests] folder contains a console test program (Main.cs) and a test program NUnit (NUnit.cs).
- In [3], the references used by the project. Note the DLL and [pam-dao-nhibernate] from the [dao] layer studied previously.
7.4.2. The [IPamMetier] interface of the [metier] layer
Let’s return to the application’s overall architecture:
![]() |
What interface must the [metier] layer provide to the [ui] layer? What interactions are possible between these two layers? Let’s recall the web interface that will be presented to the user:
![]() |
- When the form is first displayed, the list of employees must be found in [1]. A simplified list is sufficient (Last Name, First Name, SS). The SS number is required to access additional information about the selected employee (fields 6 through 11).
- Information 12 through 15 are the various rates for cotisations.
- Information 16 through 19 are the allowances linked to the employee’s index
- Information 20 through 24 are the salary components calculated based on user entries 1 through 3.
The [IPamMetier] interface provided to the [ui] layer by the [metier] layer must meet the above requirements. There are many possible interfaces. We propose the following:
using Pam.Dao.Entites;
using Pam.Metier.Entites;
namespace Pam.Metier.Service {
public interface IPamMetier {
// list of all employee identities
Employe[] GetAllIdentitesEmployes();
// ------- salary calculation
FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés);
}
}
- line 7: the method that will populate the [1] combo box
- line 10: the method that will retrieve information 6 through 24. This information has been collected in an object of type [FeuilleSalaire].
7.4.3. Entities in the [metier] layer
The [entites] folder in the Visual Studio project contains the objects handled by the business class: [FeuilleSalaire] and [ElementsSalaire].
![]() |
The class [FeuilleSalaire] encapsulates fields 6 through 24 from the previous form:
using Pam.Dao.Entites;
namespace Pam.Metier.Entites {
public class FeuilleSalaire {
// automatic properties
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);
}
}
}
- line 8: information 6 through 11 about the employee whose salary is being calculated, and information 16 through 19 about their allowances. It is important to note here that a [Employe] object encapsulates a [Indemnites] object representing the employee's allowances.
- line 9: information 12 through 15
- line 10: information 20 through 24
- Lines 13–15: the [ToString] method
The class [ElementsSalaire] encapsulates information 20 through 24 from the form:
namespace Pam.Metier.Entites {
public class ElementsSalaire {
// automatic properties
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);
}
}
}
- lines 4–8: the salary components as explained in the business rules described in section 3.2.
- line 4: the employee's base salary, based on the number of hours worked
- line 5: the cotisations deducted from this base salary
- lines 6 and 7: allowances to be added to the base salary, based on the employee’s index and the number of days worked
- line 8: the net salary to be paid
- Lines 12–15: the [ToString] method of the class.
7.4.4. Implementation of the [metier] layer
![]() |
We will implement the [IPamMetier] interface with two classes:
- [AbstractBasePamMetier], which is an abstract class in which we will implement data access for the [IPamMetier] interface. This class will have a reference to the [dao] layer.
- [PamMetier], a class derived from [AbstractBasePamMetier], which will implement the business rules of the [IPamMetier] interface. It will be unaware of the [dao] layer.
The [AbstractBasePamMetier] class will be as follows:
using Pam.Dao.Entites;
using Pam.Dao.Service;
using Pam.Metier.Entites;
namespace Pam.Metier.Service {
public abstract class AbstractBasePamMetier : IPamMetier {
// data access object
public IPamDao PamDao { get; set; }
// list of all employee identities
public Employe[] GetAllIdentitesEmployes() {
return PamDao.GetAllIdentitesEmployes();
}
// an individual employee with benefits
protected Employe GetEmploye(string ss) {
return PamDao.GetEmploye(ss);
}
// the cotisations
protected Cotisations GetCotisations() {
return PamDao.GetCotisations();
}
// salary calculation
public abstract FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés);
}
}
- line 5: the class belongs to the [Pam.Metier.Service] namespace, like all classes and interfaces in the [metier] layer.
- line 6: the class is abstract (abstract attribute) and implements the [IPamMetier] interface
- line 9: the class holds a reference to the [dao] layer in the form of a public property
- lines 12–14: implementation of the [GetAllIdentitesEmployes] method of the [IPamMetier] interface – uses the method of the same name from the [dao] layer
- lines 17–19: internal method (protected) [GetEmploye] that calls the method of the same name in the [dao] layer – declared as protected so that derived classes can access it without it being public.
- lines 22–24: internal method (protected) [GetCotisations], which calls the method of the same name in the [dao] layer
- line 27: abstract implementation (abstract attribute) of the [GetSalaire] method of the [IPamMetier] interface.
The salary calculation is implemented by the following [PamMetier] class:
using System;
using Pam.Dao.Entites;
using Pam.Metier.Entites;
namespace Pam.Metier.Service {
public class PamMetier : AbstractBasePamMetier {
// wage calculation
public override FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés) {
// SS : employee's SS number
// HeuresTravaillées: number of hours worked
// Days worked: number of days worked
// we get the employee back with his benefits
...
// we recover the various contribution rates
...
// salary components are calculated
...
// we return the payslip
return ...;
}
}
}
- line 7: the class derives from [AbstractBasePamMetier] and therefore implements the [IPamMetier] interface
- line 10: the [GetSalaire] method to be implemented
Question: Write the code for the [GetSalaire] method.
7.4.5. The console test for layer [metier]
Recall the Visual Studio project for layer [metier]:
![]() |
The [Main] test program above tests the methods of the [IPamMetier] interface. A basic example might look like this:
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 {
// instantiation layer [metier]
IPamMetier pamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
// payslip calculations
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()));
}
// break
Console.ReadLine();
}
}
}
- line 11: Spring instantiation of the [metier] layer.
- lines 13–14: tests of the [GetSalaire] method of the [IPamMetier] interface
- lines 15-22: testing the [GetSalaire] method when an exception occurs
The test program uses the following configuration file [App.config] :
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<!-- configuration sections -->
<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 configuration -->
<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>
<!-- configuration NHibernate -->
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
....
</hibernate-configuration>
<!-- This section contains the log4net configuration settings -->
<!-- NOTE IMPORTANTE: logs are not active by default. They must be activated by program
avec l'instruction log4net.Config.XmlConfigurator.Configure();
! -->
<log4net>
...
</log4net>
</configuration>
This file is identical to the [App.config] file used for the [dao] layer project (see section 7.3.2) except for the following details:
- line 20: the id object "pamdao" has the type [Pam.Dao.Service.PamDaoNHibernate] and is found in the [pam-dao-nhibernate] assembly. The layer [dao] is the one discussed previously.
- Lines 21–23: The id object "pammetier" has the type [Pam.Metier.Service.PamMetier] and is found in the [pam-metier-dao-nhibernate] assembly. The project must be configured as follows:
![]() |
- Line 22: The [PamMetier] object instantiated by Spring has a public property [PamDao], which is a reference to the [dao] layer. This property is initialized with the reference to the [dao] layer created on line 20.
Running the code with the database described in Section 6.2 produces the following console output:
- Lines 1-2: The 2 requested pay stubs
- line 3: the exception of type [PamException] caused by a non-existent employee.
7.4.6. Unit tests of the business layer
The previous test was visual: we verified on screen that we were indeed getting the expected results. We will now move on to the non-visual tests for NUnit.
Let’s return to the Visual Studio project for [metier]:
![]() |
- in [1], the test program NUnit
- in [2], the reference to DLL [nunit.framework]
![]() |
- in [3,4], the project build will produce DLL and [pam-metier-dao-nhibernate.dll].
- In [5], the file [NUnit.cs] will be included in the assembly [pam-metier-dao-nhibernate.dll] but not in [Main.cs] or [6]
The test class NUnit is as follows:
using NUnit.Framework;
using Pam.Dao.Entites;
using Pam.Metier.Entites;
using Pam.Metier.Service;
using Spring.Context.Support;
namespace Pam.Metier.Tests {
[TestFixture()]
public class NunitTestPamMetier : AssertionHelper {
// the [metier] layer to test
private IPamMetier pamMetier;
// manufacturer
public NunitTestPamMetier() {
// instantiation layer [dao]
pamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
}
[Test]
public void GetAllIdentitesEmployes() {
// audit no. of employees
Expect(2, EqualTo(pamMetier.GetAllIdentitesEmployes().Length));
}
[Test]
public void GetSalaire1() {
// wage sheet calculation
FeuilleSalaire feuilleSalaire = pamMetier.GetSalaire("254104940426058", 150, 20);
// checks
Expect(368.77, EqualTo(feuilleSalaire.ElementsSalaire.SalaireNet).Within(1E-06));
// non-existent employee payslip
bool erreur = false;
try {
feuilleSalaire = pamMetier.GetSalaire("xx", 150, 20);
} catch (PamException) {
erreur = true;
}
Expect(erreur, True);
}
}
}
- Line 13: The private field [pamMetier] is an instance of the [metier] layer access interface. Note that the type of this field is an interface, not a class. This means that the [PamMetier] instance makes only the methods of the [IPamMetier] interface accessible.
- lines 16–19: The class constructor initializes the private field [pamMetier] using Spring and the configuration file [App.config].
- Lines 23–26: Test the [GetAllIdentitesEmployes] method
- lines 29-42: test the [GetSalaire] method
The above project generates DLL and [pam-metier.dll] in the [bin/Release] folder.
![]() |
The [bin/Release] folder also contains:
- the DLL files that are part of the project references and have the [Copie locale] attribute set to true: [Spring.Core, MySql.data, NHibernate, log4net, pam-dao-nhibernate]. These DLL files are accompanied by copies of the DLL files that they themselves use:
- [CastleDynamicProxy, Iesi.Collections] for the NHibernate tool
- [antlr.runtime, Common.Logging] for the Spring tool
- The file [pam-metier-dao-nhibernate.dll.config] is a copy of the configuration file [App.config].
We load DLL and [pam-metier-dao-nhibernate.dll] using the [NUnit-Gui, version 2.4.6] tool and run the tests:

The tests above were successful.
Practical exercise:
Implement the tests for the [PamMetier] class on the machine.- Use different App.config configuration files to use different SGBD databases (Firebird, MySQL, Postgres, SQL Server)
7.4.7. Generation of the DLL from the [metier] layer
Once the [PamMetier] class has been written and tested, the DLL and [pam-metier-dao-nhibernate.dll] classes will be generated from the [metier] layer by following the method described in section 7.3.5 Care must be taken not to include the test programs [Main.cs] and [NUnit.cs] in DLL. It should then be placed in the [lib] folder of the DLL and [1] layers.
![]() |
7.5. The [web] layer
Let’s review the general architecture of the [SimuPaie] application:
![]() |
We assume that the [dao] and [métier] layers are complete and encapsulated within the DLL and [pam-dao-nhibernate, pam-metier-dao-nhibernate.dll] layers. We will now describe the web layer.
7.5.1. The Visual Web Developer project for the [web] layer
![]() |
- in [1], the project as a whole:
- [Global.asax]: the class instantiated when the web application starts and which handles the application’s initialization
- [Default.aspx]: the web form page
- in [2], the DLL files required by the web application. Note the DLL files for the [dao] and [metier] layers created previously.
7.5.2. Application Configuration
The [Web.config] file that configures the application defines the same data as the [App.config] file configuring the [metier] layer examined previously. This data must be included in the pre-generated code of the [Web.config] file:
<?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 configuration -->
<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>
<!-- configuration 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>
<!-- This section contains the log4net configuration settings -->
<!-- NOTE IMPORTANTE: logs are not active by default. They must be activated by program
avec l'instruction log4net.Config.XmlConfigurator.Configure();
! -->
<log4net>
....
</log4net>
<appSettings/>
<connectionStrings/>
<system.web>
....
....
</configuration>
Lines 9–12, 18–28, and 31–44 contain the Spring and NHibernate configuration described in the [App.config] file of the [metier] layer (see Section 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
{
// --- static application data ---
public static Employe[] Employes;
public static IPamMetier PamMetier = null;
public static string Msg;
public static bool Erreur = false;
// application startup
public void Application_Start(object sender, EventArgs e)
{
// using the configuration file
try
{
// instantiation layer [metier]
PamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
// simplified list of employees
Employes = PamMetier.GetAllIdentitesEmployes();
// we succeeded
Msg = "Base chargée...";
}
catch (Exception ex)
{
// we note the error
Msg = string.Format("L'erreur suivante s'est produite lors de l'accès à la base de données : {0}", ex);
Erreur = true;
}
}
}
}
Note that:
- the [Global.asax.cs] class is instantiated when the application starts, and this instance is accessible to all requests from all users. The static fields in lines 11–14 are thus shared among all users.
- the method [Application_Start] is executed only once after the class is instantiated. This is the method where the application is generally initialized.
The data shared by all users is as follows:
- line 11: the array of objects of type [Employe] that will store the simplified list (SS, NOM, PRENOM) of all employees
- line 12: a reference to the [metier] layer encapsulated in DLL [pam-metier-dao-nhibernate.dll]
- line 13: a message indicating how the initialization ended (successful or with an error)
- line 14: a Boolean indicating whether the initialization ended with an error or not.
In [Application_Start]:
- line 23: Spring instantiates the layers [metier] and [dao] and returns a reference to the layer [metier]. This is stored in the static field [PamMetier] on line 12.
- Line 25: The employee table is requested from layer [metier]
- Line 27: the success message
- Line 32: the error message
7.5.3. The form [Default.aspx]
The form is the one from version 2.

Question: Using the C# code from the [Default.aspx.cs] page of version 2 as a reference, write the [Default.aspx.cs] code for version 3. The only difference is in the salary calculation. Whereas in version 2, we used the API ADO.NET to retrieve information from the database, here we will use the GetSalaire method from the [metier] layer.
Practical exercise:
Deploy the previous web application on a machine- Use different [Web.config] configuration files to use different SGBD databases (Firebird, MySQL, Postgres, SQL Server)





























