7. Додаток [SimuPaie] — , версія 3 — трирівнева архітектура з NHibernate
Рекомендована література: «Мова C# 2008, Розділ 4: Трирівневі архітектури, тести NUnit, фреймворк Spring».
7.1. Загальна архітектура додатка
Додаток [SimuPaie] тепер матиме таку трирівневу структуру:
![]() |
- шар [1-dao] (DAO = Data Access Object) відповідатиме за доступ до даних.
- Рівень [2-métier] відповідатиме за бізнес-логіку додатка — розрахунок заробітної плати.
- Рівень [3-ui] (ui = User Interface) відповідатиме за представлення даних користувачеві та виконання його запитів. Сукупність модулів, що забезпечують цю функцію, ми називаємо [Application]. Вона є посередником між користувачем і системою.
- Ці три шари стануть незалежними завдяки використанню інтерфейсів .NET
- Інтеграція різних рівнів буде здійснена за допомогою Spring IoC
Обробка запиту клієнта відбувається за такими етапами:
- клієнт надсилає запит до додатка.
- додаток обробляє цей запит. Для цього йому може знадобитися допомога шару [métier], якому, у свою чергу, може знадобитися шар [dao], якщо необхідно обмінятися даними з базою даних.
- додаток отримує відповідь від шару [métier]. На основі цієї відповіді він надсилає клієнту відповідний вигляд (= відповідь).
Розглянемо приклад розрахунку заробітної плати виховательки дитячого садка. Для цього знадобиться кілька етапів:
![]() |
- шар [ui] повинен запитати у користувача
- ідентифікаційні дані особи, для якої потрібно розрахувати заробітну плату
- кількість відпрацьованих нею днів
- кількість відпрацьованих годин
- Для цього їй доведеться надати користувачеві список осіб (прізвище, ім’я, SS), що містяться в таблиці [EMPLOYES], щоб користувач міг вибрати одну з них. Шар [ui] використовуватиме шлях [2, 3, 4, 5, 6, 7] для їх отримання. Операція [2] — це запит на список співробітників, а операція [7] — відповідь на цей запит. Після цього рівень [ui] може представити список співробітників користувачеві за допомогою [8].
- Користувач передасть на рівень [ui] кількість відпрацьованих днів, а також кількість відпрацьованих годин. Це операція [1], зазначена вище. На цьому етапі користувач взаємодіє лише з рівнем [ui]. Саме цей рівень, зокрема, перевіряє правильність введених даних. Після цього користувач подає запит на розрахунок заробітної плати.
- Рівень [ui] звернеться до бізнес-рівня з проханням виконати цей розрахунок. Для цього він передасть йому дані, отримані від користувача. Це операція [2].
- Рівень [metier] потребує певної інформації для виконання своєї роботи:
- більш повну інформацію про особу (адреса, індекс тощо)
- виплати, пов’язані з її індексом
- ставки різних соціальних внесків, що стягуються з валової заробітної плати
Вона запитає цю інформацію у шару [dao] за шляхом [3, 4, 5, 6]. [3] — це початковий запит, а [6] — відповідь на цей запит.
- Отримавши всі необхідні дані, шар [metier] розраховує заробітну плату особи, обраної користувачем.
- Тепер рівень [metier] може відповісти на запит рівня [ui], зроблений у пункті (d). Це шлях [7].
- шар [ui] оформить ці результати, щоб представити їх користувачеві у відповідному вигляді, а потім відобразить їх. Це шлях [8].
- Можна припустити, що ці результати мають бути збережені у файлі або базі даних. Це можна зробити автоматично. У цьому випадку після операції (f) шар [metier] звернеться до шару [dao] із запитом на збереження результатів. Це буде шлях [3, 4, 5, 6]. Це також можна зробити за запитом користувача. У цьому випадку цикл «запит — відповідь» використовуватиме шлях [1-8].
З цього опису видно, що кожен рівень використовує ресурси рівня, розташованого праворуч від нього, але ніколи — рівня, розташованого ліворуч.
Нашою першою реалізацією цієї трирівневої архітектури стане додаток ASP.NET, де
- шари [dao] та [metier] будуть реалізовані за допомогою DLL
- а рівень [ui] буде реалізовано за допомогою веб-форми версії 1 (див. параграф 4.2.1).
Почнемо з реалізації шару [dao] за допомогою фреймворку NHibernate.
7.2. Рівень [dao] для доступу до даних
![]() |
7.2.1. Проєкт Visual Studio C# для шару [dao]
Проєкт Visual Studio для шару [dao] має такий вигляд:
![]() |
- у [1] — проект у цілому
- у [2] — різні класи проекту
- у [3] — посилання проекту.
- у [4] — папка [lib], у якій зібрано DLL, необхідні для різних проектів, що будуть розглянуті далі
У посиланнях [3] проекту містяться такі DLL:
- NHibernate: для ORM та NHibernate
- MySql.Data: драйвер ADO.NET для SGBD MySQL
- Spring.Core: для фреймворку Spring
- log4net: бібліотека логів
- nunit.framework: бібліотека модульних тестів
Ці посилання були взяті з папки [lib] [4]. Слід переконатися, що для всіх цих посилань властивість «Локальна копія» має значення «True» [5]:
![]() |
7.2.2. Об’єкти шару [dao]
![]() |
Елементи (об’єкти), необхідні для шару [dao], зібрано у папці [entites] цього проєкту. Деякі з них нам уже відомі: [Cotisations], описана в параграфі 6.3.2.1, [Employe], описана в параграфі 6.3.2.3, [Indemnites], описана в параграфі 6.3.2.2. Усі вони знаходяться в просторі імен [Pam.Dao.Entites].
Клас [Employe] розвивається наступним чином:
namespace Pam.Dao.Entites {
public class Employe {
// автоматичні властивості
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; }
// конструктори
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. Клас [PamException]
Рівень [dao] відповідає за обмін даними із зовнішнім джерелом. Цей обмін може завершитися невдачею. Наприклад, якщо інформація запитується у віддаленої служби в Інтернеті, її отримання завершиться невдачею у разі будь-якої несправності мережі. У разі таких помилок у Java зазвичай генерується виняток. Якщо виняток не належить до типу [RunTimeException] або його похідних, у сигнатурі методу слід вказати, що він генерує (throws) виняток. У .NET усі винятки є неконтрольованими, c.a.d. еквівалентні типу [RunTimeException] у Java. Отже, немає потреби заявляти, що методи [GetAllIdentitesEmployes, GetEmploye, GetCotisations] можуть генерувати виняток.
Проте цікаво мати можливість розрізняти винятки між собою, оскільки їх обробка може відрізнятися. Таким чином, код, що обробляє різні типи винятків, можна написати наступним чином:
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{
...
}
Отже, ми створюємо тип винятків для шару [dao] нашого додатка. Це такий тип [PamException]:
using System;
namespace Pam.Dao.Entites {
public class PamException : Exception {
// код помилки
public int Code { get; set; }
// конструктори
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;
}
}
}
- рядок 2: клас належить до простору імен [Pam.Dao.Entites]
- рядок 4: клас походить від класу [Exception]
- рядок 7: він має публічну властивість [Code], яка є кодом помилки
- у нашому шарі [dao] ми будемо використовувати два типи конструкторів:
- той, що на рядках 18–21, який можна використовувати, як показано нижче:
- (продовження)
- або той, що наведено в рядках 23–26, призначений для передачі винятку, що вже виник, шляхом його інкапсуляції у виняток типу [PamException]:
try{
....
}catch (IOException ex){
// виняток інкапсулюється
throw new PamException("Problème d'accès aux données",ex,10);
}
Перевага цього другого методу полягає в тому, що інформація, яка може міститися в першому винятку, не втрачається.
7.2.4. Файли відповідності <--> класи NHibernate
Повернемося до архітектури додатка:
![]() |
Під час читання фреймворк NHibernate використовує дані з бази даних і перетворює їх на об’єкти, класи яких ми щойно розглянули. Під час запису він робить навпаки: на основі об’єктів він створює, оновлює та видаляє рядки в таблицях бази даних. Файли, що забезпечують перетворення таблиць <--> класів, вже були представлені:
![]() |
- файл [Cotisations.hbm.xml], представлений у параграфі 6.3.2.1, встановлює відповідність між таблицею [COTISATIONS] та класом [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>
- файл [Employe.hbm.xml], наведений у розділі 6.3.2.3, встановлює відповідність між таблицею [EMPLOYES] та класом [Employe]
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="Pam.Dao.Entites" assembly="pam-dao-nhibernate">
<class name="Employe" table="EMPLOYES">
<id name="Id" column="ID">
<generator class="native" />
</id>
<version name="Version" column="VERSION"/>
<property name="SS" column="SS" length="15" not-null="true" unique="true"/>
<property name="Nom" column="NOM" length="30" not-null="true"/>
<property name="Prenom" column="PRENOM" length="20" not-null="true"/>
<property name="Adresse" column="ADRESSE" length="50" not-null="true" />
<property name="Ville" column="VILLE" length="30" not-null="true"/>
<property name="CodePostal" column="CP" length="5" not-null="true"/>
<many-to-one name="Indemnites" column="INDEMNITE_ID" cascade="save-update" lazy="false"/>
</class>
</hibernate-mapping>
- файл [Indemnites.hbm.xml], наведений у розділі 6.3.2.2, встановлює відповідність між таблицею [INDEMNITES] та класом [Indemnites]
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="Pam.Dao.Entites" assembly="pam-dao-nhibernate">
<class name="Indemnites" table="INDEMNITES">
<id name="Id" column="ID">
<generator class="native" />
</id>
<version name="Version" column="VERSION"/>
<property name="Indice" column="INDICE" not-null="true" unique="true"/>
<property name="BaseHeure" column="BASE_HEURE" not-null="true"/>
<property name="EntretienJour" column="ENTRETIEN_JOUR" not-null="true"/>
<property name="RepasJour" column="REPAS_JOUR" not-null="true" />
<property name="IndemnitesCp" column="INDEMNITES_CP" not-null="true"/>
</class>
</hibernate-mapping>
Слід зазначити, що в тезі <hibernate-mapping> цих файлів (рядок 2) містяться такі атрибути:
- namespace : Pam.Dao.Entites. Класи [Cotisations], [Employe] та [Indemnites] повинні знаходитися в цьому просторі імен.
- assembly: pam-dao-nhibernate. Файли маппінгу [*.hbm.xml] мають бути вкладені у файл DLL з іменем [pam-dao-nhibernate]. Щоб досягти цього результату, проект C# налаштовується наступним чином:
![]() |
- у [1], збірка проєкту має назву [pam-dao-nhibernate]
- у [2] файли маппінгу [*.hbm.xml] інтегровані в збірку проекту [3]
7.2.5. Інтерфейс [IPamDao] шару [dao]
Повернемося до архітектури нашого додатка:
![]() |
У простих випадках можна почати з шару [metier], щоб виявити інтерфейси додатка. Для роботи йому потрібні дані:
- які вже є у файлах, базах даних або через мережу. Їх надає рівень [dao].
- ще недоступні. У такому разі їх надає рівень [ui], який отримує їх від користувача додатка.
Який інтерфейс повинен надавати рівень [dao] рівню [metier]? Які можливі взаємодії між цими двома рівнями? Рівень [dao] повинен надавати рівень [metier] такі дані:
- перелік нянь, щоб користувач міг вибрати конкретну
- повну інформацію про обрану особу (адреса, індекс тощо)
- виплати, пов’язані з індексом особи
- ставки різних соціальних внесків
Ця інформація, насправді, відома ще до розрахунку заробітної плати і тому може бути збережена. У напрямку [metier] -> [dao] рівень [metier] може звернутися до рівня [dao] із запитом на збереження результату розрахунку заробітної плати. Тут ми цього не робитимемо.
Маючи цю інформацію, можна спробувати дати перше визначення інтерфейсу шару [dao]:
using Pam.Dao.Entites;
namespace Pam.Dao.Service {
public interface IPamDao {
// список усіх ідентифікаторів співробітників
Employe[] GetAllIdentitesEmployes();
// конкретний працівник із його виплатами
Employe GetEmploye(string ss);
// список усіх внесків
Cotisations GetCotisations();
}
}
- рядок 1: імпортуємо простір імен сутностей шару [dao].
- рядок 3: шар [dao] знаходиться в просторі імен [Pam.Dao.Service]. Елементи простору імен [Pam.Dao.Entites] можуть створюватися у декількох екземплярах. Елементи простору імен [Pam.Dao.Service] створюються в єдиному екземплярі (синглтон). Саме це зумовило вибір назв просторів імен.
- рядок 4: інтерфейс має назву [IPamDao]. Він визначає три методи:
- рядок 6: [GetAllIdentitesEmployes] повертає масив об’єктів типу [Employe], який представляє список нянь у спрощеній формі (прізвище, ім’я, SS).
- у рядку 8 метод [GetEmploye] повертає об’єкт [Employe]: працівника, номер соціального страхування якого передано як параметр методу, разом із виплатами, пов’язаними з його індексом.
- рядок 10, [GetCotisations] повертає об’єкт [Cotisations], який інкапсулює ставки різних соціальних внесків, що підлягають утриманню з валової заробітної плати.
7.3. Реалізація та тестування шару [dao]
7.3.1. Проєкт Visual Studio
Проект Visual Studio вже було представлено. Нагадаємо:
![]() |
- у [1], проект у цілому
- у [2] — різні класи проекту. Папка [entites] містить сутності, з якими працює шар [dao], а також файли маппінгу NHibernate. Папка [service] містить інтерфейс [IPamDao] та його реалізацію [PamDaoNHibernate]. Папка [tests] містить консольний тест [Main.cs] та модульний тест [NUnit.cs].
- У файлі [3] містяться посилання на проект.
7.3.2. Консольна тестова програма [Main.cs]
Тестова програма [Main.cs] виконується в такій архітектурі:
![]() |
Вона призначена для тестування методів інтерфейсу [IPamDao]. Ось приклад базового коду:
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 {
// інстанціювання шару [dao]
IPamDao pamDao = (IPamDao)ContextRegistry.GetContext().GetObject("pamdao");
// список ідентифікаційних даних співробітників
foreach (Employe Employe in pamDao.GetAllIdentitesEmployes()) {
Console.WriteLine(Employe.ToString());
}
// працівник із його виплатами
Console.WriteLine("------------------------------------");
Console.WriteLine(pamDao.GetEmploye("254104940426058"));
Console.WriteLine("------------------------------------");
// перелік внесків
Cotisations cotisations = pamDao.GetCotisations();
Console.WriteLine(cotisations.ToString());
} catch (Exception ex) {
// відображення винятку
Console.WriteLine(ex.ToString());
}
//пауза
Console.ReadLine();
}
}
}
- рядок 11: до Spring надсилається запит на отримання посилання на рівень [dao].
- рядки 13–15: тестування методу [GetAllIdentitesEmployes] інтерфейсу [IPamDao]
- рядок 18: тестування методу [GetEmploye] інтерфейсу [IPamDao]
- рядок 21: тестування методу [GetCotisations] інтерфейсу [IPamDao]
Spring, NHibernate та log4net налаштовуються за допомогою такого файлу [App.config] :
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<!-- розділи налаштувань -->
<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 -->
<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>
<!-- конфігурація 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>
<!-- Цей розділ містить параметри конфігурації log4net -->
<!-- NOTE IMPORTANTE: журнали за замовчуванням неактивні. Їх потрібно активувати програмно
avec l'instruction log4net.Config.XmlConfigurator.Configure();
! -->
<log4net>
...
</log4net>
</configuration>
Налаштування NHibernate (рядок 10, рядки 25–36) було описано в розділі 6.3.1. Зверніть увагу на рядок 34, який вказує, що файли маппінгу знаходяться в збірці [pam-dao-nhibernate]. Це збірка проекту.
Налаштування Spring здійснюється у рядках 6–9, 15–22. У рядку 20 визначається об’єкт [pamdao], який використовується консольною програмою [Main.cs]. Тег <object> тут має такі атрибути:
- type: визначає клас, який потрібно інстанціювати. Саме клас [PamDaoNHibernate] реалізує інтерфейс [IPamDao]. Його можна знайти у файлі DLL [pam-dao-nhibernate] цього проєкту.
- init-method: метод класу [PamDaoNHibernate], який слід виконати після створення екземпляра класу
- destroy-method: метод класу [PamDaoNHibernate], який має виконуватися під час знищення контейнера Spring після завершення виконання проекту.
Виконання з використанням бази даних, описаної в розділі 6.2, дає такий результат у консолі:
- рядки 1–2: 2 співробітники типу [Employe] з єдиною інформацією [SS, Nom, Prenom]
- рядок 4: працівник типу [Employe] із номером соціального страхування [254104940426058]
- рядок 5: ставки внесків
7.3.3. Запис класу [PamDaoNHibernate]
![]() |
Інтерфейс [IPamDao], реалізований рівнем [dao], має такий вигляд:
using Pam.Dao.Entites;
namespace Pam.Dao.Service {
public interface IPamDao {
// перелік усіх ідентифікаційних даних співробітників
Employe[] GetAllIdentitesEmployes();
// конкретний працівник із його надбавками
Employe GetEmploye(string ss);
//— перелік усіх внесків
Cotisations GetCotisations();
}
}
Завдання: написати код класу [PamDaoNHibernate], що реалізує наведений вище інтерфейс [IPamDao], використовуючи фреймворк NHibernate, налаштований так, як було показано раніше. Також слід реалізувати методи init та destroy, що виконуються Spring. Метод init створить SessionFactory, з якого ми отримаємо об’єкти Session. Метод destroy закриє цей SessionFactory. Скористаємося прикладами з розділу 6.5.
Обмеження:
Припустимо, що певні дані, які запитуються у шарі [dao], можуть повністю вміститися в пам’яті. Отже, для підвищення продуктивності клас [PamDaoNHibernate] зберігатиме:
- таблицю [EMPLOYES] у вигляді (SS, NOM, PRENOM), необхідну для методу [GetAllIdentitesEmployes] у вигляді масиву об’єктів типу [Employe]
- таблиці [COTISATIONS] у вигляді єдиного об’єкта типу [Cotisations]
Це буде зроблено в методі [init] класу. Скелет класу [PamDaoNHibernate] може виглядати наступним чином:
using System;
...
namespace Pam.Dao.Service {
class PamDaoNHibernate : IPamDao {
// приватні поля
private Cotisations cotisations;
private Employe[] employes;
private ISessionFactory sessionFactory = null;
// ініціалізація
public void init() {
try {
// ініціалізація фабрики
sessionFactory = new Configuration().Configure().BuildSessionFactory();
// отримання ставок внесків та даних про працівників для збереження їх у кеші
.......................
}
// закриття SessionFactory
public void destroy() {
if (sessionFactory != null) {
sessionFactory.Close();
}
}
// список усіх ідентифікаційних даних співробітників
public Employe[] GetAllIdentitesEmployes() {
return employes;
}
// конкретний працівник із його виплатами
public Employe GetEmploye(string ss) {
................................
}
// перелік внесків
public Cotisations GetCotisations() {
return cotisations;
}
}
}
7.3.4. Юнітарні тести з NUnit
Рекомендована література: «Мова C# 2008, Розділ 4: Трирівневі архітектури, тестування NUnit, фреймворк Spring».
Попереднє тестування було візуальним: на екрані перевіряли, чи дійсно отримуємо очікувані результати. У професійному середовищі такого підходу недостатньо. Тестування завжди має бути максимально автоматизованим і спрямованим на те, щоб не потребувати жодного втручання людини. Адже людина схильна до втоми, і її здатність перевіряти тести з часом знижується. Інструмент [NUnit] допомагає здійснити цю автоматизацію. Він доступний на URL [http://www.nunit.org/].
Проєкт Visual Studio для шару [dao] буде розвиватися наступним чином:
![]() |
- до [1], тестова програма [NUnit.cs]
- у [2,3], проект згенерує файл DLL з назвою [pam-dao-nhibernate.dll]
- у [4], посилання на DLL з фреймворку NUnit: [nunit.framework.dll]
- в [5], клас [Main.cs] не буде включено до DLL [pam-dao-nhibernate]
- у [6] клас [NUnit.cs] буде включено до DLL [pam-dao-nhibernate]
Тестовий клас NUnit має такий вигляд:
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 {
// шар [dao] для тестування
private IPamDao pamDao = null;
// конструктор
public NunitPamDao() {
// інстанціювання шару [dao]
pamDao = (IPamDao)ContextRegistry.GetContext().GetObject("pamdao");
}
// ініціалізація
[SetUp]
public void Init() {
}
[Test]
public void GetAllIdentitesEmployes() {
// перевірка кількості працівників
Expect(2, EqualTo(pamDao.GetAllIdentitesEmployes().Length));
}
[Test]
public void GetCotisations() {
// перевірка ставки внесків
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() {
// перевірка осіб
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() {
// перевірка неіснуючої особи
bool erreur = false;
try {
Employe employe1 = pamDao.GetEmploye("xx");
} catch {
erreur = true;
}
Expect(erreur, True);
}
}
}
- рядок 11: клас має атрибут [TestFixture], що робить його тестовим класом [NUnit].
- рядок 12: клас походить від утилітарного класу AssertionHelper фреймворку NUnit (починаючи з версії 2.4.6).
- рядок 14: приватне поле [pamDao] є екземпляром інтерфейсу доступу до шару [dao]. Слід зауважити, що тип цього поля — це інтерфейс, а не клас. Це означає, що екземпляр [pamDao] надає доступ лише до методів інтерфейсу [IPamDao].
- Методи, що тестуються в класі, — це ті, що мають атрибут [Test]. Для всіх цих методів процес тестування відбувається наступним чином:
- спочатку виконується метод з атрибутом [SetUp]. Він слугує для підготовки ресурсів (мережевих з’єднань, з’єднань із базами даних тощо), необхідних для тестування.
- потім виконується метод, що тестується
- і, нарешті, виконується метод з атрибутом [TearDown]. Він, як правило, слугує для звільнення ресурсів, задіяних методом з атрибутом [SetUp].
- У нашому тесті немає ресурсів, які потрібно виділяти перед кожним тестом і потім звільняти. Тому нам не потрібні методи з атрибутами [SetUp] та [TearDown]. Для прикладу ми навели у рядках 23–26 метод з атрибутом [SetUp].
- рядки 17–20: конструктор класу ініціалізує приватне поле [pamDao] за допомогою Spring та [App.config].
- рядки 29–32: тестують метод [GetAllIdentitesEmployes]
- рядки 35–42: тестують метод [GetCotisations]
- рядки 45–53: тестують метод [GetEmploye]
- рядки 56–65: тестують метод [GetEmploye] під час виникнення винятку.
Під час генерації проєкту у папці [bin/Release] створюються файли DLL та [pam-dao-nhibernate.dll].
![]() |
Папка [bin/Release] також містить:
- файли DLL, які входять до списку посилань проекту та мають атрибут [Copie locale] із значенням «true»: [Spring.Core, MySql.data, NHibernate, log4net]. Ці DLL супроводжуються копіями DLL, які вони самі використовують:
- [CastleDynamicProxy, Iesi.Collections] для інструменту NHibernate
- [antlr.runtime, Common.Logging] для інструменту Spring
- файл [pam-dao-nhibernate.dll.config] є копією конфігураційного файлу [App.config]. Це дублювання здійснює файл VS. Під час виконання використовується файл [pam-dao-nhibernate.dll.config], а не [App.config].
Завантажуємо DLL та [pam-dao-nhibernate.dll] за допомогою інструменту [NUnit-Gui], версія 2.4.6, і виконуємо тести:

Як бачимо, тести пройшли успішно.
Практичне завдання:
запустити на комп’ютері тести класу [PamDaoNHibernate].- використовувати різні конфігураційні файли [App.config] для роботи з різними базами даних (Firebird, MySQL, Postgres, SQL Server)
7.3.5. Генеруванн з DLL на основі шару [dao]
Після написання та тестування класу [PamDaoNHibernate] буде згенеровано DLL з шару [dao] наступним чином:
![]() |
- [1], тестові програми виключаються зі збірки проекту
- [2,3], конфігурація проекту
- [4], генерація проекту
- файл DLL генерується у папці [bin/Release] [5]. Ми додаємо його до файлів DLL, які вже знаходяться у папці [lib] [6]:
![]() |
7.4. Бізнес-шар
Повернемося до загальної архітектури додатка [SimuPaie]:
![]() |
Тепер ми вважаємо, що рівень [dao] вже реалізовано і що він був інкапсульований у DLL [pam-dao-nhibernate.dll]. Тепер ми переходимо до шару [metier]. Саме він реалізує бізнес-правила, в даному випадку — правила розрахунку заробітної плати.
7.4.1. Проєкт у Visual Studio для шару [metier]
Проєкт Visual Studio для бізнес-шару може виглядати наступним чином:
![]() |
- у [1] весь проект, налаштований файлом [App.config]
- у [2], бізнес-шар [metier] складається з двох папок [entites, service]. Папка [tests] містить консольну тестову програму (Main.cs) та тестову програму NUnit (NUnit.cs).
- У [3] містяться посилання, що використовуються в проєкті. Слід звернути увагу на DLL та [pam-dao-nhibernate] з раніше розглянутого шару [dao].
7.4.2. Інтерфейс [IPamMetier] шару [metier]
Повернемося до загальної архітектури додатка:
![]() |
Який інтерфейс повинен надавати рівень [metier] рівню [ui]? Які можливі взаємодії між цими двома рівнями? Згадаймо веб-інтерфейс, який буде представлений користувачеві:
![]() |
- при першому відображенні форми у [1] має бути список співробітників. Достатньо спрощеного списку (Прізвище, Ім’я, SS). Номер SS необхідний для доступу до додаткової інформації про вибраного співробітника (поля 6–11).
- Позиції 12–15 — це різні ставки внесків.
- Позиції 16–19 — це надбавки, пов’язані з індексом працівника
- дані з 20 по 24 — це складові заробітної плати, розраховані на основі даних з 1 по 3, введених користувачем.
Інтерфейс [IPamMetier], що надається рівнем [ui] рівню [metier], повинен відповідати наведеним вище вимогам. Існує багато можливих варіантів інтерфейсів. Ми пропонуємо такий:
using Pam.Dao.Entites;
using Pam.Metier.Entites;
namespace Pam.Metier.Service {
public interface IPamMetier {
// перелік усіх ідентифікаційних даних працівників
Employe[] GetAllIdentitesEmployes();
// ------- розрахунок заробітної плати
FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés);
}
}
- рядок 7: метод, який дозволить заповнити комбінований список [1]
- рядок 10: метод, який дозволить отримати дані з 6 по 24. Вони були зібрані в об’єкті типу [FeuilleSalaire].
7.4.3. Елементи шару [metier]
Папка [entites] проекту Visual Studio містить об’єкти, з якими працює бізнес-клас: [FeuilleSalaire] та [ElementsSalaire].
![]() |
Клас [FeuilleSalaire] містить інформацію з полів 6–24 попередньої форми:
using Pam.Dao.Entites;
namespace Pam.Metier.Entites {
public class FeuilleSalaire {
// автоматичні параметри
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);
}
}
}
- рядок 8: дані з 6 по 11 про працівника, для якого розраховується заробітна плата, та дані з 16 по 19 про його надбавки. Слід пам’ятати, що об’єкт [Employe] містить об’єкт [Indemnites], який представляє надбавки працівника.
- рядок 9: дані з 12 по 15
- рядок 10: дані з 20 по 24
- рядки 13–15: метод [ToString]
Клас [ElementsSalaire] інкапсулює дані з 20 по 24 форми:
namespace Pam.Metier.Entites {
public class ElementsSalaire {
// автоматичні властивості
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);
}
}
}
- рядки 4–8: складові заробітної плати, як пояснено в бізнес-правилах, описаних у розділі 3.2.
- рядок 4: базова заробітна плата працівника, що залежить від кількості відпрацьованих годин
- рядок 5: внески, що утримуються з цієї базової заробітної плати
- рядки 6 і 7: надбавки, що додаються до базової заробітної плати, залежно від індексу працівника та кількості відпрацьованих днів
- рядок 8: чиста заробітна плата до виплати
- рядки 12–15: метод [ToString] класу.
7.4.4. Реалізація шару [metier]
![]() |
Ми реалізуємо інтерфейс [IPamMetier] за допомогою двох класів:
- [AbstractBasePamMetier] — це абстрактний клас, у якому ми реалізуємо доступ до даних інтерфейсу [IPamMetier]. Цей клас матиме посилання на шар [dao].
- [PamMetier] — клас, похідний від [AbstractBasePamMetier], який, у свою чергу, реалізує бізнес-правила інтерфейсу [IPamMetier]. Він не буде знати про існування шару [dao].
Клас [AbstractBasePamMetier] матиме такий вигляд:
using Pam.Dao.Entites;
using Pam.Dao.Service;
using Pam.Metier.Entites;
namespace Pam.Metier.Service {
public abstract class AbstractBasePamMetier : IPamMetier {
// об'єкт доступу до даних
public IPamDao PamDao { get; set; }
// список усіх ідентифікаторів співробітників
public Employe[] GetAllIdentitesEmployes() {
return PamDao.GetAllIdentitesEmployes();
}
// конкретний працівник із його виплатами
protected Employe GetEmploye(string ss) {
return PamDao.GetEmploye(ss);
}
// внески
protected Cotisations GetCotisations() {
return PamDao.GetCotisations();
}
// розрахунок заробітної плати
public abstract FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés);
}
}
- рядок 5: клас належить до простору імен [Pam.Metier.Service], як і всі класи та інтерфейси рівня [metier].
- рядок 6: клас є абстрактним (атрибут abstract) і реалізує інтерфейс [IPamMetier]
- рядок 9: клас містить посилання на прошарок [dao] у вигляді публічної властивості
- рядки 12–14: реалізація методу [GetAllIdentitesEmployes] інтерфейсу [IPamMetier] — використовує однойменний метод шару [dao]
- рядки 17–19: внутрішній (protected) метод [GetEmploye], який викликає однойменний метод шару [dao] — оголошений як protected, щоб похідні класи могли мати до нього доступ, не роблячи його публічним.
- рядки 22–24: внутрішній (protected) метод [GetCotisations], який викликає однойменний метод шару [dao]
- рядок 27: абстрактна реалізація (атрибут abstract) методу [GetSalaire] інтерфейсу [IPamMetier].
Розрахунок заробітної плати реалізовано за допомогою наступного класу [PamMetier]:
using System;
using Pam.Dao.Entites;
using Pam.Metier.Entites;
namespace Pam.Metier.Service {
public class PamMetier : AbstractBasePamMetier {
// розрахунок заробітної плати
public override FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés) {
// SS: номер SS працівника
// HeuresTravaillées: кількість відпрацьованих годин
// Відпрацьовані дні: кількість відпрацьованих днів
// отримуємо дані про працівника разом з його виплатами
...
// отримуємо різні ставки внесків
...
// розраховуються складові заробітної плати
...
// видача відомості про заробітну плату
return ...;
}
}
}
- рядок 7: клас походить від [AbstractBasePamMetier] і, отже, реалізує інтерфейс [IPamMetier]
- рядок 10: метод [GetSalaire], який потрібно реалізувати
Завдання: написати код методу [GetSalaire].
7.4.5. Консольний тест шару [metier]
Нагадаємо про проект Visual Studio для шару [metier]:
![]() |
Наведена вище тестова програма [Main] перевіряє методи інтерфейсу [IPamMetier]. Ось приклад простого коду:
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 {
// інстанціювання шару [metier]
IPamMetier pamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
// розрахунок відомостей про заробітну плату
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()));
}
// пауза
Console.ReadLine();
}
}
}
- рядок 11: створення екземпляра шару [metier] за допомогою Spring.
- рядки 13–14: тестування методу [GetSalaire] інтерфейсу [IPamMetier]
- рядки 15–22: тестування методу [GetSalaire] у разі виникнення винятку
Програма тестування використовує такий файл конфігурації [App.config] :
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<!-- розділи конфігурації -->
<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 -->
<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>
<!-- конфігурація NHibernate -->
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
....
</hibernate-configuration>
<!-- Цей розділ містить параметри конфігурації log4net -->
<!-- NOTE IMPORTANTE: журнали за замовчуванням неактивні. Їх потрібно активувати програмно
avec l'instruction log4net.Config.XmlConfigurator.Configure();
! -->
<log4net>
...
</log4net>
</configuration>
Цей файл ідентичний файлу [App.config], що використовується для проекту шару [dao] (див. розділ 7.3.2), за винятком таких деталей:
- рядок 20: об’єкт з ідентифікатором «pamdao» має тип [Pam.Dao.Service.PamDaoNHibernate] і знаходиться в збірці [pam-dao-nhibernate]. Шар [dao] — це той, що розглядався раніше.
- рядки 21–23: об’єкт з ідентифікатором «pammetier» має тип [Pam.Metier.Service.PamMetier] і знаходиться в збірці [pam-metier-dao-nhibernate]. Проект потрібно налаштувати таким чином:
![]() |
- рядок 22: об’єкт [PamMetier], інстанційований Spring, має публічну властивість [PamDao], яка є посиланням на шар [dao]. Ця властивість ініціалізується посиланням на шар [dao], створений у рядку 20.
Виконання коду з використанням бази даних, описаної в розділі 6.2, дає такий результат у консолі:
- рядки 1–2: 2 запитувані відомості про заробітну плату
- рядок 3: виняток типу [PamException], спричинений відсутністю співробітника.
7.4.6. Модульні тести бізнес-шару
Попереднє тестування було візуальним: ми перевіряли на екрані, чи дійсно отримуємо очікувані результати. Тепер перейдемо до невізуальних тестів NUnit.
Повернемося до проекту Visual Studio з назвою [metier]:
![]() |
- у [1], тестову програму NUnit
- у [2], посилання на DLL [nunit.framework]
![]() |
- у [3,4], під час генерації проекту будуть створені файли DLL та [pam-metier-dao-nhibernate.dll].
- у [5] файл [NUnit.cs] буде включено до збірки [pam-metier-dao-nhibernate.dll], але не [Main.cs] та [6]
Тестовий клас NUnit має такий вигляд:
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 {
// необхідно протестувати рівень [metier]
private IPamMetier pamMetier;
// конструктор
public NunitTestPamMetier() {
// інстанціювання шару [dao]
pamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
}
[Test]
public void GetAllIdentitesEmployes() {
// перевірка кількості працівників
Expect(2, EqualTo(pamMetier.GetAllIdentitesEmployes().Length));
}
[Test]
public void GetSalaire1() {
// розрахунок відомості про заробітну плату
FeuilleSalaire feuilleSalaire = pamMetier.GetSalaire("254104940426058", 150, 20);
// перевірки
Expect(368.77, EqualTo(feuilleSalaire.ElementsSalaire.SalaireNet).Within(1E-06));
// платіжна відомість неіснуючого працівника
bool erreur = false;
try {
feuilleSalaire = pamMetier.GetSalaire("xx", 150, 20);
} catch (PamException) {
erreur = true;
}
Expect(erreur, True);
}
}
}
- рядок 13: приватне поле [pamMetier] є екземпляром інтерфейсу доступу до шару [metier]. Слід зауважити, що тип цього поля — це інтерфейс, а не клас. Це означає, що екземпляр [PamMetier] надає доступ лише до методів інтерфейсу [IPamMetier].
- рядки 16–19: конструктор класу ініціалізує приватне поле [pamMetier] за допомогою Spring та файлу конфігурації [App.config].
- рядки 23–26: перевіряють метод [GetAllIdentitesEmployes]
- рядки 29–42: тестують метод [GetSalaire]
Вищезазначений проєкт генерує файли DLL та [pam-metier.dll] у папці [bin/Release].
![]() |
Папка [bin/Release] також містить:
- файли DLL, які входять до списку посилань проекту та мають атрибут [Copie locale] із значенням «true»: [Spring.Core, MySql.data, NHibernate, log4net, pam-dao-nhibernate]. Ці DLL супроводжуються копіями DLL, які вони самі використовують:
- [CastleDynamicProxy, Iesi.Collections] для інструменту NHibernate
- [antlr.runtime, Common.Logging] для інструменту Spring
- файл [pam-metier-dao-nhibernate.dll.config] є копією файлу конфігурації [App.config].
Завантажуємо DLL та [pam-metier-dao-nhibernate.dll] за допомогою інструменту [NUnit-Gui, version 2.4.6] і виконуємо тести:

Як бачимо вище, тести пройшли успішно.
Практичне завдання:
запустити на машині тести класу [PamMetier].- використовувати різні конфігураційні файли App.config для роботи з різними базами даних (Firebird, MySQL, Postgres, SQL Server)
7.4.7. Генерація DLL з шару [metier]
Після написання та тестування класу [PamMetier] буде згенеровано DLL та [pam-metier-dao-nhibernate.dll] з шару [metier], дотримуючись методу, описаного в розділі 7.3.5 Слід подбати про те, щоб не включати в DLL тестові програми [Main.cs] та [NUnit.cs]. Потім її слід помістити в папку [lib], що міститься в DLL та [1].
![]() |
7.5. Рівень [web]
Повернемося до загальної архітектури додатка [SimuPaie]:
![]() |
Ми вважаємо, що рівні [dao] та [métier] вже реалізовані та інкапсульовані в DLL та [pam-dao-nhibernate, pam-metier-dao-nhibernate.dll]. Тепер ми опишемо веб-рівень.
7.5.1. Проєкт Visual Web Developer для шару [web]
![]() |
- у [1], проект у цілому:
- [Global.asax]: клас, екземпляр якого створюється під час запуску веб-додатка і який забезпечує його ініціалізацію
- [Default.aspx]: сторінка веб-форми
- у [2] — DLL, необхідні для веб-додатка. Зверніть увагу на DLL шарів [dao] та [metier], створених раніше.
7.5.2. Налаштування додатка
Файл [Web.config], який налаштовує додаток, визначає ті самі дані, що й файл [App.config], який налаштовує розглянутий раніше шар [metier]. Ці дані повинні бути розміщені у попередньо згенерованому коді файлу [Web.config]:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<sectionGroup name="system.web.extensions" type="System.Web.Configuration.SystemWebExtensionsSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
..........
</sectionGroup>
<sectionGroup name="spring">
<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
</sectionGroup>
<section name="hibernate-configuration" type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" />
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
</configSections>
<!-- конфігурація Spring -->
<spring>
<context>
<resource uri="config://spring/objects" />
</context>
<objects xmlns="http://www.springframework.net">
<object id="pamdao" type="Pam.Dao.Service.PamDaoNHibernate, pam-dao-nhibernate" init-method="init" destroy-method="destroy"/>
<object id="pammetier" type="Pam.Metier.Service.PamMetier, pam-metier-dao-nhibernate" >
<property name="PamDao" ref="pamdao"/>
</object>
</objects>
</spring>
<!-- конфігурація 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>
<!-- Цей розділ містить параметри конфігурації log4net -->
<!-- NOTE IMPORTANTE: журнали за замовчуванням неактивні. Їх потрібно активувати програмно
avec l'instruction log4net.Config.XmlConfigurator.Configure();
! -->
<log4net>
....
</log4net>
<appSettings/>
<connectionStrings/>
<system.web>
....
....
</configuration>
У рядках 9–12, 18–28 та 31–44 міститься конфігурація Spring та NHibernate, описана у файлі [App.config] шару [metier] (див. розділ 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
{
// --- статичні дані програми ---
public static Employe[] Employes;
public static IPamMetier PamMetier = null;
public static string Msg;
public static bool Erreur = false;
// запуск програми
public void Application_Start(object sender, EventArgs e)
{
// обробка файлу конфігурації
try
{
// інстанціювання шару [metier]
PamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
// спрощений список співробітників
Employes = PamMetier.GetAllIdentitesEmployes();
// операція виконана успішно
Msg = "Base chargée...";
}
catch (Exception ex)
{
// зафіксовано помилку
Msg = string.Format("L'erreur suivante s'est produite lors de l'accès à la base de données : {0}", ex);
Erreur = true;
}
}
}
}
Нагадаємо, що:
- клас [Global.asax.cs] інстанціюється під час запуску додатка, і цей екземпляр доступний для всіх запитів усіх користувачів. Таким чином, статичні поля рядків 11–14 є спільними для всіх користувачів.
- метод [Application_Start] виконується лише один раз після створення екземпляра класу. Саме в цьому методі, як правило, відбувається ініціалізація програми.
Дані, спільні для всіх користувачів, такі:
- рядок 11: масив об’єктів типу [Employe], який зберігатиме спрощений список (SS, NOM, PRENOM) усіх співробітників
- рядок 12: посилання на шар [metier], інкапсульований у DLL [pam-metier-dao-nhibernate.dll]
- рядок 13: повідомлення про те, як завершилася ініціалізація (успішно чи з помилкою)
- рядок 14: логічне значення, що вказує, чи завершилася ініціалізація з помилкою чи ні.
У [Application_Start]:
- рядок 23: Spring створює екземпляри шарів [metier] та [dao] і повертає посилання на шар [metier]. Ця посилання зберігається у статичному полі [PamMetier] у рядку 12.
- рядок 25: з шару [metier] запитується масив співробітників
- рядок 27: повідомлення у разі успіху
- рядок 32: повідомлення у разі помилки
7.5.3. Форма [Default.aspx]
Форма відповідає версії 2.

Завдання: На основі коду C# зі сторінки [Default.aspx.cs] версії 2 напишіть код [Default.aspx.cs] для версії 3. Єдина відмінність полягає в розрахунку заробітної плати. Якщо у версії 2 для отримання інформації з бази даних використовувалися методи API та ADO.NET, то тут ми будемо використовувати метод GetSalaire з шару [metier].
Практичне завдання:
реалізувати на комп’ютері попередній веб-додаток- використовувати різні конфігураційні файли [Web.config] для роботи з різними базами даних (Firebird, MySQL, Postgres, SQL Server)





























