7. تطبيق [SimuPaie] – إصدار 3 من – بنية ثلاثية الطبقات مع NHibernate
قراءة موصى بها: "C# 2008، الفصل 4: بنى ثلاثية المستويات، اختبارات NUnit، إطار عمل Spring".
7.1. البنية العامة للتطبيق
سيكون لتطبيق [SimuPaie] الآن الهيكل ثلاثي المستويات التالي:
![]() |
- ستتولى طبقة [1-dao] (dao = كائن الوصول إلى البيانات) الوصول إلى البيانات.
- ستتولى طبقة [2-business] منطق الأعمال للتطبيق، وتحديدًا حسابات الرواتب.
- ستتولى الطبقة [3-ui] (ui = واجهة المستخدم) عرض البيانات للمستخدم وتنفيذ طلبات المستخدم. نشير إلى مجموعة الوحدات النمطية التي تؤدي هذه الوظيفة باسم [التطبيق]. وهي تعمل كواجهة المستخدم.
- ستصبح الطبقات الثلاث مستقلة عن بعضها البعض من خلال استخدام واجهات .NET
- سيتم التعامل مع تكامل الطبقات المختلفة بواسطة Spring IoC
تتبع معالجة طلب العميل الخطوات التالية:
- يقوم العميل بتقديم طلب إلى التطبيق.
- يقوم التطبيق بمعالجة هذا الطلب. وللقيام بذلك، قد يحتاج إلى مساعدة من طبقة [الأعمال]، والتي قد تحتاج بدورها إلى طبقة [DAO] في حال لزم تبادل البيانات مع قاعدة البيانات.
- يتلقى التطبيق استجابة من طبقة [الأعمال]. وبناءً على هذه الاستجابة، يرسل العرض المناسب (= الاستجابة) إلى العميل.
لنأخذ مثال حساب أجر مربية الأطفال. سيتطلب هذا عدة خطوات:
![]() |
- سيتعين على طبقة [UI] أن تطلب من المستخدم
- عن هوية الشخص الذي سيتم حساب راتبه
- عدد أيام العمل التي قضاها ذلك الشخص
- عدد ساعات العمل
- للقيام بذلك، سيتعين عليها عرض قائمة بالأشخاص (اللقب، الاسم الأول، رقم الضمان الاجتماعي) من الجدول [EMPLOYEES] على المستخدم حتى يتمكن من اختيار أحدهم. ستستخدم طبقة [ui] المسار [2، 3، 4، 5، 6، 7] لاسترداد هذه المعلومات. العملية [2] هي طلب قائمة الموظفين؛ والعملية [7] هي الاستجابة لهذا الطلب. بمجرد الانتهاء من ذلك، يمكن لطبقة [ui] عرض قائمة الموظفين للمستخدم عبر [8].
- سيقوم المستخدم بإرسال عدد أيام العمل وعدد ساعات العمل إلى طبقة [ui]. هذه هي العملية [1] أعلاه. خلال هذه الخطوة، يتفاعل المستخدم فقط مع طبقة [ui]. وهذه الطبقة هي التي ستتحقق من صحة البيانات المدخلة. بمجرد الانتهاء من ذلك، سيطلب المستخدم حساب الرواتب.
- ستطلب طبقة [ui] من طبقة الأعمال إجراء هذا الحساب. للقيام بذلك، سترسل البيانات التي تلقتها من المستخدم إلى طبقة الأعمال. هذه هي العملية [2].
- تحتاج طبقة [business] إلى معلومات معينة لأداء مهمتها:
- معلومات أكثر اكتمالاً عن الشخص (العنوان، الرقم التعريفي، إلخ)
- البدلات المرتبطة بدرجة راتبه
- معدلات مختلف اشتراكات الضمان الاجتماعي التي سيتم خصمها من الراتب الإجمالي
وستطلب هذه المعلومات من طبقة [DAO] باستخدام المسار [3، 4، 5، 6]. [3] هو الطلب الأولي و[6] هو الرد على هذا الطلب.
- بعد الحصول على جميع البيانات المطلوبة، تقوم طبقة [business] بحساب راتب الشخص الذي اختاره المستخدم.
- يمكن لطبقة [الأعمال] الآن الرد على الطلب المقدم من طبقة [ui] في (د). هذا هو المسار [7].
- ستقوم طبقة [ui] بتنسيق هذه النتائج لتقديمها للمستخدم في شكل مناسب ثم عرضها. هذا هو المسار [8].
- يمكن للمرء أن يتخيل أن هذه النتائج تحتاج إلى تخزينها في ملف أو قاعدة بيانات. ويمكن القيام بذلك تلقائيًا. في هذه الحالة، بعد العملية (و)، ستطلب طبقة [الأعمال] من طبقة [DAO] حفظ النتائج. سيكون هذا المسار [3، 4، 5، 6]. يمكن أيضًا القيام بذلك بناءً على طلب المستخدم. سيتم استخدام المسار [1-8] في دورة الطلب والاستجابة.
كما هو موضح في هذا الوصف، تستخدم الطبقة موارد الطبقة الموجودة على يمينها، ولا تستخدم أبدًا موارد الطبقة الموجودة على يسارها.
سيكون أول تطبيق لنا لهذه البنية ثلاثية المستويات هو تطبيق ASP.NET حيث
- سيتم تنفيذ طبقتي [DAO] و[business] بواسطة مكتبات 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: لـ NHibernate ORM
- MySql.Data: برنامج تشغيل ADO.NET لنظام إدارة قواعد البيانات MySQL
- Spring.Core: لإطار عمل Spring
- log4net: مكتبة تسجيل
- nunit.framework: مكتبة اختبار الوحدات
تم أخذ هذه المراجع من المجلد [lib] [4]. تأكد من تعيين الخاصية "Local Copy" لجميع هذه المراجع على "True" [5]:
![]() |
7.2.2. الكيانات في طبقة [dao]
![]() |
تم تجميع الكيانات (الكائنات) المطلوبة لطبقة [dao] في مجلد [entities] الخاص بالمشروع. بعضها مألوف لنا بالفعل: [Contributions] الموصوفة في القسم 6.3.2.1، و[Employee] الموصوفة في القسم 6.3.2.3، و[Allowances] الموصوفة في القسم 6.3.2.2. جميعها موجودة في مساحة الاسم [Pam.Dao.Entities].
يتم تعريف فئة [Employee] على النحو التالي:
namespace Pam.Dao.Entites {
public class Employe {
// automatic properties
public virtual int Id { get; set; }
public virtual int Version { get; set; }
public virtual string SS { get; set; }
public virtual string Nom { get; set; }
public virtual string Prenom { get; set; }
public virtual string Adresse { get; set; }
public virtual string Ville { get; set; }
public virtual string CodePostal { get; set; }
public virtual Indemnites Indemnites { get; set; }
// manufacturers
public Employe() {
}
// ToString
public override string ToString() {
return string.Format("[{0},{1},{2},{3},{4},{5},{6}]", SS, Nom, Prenom, Adresse, Ville, CodePostal, Indemnites);
}
}
}
7.2.3. فئة [PamException]
طبقة [dao] مسؤولة عن تبادل البيانات مع مصدر خارجي. قد يفشل هذا التبادل. على سبيل المثال، إذا تم طلب معلومات من خدمة بعيدة على الإنترنت، فسيفشل استردادها بسبب أي انقطاع في الشبكة. بالنسبة لهذا النوع من الأخطاء، من الممارسات المعتادة في Java إلقاء استثناء. إذا لم يكن الاستثناء من النوع [RunTimeException] أو نوع مشتق، فيجب عليك الإشارة في توقيع الأسلوب إلى أن الأسلوب يلقي استثناءً. في .NET، جميع الاستثناءات غير معالجة، أي أنها تعادل نوع [RunTimeException] في Java. لذلك، لا داعي للإعلان عن أن الطرق [GetAllEmployeeIDs، GetEmployee، GetContributions] من المحتمل أن ترمي استثناءً.
ومع ذلك، من المفيد التمييز بين الاستثناءات المختلفة، حيث قد تختلف طريقة معالجتها. وبالتالي، يمكن كتابة الكود الذي يعالج أنواعًا مختلفة من الاستثناءات على النحو التالي:
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 {
// the error code
public int Code { get; set; }
// manufacturers
public PamException() {
}
public PamException(int Code)
: base() {
this.Code = Code;
}
public PamException(string message, int Code)
: base(message) {
this.Code = Code;
}
public PamException(string message, Exception ex, int Code)
: base(message, ex) {
this.Code = Code;
}
}
}
- السطر 2: تنتمي الفئة إلى مساحة الاسم [Pam.Dao.Entities]
- السطر 4: الفئة مشتقة من فئة [Exception]
- السطر 7: تحتوي على خاصية عامة [Code] وهي رمز خطأ
- في طبقة [dao] الخاصة بنا، سنستخدم نوعين من المنشئات:
- المنشئ الموجود في الأسطر 18-21، والذي يمكن استخدامه كما هو موضح أدناه:
- (تابع)
- أو تلك الموجودة في الأسطر 23-26، والمصممة لنقل استثناء قد حدث بالفعل عن طريق تغليفه في استثناء [PamException]:
try{
....
}catch (IOException ex){
// on encapsule l'exception
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 بتعيين الجدول [EMPLOYEES] إلى فئة [Employee]
<?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) تحتوي على السمات التالية:
- مساحة الاسم: Pam.Dao.Entities. يجب أن تكون فئات [Contributions] و [Employee] و [Allowances] موجودة في مساحة الاسم هذه.
- assembly: pam-dao-nhibernate. يجب تغليف ملفات التعيين [*.hbm.xml] في مكتبة DLL باسم [pam-dao-nhibernate]. لتحقيق ذلك، يتم تكوين مشروع C# على النحو التالي:
![]() |
- في [1]، يُسمى تجميع المشروع [pam-dao-nhibernate]
- في [2]، يتم تضمين ملفات التعيين [*.hbm.xml] [3] في تجميع المشروع
7.2.5. واجهة [ IPamDao] لطبقة [DAO]
لنعد إلى بنية تطبيقنا:
![]() |
في الحالات البسيطة، يمكننا البدء من طبقة [الأعمال] لاكتشاف واجهات التطبيق. لكي تعمل، تحتاج إلى بيانات:
- المتوفرة بالفعل في الملفات أو قواعد البيانات أو عبر الشبكة. يتم توفير هذه البيانات بواسطة طبقة [dao].
- غير متوفرة بعد. يتم توفيرها بعد ذلك بواسطة طبقة [ui]، التي تحصل عليها من مستخدم التطبيق.
ما هي الواجهة التي يجب أن توفرها طبقة [DAO] لطبقة [الأعمال]؟ ما هي التفاعلات الممكنة بين هاتين الطبقتين؟ يجب أن توفر طبقة [DAO] البيانات التالية لطبقة [الأعمال]:
- قائمة مقدمي خدمات رعاية الأطفال لتمكين المستخدم من اختيار مقدم خدمات معين
- معلومات كاملة عن الشخص المحدد (العنوان، الرقم التعريفي، إلخ)
- البدلات المرتبطة برقم التعريف الخاص بالشخص
- معدلات مختلف اشتراكات الضمان الاجتماعي
هذه المعلومات معروفة قبل حساب الرواتب وبالتالي يمكن تخزينها. في الاتجاه [الأعمال] -> [DAO]، يمكن لطبقة [الأعمال] أن تطلب من طبقة [DAO] تسجيل نتيجة حساب الرواتب. لن نقوم بذلك هنا.
باستخدام هذه المعلومات، يمكننا محاولة وضع تعريف أولي لواجهة طبقة [dao]:
using Pam.Dao.Entites;
namespace Pam.Dao.Service {
public interface IPamDao {
// list of all employee identities
Employe[] GetAllIdentitesEmployes();
// an individual employee with benefits
Employe GetEmploye(string ss);
// list of all contributions
Cotisations GetCotisations();
}
}
- السطر 1: نقوم باستيراد مساحة الاسم للكيانات في طبقة [dao].
- السطر 3: تقع طبقة [dao] في مساحة اسم [Pam.Dao.Service]. يمكن إنشاء العناصر الموجودة في مساحة اسم [Pam.Dao.Entities] في مثيلات متعددة. يتم إنشاء العناصر الموجودة في مساحة اسم [Pam.Dao.Service] كمثيل واحد (singleton). وهذا ما برر اختيار أسماء مساحات الأسماء.
- السطر 4: تسمى الواجهة [IPamDao]. وهي تحدد ثلاث طرق:
- السطر 6: تُرجع [GetAllIdentitesEmployes] مصفوفة من الكائنات من النوع [Employe] تمثل قائمة مقدمي رعاية الأطفال بتنسيق مبسط (اللقب، الاسم الأول، رقم الضمان الاجتماعي).
- السطر 8: تُرجع [GetEmploye] كائن [Employe]: الموظف الذي يحمل رقم الضمان الاجتماعي الذي تم تمريره كمعلمة إلى الطريقة، إلى جانب المزايا المرتبطة بدرجة راتبه.
- السطر 10، [GetCotisations] تُرجع كائن [Cotisations]، الذي يغلف معدلات الاشتراكات المختلفة في الضمان الاجتماعي التي سيتم خصمها من الراتب الإجمالي.
7.3. تنفيذ واختبار طبقة [dao]
7.3.1. مشروع Visual Studio
تم عرض مشروع Visual Studio بالفعل. للتذكير:
![]() |
- في [1]، المشروع ككل
- في [2]، الفئات المختلفة للمشروع. يحتوي المجلد [entities] على الكيانات التي تتعامل معها طبقة [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 {
// layer instantiation [dao]
IPamDao pamDao = (IPamDao)ContextRegistry.GetContext().GetObject("pamdao");
// list of employee identities
foreach (Employe Employe in pamDao.GetAllIdentitesEmployes()) {
Console.WriteLine(Employe.ToString());
}
// an employee with benefits
Console.WriteLine("------------------------------------");
Console.WriteLine(pamDao.GetEmploye("254104940426058"));
Console.WriteLine("------------------------------------");
// list of contributions
Cotisations cotisations = pamDao.GetCotisations();
Console.WriteLine(cotisations.ToString());
} catch (Exception ex) {
// exception display
Console.WriteLine(ex.ToString());
}
//break
Console.ReadLine();
}
}
}
- السطر 11: نطلب من Spring مرجعًا إلى طبقة [dao].
- الأسطر 13–15: اختبار طريقة [GetAllEmployeeIDs] لواجهة [IPamDao]
- السطر 18: اختبار طريقة [GetEmploye] لواجهة [IPamDao]
- السطر 21: اختبار طريقة [GetCotisations] لواجهة [IPamDao]
يتم تكوين Spring و NHibernate و log4net بواسطة ملف [ App.config] التالي:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<!-- configuration sections -->
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
<sectionGroup name="spring">
<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
</sectionGroup>
<section name="hibernate-configuration" type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" />
</configSections>
<!-- spring configuration -->
<spring>
<context>
<resource uri="config://spring/objects" />
</context>
<objects xmlns="http://www.springframework.net">
<object id="pamdao" type="Pam.Dao.Service.PamDaoNHibernate, pam-dao-nhibernate" init-method="init" destroy-method="destroy"/>
</objects>
</spring>
<!-- configuration NHibernate -->
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
<session-factory>
<property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
<property name="connection.driver_class">NHibernate.Driver.MySqlDataDriver</property>
<property name="dialect">NHibernate.Dialect.MySQLDialect</property>
<property name="connection.connection_string">
Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;
</property>
<property name="show_sql">false</property>
<mapping assembly="pam-dao-nhibernate"/>
</session-factory>
</hibernate-configuration>
<!-- This section contains the log4net configuration settings -->
<!-- NOTE IMPORTANTE: logs are not active by default. They must be activated by program
avec l'instruction log4net.Config.XmlConfigurator.Configure();
! -->
<log4net>
...
</log4net>
</configuration>
تم شرح تكوين 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: الموظفان من النوع [Employee] مع المعلومات [SS، Last Name، First Name] فقط
- السطر 4: الموظف من النوع [Employee] الذي يحمل رقم الضمان الاجتماعي [254104940426058]
- الصف 5: معدلات الاشتراكات
7.3.3. تعريف فئة [PamDaoNHibernate]
![]() |
واجهة [IPamDao] التي تنفذها طبقة [dao] هي كما يلي:
using Pam.Dao.Entites;
namespace Pam.Dao.Service {
public interface IPamDao {
// list of all employee identities
Employe[] GetAllIdentitesEmployes();
// an individual employee with benefits
Employe GetEmploye(string ss);
// list of all contributions
Cotisations GetCotisations();
}
}
السؤال: اكتب الكود الخاص بفئة [PamDaoNHibernate] التي تنفذ واجهة [IPamDao] المذكورة أعلاه باستخدام إطار عمل NHibernate المُهيأ كما هو موضح سابقًا. سنقوم أيضًا بتنفيذ طريقتي init و destroy اللتين يتم تنفيذهما بواسطة Spring. ستقوم طريقة init بإنشاء SessionFactory التي سنحصل منها على كائنات Session. وستقوم طريقة destroy بإغلاق SessionFactory هذه. سنستخدم الأمثلة الواردة في القسم 6.5.
القيود:
سنفترض أن بعض البيانات المطلوبة من طبقة [dao] يمكن أن تتسع بالكامل في الذاكرة. وبالتالي، لتحسين الأداء، ستخزن فئة [PamDaoNHibernate]:
- جدول [EMPLOYEES] بالتنسيق (SS, LAST_NAME, FIRST_NAME) المطلوب بواسطة طريقة [GetAllEmployeeIDs]، كمصفوفة من كائنات [Employee]
- جدول [COTISATIONS] في شكل كائن واحد من النوع [Cotisations]
سيتم تنفيذ ذلك في طريقة [init] للفئة. قد يكون الهيكل الأساسي لفئة [PamDaoNHibernate] كما يلي:
using System;
...
namespace Pam.Dao.Service {
class PamDaoNHibernate : IPamDao {
// private fields
private Cotisations cotisations;
private Employe[] employes;
private ISessionFactory sessionFactory = null;
// init
public void init() {
try {
// factory initialization
sessionFactory = new Configuration().Configure().BuildSessionFactory();
// retrieve contribution rates and employees for caching
.......................
}
// closure SessionFactory
public void destroy() {
if (sessionFactory != null) {
sessionFactory.Close();
}
}
// list of all employee identities
public Employe[] GetAllIdentitesEmployes() {
return employes;
}
// an individual employee with benefits
public Employe GetEmploye(string ss) {
................................
}
// list of contributions
public Cotisations GetCotisations() {
return cotisations;
}
}
}
7.3.4. اختبار الوحدات باستخدام NUnit
قراءة موصى بها: "C# 2008، الفصل 4: البنى ثلاثية المستويات، اختبار NUnit، إطار عمل Spring".
كان الاختبار السابق بصريًا: فقد تحققنا على الشاشة للتأكد من حصولنا على النتائج المتوقعة. هذه الطريقة غير كافية في بيئة عمل احترافية. يجب أن تكون الاختبارات آلية قدر الإمكان وأن تهدف إلى عدم الحاجة إلى تدخل بشري. فالإنسان معرض للتعب بالفعل، وتقل قدرته على التحقق من الاختبارات مع مرور اليوم. تساعد أداة [NUnit] في تحقيق هذه الأتمتة. وهي متاحة على الرابط [http://www.nunit.org/].
سيتطور مشروع Visual Studio لطبقة [dao] على النحو التالي:
![]() |
- في [1]، برنامج الاختبار [NUnit.cs]
- في [2،3]، سيقوم المشروع بإنشاء ملف DLL باسم [pam-dao-hibernate.dll]
- في [4]، الإشارة إلى ملف DLL الخاص بإطار عمل NUnit: [nunit.framework.dll]
- في [5]، لن يتم تضمين فئة [Main.cs] في مكتبة DLL [pam-dao-nhibernate]
- في [6]، سيتم تضمين فئة [NUnit.cs] في مكتبة DLL [pam-dao-hibernate]
فئة اختبار NUnit <a id="pam-dao-nhibernate-nunit"></a> هي كما يلي:
using System.Collections;
using NUnit.Framework;
using Pam.Dao.Service;
using Pam.Dao.Entites;
using Spring.Objects.Factory.Xml;
using Spring.Core.IO;
using Spring.Context.Support;
namespace Pam.Dao.Tests {
[TestFixture]
public class NunitPamDao : AssertionHelper {
// the [dao] layer to be tested
private IPamDao pamDao = null;
// manufacturer
public NunitPamDao() {
// layer instantiation [dao]
pamDao = (IPamDao)ContextRegistry.GetContext().GetObject("pamdao");
}
// init
[SetUp]
public void Init() {
}
[Test]
public void GetAllIdentitesEmployes() {
// audit no. of employees
Expect(2, EqualTo(pamDao.GetAllIdentitesEmployes().Length));
}
[Test]
public void GetCotisations() {
// checking contribution rates
Cotisations cotisations = pamDao.GetCotisations();
Expect(3.49, EqualTo(cotisations.CsgRds).Within(1E-06));
Expect(6.15, EqualTo(cotisations.Csgd).Within(1E-06));
Expect(9.39, EqualTo(cotisations.Secu).Within(1E-06));
Expect(7.88, EqualTo(cotisations.Retraite).Within(1E-06));
}
[Test]
public void GetEmployeIdemnites() {
// individual verification
Employe employe1 = pamDao.GetEmploye("254104940426058");
Employe employe2 = pamDao.GetEmploye("260124402111742");
Expect("Jouveinal", EqualTo(employe1.Nom));
Expect(2.1, EqualTo(employe1.Indemnites.BaseHeure).Within(1E-06));
Expect("Laverti", EqualTo(employe2.Nom));
Expect(1.93, EqualTo(employe2.Indemnites.BaseHeure).Within(1E-06));
}
[Test]
public void GetEmployeIdemnites2() {
// non-existent individual verification
bool erreur = false;
try {
Employe employe1 = pamDao.GetEmploye("xx");
} catch {
erreur = true;
}
Expect(erreur, True);
}
}
}
- السطر 11: تحتوي الفئة على السمة [TestFixture]، مما يجعلها فئة اختبار [NUnit].
- السطر 12: الفئة مشتقة من فئة الأداة المساعدة AssertionHelper في إطار عمل NUnit (بدءًا من الإصدار 2.4.6).
- السطر 14: الحقل الخاص [pamDao] هو مثيل للواجهة التي توفر الوصول إلى طبقة [dao]. لاحظ أن نوع هذا الحقل هو واجهة، وليس فئة. وهذا يعني أن مثيل [pamDao] يجعل الطرق فقط قابلة للوصول — على وجه التحديد، تلك الموجودة في واجهة [IPamDao].
- الطرق التي يتم اختبارها في الفئة هي تلك التي تحمل السمة [Test]. بالنسبة لجميع هذه الطرق، تكون عملية الاختبار كما يلي:
- يتم تنفيذ الطريقة التي تحمل السمة [SetUp] أولاً. وهي تُستخدم لإعداد الموارد (اتصالات الشبكة، اتصالات قاعدة البيانات، إلخ) المطلوبة للاختبار.
- ثم يتم تنفيذ الطريقة المراد اختبارها
- وأخيرًا، يتم تنفيذ الطريقة التي تحمل السمة [TearDown]. تُستخدم هذه الطريقة عمومًا لتحرير الموارد التي خصصتها الطريقة التي تحمل السمة [SetUp].
- في اختبارنا، لا توجد موارد لتخصيصها قبل كل اختبار ثم إلغاء تخصيصها بعد ذلك. لذلك، لا نحتاج إلى طرق ذات السمتين [SetUp] و [TearDown]. في المثال، قمنا بتضمين طريقة ذات السمة [SetUp] في الأسطر 23–26.
- الأسطر 17-20: يقوم منشئ الفئة بتهيئة الحقل الخاص [pamDao] باستخدام Spring و [App.config].
- الأسطر 29-32: اختبار طريقة [GetAllIdentitesEmployes]
- الأسطر 35-42: اختبار الطريقة [GetCotisations]
- الأسطر 45–53: اختبار طريقة [GetEmploye]
- الأسطر 56–65: اختبار طريقة [GetEmploye] عند حدوث استثناء.
يؤدي إنشاء المشروع إلى إنشاء ملف DLL [pam-dao-nhibernate.dll] في المجلد [bin/Release].
![]() |
يحتوي المجلد [bin/Release] أيضًا على:
- ملفات DLL التي تشكل جزءًا من مراجع المشروع والتي تم تعيين السمة [Local Copy] لها على 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]. سنركز الآن على طبقة [business]. تنفذ هذه الطبقة قواعد العمل، وفي هذه الحالة قواعد حساب الراتب.
7.4.1. مشروع Visual Studio [ ] لطبقة [business]
قد يبدو مشروع Visual Studio الخاص بطبقة الأعمال كما يلي:
![]() |
- في [1]، يتم تكوين المشروع بأكمله بواسطة ملف [App.config]
- في [2]، تتكون طبقة [الأعمال] من مجلدين [entities، service]. يحتوي المجلد [tests] على برنامج اختبار وحدة التحكم (Main.cs) وبرنامج اختبار NUnit (NUnit.cs).
- في [3] المراجع المستخدمة من قبل المشروع. لاحظ DLL [pam-dao-hibernate] من طبقة [DAO] التي تمت مناقشتها سابقًا.
7.4.2. واجهة [IPamM tier] لطبقة [business]
لنعد إلى البنية العامة للتطبيق:
![]() |
ما هي الواجهة التي يجب أن توفرها طبقة [الأعمال] لطبقة [واجهة المستخدم]؟ ما هي التفاعلات المحتملة بين هاتين الطبقتين؟ دعونا نستذكر واجهة الويب التي سيتم عرضها على المستخدم:
![]() |
- عند عرض النموذج لأول مرة، يجب أن تظهر قائمة الموظفين في [1]. يكفي وجود قائمة مبسطة (اللقب، الاسم الأول، رقم الضمان الاجتماعي). رقم الضمان الاجتماعي مطلوب للوصول إلى معلومات إضافية عن الموظف المحدد (المعلومات من 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 {
// list of all employee identities
Employe[] GetAllIdentitesEmployes();
// ------- salary calculation
FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés);
}
}
- السطر 7: الطريقة التي ستملأ مربع القائمة المنسدلة [1]
- السطر 10: الطريقة التي ستسترد المعلومات من 6 إلى 24. تم جمع هذه المعلومات في كائن من النوع [PayrollSheet].
7.4.3. الكيانات في طبقة [business]
يحتوي المجلد [entities] في مشروع Visual Studio على الكائنات التي تتعامل معها فئة الأعمال: [Payroll] و [PayrollItems].
![]() |
تغلف فئة [FeuilleS al] الحقول من 6 إلى 24 من النموذج السابق:
using Pam.Dao.Entites;
namespace Pam.Metier.Entites {
public class FeuilleSalaire {
// automatic properties
public Employe Employe { get; set; }
public Cotisations Cotisations { get; set; }
public ElementsSalaire ElementsSalaire { get; set; }
// ToString
public override string ToString() {
return string.Format("[{0},{1},{2}", Employe, Cotisations, ElementsSalaire);
}
}
}
- السطر 8: المعلومات من 6 إلى 11 عن الموظف الذي يتم حساب راتبه، والمعلومات من 16 إلى 19 عن بدلاته. لاحظ أن كائن [Employee] يغلف كائن [Allowances] الذي يمثل بدلات الموظف.
- السطر 9: المعلومات من 12 إلى 15
- السطر 10: المعلومات من 20 إلى 24
- الأسطر 13–15: طريقة [ToString]
تغلف فئة [Elements Salaire] المعلومات من 20 إلى 24 من النموذج:
namespace Pam.Metier.Entites {
public class ElementsSalaire {
// automatic properties
public double SalaireBase { get; set; }
public double CotisationsSociales { get; set; }
public double IndemnitesEntretien { get; set; }
public double IndemnitesRepas { get; set; }
public double SalaireNet { get; set; }
// ToString
public override string ToString() {
return string.Format("[{0} : {1} : {2} : {3} : {4} ]", SalaireBase, CotisationsSociales, IndemnitesEntretien, IndemnitesRepas, SalaireNet);
}
}
}
- السطور 4–8: مكونات الراتب كما هو موضح في قواعد العمل الموضحة في القسم 3.2.
- السطر 4: الراتب الأساسي للموظف، بناءً على عدد ساعات العمل
- السطر 5: الاشتراكات المخصومة من هذا الراتب الأساسي
- السطران 6 و7: البدلات التي تضاف إلى الراتب الأساسي، بناءً على رتبة الموظف وعدد أيام العمل
- السطر 8: الراتب الصافي المقرر دفعه
- الأسطر 12–15: طريقة [ToString] للفئة.
7.4.4. تنفيذ طبقة [business]
![]() |
سنقوم بتنفيذ واجهة [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 {
// data access object
public IPamDao PamDao { get; set; }
// list of all employee identities
public Employe[] GetAllIdentitesEmployes() {
return PamDao.GetAllIdentitesEmployes();
}
// an individual employee with benefits
protected Employe GetEmploye(string ss) {
return PamDao.GetEmploye(ss);
}
// contributions
protected Cotisations GetCotisations() {
return PamDao.GetCotisations();
}
// salary calculation
public abstract FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés);
}
}
- السطر 5: تنتمي الفئة إلى مساحة الاسم [Pam.Metier.Service]، مثل جميع الفئات والواجهات في طبقة [business].
- السطر 6: الفئة مجردة (السمة abstract) وتنفذ واجهة [IPamMetier]
- السطر 9: تحتوي الفئة على مرجع إلى طبقة [dao] في شكل خاصية عامة
- الأسطر 12–14: تنفيذ طريقة [GetAllEmployeIDs] الخاصة بواجهة [IPamMetier] – تستخدم الطريقة التي تحمل الاسم نفسه في طبقة [dao]
- الأسطر 17–19: الطريقة الداخلية (المحمية) [GetEmployee] التي تستدعي الطريقة التي تحمل الاسم نفسه في طبقة [dao] – تم إعلانها على أنها محمية حتى تتمكن الفئات المشتقة من الوصول إليها دون أن تكون عامة
- الأسطر 22-24: الطريقة الداخلية (المحمية) [GetContributions] التي تستدعي الطريقة التي تحمل الاسم نفسه في طبقة [dao]
- السطر 27: تنفيذ مجرد (سمة مجردة) لطريقة [GetSalary] الخاصة بواجهة [IPamMetier].
يتم تنفيذ حساب الراتب بواسطة فئة [PamMetier] التالية:
using System;
using Pam.Dao.Entites;
using Pam.Metier.Entites;
namespace Pam.Metier.Service {
public class PamMetier : AbstractBasePamMetier {
// wage calculation
public override FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés) {
// SS : employee's SS number
// HeuresTravaillées: number of hours worked
// Days worked: number of days worked
// we get the employee back with his benefits
...
// we recover the various contribution rates
...
// salary components are calculated
...
// we return the payslip
return ...;
}
}
}
- السطر 7: الفئة مشتقة من [AbstractBasePamMetier] وبالتالي تنفذ واجهة [IPamMetier]
- السطر 10: طريقة [GetSalary] المراد تنفيذها
السؤال: اكتب كود الأسلوب [GetSalaire].
7.4.5. اختبار وحدة التحكم لطبقة [business]
دعونا نراجع مشروع Visual Studio لطبقة [business]:
![]() |
يختبر برنامج الاختبار [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 {
// instantiation layer [metier]
IPamMetier pamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
// payslip calculations
Console.WriteLine(pamMetier.GetSalaire("260124402111742", 30, 5));
Console.WriteLine(pamMetier.GetSalaire("254104940426058", 150, 20));
try {
Console.WriteLine(pamMetier.GetSalaire("xx", 150, 20));
} catch (PamException ex) {
Console.WriteLine(string.Format("PamException : {0}", ex.Message));
}
} catch (Exception ex) {
Console.WriteLine(string.Format("Exception : {0}", ex.ToString()));
}
// break
Console.ReadLine();
}
}
}
- السطر 11: إنشاء مثيل لطبقة [business] باستخدام Spring.
- السطران 13-14: اختبار طريقة [GetSalary] لواجهة [IPamMetier]
- الأسطر 15–22: اختبار طريقة [GetSalaire] عند حدوث استثناء
يستخدم برنامج الاختبار ملف التكوين التالي [ App.config]:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<!-- configuration sections -->
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
<sectionGroup name="spring">
<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
</sectionGroup>
<section name="hibernate-configuration" type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" />
</configSections>
<!-- spring configuration -->
<spring>
<context>
<resource uri="config://spring/objects" />
</context>
<objects xmlns="http://www.springframework.net">
<object id="pamdao" type="Pam.Dao.Service.PamDaoNHibernate, pam-dao-nhibernate" init-method="init" destroy-method="destroy"/>
<object id="pammetier" type="Pam.Metier.Service.PamMetier, pam-metier-dao-nhibernate" >
<property name="PamDao" ref="pamdao"/>
</object>
</objects>
</spring>
<!-- configuration NHibernate -->
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
....
</hibernate-configuration>
<!-- This section contains the log4net configuration settings -->
<!-- NOTE IMPORTANTE: logs are not active by default. They must be activated by program
avec l'instruction log4net.Config.XmlConfigurator.Configure();
! -->
<log4net>
...
</log4net>
</configuration>
هذا الملف مطابق لملف [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: استثناء [PamException] الناجم عن عدم وجود الموظف.
7.4.6. اختبارات الوحدة لطبقة الأعمال
كان الاختبار السابق بصريًا: تحققنا على الشاشة من أننا نحصل بالفعل على النتائج المتوقعة. سننتقل الآن إلى اختبارات NUnit غير البصرية.
لنعد إلى مشروع Visual Studio الخاص بمشروع [business]:
![]() |
- في [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]
using NUnit.Framework;
using Pam.Dao.Entites;
using Pam.Metier.Entites;
using Pam.Metier.Service;
using Spring.Context.Support;
namespace Pam.Metier.Tests {
[TestFixture()]
public class NunitTestPamMetier : AssertionHelper {
// the [metier] layer to test
private IPamMetier pamMetier;
// manufacturer
public NunitTestPamMetier() {
// layer instantiation [dao]
pamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
}
[Test]
public void GetAllIdentitesEmployes() {
// audit no. of employees
Expect(2, EqualTo(pamMetier.GetAllIdentitesEmployes().Length));
}
[Test]
public void GetSalaire1() {
// wage sheet calculation
FeuilleSalaire feuilleSalaire = pamMetier.GetSalaire("254104940426058", 150, 20);
// checks
Expect(368.77, EqualTo(feuilleSalaire.ElementsSalaire.SalaireNet).Within(1E-06));
// non-existent employee payslip
bool erreur = false;
try {
feuilleSalaire = pamMetier.GetSalaire("xx", 150, 20);
} catch (PamException) {
erreur = true;
}
Expect(erreur, True);
}
}
}
- السطر 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 التي تشكل جزءًا من مراجع المشروع والتي تم تعيين السمة [Local Copy] لها على 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، الإصدار 2.4.6] ونقوم بتشغيل الاختبارات:

في الأعلى، كانت الاختبارات ناجحة.
تمرين عملي:
قم بتنفيذ الاختبارات الخاصة بفئة [PamMetier] على الجهاز.- استخدم ملفات تكوين App.config مختلفة لاستخدام أنظمة إدارة قواعد البيانات المختلفة (Firebird، MySQL، Postgres، SQL Server)
7.4.7. إنشاء ملف DLL لطبقة [business]
بمجرد كتابة فئة [PamMetier] واختبارها، سنقوم بإنشاء ملف DLL [pam-metier-dao-hibernate.dll] لطبقة [business] باتباع الطريقة الموضحة في القسم 7.3.5. سنحرص على عدم تضمين برامج الاختبار [Main.cs] و [NUnit.cs] في ملف DLL. ثم سنضعه في مجلد [lib] الخاص بملفات DLL [1].
![]() |
7.5. طبقة [web]
دعونا نعيد النظر في البنية العامة لتطبيق [SimuPaie]:
![]() |
نفترض أن طبقتي [dao] و[business] مكتملتان ومغلفتان في ملفات DLL [pam-dao-hibernate، pam-business-dao-hibernate.dll]. سنقوم الآن بوصف طبقة الويب.
7.5.1. مشروع Visual Web Developer لطبقة [web]
![]() |
- في [1]، المشروع ككل:
- [Global.asax]: الفئة التي يتم إنشاء مثيل لها عند بدء تشغيل تطبيق الويب، والتي تتولى تهيئة التطبيق
- [Default.aspx]: صفحة نموذج الويب
- في [2]، مكتبات DLL المطلوبة من قبل تطبيق الويب. لاحظ مكتبات DLL الخاصة بطبقتي [DAO] و[business] التي تم إنشاؤها سابقًا.
7.5.2. تكوين التطبيق
يحدد ملف [Web.config] الذي يقوم بتكوين التطبيق نفس البيانات التي يحددها ملف [App.config] الذي يقوم بتكوين طبقة [business] التي تمت مناقشتها سابقًا. يجب وضع هذه البيانات في الكود المُعد مسبقًا لملف [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 configuration -->
<spring>
<context>
<resource uri="config://spring/objects" />
</context>
<objects xmlns="http://www.springframework.net">
<object id="pamdao" type="Pam.Dao.Service.PamDaoNHibernate, pam-dao-nhibernate" init-method="init" destroy-method="destroy"/>
<object id="pammetier" type="Pam.Metier.Service.PamMetier, pam-metier-dao-nhibernate" >
<property name="PamDao" ref="pamdao"/>
</object>
</objects>
</spring>
<!-- configuration NHibernate -->
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
<session-factory>
<property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
<!--
<property name="connection.driver_class">NHibernate.Driver.MySqlDataDriver</property>
-->
<property name="dialect">NHibernate.Dialect.MySQLDialect</property>
<property name="connection.connection_string">
Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;
</property>
<property name="show_sql">false</property>
<mapping assembly="pam-dao-nhibernate"/>
</session-factory>
</hibernate-configuration>
<!-- This section contains the log4net configuration settings -->
<!-- NOTE IMPORTANTE: logs are not active by default. They must be activated by program
avec l'instruction log4net.Config.XmlConfigurator.Configure();
! -->
<log4net>
....
</log4net>
<appSettings/>
<connectionStrings/>
<system.web>
....
....
</configuration>
تحتوي الأسطر 9–12 و18–28 و31–44 على تكوين Spring وNHibernate الموصوف في ملف [App.config] الخاص بطبقة [business] (انظر القسم 7.4.5).
Global.asax.cs
using System;
using Pam.Dao.Entites;
using Pam.Metier.Service;
using Spring.Context.Support;
namespace pam_v3
{
public class Global : System.Web.HttpApplication
{
// --- static application data ---
public static Employe[] Employes;
public static IPamMetier PamMetier = null;
public static string Msg;
public static bool Erreur = false;
// application startup
public void Application_Start(object sender, EventArgs e)
{
// using the configuration file
try
{
// instantiation layer [metier]
PamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
// simplified list of employees
Employes = PamMetier.GetAllIdentitesEmployes();
// we succeeded
Msg = "Base chargée...";
}
catch (Exception ex)
{
// we note the error
Msg = string.Format("L'erreur suivante s'est produite lors de l'accès à la base de données : {0}", ex);
Erreur = true;
}
}
}
}
لاحظ أن:
- يتم إنشاء مثيل لفئة [Global.asax.cs] عند بدء تشغيل التطبيق، ويكون هذا المثيل متاحًا لجميع الطلبات من جميع المستخدمين. وبالتالي، يتم مشاركة الحقول الثابتة في الأسطر 11-14 بين جميع المستخدمين.
- يتم تنفيذ الأسلوب [Application_Start] مرة واحدة فقط بعد إنشاء مثيل للفئة. هذا هو الأسلوب الذي يتم فيه عادةً تهيئة التطبيق.
البيانات المشتركة بين جميع المستخدمين هي كما يلي:
- السطر 11: مصفوفة كائنات [Employee] التي ستخزن القائمة المبسطة (SS، LAST_NAME، FIRST_NAME) لجميع الموظفين
- السطر 12: مرجع إلى طبقة [business] المُغلفة في مكتبة DLL [pam-metier-dao-nhibernate.dll]
- السطر 13: رسالة تشير إلى ما إذا كانت عملية التهيئة قد اكتملت بنجاح أم بحدوث خطأ
- السطر 14: قيمة منطقية تشير إلى ما إذا كانت عملية التهيئة قد اكتملت مع حدوث خطأ أم لا.
في [Application_Start]:
- السطر 23: يقوم Spring بإنشاء مثيلات لطبقتي [business] و [DAO] ويعيد مرجعًا إلى طبقة [business]. يتم تخزين هذا في الحقل الثابت [PamMetier] من السطر 12.
- السطر 25: يتم طلب مصفوفة الموظفين من طبقة [business]
- السطر 27: رسالة النجاح
- السطر 32: رسالة الخطأ
7.5.3. نموذج [Default.a spx]
النموذج هو النموذج الموجود في الإصدار 2.

السؤال: باستخدام كود C# من صفحة [Default.aspx.cs] للإصدار 2 كدليل، اكتب كود [Default.aspx.cs] للإصدار 3. الفرق الوحيد هو في حساب الراتب. بينما استخدم الإصدار 2 واجهة برمجة تطبيقات ADO.NET لاسترداد المعلومات من قاعدة البيانات، سنستخدم هنا طريقة GetSalaire من [business].
تمرين عملي:
قم بنشر تطبيق الويب السابق على جهاز- استخدم ملفات تكوين [Web.config] مختلفة لاستخدام أنظمة إدارة قواعد البيانات المختلفة (Firebird، MySQL، Postgres، SQL Server)





























