Skip to content

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

Обробка запиту клієнта відбувається за такими етапами:

  1. клієнт надсилає запит до додатка.
  2. додаток обробляє цей запит. Для цього йому може знадобитися допомога шару [métier], якому, у свою чергу, може знадобитися шар [dao], якщо необхідно обмінятися даними з базою даних.
  3. додаток отримує відповідь від шару [métier]. На основі цієї відповіді він надсилає клієнту відповідний вигляд (= відповідь).

Розглянемо приклад розрахунку заробітної плати виховательки дитячого садка. Для цього знадобиться кілька етапів:

  1. шар [ui] повинен запитати у користувача
    • ідентифікаційні дані особи, для якої потрібно розрахувати заробітну плату
    • кількість відпрацьованих нею днів
    • кількість відпрацьованих годин
  1. Для цього їй доведеться надати користувачеві список осіб (прізвище, ім’я, SS), що містяться в таблиці [EMPLOYES], щоб користувач міг вибрати одну з них. Шар [ui] використовуватиме шлях [2, 3, 4, 5, 6, 7] для їх отримання. Операція [2] — це запит на список співробітників, а операція [7] — відповідь на цей запит. Після цього рівень [ui] може представити список співробітників користувачеві за допомогою [8].
  2. Користувач передасть на рівень [ui] кількість відпрацьованих днів, а також кількість відпрацьованих годин. Це операція [1], зазначена вище. На цьому етапі користувач взаємодіє лише з рівнем [ui]. Саме цей рівень, зокрема, перевіряє правильність введених даних. Після цього користувач подає запит на розрахунок заробітної плати.
  3. Рівень [ui] звернеться до бізнес-рівня з проханням виконати цей розрахунок. Для цього він передасть йому дані, отримані від користувача. Це операція [2].
  4. Рівень [metier] потребує певної інформації для виконання своєї роботи:
    • більш повну інформацію про особу (адреса, індекс тощо)
    • виплати, пов’язані з її індексом
    • ставки різних соціальних внесків, що стягуються з валової заробітної плати

Вона запитає цю інформацію у шару [dao] за шляхом [3, 4, 5, 6]. [3] — це початковий запит, а [6] — відповідь на цей запит.

  1. Отримавши всі необхідні дані, шар [metier] розраховує заробітну плату особи, обраної користувачем.
  2. Тепер рівень [metier] може відповісти на запит рівня [ui], зроблений у пункті (d). Це шлях [7].
  3. шар [ui] оформить ці результати, щоб представити їх користувачеві у відповідному вигляді, а потім відобразить їх. Це шлях [8].
  4. Можна припустити, що ці результати мають бути збережені у файлі або базі даних. Це можна зробити автоматично. У цьому випадку після операції (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, який можна використовувати, як показано нижче:
throw new PamException("Problème d'accès aux données",5);
  • (продовження)
    • або той, що наведено в рядках 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
3
4
5
6
[254104940426058,Jouveinal,Marie,,,,]
[260124402111742,Laverti,Justine,,,,]
------------------------------------
[254104940426058,Jouveinal,Marie,5 rue des oiseaux,St Corentin,49203,[2, 2,1, 2,1, 3,1, 15]]
------------------------------------
[3,49,6,15,9,39,7,88]
  • рядки 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, і виконуємо тести:

Image

Як бачимо, тести пройшли успішно.

Практичне завдання:


  • запустити на комп’ютері тести класу [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. при першому відображенні форми у [1] має бути список співробітників. Достатньо спрощеного списку (Прізвище, Ім’я, SS). Номер SS необхідний для доступу до додаткової інформації про вибраного співробітника (поля 6–11).
  2. Позиції 12–15 — це різні ставки внесків.
  3. Позиції 16–19 — це надбавки, пов’язані з індексом працівника
  4. дані з 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
3
[[260124402111742,Laverti,Justine,La Brûlerie,St Marcel,49014,[1, 1,93, 2, 3, 12]],[3,49,6,15,9,39,7,88],[1, 1,93, 2, 3, 12],[64,85 : 17,45 : 10 : 15 : 72,4 ]
[[254104940426058,Jouveinal,Marie,5 rue des oiseaux,St Corentin,49203,[2, 2,1, 2,1, 3,1, 15]],[3,49,6,15,9,39,7,88],[2, 2,1, 2,1, 3,1, 15],[362,25 : 97,48 : 42: 62 : 368,77 ]
PamException : L'employé de n° ss [xx] n'existe pas
  • рядки 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] і виконуємо тести:

Image

Як бачимо вище, тести пройшли успішно.

Практичне завдання:


  • запустити на машині тести класу [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.

Image


Завдання: На основі коду C# зі сторінки [Default.aspx.cs] версії 2 напишіть код [Default.aspx.cs] для версії 3. Єдина відмінність полягає в розрахунку заробітної плати. Якщо у версії 2 для отримання інформації з бази даних використовувалися методи API та ADO.NET, то тут ми будемо використовувати метод GetSalaire з шару [metier].


Практичне завдання:


  • реалізувати на комп’ютері попередній веб-додаток
  • використовувати різні конфігураційні файли [Web.config] для роботи з різними базами даних (Firebird, MySQL, Postgres, SQL Server)