Skip to content

9. دراسة حالة

9.1. Introduction

سنقدم دراسة حالة سبق نشرها في مقال متاح على URL [http://tahe.developpez.com/dotnet/pam-aspnet/]. في هذا المقال، تم إجراء دراسة الحالة باستخدام ASP.NET الكلاسيكي وORM NHibernate. سنقوم هنا بتنفيذها باستخدام ASP.NET و MVC و ORM Entity Framework. كما هو الحال في المقالة الحالية، يتم تقديم دراسة الحالة على أنها مشروع جامعي. لذا فهي موجهة للطلاب. بالنسبة لجميع الأسئلة، يتم الإشارة إلى الفصول التي قمنا بتفصيلها للتو للإشارة إلى القراءات المفيدة.

9.2. المشكلة المطلوب حلها

نرغب في كتابة تطبيق ويب يسمح للمستخدم بإجراء محاكاة لحساب رواتب المربيات في جمعية "Maison de la petite enfance" في إحدى البلديات. سنهتم بتنظيم كود التطبيق بقدر اهتمامنا بالكود نفسه.

سيكون التطبيق من النوع APU [Application à Page Unique] وسيستخدم حصريًا استدعاءات Ajax للتواصل مع الخادم. وسيعرض للمستخدم العروض التالية:

  • العرض [VueSaisies] الذي يعرض نموذج المحاكاة

Image

  • العرض [VueSimulation] المستخدم لعرض النتيجة التفصيلية للمحاكاة:

Image

  • الطريقة [VueSimulations] التي تعرض قائمة بالمحاكاة التي أجراها العميل

Image

  • عرض [VueSimulationsVides] الذي يشير إلى أن العميل ليس لديه محاكاة أو لم يعد لديه محاكاة:

Image

  • عرض [VueErreurs] الذي يشير إلى وجود خطأ واحد أو أكثر (هنا تم إيقاف SGBD MySQL):

Image

9.3. بنية التطبيق

ستكون بنية التطبيق كما يلي:

تشير الطبقة [EF5] إلى ORM Entity Framework 5. وسيكون SGBD المستخدم هو MySQL.

سنقوم ببناء هذا التطبيق أولاً باستخدام طبقة [métier] محاكاة:

وهذا سيسمح لنا بالتركيز فقط على الطبقة [web]. وستتوافق الطبقة [métier] المحاكاة مع واجهة الطبقة [métier] الحقيقية. عندما تصبح الطبقة [web] جاهزة للعمل، سنقوم عندئذٍ بإنشاء الطبقات [métier] و [DAO] و [EF5].

9.4. قاعدة البيانات

يتم وضع البيانات الثابتة المفيدة لإنشاء كشف الراتب في قاعدة بيانات MySQL تسمى [dbpam_ef5] (pam=Paie Assistante Maternelle). لهذه القاعدة مسؤول يدعى root بدون كلمة مرور. وتحتوي على ثلاثة جداول:

Image

هناك علاقة مفتاح خارجي بين العمود EMPLOYES(INDEMNITE_ID) والعمود INDEMNITES(ID). يتم تحديد بنية قاعدة البيانات هذه من خلال استخدامها مع EF5. سنعود إلى هذا الموضوع عند إنشاء الطبقات السفلية للتطبيق.

الجدول EMPLOYES: يجمع معلومات عن مختلف المربيات

الهيكل:

ID
مفتاح أساسي يتم زيادة قيمته تلقائيًا بواسطة SGBD
SS
رقم الضمان الاجتماعي للموظف - فريد
NOM
اسم الموظف
PRENOM
اسمه الأول
ADRESSE
عنوانه
VILLE
مدينته
CP
الرمز البريدي
VERSIONING
عدد صحيح يتم زيادة قيمته تلقائيًا في كل مرة يتم فيها تعديل السجل
INDEMNITE_ID
مفتاح خارجي في الحقل [ID] من الجدول [INDEMNITES]

قد يكون محتواه كما يلي:

Image

الجدول COTISATIONS: يجمع معدلات الاشتراكات الاجتماعية المقتطعة من الراتب

الهيكل:

ID
مفتاح أساسي يتم زيادته تلقائيًا بواسطة SGBD
CSGRDS
النسبة المئوية: الاشتراك الاجتماعي العام + الاشتراك في سداد الدين الاجتماعي
CSGD
النسبة المئوية: الاشتراك الاجتماعي العام القابل للخصم
SECU
النسبة المئوية: الضمان الاجتماعي
RETRAITE
النسبة المئوية: المعاش التكميلي + التأمين ضد البطالة
VERSIONING
عدد صحيح يتم زيادة قيمته تلقائيًا في كل مرة يتم فيها تعديل السجل

قد يكون محتواه كما يلي:

Image

معدلات الاشتراكات الاجتماعية مستقلة عن الموظف. الجدول السابق يحتوي على سطر واحد فقط.

الجدول INDEMNITES: يجمع مختلف التعويضات التي تعتمد على مؤشر الموظف
ID
مفتاح أساسي يتم زيادته تلقائيًا بواسطة SGBD
INDICE
مؤشر الراتب - فريد
BASE_HEURE
السعر الصافي باليورو لساعة واحدة من الحراسة
ENTRETIEN_JOUR
بدل الإعاشة باليورو لكل يوم من أيام الحراسة
REPAS_JOUR
بدل الوجبات باليورو لكل يوم رعاية
INDEMNITES_CP
بدل الإجازات المدفوعة الأجر. وهي نسبة مئوية تُطبق على الراتب الأساسي.
VERSIONING
عدد صحيح يتم زيادة قيمته تلقائيًا في كل مرة يتم فيها تعديل السجل

قد يكون محتواه كما يلي:

Image

9.5. طريقة حساب راتب مربية أطفال

نقدم الآن طريقة حساب الراتب الشهري لمربية أطفال. نأخذ كمثال راتب السيدة ماري جوفينال التي عملت 150 ساعة على مدار 20 يومًا خلال الشهر المراد الدفع فيه.

يتم أخذ العناصر التالية في الاعتبار:
[TOTALHEURES]: إجمالي ساعات العمل في الشهر
[TOTALJOURS]: إجمالي أيام العمل في الشهر
[TOTALHEURES]=150
[TOTALJOURS]= 20
يتم حساب الراتب الأساسي للمربية باستخدام الصيغة التالية:
[SALAIREBASE]=([TOTALHEURES]*[BASEHEURE])*(1+[INDEMNITESCP]/100)
[SALAIREBASE]=(150*[2.1])*(1+0.15)= 362,25
يجب اقتطاع عدد من الاشتراكات الاجتماعية من هذا الراتب الأساسي:
الاشتراك الاجتماعي العام واشتراك سداد الدين الاجتماعي: [SALAIREBASE]*[CSGRDS/100]
الاشتراك الاجتماعي العام القابل للخصم: [SALAIREBASE]*[CSGD/100]
الضمان الاجتماعي، الترمل، الشيخوخة: [SALAIREBASE]*[SECU/100]
التقاعد التكميلي + AGPF + التأمين ضد البطالة: [SALAIREBASE]*[RETRAITE/100]
CSGRDS: 12,64
CSGD: 22,28
الضمان الاجتماعي: 34,02
التقاعد: 28,55
إجمالي الاشتراكات الاجتماعية:
[COTISATIONSSOCIALES]=[SALAIREBASE]*(CSGRDS+CSGD+SECU+RETRAITE)/100
[COTISATIONSSOCIALES]=97,48
بالإضافة إلى ذلك، يحق للمربية الخاصة الحصول على بدل إعاشة وبدل وجبات عن كل يوم عمل. وبهذا الصدد، تتلقى البدلات التالية:
[Indemnités]=[TOTALJOURS]*(ENTRETIENJOUR+REPASJOUR)
[INDEMNITES]=104
في النهاية، يكون الراتب الصافي المستحق الدفع للمربية كما يلي:
[SALAIREBASE]-[COTISATIONSSOCIALES]+[INDEMNITÉS]
[salaire NET]=368,77

9.6. مشروع Visual Studio للطبقة [web]

سيكون مشروع Visual Web Developer للتطبيق كما يلي:

  • في [1]، الهيكل العام للمشروع [pam-web-01
  • في [2]، المجلد [Content] هو المجلد الذي نضع فيه الموارد الثابتة للمشروع:
    • [indicator.gif]: الصورة المتحركة لانتظار انتهاء طلب Ajax،
    • [standard.jpg]: صورة الخلفية لمختلف العروض،
    • [Site.css]: ورقة أنماط التطبيق؛
  • في [3]، وحدة التحكم الوحيدة للتطبيق [PamController
  • في [4]، الفئات الضرورية للتطبيق ولكن لا يمكن تصنيفها كعناصر في MVC:
    • [ApplicationModelBinder]: الفئة التي تسمح بتضمين بيانات نطاق [Application] في نموذج الإجراءات،
    • [SessionModelBinder]: الفئة التي تسمح بتضمين بيانات نطاق [Session] في نموذج الإجراءات،
    • [Static]: فئة مساعدة تحتوي على طرق ثابتة؛
  • في [5]، نماذج التطبيق، سواء كانت نماذج الإجراءات أو العروض:
    • [ApplicationModel]: نموذج يحتوي على بيانات النطاق [Application
    • [SessionModel]: نموذج يحتوي على بيانات نطاق [Session
    • [Simulation]: فئة تغلف عناصر محاكاة حساب الراتب،
    • [IndexModel]: نموذج للطريقة الأولى [Index] التي يعرضها التطبيق؛
  • في [6]، البرامج النصية JS اللازمة لتعولمة التطبيق؛
  • في [7]، البرامج النصية JS من عائلة JQuery اللازمة لتدويل التطبيق والتحقق من صحة البيانات من جانب العميل وتحويل التطبيق إلى Ajax؛
  • في [8]، [myScripts.js] هو الملف الذي يحتوي على نصوصنا البرمجية الخاصة JS؛
  • في [9]، طرق عرض التطبيق:
    • [Index]: الصفحة الرئيسية،
    • [Formulaire]: نموذج إدخال بيانات الموظف وساعات وأيام عمله،
    • [Simulation]: العرض الذي يعرض محاكاة،
    • [Simulations]: العرض الذي يعرض قائمة المحاكاة التي تم إجراؤها،
    • [Erreurs]: العرض الذي يعرض قائمة الأخطاء المحتملة،
    • [InitFailed]: العرض الذي يعرض رسائل الأخطاء في حالة فشل تهيئة التطبيق؛
  • في [10]، الصفحة الرئيسية للتطبيق [_Layout]؛
  • في [11]، الملفات [Web.config] و [Global.asax] المستخدمة لتكوين التطبيق.

9.7. الخطوة 1 – إعداد طبقة [métier] المحاكاة

من الآن فصاعدًا، سنصف الخطوات التي يجب اتباعها لتنفيذ دراسة الحالة. وعند الضرورة، سنشير إلى رقم الفصل الذي قد تحتاج إلى إعادة قراءته لتنفيذ المهمة المطلوبة. يتم تزويدك ببعض عناصر المشروع في مجلد [aspnetmvc-support.zip] الذي يمكن العثور عليه على موقع هذا المستند. ستجد فيه المجلد [étudedecas-support] الذي يحتوي على المحتوى التالي:

  

يستند المشروع أيضًا إلى عناصر تم عرضها في الفصول السابقة. يكفي إذن استرداد هذه العناصر عن طريق النسخ واللصق بين ملف PDF و Visual Studio.

9.7.1. حل Visual Studio للتطبيق الكامل

سنقوم أولاً بإنشاء حل Visual Studio سننشئ فيه مشروعين:

  • مشروع للطبقة [métier] المحاكاة؛
  • مشروع لطبقة الويب MVC.

سنستخدم أداتين:

  • Visual Studio Express 2012 للكمبيوتر المكتبي الذي سيُستخدم لبناء الطبقة [métier
  • Visual Studio Express 2012 للويب الذي سيُستخدم لبناء الطبقة [web].

باستخدام Visual Studio Express للكمبيوتر المكتبي، نقوم بإنشاء حل [pam-td]:

  • في [1]، اختر تطبيق C#؛
  • في [2]، اختر [Application console
  • في [3]، قم بتسمية الحل؛
  • في [4]، قم بإنشاء مجلد لهذا الحل؛
  • في [5]، قم بتسمية الطبقة [métier
  • في [6]، الحل الذي تم إنشاؤه.

9.7.2. واجهة الطبقة [métier]

في بنية الطبقات، من الممارسات الجيدة أن تتم الاتصالات بين الطبقات عبر واجهات:

ما هي الواجهة التي يجب أن تقدمها الطبقة [métier] إلى الطبقة [web]؟ ما هي التفاعلات الممكنة بين هاتين الطبقتين؟ لنتذكر واجهة الويب التي سيتم عرضها على المستخدم:

  1. عند العرض الأولي للنموذج، يجب أن نجد في [1] قائمة الموظفين. يكفي وجود قائمة مبسطة (الاسم، الاسم الأول، SS). الرقم SS ضروري للوصول إلى المعلومات الإضافية عن الموظف المحدد (المعلومات من 6 إلى 11).
  2. المعلومات من 12 إلى 15 هي معدلات الاشتراكات المختلفة.
  3. المعلومات من 16 إلى 19 هي تعويضات الموظف
  4. المعلومات من 20 إلى 24 هي عناصر الراتب المحسوبة بناءً على الإدخالات من 1 إلى 3 التي قام بها المستخدم.

يجب أن تستوفي الواجهة [IPamMetier] التي توفرها الطبقة [métier] للطبقة [web] المتطلبات المذكورة أعلاه. هناك العديد من الواجهات الممكنة. ونقترح الواجهة التالية:


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] الذي سنقوم بوصفه لاحقًا.

سنضع هذه الواجهة في مجلد [metier/service]:

9.7.3. كيانات الطبقة [métier]

تستخدم الواجهة السابقة فئتين هما [Employe] و [FeuilleSalaire] اللتين يتعين علينا تعريفهما:

  • [Employe] هي صورة لسطر من الجدول [employes] في قاعدة البيانات؛
  • [FeuilleSalaire] هي كشف راتب أحد الموظفين.

سيتم وضع الكيانات في مجلد [metier / entites] الخاص بالمشروع:

في البنية النهائية، ستعالج الطبقة [métier] كيانات الصور في قاعدة البيانات:

Image

سنستخدم الفئات التالية لتمثيل صفوف الجداول الثلاثة في قاعدة البيانات. يرجى الرجوع إلى الفقرة 9.4 لمعرفة معنى الحقول المختلفة.

فئة [Employe]

وهي تمثل سطرًا من الجدول [employes]. ورمزها هو التالي:


using System;

namespace Pam.Metier.Entites
{

  public class Employe
  {
    public string SS { get; set; }
    public string Nom { get; set; }
    public string Prenom { get; set; }
    public string Adresse { get; set; }
    public string Ville { get; set; }
    public string CodePostal { get; set; }
    public Indemnites Indemnites { get; set; }

    // التوقيع
    public override string ToString()
    {
      return string.Format("Employé[{0},{1},{2},{3},{4},{5}]", SS, Nom, Prenom, Adresse, Ville, CodePostal);
    }
  }
}

الفئة [Indemnites]

تمثل سطراً من الجدول [indemnites]. رمزها هو التالي:


using System;

namespace Pam.Metier.Entites
{
  public class Indemnites
  {
    public int Indice { get; set; }
    public double BaseHeure { get; set; }
    public double EntretienJour { get; set; }
    public double RepasJour { get; set; }
    public double IndemnitesCp { get; set; }
    // التوقيع
    public override string ToString()
    {
      return string.Format("Indemnités[{0},{1},{2},{3},{4}]", Indice, BaseHeure, EntretienJour, RepasJour, IndemnitesCp);
    }
  }
}

الفئة [Cotisations]

تمثل سطراً من الجدول [cotisations]. رمزها هو التالي:


using System;

namespace Pam.Metier.Entites
{

  public class Cotisations
  {
    public double CsgRds { get; set; }
    public double Csgd { get; set; }
    public double Secu { get; set; }
    public double Retraite { get; set; }
    // التوقيع
    public override string ToString()
    {
      return string.Format("Cotisations[{0},{1},{2},{3}]", CsgRds, Csgd, Secu, Retraite);
    }
  }
}

تجدر الإشارة إلى أن الفئات لا تتضمن الأعمدة [ID] و [VERSIONING] من الجداول. هذه الأعمدة، التي تكون مفيدة عند استخدام ORM و EF5، ليست كذلك في سياق الطبقة [métier] المحاكاة.

تغلف الفئة [FeuilleSalaire] المعلومات من 6 إلى 24 من النموذج المقدم سابقًا:


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);
    }
  }
}
  • السطر 7: المعلومات من 6 إلى 11 عن الموظف الذي يتم حساب راتبه والمعلومات من 16 إلى 19 عن بدلاته. ولا ينبغي نسيان هنا أن كائن [Employe] يغلف كائن [Indemnites] الذي يمثل بدلاته؛
  • السطر 8: المعلومات من 12 إلى 15؛
  • السطر 9: المعلومات من 20 إلى 24؛
  • الأسطر 12-14: الطريقة [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);
    }
  }
}
  • السطور 6-10: عناصر الراتب كما هو موضح في القواعد المهنية الموصوفة سابقًا؛
  • السطر 6: الراتب الأساسي للموظف، حسب عدد ساعات العمل؛
  • السطر 7: الاشتراكات المقتطعة من هذا الراتب الأساسي؛
  • السطران 8 و9: البدلات التي تضاف إلى الراتب الأساسي، حسب مؤشر الموظف وعدد أيام العمل؛
  • السطر 10: الراتب الصافي المستحق الدفع؛
  • السطور 14-17: طريقة [ToString] للفئة.

9.7.4. الفئة [PamException]

نقوم بإنشاء نوع استثناءات محدد لتطبيقنا. وهو النوع [PamException] التالي:


using System;

namespace Pam.Metier.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;
    }
  }
}
  • السطر 6: الفئة مشتقة من الفئة [Exception
  • السطر 10: لها خاصية عامة [Code] وهي رمز خطأ؛
  • سنستخدم في تطبيقنا نوعين من المنشئ:
    • المنشئ الموجود في الأسطر 23-27 والذي يمكن استخدامه كما هو موضح أدناه:
throw new PamException("Problème d'accès aux données",5);
  • (تابع)
    • أو المنشئ الموجود في الأسطر 29-33 والمخصص لإبلاغ عن استثناء حدث عن طريق تغليفه في استثناء من النوع [PamException]:
try{
....
}catch (IOException ex){
     // تغليف الاستثناء ex     
    throw new PamException("Problème d'accès aux données",ex,10);
}

تتميز هذه الطريقة الثانية بأنها لا تفقد المعلومات التي قد تحتوي عليها الاستثناء الأول.

9.7.5. تنفيذ الطبقة [métier]

سيتم تنفيذ الواجهة [IPamMetier] بواسطة الفئة [PamMetier] التالية:


using System;
using Pam.Metier.Entites;
using System.Collections.Generic;

namespace Pam.Metier.Service
{
  public class PamMetier : IPamMetier
  {
    // قائمة الموظفين المخزنة في ذاكرة التخزين المؤقت
    public Employe[] Employes { get; set; }
    // الموظفون مفهرسون حسب أرقامهم SS
    private IDictionary<string, Employe> dicEmployes = new Dictionary<string, Employe>();

    // قائمة الموظفين
    public Employe[] GetAllIdentitesEmployes()
    {
...
      // عرض قائمة الموظفين
      return Employes;
    }

    // حساب الراتب
    public FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés)
    {
...
  }
}
  • السطر 7: تنفذ الفئة [PamMetier] الواجهة [IPamMetier
  • السطر 10: تحتفظ الفئة [PamMetier] بقائمة الموظفين في ذاكرة التخزين المؤقت؛
  • السطر 12: قاموس يربط الموظف برقم الضمان الاجتماعي الخاص به؛
  • الأسطر 15-20: الطريقة التي تعرض قائمة الموظفين؛
  • الأسطر 23-26: الطريقة التي تحسب راتب الموظف.

الطريقة [GetAllIdentitesEmploye] هي كما يلي:


// قائمة الموظفين
    public Employe[] GetAllIdentitesEmployes()
    {
      if (Employes == null)
      {
        // إنشاء جدول بثلاثة موظفين
        Employes = new Employe[3];
        Employes[0] = new Employe()
        {
          SS = "254104940426058",
          Nom = "Jouveinal",
          Prenom = "Marie",
          Adresse = "5 rue des oiseaux",
          Ville = "St Corentin",
          CodePostal = "49203",
          Indemnites = new Indemnites() { Indice = 2, BaseHeure = 2.1, EntretienJour = 2.1, RepasJour = 3.1, IndemnitesCp = 15 }
        };
        dicEmployes.Add(Employes[0].SS, Employes[0]);
        Employes[1] = new Employe()
        {
          SS = "260124402111742",
          Nom = "Laverti",
          Prenom = "Justine",
          Adresse = "La brûlerie",
          Ville = "St Marcel",
          CodePostal = "49014",
          Indemnites = new Indemnites() { Indice = 1, BaseHeure = 1.93, EntretienJour = 2, RepasJour = 3, IndemnitesCp = 12 }
        };
        dicEmployes.Add(Employes[1].SS, Employes[1]);
        // موظف وهمي لن يتم إدراجه في القاموس
        // لإجراء محاكاة لموظف غير موجود
        Employes[2] = new Employe()
        {
          SS = "XX",
          Nom = "X",
          Prenom = "X",
          Adresse = "X",
          Ville = "X",
          CodePostal = "X",
          Indemnites = new Indemnites() { Indice = 0, BaseHeure = 0, EntretienJour = 0, RepasJour = 0, IndemnitesCp = 0 }
        };
      }
      // عرض قائمة الموظفين
      return Employes;
    }
  • السطر 4: يتم التحقق مما إذا كانت قائمة الموظفين قد تم إنشاؤها بالفعل أم لا؛
  • السطر 7: إذا لم يكن الأمر كذلك، يتم إنشاء مصفوفة تضم ثلاثة موظفين؛
  • السطور 8-17: الموظف الأول؛
  • السطر 18: يتم إدراجه في القاموس؛
  • السطور 19-28: الموظف الثاني؛
  • السطر 29: يتم إدراجه في القاموس؛
  • السطور 32-42: الموظف الثالث. لا يتم إدراجه في القاموس لسبب سنشرحه لاحقًا.

ستكون الطريقة [GetSalaire] كما يلي:


    // حساب الراتب
    public FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés)
    {
      // نسترد الموظف رقم SS
      Employe e = dicEmployes.ContainsKey(ss) ? dicEmployes[ss] : null;
      // موجود؟
      if (e == null)
      {
        throw new PamException(string.Format("L'employé de n° SS [{0}] n'existe pas", ss), 10);
      }
      // عرض كشف راتب وهمي
      return new FeuilleSalaire()
      {
        Employe = e,
        Cotisations = new Cotisations() { CsgRds = 3.49, Csgd = 6.15, Secu = 9.38, Retraite = 7.88 },
        ElementsSalaire = new ElementsSalaire() { CotisationsSociales = 100, IndemnitesEntretien = 100, IndemnitesRepas = 100, SalaireBase = 100, SalaireNet = 100 }
      };
}
  • السطر 2: تتلقى الطريقة الرقم SS الخاص بالموظف الذي نريد حساب راتبه وعدد ساعات عمله وعدد أيام عمله؛
  • السطر 5: سنبحث عن الموظف في القاموس. نتذكر أن أحدهم غير موجود فيه؛
  • السطور 7-10: إذا لم يتم العثور على الموظف، يتم إصدار استثناء [PamException
  • الأسطر 12-17: يتم إرجاع كشف راتب وهمي.

9.7.6. اختبار وحدة التحكم للطبقة [métier]

مشروع الطبقة [métier] هو حاليًا كما يلي:

ستقوم الفئة [Program] المذكورة أعلاه باختبار أساليب واجهة [IPamMetier]. قد يكون المثال الأساسي كما يلي:


using Pam.Metier.Entites;
using Pam.Metier.Service;
using System;

namespace Pam.Metier.Tests
{
  class Program
  {
    public static void Main()
    {
      // إنشاء مثيل الطبقة [métier]
      IPamMetier pamMetier = new PamMetier();
      // قائمة الموظفين
      Employe[] employes = pamMetier.GetAllIdentitesEmployes();
      Console.WriteLine("Liste des employés--------------------");
      foreach (Employe e in employes)
      {
        Console.WriteLine(e);
      }
      // حسابات كشوف الرواتب 
      Console.WriteLine("Calculs de feuilles de salaire-----------------");
      Console.WriteLine(pamMetier.GetSalaire(employes[0].SS, 30, 5));
      Console.WriteLine(pamMetier.GetSalaire(employes[1].SS, 150, 20));
      try
      {
        Console.WriteLine(pamMetier.GetSalaire(employes[2].SS, 150, 20));
      }
      catch (PamException ex)
      {
        Console.WriteLine(string.Format("PamException : {0}", ex.Message));
      }
    }
  }
}
  • السطر 12: إنشاء مثيل للطبقة [métier
  • الأسطر 14-19: اختبار الطريقة [GetAllIdentitesEmploye] للواجهة [IPamMetier
  • الأسطر 21-31: اختبار الطريقة [GetSalaire] للواجهة [IPamMetier].

يؤدي تشغيل برنامج وحدة التحكم هذا إلى النتائج التالية:

Liste des employés--------------------
Employé[254104940426058,Jouveinal,Marie,5 rue des oiseaux,St Corentin,49203]
Employé[260124402111742,Laverti,Justine,La brûlerie,St Marcel,49014]
Employé[XX,X,X,X,X,X]
Calculs de feuilles de salaire-----------------
[Employé[254104940426058,Jouveinal,Marie,5 rue des oiseaux,St Corentin,49203],Co
tisations[3,49,6,15,9,38,7,88],[100 : 100 : 100 : 100 : 100]]
[Employé[260124402111742,Laverti,Justine,La brûlerie,St Marcel,49014],Cotisation
s[3,49,6,15,9,38,7,88],[100 : 100 : 100 : 100 : 100]]
PamException : L'employé de n° SS [XX] n'existe pas

يُطلب من القارئ ربط هذه النتائج بالكود الذي تم تنفيذه.

من أجل استخدام هذا المشروع في مشروع الويب الذي سنقوم ببنائه، نقوم بتحويله إلى مكتبة فئات:

  • في [1]، في خصائص الملف [Program.cs
  • في [2]، نحدد أن الملف لن يكون جزءًا من التجميع الذي تم إنشاؤه؛
  • في [3, 4]، في خصائص المشروع [pam-metier-simule]، في الخيار [Application] [3]، يُشار في [4] إلى أن عملية الإنشاء يجب أن توفر مكتبة فئات (في شكل DLL).
  • في [5]، يُطلب تجميع من النوع [Release]. النوع الآخر هو [Debug]. يحتوي التجميع عندئذٍ على معلومات تسهل عملية تصحيح الأخطاء؛
  • في [6]، يتم إنشاء المشروع [pam-metier-simule
  • في [7]، يتم عرض جميع ملفات الحل؛
  • في [8]، في المجلد [bin / Release]، يتم إنشاء ملف DLL لمشروعنا.

9.8. الخطوة 2: إعداد تطبيق الويب

في حل Visual Studio السابق، سنقوم بإنشاء مشروع لطبقة الويب MVC.

باستخدام Visual Studio Express للويب، نفتح الحل [pam-td] الذي تم إنشاؤه مسبقًا باستخدام Visual Studio Express للكمبيوتر المكتبي.

  • في [1]، تم تحميل الحل [pam-td] في Visual Studio Express للويب؛
  • في [2]، الحل والمشروع للطبقة [métier] المحاكاة التي أنشأناها للتو.

في هذه الخطوة الجديدة، سنقوم بإنشاء الهيكل الأساسي لتطبيق الويب.

  • في [1]، نضيف مشروعًا جديدًا إلى الحل [pam-td
  • في [2]، نختار مشروعًا ASP.NET MVC
  • المسمى [pam-web-01] [3]؛
  • في [4]، نختار النموذج الأساسي ASP.NET MVC؛
  • في [5]، المشروع الذي تم إنشاؤه؛
  • في [6]، يتم إنشاء مشروع جديد، وهو مشروع بدء الحل، الذي سيتم تنفيذه عند تنفيذ [Ctrl-F5
  • في [7]، أصبح اسم المشروع الجديد بالخط العريض، مما يشير إلى أنه مشروع بدء الحل.

الآن، نستبدل باستخدام مستكشف Windows المجلد [Content] الخاص بالمشروع بالمجلد [étudedecas-support / web / Content]. بعد القيام بذلك، يجب تضمين الملفات الجديدة في المشروع [pam-web-01]. وسنقوم بذلك على النحو التالي:

  • في [1]، قم بتحديث الحل؛
  • في [2]، نعرض جميع ملفات الحل؛
  • في [3]، يظهر مجلد [Images
  • الذي يتم تضمينه في المشروع في [4].

في المجلد [Scripts]، أضف البرامج النصية JQuery Globalization [1] اللازمة للتحقق من صحة البيانات من جانب العميل.

ستحتوي الصفحة الرئيسية [_Layout.cshtml] [2] على المحتوى التالي:


<!DOCTYPE html>
<html>
<head>
  <title>@ViewBag.Title</title>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width" />
  <link rel="stylesheet" href="~/Content/Site.css" />
  <script type="text/javascript" src="~/Scripts/jquery-1.8.2.min.js"></script>
  <script type="text/javascript" src="~/Scripts/jquery.validate.min.js"></script>
  <script type="text/javascript" src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>
  <script type="text/javascript" src="~/Scripts/globalize/globalize.js"></script>
  <script type="text/javascript" src="~/Scripts/globalize/cultures/globalize.culture.fr-FR.js"></script>
  <script type="text/javascript" src="~/Scripts/jquery.unobtrusive-ajax.js"></script>
  <script type="text/javascript" src="~/Scripts/myScripts.js"></script>
</head>
<body>
  <table>
    <tbody>
      <tr>
        <td>
          <h2>Simulateur de calcul de paie</h2>
        </td>
        <td style="width: 20px">
          <img id="loading" style="display: none" src="~/Content/images/indicator.gif" />
        </td>
        <td>
          <a id="lnkFaireSimulation" href="javascript:faireSimulation()">| Faire la simulation<br />
          </a>
          <a id="lnkEffacerSimulation" href="javascript:effacerSimulation()">| Effacer la simulation<br />
          </a>
          <a id="lnkVoirSimulations" href="javascript:voirSimulations()">| Voir les simulations<br />
          </a>
          <a id="lnkRetourFormulaire" href="javascript:retourFormulaire()">| Retour au formulaire de simulation<br />
          </a>
          <a id="lnkEnregistrerSimulation" href="javascript:enregistrerSimulation()">| Enregistrer la simulation<br />
          </a>
          <a id="lnkTerminerSession" href="javascript:terminerSession()">| Terminer la session<br />
          </a>
        </td>
    </tbody>
  </table>
  <hr />
  <div id="content">
    @RenderBody()
  </div>
</body>
</html>

ملاحظة: السطر 8، قم بتكييف إصدار jQuery مع إصدار Visual Studio الخاص بك.

  • السطر 7: مرجع إلى ورقة أنماط التطبيق؛
  • الأسطر 8-10: إشارات إلى البرامج النصية اللازمة للتحقق من صحة البيانات من جانب العميل؛
  • السطران 11-12: إشارات إلى البرامج النصية اللازمة لإدخال الأعداد العشرية الفرنسية التي تستخدم الفاصلة؛
  • السطر 13: إشارة إلى البرامج النصية اللازمة لوضع Ajax؛
  • السطر 14: البرامج النصية الخاصة بالتطبيق؛
  • السطر 24: صورة انتظار انتهاء استدعاءات Ajax؛
  • السطور 26-39: ستة روابط جافا سكريبت؛
  • السطر 43: القسم الذي ستظهر فيه طرق العرض المختلفة للتطبيق؛
  • السطر 44: نص العروض المختلفة للتطبيق.

بعد ذلك، سنقوم بتعديل المسار الافتراضي للتطبيق:

سيحتوي الملف [RouteConfig] على المحتوى التالي:


using System.Web.Mvc;
using System.Web.Routing;

namespace pam_web_01
{
  public class RouteConfig
  {
    public static void RegisterRoutes(RouteCollection routes)
    {
      routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

      routes.MapRoute(
          name: "Default",
          url: "{controller}/{action}",
          defaults: new { controller = "Pam", action = "Index" }
      );
    }
  }
}
  • السطر 14: سيكون شكل URL هو [{controller}/{action}]؛
  • السطر 15: في حالة عدم وجود إجراء، سيتم استخدام الإجراء [Index]. في حالة عدم وجود وحدة تحكم، سيتم استخدام وحدة التحكم [Pam].

ينتج عن هذا التكوين أن URL [/] تعادل URL [/Pam/Index]. نظرًا لأن تطبيقنا من النوع APU، فإن URL و [/] سيكونان الوحيدين من نوع URL في هذا التطبيق.

قم بإنشاء وحدة التحكم [Pam]:

  

قم بتعديل وحدة التحكم [PamController] بالطريقة التالية:


using System.Web.Mvc;

namespace Pam.Web.Controllers
{
    public class PamController : Controller
    {
        [HttpGet]
        public ViewResult Index()
        {
            return View();
        }

    }
}
  • السطر 3: نضع وحدة التحكم في مساحة الاسم [Pam.Web.Controllers
  • السطر 7: ستعالج الإجراء [Index] الأمر HTTP GET فقط؛
  • السطر 8: نُرجع نوع [ViewResult] بدلاً من نوع [ActionResult].

أنشئ الآن العرض [Index.cshtml] الذي يتم عرضه بواسطة الإجراء [Index] أعلاه:

قم بتعديل [Index.cshtml] على النحو التالي:


@{
  ViewBag.Title = "Pam";
}
<h2>Formulaire</h2>

قم بتشغيل التطبيق بواسطة [Ctrl-F5]. يجب أن تحصل على الصفحة التالية:

 

المهمة: اشرح ما حدث.


يستخدم التطبيق ورقة أنماط مشار إليها في الصفحة الرئيسية [_Layout.cshtml]:


  <link rel="stylesheet" href="~/Content/Site.css" />

تحدد ورقة الأنماط [/Content/Site.css] صورة خلفية لصفحات التطبيق:


body {
  background-image: url("/Content/Images/standard.jpg");
}

9.9. الخطوة 3: إعداد النموذج APU

نريد كتابة تطبيق وفقًا للنموذج APU (تطبيق ذو صفحة واحدة) الموصوف في الفقرة 7.5، وكذلك في الفقرة 7.6. الصفحة الوحيدة هي تلك التي يتم تحميلها بواسطة المتصفح عند بدء تشغيل التطبيق:

  • الجزء [1] أعلاه هو الجزء الثابت من الصفحة الواحدة. وقد رأينا أنه يتم توفيره بواسطة الصفحة الرئيسية [_Layout.cshtml
  • الجزء [2] هو الجزء المتغير من الصفحة الفريدة. وهو يقع في منطقة id [content] من الصفحة الرئيسية [_Layout.cshtml]:

<!DOCTYPE html>
<html>
<head>
  <title>@ViewBag.Title</title>
  ...
  <script type="text/javascript" src="~/Scripts/myScripts.js"></script>
</head>
<body>
  <table>
...
  </table>
  <hr />
  <div id="content">
    @RenderBody()
  </div>
</body>
</html>

سيتم عرض أجزاء الصفحة المختلفة للتطبيق في منطقة معرف [content] في السطر 13. وسيتم عرضها عبر استدعاءات Ajax. توجد نصوص Javascript التي تنفذ هذه الاستدعاءات في الملف [myScripts.js] المشار إليه في السطر 6. قم بإنشاء هذا الملف الذي سنحتاج إليه:

نحن الآن نتبع النموذج APU الموصوف في الفقرة 7.6. أعد قراءة هذه الفقرة إذا كنت قد نسيتها. سنقوم الآن بتنفيذ أجزاء الصفحة المختلفة التي تعرضها التطبيق.

9.9.1. أدوات مطور جافا سكريبت

نذكركم أنه مع متصفح Chrome، تتوفر لكم مجموعة من الأدوات لتصحيح أخطاء HTML و CSS و Javascript في صفحاتكم. تم عرض هذه الأدوات جزئيًا في الفقرة 7.2. في نموذج APU، تحتفظ المتصفحات في ذاكرة التخزين المؤقتة بنصوص جافا سكريبت المشار إليها في الصفحة الأولى للتطبيق. لذلك، يجب أن تفكر في مسح ذاكرة التخزين المؤقتة هذه عند تعديل نصوصك، وإلا فقد لا يتم أخذ التعديلات في الاعتبار. إليك كيفية القيام بذلك باستخدام Chrome:

- قم بتنفيذ [Ctrl-Maj-I] لعرض بيئة التطوير

  • انقر على أيقونة [1] في أسفل يمين نافذة التطوير؛
  • ثم حدد الخيار [2] الذي يعطل ذاكرة التخزين المؤقت في وضع التطوير.

9.9.2. استخدام عرض جزئي لعرض النموذج

نموذج الإدخال هو أحد الأجزاء التي تعرضها التطبيق. في الوقت الحالي، يتم عرض هذا النموذج من خلال العرض [Index.cshtml] وهو عرض كامل:


@{
  ViewBag.Title = "Pam";
}
<h2>Formulaire</h2>

يتم عرض هذه العرضة بواسطة الإجراء [Index]:


    [HttpGet]
    public ViewResult Index()
    {
      return View();
}

السطر 4 أعلاه، يتم عرض عرض [View] وليس عرضًا جزئيًا [PartialView]. نحتاج إلى عرض جزئي للنموذج الذي سيكون جزءًا من الصفحة. نقوم بتطوير العرض [Index.cshtml] بالطريقة التالية:


@{
  ViewBag.Title = "Pam";
}
@Html.Partial("Formulaire")

السطر 4، لم يعد النموذج جزءًا من الصفحة [Index.cshtml]. وهو موجود الآن في عرض جزئي [Formulaire.cshtml]:

رمز [Formulaire.cshtml] هو ببساطة ما يلي:


<h2>Formulaire</h2>

قم بإجراء هذه التعديلات وتأكد من أنك لا تزال تحصل على العرض التالي عند بدء تشغيل التطبيق:

 

9.9.3. استدعاء Ajax [faireSimulation]

نحن مهتمون بالجزء الذي يظهر عندما ينقر المستخدم على الرابط [Faire la simulation]:

  • في [1]، ينقر المستخدم على الرابط [Faire la simulation
  • في [2]، تظهر المحاكاة أسفل النموذج.

نقوم بتطوير العرض الجزئي [Formulaire.cshtml] الذي يعرض النموذج بالطريقة التالية:


<h2>Formulaire</h2>
<div id="simulation" />

في السطر 3، نقوم بإنشاء منطقة معرف [simulation] لاستيعاب جزء المحاكاة.

نقوم بإنشاء العرض الجزئي [Simulation.cshtml] التالي:

محتوى العرض [Simulation.cshtml] هو كما يلي:


<hr />
<h2>Simulation</h2>

علينا الآن كتابة كود جافا سكريبت الذي يدير النقر على الرابط [Faire la simulation]. سنتبع الخطوات الموضحة في الفقرة 7.6.5. أولاً، لنلقِ نظرة على كود HTML للرابط في [_Layout.cshtml]:


<a id="lnkFaireSimulation" href="javascript:faireSimulation()">| Faire la simulation<br />
</a>

نلاحظ أن النقر على الرابط [Faire la simulation] سيطلق تنفيذ الدالة JS [faireSimulation]. سيتم كتابة هذه الوظيفة في الملف [myScripts.js] بالإضافة إلى الوظائف الأخرى JS اللازمة للتطبيق:


// المتغيرات العامة
var loading;
var content;

function faireSimulation() {
  // يتم إجراء استدعاء Ajax يدويًا
...
}

function effacerSimulation() {
  // مسح إدخالات النموذج
...
}

function enregistrerSimulation() {
  // إجراء استدعاء Ajax يدويًا
  ...
}

function voirSimulations() {
  // يتم إجراء استدعاء Ajax يدويًا
  ...
}

function retourFormulaire() {
  // يتم إجراء استدعاء Ajax يدويًا
...
}

function terminerSession() {
...
}

// عند تحميل المستند
$(document).ready(function () {
  // يتم استرداد مراجع المكونات المختلفة للصفحة
  loading = $("#loading");
  content = $("#content");
});
  • الأسطر 35-39: الدالة JQuery التي يتم تنفيذها عند بدء تشغيل التطبيق؛
  • السطور 37-38: يتم تهيئة المتغيرات العامة في السطرين 2 و3.

تجدر الإشارة إلى أن عناصر id [loading] و [content] محددة في الصفحة الرئيسية [_Layout.cshtml] (السطران 14 و 21 أدناه):


<!DOCTYPE html>
<html>
<head>
...
</head>
<body>
  <table>
    <tbody>
      <tr>
        <td>
          <h2>Simulateur de calcul de paie</h2>
        </td>
        <td style="width: 20px">
          <img id="loading" style="display: none" src="~/Content/images/indicator.gif" />
        </td>
...
        </td>
    </tbody>
  </table>
  <hr />
  <div id="content">
    @RenderBody()
  </div>
</body>
</html>

المهمة: باتباع الخطوات الموضحة في الفقرة 7.6.5، اكتب الدالة JS [faireSimulation]. ستقوم هذه الدالة بإرسال استدعاء Ajax من النوع POST إلى الإجراء [/Pam/FaireSimulation]. لن يتم إرسال أي بيانات في الوقت الحالي.ستعيد الإجراء [/Pam/FaireSimulation] العرض الجزئي [Simulation.cshtml] إلى الدالة JS [faireSimulation] التي ستضع بعد ذلك هذا التدفق HTML في منطقة ذات المعرف [simulation] في النموذج.


اختبر الرابط [Faire la simulation] في تطبيقك.

9.9.4. استدعاء Ajax [enregistrerSimulation]

يتم تعريف الرابط [Enregistrer la simulation] على النحو التالي في [_Layout.cshtml]:


<a id="lnkEnregistrerSimulation" href="javascript:enregistrerSimulation()">| Enregistrer la simulation<br />
</a>

المهمة: باتباع الخطوات السابقة، اكتب الدالة JS [enregistrerSimulation]. ستقوم هذه الدالة بإرسال استدعاء Ajax من النوع POST إلى الإجراء [/Pam/EnregistrerSimulation]. لن يتم إرسال أي بيانات في الوقت الحالي.ستعيد الإجراء [/Pam/EnregistrerSimulation] العرض الجزئي [Simulations.cshtml] إلى الدالة JS [enregistrerSimulation] التي ستضع بعد ذلك هذا التدفق HTML في منطقة ذات المعرف [content] في الصفحة الرئيسية.


الطريقة [Simulations.cshtml] هي كما يلي:

محتواها كما يلي:


<h2>Simulations</h2>

فيما يلي مثال على التنفيذ:

9.9.5. استدعاء Ajax [voirSimulations]

يتم تعريف الرابط [Voir les simulations] على النحو التالي في [_Layout.cshtml]:


<a id="lnkVoirSimulations" href="javascript:voirSimulations()">| Voir les simulations<br />
</a>

المهمة: باتباع الخطوات السابقة، اكتب الدالة JS [voirSimulations]. ستقوم هذه الدالة بإرسال استدعاء Ajax من النوع POST إلى الإجراء [/Pam/VoirSimulations]. لن يتم إرسال أي بيانات في الوقت الحالي.ستقوم الإجراء [/Pam/VoirSimulations] بإرجاع العرض الجزئي [Simulations.cshtml] إلى الدالة JS [voirSimulations] التي ستضع بعد ذلك هذا التدفق HTML في منطقة ذات المعرف [content] في الصفحة الرئيسية.


العرض [Simulations.cshtml] هو نفسه الذي تم استخدامه في السؤال السابق.

فيما يلي مثال على التنفيذ:

9.9.6. استدعاء Ajax [retourFormulaire]

يتم تعريف الرابط [Retour au formulaire de simulation] على النحو التالي في [_Layout.cshtml]:


<a id="lnkRetourFormulaire" href="javascript:retourFormulaire()">| Retour au formulaire de simulation<br />
</a>

المهمة: باتباع الخطوات السابقة، اكتب الدالة JS [retourFormulaire]. ستقوم هذه الدالة بإرسال استدعاء Ajax من النوع POST إلى الإجراء [/Pam/Formulaire]. لن يتم إرسال أي بيانات في الوقت الحالي.ستعيد الإجراء [/Pam/Formulaire] العرض الجزئي [Formulaire.cshtml] إلى الدالة JS [retourFormulaire] التي ستضع بعد ذلك هذا التدفق HTML في منطقة ذات المعرف [content] في الصفحة الرئيسية.


تم تعريف العرض [Formulaire .cshtml] بالفعل. فيما يلي مثال على التنفيذ:

9.9.7. استدعاء Ajax [terminerSession]

يتم تعريف الرابط [Terminer la session] على النحو التالي في [_Layout.cshtml]:


<a id="lnkTerminerSession" href="javascript:terminerSession()">| Terminer la session<br />
</a>

المهمة: باتباع الخطوات السابقة، اكتب الدالة JS [terminerSession]. ستقوم هذه الدالة بإرسال استدعاء Ajax من النوع POST إلى الإجراء [/Pam/TerminerSession]. لن يتم إرسال أي بيانات في الوقت الحالي. ستقومستقوم الإجراء [/Pam/TerminerSession] بإرجاع العرض الجزئي [Formulaire.cshtml] إلى الدالة JS [terminerSession] التي ستضع بعد ذلك هذا التدفق HTML في منطقة ذات المعرف [content] في الصفحة الرئيسية.


فيما يلي مثال على التنفيذ:

9.9.8. الوظيفة JS [effacerSimulation]

يتم تعريف الرابط [Effacer la simulation] على النحو التالي في [_Layout.cshtml]:


<a id="lnkEffacerSimulation" href="javascript:effacerSimulation()">| Effacer la simulation<br />
</a>

الغرض من الوظيفة JS [effacerSimulation] هو:

  • إخفاء الجزء [Simulation] إن وجد؛
  • إعادة حقول الإدخال في النموذج إلى الحالة التي كانت عليها عند التحميل الأولي للتطبيق (عندما تكون هناك حقول إدخال - في الوقت الحالي لا توجد أي حقول).

المهمة: اكتب الدالة JS [effacerSimulation]. لا يوجد هنا استدعاء Ajax. ما يحدث هو عملية داخلية في المتصفح ولا تتضمن الخادم.


فيما يلي مثال على التنفيذ:

9.9.9. إدارة التنقل بين الشاشات

في الوقت الحالي، لا تزال الروابط معروضة. سنقوم الآن بإدارة عرضها باستخدام دالة جافا سكريبت. دعونا أولاً نستذكر كود الروابط الستة لجافا سكريبت في [_Layout.cshtml]:


<a id="lnkFaireSimulation" href="javascript:faireSimulation()">| Faire la simulation<br />
</a>
<a id="lnkEffacerSimulation" href="javascript:effacerSimulation()">| Effacer la simulation<br />
</a>
<a id="lnkVoirSimulations" href="javascript:voirSimulations()">| Voir les simulations<br />
</a>
<a id="lnkRetourFormulaire" href="javascript:retourFormulaire()">| Retour au formulaire de simulation<br />
</a>
<a id="lnkEnregistrerSimulation" href="javascript:enregistrerSimulation()">| Enregistrer la simulation<br />
</a>
<a id="lnkTerminerSession" href="javascript:terminerSession()">| Terminer la session<br />
</a>

تحتوي جميع الروابط على سمة [id] التي ستسمح لنا بإدارتها في جافا سكريبت. نقوم بتعديل الطريقة JS التي يتم تنفيذها عند تحميل الصفحة على النحو التالي:


// متغيرات عامة
var loading;
var content;
var lnkFaireSimulation;
var lnkEffacerSimulation
var lnkEnregistrerSimulation;
var lnkTerminerSession;
var lnkVoirSimulations;
var lnkRetourFormulaire;
var options;

...
// عند تحميل المستند
$(document).ready(function () {
  // يتم استرداد مراجع المكونات المختلفة للصفحة
  loading = $("#loading");
  content = $("#content");
  // روابط القائمة
  lnkFaireSimulation = $("#lnkFaireSimulation");
  lnkEffacerSimulation = $("#lnkEffacerSimulation");
  lnkEnregistrerSimulation = $("#lnkEnregistrerSimulation");
  lnkVoirSimulations = $("#lnkVoirSimulations");
  lnkTerminerSession = $("#lnkTerminerSession");
  lnkRetourFormulaire = $("#lnkRetourFormulaire");
  // يتم وضعها في جدول
  options = [lnkFaireSimulation, lnkEffacerSimulation, lnkEnregistrerSimulation, lnkVoirSimulations, lnkTerminerSession, lnkRetourFormulaire];
  // إخفاء بعض عناصر الصفحة
  loading.hide();
  // يتم تثبيت القائمة
  setMenu([lnkFaireSimulation, lnkVoirSimulations, lnkTerminerSession]);
});

  • الأسطر 19-24: نسترد المراجع الخاصة بالروابط الستة. يتم تعريف هذه المراجع كمتغيرات عامة في الأسطر 4-9؛
  • السطر 26: يتم تهيئة المصفوفة [options] بالمرجعيات الستة. يتم تعريف هذه المصفوفة كمتغير عام في السطر 10؛
  • السطر 28: يتم إخفاء الصورة المتحركة التي تشير إلى انتظار انتهاء استدعاءات Ajax؛
  • السطر 30: يتم عرض الروابط [lnkFaireSimulation, lnkVoirSimulations, lnkTerminerSession]. وسيتم إخفاء الروابط الأخرى.

الدالة JS [setMenu] هي كما يلي:


function setMenu(show) {
  // عرض روابط الجدول [show]
...
}

المهمة: اكتب الدالة JS [setMenu].


إذا كان T مصفوفة من الروابط:

  • T.length هو عدد الروابط؛
  • T[i] هو الرابط رقم i؛
  • T[i].show() تعرض الرابط رقم i؛
  • T[i].hide() يخفي الرابط رقم i.

باستخدام هذه الوظائف الجديدة JS، تكون الصفحة المعروضة عند بدء التشغيل هي التالية:

 

قم بتعديل الوظائف JS و [faireSimulation, effacerSimulation, enregistrerSimulation, voirSimulations, retourFormulaire, terminerSession] للحصول على الشاشات التالية:

الآن بعد أن أصبح النموذج APU وروابط التنقل جاهزة، يمكننا الانتقال إلى كتابة الإجراءات والعروض على جانب الخادم. خلال هذه الخطوات، ستكتشف أن بعض روابط Ajax التي تعمل الآن لم تعد تعمل، وذلك لأنك ستقوم بتعديل العروض الجزئية المرسلة إلى العميل. مع قيامك بإنشاء الإجراءات والعروض المختلفة على جانب الخادم، ستستعيد روابط Ajax على جانب العميل الوظيفة التي قمت بتعيينها لها.

9.10. الخطوة 4: كتابة إجراء الخادم [Index]

حاليًا عند بدء تشغيل التطبيق، تظهر الشاشة التالية:

 

بدلاً من هذه الشاشة، نرغب في الحصول على الشاشة التالية:

 

إن الإجراء [Index] هو الذي يجب أن ينتج هذه الصفحة. دعونا نقدم بعض الملاحظات:

  • تحتوي الصفحة على نموذج به ثلاثة حقول إدخال:
    • الموظف الذي يتم حساب راتبه،
    • عدد ساعات عمله،
    • عدد أيام العمل؛
  • يتم إرسال النموذج عبر الرابط [Faire la simulation
  • يجب التحقق من صحة حقول الإدخال [Heures travaillées] و [Jours travaillés
  • قائمة الموظفين تأتي من الطبقة [métier] التي أنشأناها سابقًا.

دعونا نستذكر الكود الحالي للإجراء [Index]:


    [HttpGet]
    public ViewResult Index()
    {
      return View();
}

ورمز طريقة العرض [Index.cshtml] التي تعرضها هذه الإجراء:


@{
  ViewBag.Title = "Pam";
}
@Html.Partial("Formulaire")

ورمز العرض الجزئي [Formulaire.cshtml]:


<h2>Formulaire</h2>

ستتم إجراء تعديلات في هذه الأماكن الثلاثة.

9.10.1. نموذج النموذج

لنعد إلى سلسلة معالجة URL [/Pam/Index]:

  • يصل طلب العميل HTTP إلى [1]؛
  • في [2]، سيتم تحويل المعلومات الواردة في الطلب إلى نموذج الإجراء [3] الذي سيُستخدم كمدخل للإجراء [4]؛
  • في [4]، ستقوم الإجراء، انطلاقًا من هذا النموذج، بتوليد استجابة. وستتألف هذه الاستجابة من مكونين: عرض V [6] ونموذج M لهذا العرض [5]؛
  • ستستخدم طريقة العرض V [6] نموذجها M [5] لتوليد الاستجابة HTTP الموجهة للعميل.

الإجراء الذي يهمنا هو الإجراء [Index] الذي يبدو في الوقت الحالي كما يلي:


    [HttpGet]
    public ViewResult Index()
    {
      return View();
}

لا تمرر الإجراء [Index] أي نموذج إلى العرض [Index.cshtml]. وبالتالي، لن يتمكن هذا العرض من عرض قائمة الموظفين. يمكن طلب هذه القائمة من الطبقة [métier]. ولذلك، يجب أن يحتوي المشروع [pam-web-01] على مرجع إلى المشروع [pam-metier-simule]. سنقوم الآن بإنشاء هذا المرجع:

  • في [1]، انقر بزر الماوس الأيمن على [References] من المشروع [pam-web-01] ثم [Ajouter une référence
  • في [2]، حدد الخيار [Solution] ثم المشروع [pam-metier-simule] في [3]؛
  • في [4]، تمت إضافة المشروع [pam-metier-simule] إلى مراجع المشروع [pam-web-01].

9.10.2. نموذج التطبيق

لقد قدمنا المفاهيم المهمة لنموذج التطبيق ونموذج الجلسة في الفقرة 4.10، الصفحة 78. سنستخدمها الآن. نذكر أن النموذج يتضمن:

  • نموذج التطبيق: بيانات للقراءة فقط لجميع المستخدمين. يشكل هذا النموذج ذاكرة مشتركة لجميع طلبات جميع المستخدمين؛
  • نموذج الجلسة للبيانات القابلة للقراءة والكتابة لمستخدم معين. يشكل هذا النموذج ذاكرة مشتركة بين جميع طلبات هذا المستخدم.

ما الذي سنضعه في نموذج التطبيق؟ لنعد إلى بنية هذا النموذج:

تحتوي الطبقة [web] على مرجع إلى الطبقة [métier]. يمكن مشاركة هذه الطبقة بين جميع المستخدمين. لذلك يمكن وضعها في نموذج التطبيق. علاوة على ذلك، سنفترض أن قائمة الموظفين لا تتغير. لذلك يمكن قراءتها مرة واحدة ثم مشاركتها بين جميع المستخدمين. نقترح إذن نموذج التطبيق التالي:

قد يكون كود فئة [ApplicationModel] كما يلي:


using Pam.Metier.Entites;
using Pam.Metier.Service;
namespace PamWeb.Models
{
  public class ApplicationModel
  {
    // --- بيانات نطاق التطبيق ---
    public Employe[] Employes { get; set; }
    public IPamMetier PamMetier { get; set; }
  }
}

لعرض قائمة منسدلة في عرض ما، نكتب شيئًا مثل ما يلي:


        <!-- القائمة المنسدلة -->
        <tr>
          <td>Liste déroulante</td>
          <td>@Html.DropDownListFor(m => m.DropDownListField,
           new SelectList(@Model.DropDownListFieldItems, "Value", "Label"))
          </td>
</tr>

تتوقع الطريقة [DropDownListFor] كمعلمة ثانية نوع SelectListItem[] الذي تم توفيره أعلاه بواسطة نوع [SelectList]. علينا إنشاء مثل هذا المصفوفة باستخدام قائمة الموظفين. وبما أن الموظفين لا يتغيرون، يمكن أيضًا وضع هذا المصفوفة في نموذج التطبيق. نقوم بتطوير هذا النموذج على النحو التالي:


using Pam.Metier.Entites;
using Pam.Metier.Service;
using System.Web.Mvc;

namespace Pam.Web.Models
{
  public class ApplicationModel
  {
    // --- بيانات نطاق التطبيق ---
    public Employe[] Employes { get; set; }
    public IPamMetier PamMetier { get; set; }
    public SelectListItem[] EmployesItems { get; set; }
  }
}

متى يجب إنشاء هذا النموذج؟ لقد أوضحنا ذلك في الفقرة 4.10. يتم ذلك عند تنفيذ الطريقة [Application_Start] من الملف [Global.asax]:

الطريقة [Application_Start] هي في الوقت الحالي كما يلي:


using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;

namespace pam_web_01
{
  public class MvcApplication : System.Web.HttpApplication
  {
    protected void Application_Start()
    {
      AreaRegistration.RegisterAllAreas();

      WebApiConfig.Register(GlobalConfiguration.Configuration);
      FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
      RouteConfig.RegisterRoutes(RouteTable.Routes);
      BundleConfig.RegisterBundles(BundleTable.Bundles);
    }
  }
}

ونقوم بتطويرها على النحو التالي:


using Pam.Metier.Entites;
using Pam.Metier.Service;
using PamWeb.Infrastructure;
using PamWeb.Models;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;

namespace pam_web_01
{
  public class MvcApplication : System.Web.HttpApplication
  {
    protected void Application_Start()
    {
      // ----------تم إنشاؤها تلقائيًا
      AreaRegistration.RegisterAllAreas();
      WebApiConfig.Register(GlobalConfiguration.Configuration);
      FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
      RouteConfig.RegisterRoutes(RouteTable.Routes);
      BundleConfig.RegisterBundles(BundleTable.Bundles);
      // -------------------------------------------------------------------
      // ---------- تكوين محدد
      // -------------------------------------------------------------------
      // بيانات نطاق التطبيق
      ApplicationModel application = new ApplicationModel();
      Application["data"] = application;
        // مثيل طبقة [métier]
        application.PamMetier = ...
        // جدول الموظفين 
        application.Employes = ...
        // عناصر قائمة الموظفين
        application.EmployesItems = ...
      // رابط النموذج لـ [ApplicationModel]
      ...
    }
  }
}

المهمة: أكمل كود الطريقة [Application_Start]. كل ما تحتاجه موجود في الفقرة 4.10. خذ الوقت الكافي لإعادة قراءة هذه الفقرة الطويلة ولكن المهمة.


تتكون السطر 33 في الواقع من عدة أسطر. لإنشاء كائن من النوع [SelectListItem]، يمكنك استخدام الطريقة التالية:


new SelectListItem() { Text = unTexte, Value = uneValeur };

سيُستخدم هذا [SelectListItem] لتوليد العلامة HTML <option> التالية:

<option value='uneValeur'>unTexte</option>

من القائمة المنسدلة. سنحرص على أن يكون:

  • unTexte هو الاسم الأول متبوعًا باسم العائلة للموظف؛
  • uneValeur هو رقم الموظف SS.

السطر 35 أعلاه، ستحتاج إلى الفئة [ApplicationModelBinder] الموضحة في الفقرة 4.10، الصفحة 82:

9.10.3. رمز الإجراء [Index]

الآن بعد أن حددنا نموذجًا للتطبيق، يمكننا تطوير رمز الإجراء [Index] بالطريقة التالية:


    [HttpGet]
    public ViewResult Index(ApplicationModel application)
    {
      return View();
}
  • السطر 4: أصبح نموذج التطبيق الآن معلمة للإجراء [Index]. وقد أوضحنا في الفقرة 4.10 كيف تم تهيئة هذه المعلمة بواسطة إطار العمل.

9.10.4. نموذج العرض [Index.cshtml]

الآن، يمكن للإجراء [Index] الوصول إلى الموظفين المسجلين في نموذج التطبيق. يجب الآن أن يمررهم إلى العرض [Index.cshtml] الذي سيعرضه. يمكننا تمرير نوع [ApplicationModel] كنموذج لعرض [Index.cshtml]، لكننا سنرى سريعًا أن هذا العرض يحتاج إلى معلومات أخرى غير موجودة في [ApplicationModel]. سنستخدم نموذج العرض [IndexModel] التالي:


namespace Pam.Web.Models
{
  public class IndexModel
  {
    // بيانات نطاق التطبيق
    public ApplicationModel Application { get; set; }
  }
}
  • السطر 6: [IndexModel] يقوم بتحميل نموذج التطبيق.

تصبح الإجراء [Index] كما يلي:


    [HttpGet]
    public ViewResult Index(ApplicationModel application)
    {
      return View(new IndexModel() { Application = application });
}
  • السطر 4، يتم عرض العرض الافتراضي [Index.cshtml] مع نموذج من النوع [IndexModel] تم تهيئته ببيانات نموذج التطبيق.

نحن نعلم أن العرض [Index.cshtml] يجب أن يعرض نموذجًا:

Image

لنعد إلى سلسلة معالجة الطلب:

بالنسبة لطلب [GET /Pam/Index]:

  • الإجراء هو [Index
  • نموذج هذا الإجراء هو [ApplicationModel
  • الطريقة هي [Index.cshtml
  • نموذج هذه العرض هو [IndexModel].

عندما يتم إرسال النموذج، سيكون لدينا سلسلة معالجة مماثلة:

  • الإجراء هو الذي يعالج POST؛
  • نموذجها يجمع القيم المرسلة، وهنا:
    • رقم SS للموظف المحدد؛
    • عدد ساعات العمل؛
    • عدد أيام العمل؛

يمكن إنشاء نموذج إجراء يجمع هذه القيم الثلاث. ومن الشائع أيضًا إعادة استخدام النموذج الذي استُخدم لعرض النموذج. وهذا ما سنفعله هنا. تتطور الفئة [IndexModel] على النحو التالي:


using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
namespace Pam.Web.Models
{
  [Bind(Exclude = "Application")]
  public class IndexModel
  {
    // بيانات نطاق التطبيق
    public ApplicationModel Application { get; set; }

    // القيم المرسلة
    [Display(Name = "Employé")]
    public string SS { get; set; }
    [Display(Name = "Heures travaillées")]
    [UIHint("Decimal")]
    public double HeuresTravaillées { get; set; }
    [Display(Name = "Jours travaillés")]
    public double JoursTravaillés { get; set; }
  }
}
  • السطور 13 و16 و18: القيم الثلاث التي تم إرسالها. تجدر الإشارة إلى أن [joursTravaillés] تم تعريفه على أنه من النوع [double] في حين أننا نتوقع في الواقع عددًا صحيحًا. تم إدخال النوع [double] لتسهيل التحقق من صحة هذا الحقل من جانب العميل، حيث أن التحقق من صحة النوع [int] قد تسبب في مشكلة؛
  • السطور 12 و14 و17: تسميات لطرق [Html.LabelFor] للطريقة المرتبطة بالنموذج؛
  • السطر 15: تعليق لتعرض الحقل [HeuresTravaillées] برقمين عشريين؛
  • السطر 5: يُشار إلى أن الخاصية المسماة [Application] ليست جزءًا من القيم المنشورة.

9.10.5. طريقتا العرض [Index.cshtml] و [Formulaire.cshtml]

يتم عرض العرض [Index.cshtml] بواسطة الإجراء [Index] التالي:


    [HttpGet]
    public ViewResult Index(ApplicationModel application)
    {
      return View(new IndexModel() { Application = application });
}

ومن المثير للاهتمام أن طريقة العرض [Index.cshtml] تظل دون تغيير:


@{
  ViewBag.Title = "Pam";
}
@Html.Partial("Formulaire")
  • لا تعلن طريقة العرض عن أي نموذج؛
  • السطر 4: تتضمن العرض الجزئي [Formulaire.cshtml]، وهنا أيضًا دون الانتقال إلى هذا النموذج. خلال الاختبارات، لوحظ أن النموذج [IndexModel] الذي تم تمريره إلى العرض [Index.cshtml] ينتشر ضمنيًا إلى العرض الجزئي [Formulaire.cshtml]. قد يكون شكل هذا العرض الأخير الآن كما يلي:

@model Pam.Web.Models.IndexModel

@using (Html.BeginForm("FaireSimulation", "Pam", FormMethod.Post, new { id = "formulaire" }))
{
  <table>
    <thead>
      <tr>
...
      </tr>
    </thead>
    <tbody>
      <tr>
...
      </tr>
      <tr>
...
      </tr>
    </tbody>
  </table>
}
<div id="simulation" />
  • السطر 1: تتلقى طريقة العرض نموذجًا من النوع [IndexModel
  • السطر 3: النموذج؛
  • الأسطر 6-10: رؤوس جدول الإدخالات؛
  • الأسطر 12-14: سطر الإدخالات؛
  • الأسطر 15-17: رسائل الخطأ المحتملة.

المهمة: أكمل كود العرض [Formulaire.cshtml]. سنستخدم طرق [DropDownListFor, EditorFor, LabelFor, ValidationMessageFor] الموضحة في الفقرة 5.7.


9.10.6. اختبار الإجراء [Index]

لقد كتبنا جميع عناصر سلسلة معالجة URL [/Pam/Index]:

نقوم باختبار التطبيق باستخدام [Ctrl-F5]:

يجب عليك التحقق من أن القائمة المنسدلة قد تم ملؤها بقائمة الموظفين التي حددناها في الطبقة [métier] المحاكاة.

9.11. الخطوة 5: إعداد التحقق من صحة الإدخالات

9.11.1. المشكلة

على الرغم من أننا لم نقم بأي شيء لهذا الغرض، إلا أن عمليات التحقق من صحة البيانات من جانب العميل تعمل بالفعل:

 
 

يتم تنفيذ التحقق من صحة البيانات من جانب العميل بشكل افتراضي بسبب السطر 3 أدناه في ملف [Web.config] الخاص بالتطبيق.


  <appSettings>
    ...
    <add key="ClientValidationEnabled" value="true" />
</appSettings>

ولكن نظرًا لأننا في [IndexModel]، قمنا بتعريف الحقل [JoursTravaillés] من النوع [double]:


    public double JoursTravaillés { get; set; }

يمكن إدخال رقم حقيقي في هذا الحقل:

 

بالإضافة إلى ذلك، يمكن إدخال قيم خيالية في الحقلين:

 

نموذج [IndexModel] للنموذج هو حاليًا كما يلي:


using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
namespace Pam.Web.Models
{
  [Bind(Exclude = "Application")]
  public class IndexModel
  {
    // بيانات نطاق التطبيق
    public ApplicationModel Application { get; set; }

    // القيم المنشورة
    [Display(Name = "Employé")]
    public string SS { get; set; }
    [Display(Name = "Heures travaillées")]
    [UIHint("Decimal")]
    public double HeuresTravaillées { get; set; }
    [Display(Name = "Jours travaillés")]
    public double JoursTravaillés { get; set; }
  }
}

المهمة: قم بتحسين هذا النموذج من أجل:

  • الحصول على رسائل خطأ مخصصة؛
  • قبول القيم الحقيقية فقط في النطاق [0,400] للحقل [HeuresTravaillées
  • قبول القيم الصحيحة فقط في النطاق [0,31] للحقل [JoursTravaillées

يمكن الاستعانة بالمثال الوارد في الفقرة 7.6.2. للتحقق من أن عدد أيام العمل عدد صحيح، يمكن استخدام تعبير عادي (انظر الأمثلة الواردة في الفقرة 5.9.1).

فيما يلي أمثلة على ما هو متوقع:

 
 
 

9.11.2. إدخال الأرقام العشرية بالصيغة الفرنسية

في الإصدار الحالي من التطبيق، يجب أن يكون عدد ساعات العمل رقمًا عشريًا بالتنسيق الإنجليزي (مع النقطة العشرية). لا يُقبل التنسيق الفرنسي الذي يستخدم الفاصلة:

 

تم تحديد هذه المشكلة ومعالجتها في الفقرة 6.1.


المهمة: باتباع الخطوات الموضحة في الفقرة المذكورة أعلاه، قم بإجراء التعديلات اللازمة حتى يمكن إدخال الأرقام الحقيقية بالتنسيق العشري الفرنسي. اختبر تطبيقك.


الآن، يصبح الشاشة السابقة:

 

9.11.3. التحقق من صحة النموذج عبر رابط جافا سكريبت [Faire la simulation]

حاليًا، يمكن إرسال قيم غير صالحة كما يوضح التسلسل التالي:

 

إن وجود المحاكاة في [1] وتغيير القائمة في [2] يظهران أن النقر على الرابط [Faire la simulation] أدى إلى إرسال النموذج على الرغم من أن القيم المدخلة كانت غير صالحة. تم تحديد هذه المشكلة ومعالجتها في الفقرة 7.6.5.


المهمة: باتباع الإجراء الموضح في الفقرة المذكورة أعلاه، احرص على أن لا يتم تنفيذ POST للرابط [Faire la simulation] إذا كانت القيم المدخلة غير صالحة. تذكر مسح ذاكرة التخزين المؤقت للمتصفح قبل اختبار التعديلات التي أجريتها.


نذكرك بأن العرض الجزئي [Formulaire.cshtml] يولد نموذجًا HTML ذي المعرف [formulaire] (السطر 1 أدناه):


@using (Html.BeginForm("FaireSimulation", "Pam", FormMethod.Post, new { id = "formulaire" }))
{
...
}

يمكن التحقق من ذلك عن طريق عرض كود مصدر النموذج في المتصفح:


<div id="content">
    
    <form action="/Pam/FaireSimulation" id="formulaire" method="post">
    ...
    </form>
    <div id="simulation" />
</div>

9.12. الخطوة 6: إجراء محاكاة

9.12.1. المشكلة

عند إجراء محاكاة، نريد الحصول على النتيجة التالية:

تعرض الآن طريقة العرض الجزئية [Simulation.cshtml] كشف راتب أحد الموظفين.

9.12.2. كتابة العرض [Simulation.cshtml]

تتطور طريقة عرض [Simulation.cshtml] على النحو التالي:


@model Pam.Metier.Entites.FeuilleSalaire
<hr />
<p><span class="info">Informations Employé</span></p>
<table>
  <tbody>
    <tr>
      <td><span class="libellé">Nom</span>
      </td>
      <td><span class="libellé">Prénom</span>
      </td>
      <td><span class="libellé">Adresse</span>
      </td>
    </tr>
    <tr>
      <td>
        <span class="valeur">@Model.Employe.Nom</span>
      </td>
...
    </tr>
    <tr>
      <td><span class="libellé">Ville</span>
      </td>
      <td><span class="libellé">Code Postal</span>
      </td>
      <td><span class="libellé">Indice</span>
      </td>
    </tr>
    <tr>
...
    </tr>
  </tbody>
</table>
<br />
<p><span class="info">Informations Cotisations</span></p>
<table>
...
  </tbody>
</table>
<br />
<p><span class="info">Informations Indemnités</span></p>
<table>
...
</table>
<br />
<p><span class="info">Informations Salaire</span></p>
<table>
...
</table>
<br />
<table>
...
</table>
  • السطر 1: العرض [Simulation.cshtml] يستند إلى النموذج من النوع [FeuilleSalaire] المحدد في الفقرة 9.7.3؛
  • تستخدم طريقة العرض الفئات [libellé, info, valeur] المحددة في ورقة أنماط التطبيق [Content / Site.css]:

.libellé {
  background-color: azure;
  margin: 5px;
  padding: 5px;
}

.info {
  background-color: antiquewhite;
  margin: 5px;
  padding: 5px;
}

.valeur {
  background-color: beige;
  padding: 5px;
  margin: 5px;
}

بالإضافة إلى ذلك، وفي [Site.css] أيضًا، يتم تحديد ارتفاع أسطر الجداول المختلفة HTML في منطقة id [simulation]، وتحديدًا في المكان الذي يتم فيه عرض كشف الراتب:


#محاكاة الجدول tr {
  height: 30px;
}

المهمة: أكمل العرض [Simulation.cshtml].


لعرض قيمة المبلغ باليورو، سنستخدم الطريقة [string.Format]:

string.Format("{0:C2}",somme)

تعرض التعليمات المذكورة أعلاه [somme] كقيمة نقدية [C] (العملة) مع رقمين عشريين [C2].

لاختبار هذا العرض، يجب تزويده بورقة رواتب. يجب توفير هذه الورقة من خلال الإجراء [/Pam/FaireSimulation] الذي يمثل هدف استدعاء Ajax للرابط [Faire la simulation]. حاليًا، هذا الإجراء هو كما يلي:


    [HttpGet]
    public ViewResult Index(ApplicationModel application)
    {
      return View(new IndexModel() { Application = application });
    }

    // إجراء محاكاة
    [HttpPost]
    public PartialViewResult FaireSimulation()
    {
      return PartialView("Simulation");
}

فيما سبق، لا تمرر الإجراء [FaireSimulation] أي نموذج إلى العرض [Simulation.cshtml]. يجب أن تمرر له كشف راتب. نعلم أن الطبقة [métier] هي التي تقوم بحساب كشوف الرواتب. يمكن الوصول إلى هذه الطبقة [métier] عبر نموذج التطبيق [ApplicationModel] الذي حددناه في الفقرة 9.10.2:


  public class ApplicationModel
  {
    // --- بيانات نطاق التطبيق ---
    public Employe[] Employes { get; set; }
    public IPamMetier PamMetier { get; set; }
    public SelectListItem[] EmployesItems { get; set; }
}

يمكن الوصول إلى الطبقة [métier] عبر الخاصية الموجودة في السطر 5 أعلاه. لكي تتمكن الإجراء [FaireSimulation] من الوصول إلى الطبقة [métier]، سنقوم بتمرير نموذج التطبيق إليه كما فعلنا مع الإجراء [Index]. يتطور الكود عندئذٍ على النحو التالي:


    // إجراء محاكاة
    [HttpPost]
    public PartialViewResult FaireSimulation(ApplicationModel application)
    {
      return PartialView("Simulation");
}

الآن، أصبح بإمكاننا، داخل الإجراء، حساب كشف راتب وهمي. يتطور الكود على النحو التالي:


// إجراء محاكاة
    [HttpPost]
    public PartialViewResult FaireSimulation(ApplicationModel application)
    {
      FeuilleSalaire feuilleSalaire = application.PamMetier.GetSalaire("254104940426058", 150, 20);
      return PartialView("Simulation", feuilleSalaire);
    }
  • في السطر 5، يتم حساب راتب افتراضي. المعلمة الأولى هي رقم SS موجود. وقد تم تعريفه في الفئة [métier] التي تمت محاكاتها في الفقرة 9.7.5. المعلمة الثانية هي عدد ساعات العمل، والثالثة هي عدد أيام العمل؛
  • السطر 6: تم تمرير ورقة الراتب هذه كنموذج إلى العرض [Simulation.cshtml].

نحن الآن جاهزون لاختبار العرض [Simulation.cshtml]:

 

لا نقوم بإدخال أي بيانات ونطلب إجراء محاكاة. نحصل عندئذٍ على النتيجة التالية:

 

9.12.3. حساب الراتب الفعلي

تقوم الإجراء الحالي [FaireSimulation] دائمًا بحساب نفس كشف الراتب:


    // إجراء محاكاة
    [HttpPost]
    public PartialViewResult FaireSimulation(ApplicationModel application)
    {
      FeuilleSalaire feuilleSalaire = application.PamMetier.GetSalaire("254104940426058", 150, 20);
      return PartialView("Simulation", feuilleSalaire);
}

وهي لا تأخذ في الاعتبار المعلومات التي تم إدخالها:

  • الموظف الذي يتم حساب راتبه؛
  • عدد ساعات عمله؛
  • عدد أيام العمل.

تصل القيم المدخلة إلى الإجراء [FaireSimulation] بالطريقة التالية:

  1. ينقر المستخدم على الرابط [Faire la simulation]. يؤدي ذلك إلى تشغيل الوظيفة JS [faireSimulation] التي قمنا بكتابتها مسبقًا؛
  2. ثم تقوم الدالة JS [faireSimulation] بإجراء استدعاء Ajax إلى إجراء الخادم [/Pam/FaireSimulation] الذي نعمل عليه حاليًا. في الوقت الحالي، لا ترسل الدالة JS [faireSimulation] أي معلومات إلى إجراء الخادم. سيتعين عليها أن ترسل إليه القيم التي أدخلها المستخدم؛
  3. ستسترد إجراء الخادم [/Pam/FaireSimulation] القيم التي تم إدخالها من القيم التي أرسلتها الوظيفة JS [faireSimulation].

لنبدأ بالنقطة 2: يجب أن ترسل الدالة JS [faireSimulation] القيم التي أدخلها المستخدم إلى إجراء الخادم [/Pam/FaireSimulation].


المهمة: أكمل الدالة JS [faireSimulation] بحيث ترسل القيم التي أدخلها المستخدم. يمكن الاستعانة بالمثال الوارد في الفقرة 7.6.5 حيث تمت معالجة هذه المشكلة.


لنتناول الآن النقطة 3 أعلاه. يجب أن تسترد إجراء الخادم [/Pam/FaireSimulation] القيم التي تم إرسالها بواسطة الدالة JS [faireSimulation].


المهمة: أكمل طريقة الخادم [FaireSimulation] بحيث تحسب الراتب باستخدام القيم التي تم إرسالها بواسطة الدالة JS [faireSimulation]. يمكننا الاستعانة مرة أخرى بالمثال الوارد في الفقرة 7.6.5 حيث تمت معالجة هذه المشكلة. في الوقت الحالي، سنفترض أن النموذج الناتج عن القيم المنشورة لا يزال صالحًا.


تلميح: تتطور إجراء الخادم [FaireSimulation] على النحو التالي:


// إجراء محاكاة
    [HttpPost]
    public PartialViewResult FaireSimulation(ApplicationModel application, FormCollection data)
    {
      // إنشاء نموذج الإجراء
      ...
      // محاولة استرداد القيم التي تم إدخالها في هذا النموذج
      ...
      // يتم حساب الراتب
      FeuilleSalaire feuilleSalaire = ...
      // عرض كشف الراتب
      return PartialView("Simulation", feuilleSalaire);
    }

فيما يلي مثال على التنفيذ:

نختار [Justine Laverti]. ونحصل عندئذٍ على النتيجة التالية:

لقد حصلنا بالفعل على كشف الراتب الوهمي لـ [Justine Laverti]. في السابق، كان كشف الراتب الوحيد الذي تم حسابه هو [Marie Jouveinal]. لذلك تم استخدام القيمة التي تم إدخالها لاختيار الموظف. بالنسبة لعدد الساعات وعدد الأيام، لا يمكننا الجزم بأي شيء لأن الطبقة [métier] التي قمنا بمحاكاتها لا تأخذ ذلك في الاعتبار.

9.12.4. إدارة الأخطاء

لنلقِ نظرة على المثال التالي:

  • في [1]، يتم اختيار موظف غير موجود (انظر تعريف الطبقة [métier] المحاكاة في الفقرة 9.7.5؛
  • في [2]، يتم إجراء المحاكاة؛
  • في [3] أدناه، يتم استرداد صفحة خطأ.

ماذا حدث؟

تم تنفيذ الدالة JS [faireSimulation]. يبدو كودها كما يلي:


function faireSimulation() {
...
  // إجراء استدعاء Ajax يدويًا
  $.ajax({
    url: '/Pam/FaireSimulation',
...
    beforeSend: function () {
      // إشارة الانتظار مضاءة
      loading.show();
    },
    success: function (data) {
...
    },
    error: function (jqXHR) {
      // عرض الخطأ
      simulation.html(jqXHR.responseText);
      simulation.show();
    },
    complete: function () {
      // إشارة الانتظار مطفأة
      loading.hide();
    }
  });
  // القائمة
  setMenu([lnkEffacerSimulation, lnkEnregistrerSimulation, lnkTerminerSession, lnkVoirSimulations]);
}

فشل استدعاء Ajax وتم تنفيذ الوظيفة الموجودة في الأسطر 14-18. تم عرض صفحة الخطأ [jqXHR.responseText] التي أرسلها الخادم. هذه الصفحة دقيقة إلى حد ما. أطلقت الطبقة [métier] المحاكاة استثناءً لأن الرقم SS الذي تم تزويدها به ليس رقم موظف موجود (انظر كود الطبقة [métier] المحاكاة في الفقرة 9.7.5). علينا معالجة هذه الحالة بشكل صحيح.

سنقوم بإنشاء عرض جزئي [Erreurs.chtml] سيتم إرجاعه إلى العميل JS في كل مرة يتم فيها اكتشاف خطأ على جانب الخادم:

رمز العرض الجزئي [Erreurs.chtml] هو كما يلي:


@model IEnumerable<string>

<hr />
<h2>Les erreurs suivantes se sont produites</h2>
<ul>
  @foreach (string msg in Model)
  {
    <li>@msg</li>
  }
</ul>
  • السطر 1: تتلقى العرض كقالب قائمة برسائل الخطأ؛
  • الأسطر 5-10: التي يتم عرضها في قائمة HTML؛

الآن، لنقوم بتعديل كود إجراء الخادم [FaireSimulation] على النحو التالي:


    // إجراء محاكاة
    [HttpPost]
    public PartialViewResult FaireSimulation(ApplicationModel application, FormCollection data)
    {
    ...
      // يتم حساب الراتب
      FeuilleSalaire feuilleSalaire = null;
      Exception exception=null;
      try
      {
        // حساب الراتب
        feuilleSalaire = ...
      }
      catch (Exception ex)
      {
        exception = ex;
      }
      // خطأ؟
      if (exception == null)
      {
        // عرض كشف الراتب
        return PartialView("Simulation", feuilleSalaire);
      }
      else
      {
        // عرض صفحة الأخطاء
        return PartialView("Erreurs", Static.GetErreursForException(exception));
      }
}
  • السطور 9-17: يتم حساب الراتب الآن في try / catch؛
  • السطر 27: في حالة حدوث خطأ، يتم عرض العرض الجزئي [Erreurs.cshtml] باستخدام قائمة رسائل الخطأ المقدمة من الطريقة الثابتة [Static.GetErreursForException(exception)] كنموذج.

يتم تجميع وظيفتين مساعدتين ثابتتين [1] في الفئة [Static]:


using System;
using System.Collections.Generic;
using System.Web.Mvc;

namespace PamWeb.Infrastructure
{
  public class Static
  {
    // قائمة رسائل الخطأ الخاصة باستثناء
    public static List<string> GetErreursForException(Exception ex)
    {
      List<string> erreurs = new List<string>();
      while (ex != null)
      {
        erreurs.Add(ex.Message);
        ex = ex.InnerException;
      }
      return erreurs;
    }

    // قائمة رسائل الخطأ المتعلقة بنموذج غير صالح
    public static List<string> GetErreursForModel(ModelStateDictionary état)
    {
      List<string> erreurs = new List<string>();
      if (!état.IsValid)
      {
        foreach (ModelState modelState in état.Values)
        {
          foreach (ModelError error in modelState.Errors)
          {
            erreurs.Add(getErrorMessageFor(error));
          }
        }
      }
      return erreurs;
    }

    // رسالة الخطأ المتعلقة بعنصر من نموذج الإجراء
    static private string getErrorMessageFor(ModelError error)
    {
      if (error.ErrorMessage != null && error.ErrorMessage.Trim() != string.Empty)
      {
        return error.ErrorMessage;
      }
      if (error.Exception != null && error.Exception.InnerException == null && error.Exception.Message != string.Empty)
      {
        return error.Exception.Message;
      }
      if (error.Exception != null && error.Exception.InnerException != null && error.Exception.InnerException.Message != string.Empty)
      {
        return error.Exception.InnerException.Message;
      }
      return string.Empty;
    }

  }
}
  • الأسطر 10-19: تعرض الدالة الثابتة [GetErreursForException] قائمة أخطاء مكدس الاستثناءات؛
  • الأسطر 22-36: تعرض الدالة الثابتة [GetErreursForModel] قائمة أخطاء نموذج إجراء غير صالح. وقد سبق أن تناولنا كود هذه الدالة وكود الطريقة الخاصة [getErrorMessageFor] (الأسطر 39-54).

وبعد ذلك، يمكننا اختبار حالة الخطأ مرة أخرى:

  • في [1]، نختار الموظف غير الموجود؛
  • في [2]، نقوم بالمحاكاة؛
  • في [3]، نسترد صفحة الأخطاء الجديدة.

لنعد إلى إجراء الخادم [FaireSimulation]:


    // إجراء محاكاة
    [HttpPost]
    public PartialViewResult FaireSimulation(ApplicationModel application, FormCollection data)
    {
      // إنشاء نموذج الإجراء
      IndexModel modèle = new IndexModel() { Application = application};
      // محاولة استرداد القيم التي تم إرسالها إلى النموذج
      TryUpdateModel(modèle, data);
      // يتم حساب الراتب
...
}

في السطر 8، نقوم بتحديث نموذج السطر 6 بالقيم التي تم إرسالها بواسطة استدعاء Ajax. لا نتحقق من صحة النموذج. يجب القيام بذلك لأنه لا يمكننا معرفة مصدر القيم المرسلة. فقد يكون شخص ما قد تلاعب بـ POST وأرسل لنا بيانات غير صالحة.


المهمة: باتباع النموذج الذي طورناه لحالة الاستثناء، قم بتعديل إجراء الخادم [FaireSimulation] لإرسال صفحة أخطاء عندما تكون البيانات المرسلة غير صالحة. للقيام بذلك، سنستخدم الطريقة الثابتة [GetErreursForModel] من الفئة [Static].


كيف يمكن اختبار هذا التعديل؟ في الفقرة 9.11.3، قمت بالتأكد من أن الدالة JS [faireSimulation] لا تقوم بـ POST للقيم المدخلة إذا كانت غير صالحة. ضع تعليقات على الأسطر التي تقوم بذلك ثم قم بإجراء الاختبار التالي:

  • في [1]، نقوم بالمحاكاة باستخدام قيم غير صالحة؛
  • في [2]، يتم استرداد صفحة الأخطاء التي أنشأناها للتو، مما يثبت أن أدوات التحقق من الصحة على جانب الخادم قد عملت بشكل صحيح.

للمتابعة، تذكر إزالة التعليقات من الأسطر التي قمت بتعليقها للتو في الدالة JS [faireSimulation].

9.13. الخطوة 7: إنشاء جلسة مستخدم

يتيح التطبيق [Simulateur de calcul de paie] للمستخدم إجراء محاكاة متنوعة للرواتب باستخدام الرابط [Faire la simulation]، وحفظها باستخدام الرابط [Enregistrer la simulation]، وعرضها باستخدام الرابط [Voir les simulations] وحذفها باستخدام الرابط [Retirer la simulation]. ونعلم أنه بين طلبين متتاليين من المستخدم، لا توجد ذاكرة ما لم يتم إنشاؤها عبر آلية الجلسة (انظر الفقرة 4.10). من الواضح تمامًا هنا أننا يجب أن نحتفظ في الجلسة بقائمة المحاكاة التي سجلها المستخدم بمرور الوقت. هناك بيانات أخرى يجب حفظها: عندما يقوم المستخدم بإجراء محاكاة، لا يتم تسجيلها في قائمة المحاكاة إلا إذا طلب المستخدم ذلك عبر الرابط [Enregistrer la simulation]. وعندما يفعل ذلك، يجب أن نكون قادرين على العثور على المحاكاة المحسوبة في الطلب السابق. ولهذا الغرض، سيتم وضعها أيضًا في الجلسة. وأخيرًا، سنقوم بترقيم المحاكاة بدءًا من 1. لترقيم محاكاة جديدة بشكل صحيح، يجب الاحتفاظ برقم المحاكاة السابقة، وهنا أيضًا في الجلسة.

في الفقرة 4.10، قدمنا مفهوم نموذج الجلسة كمعلمة إدخال لإجراء ما حتى يتمكن هذا الإجراء من الوصول إلى الجلسة. سنستأنف هذا المفهوم. ندعوكم إلى إعادة قراءة الفقرة المعنية إذا كان هذا المفهوم غامضًا بالنسبة لكم.

نقوم بإنشاء الفئة [SessionModel] التالية:

وإليكم كودها:


using Pam.Web.Models;
using System.Collections.Generic;

namespace Pam.Web.Models
{
  public class SessionModel
  {
    // قائمة عمليات المحاكاة
    public List<Simulation> Simulations { get; set; }
    // رقم المحاكاة التالية
    public int NumNextSimulation { get; set; }
    // آخر محاكاة
    public Simulation Simulation { get; set; }

    // الشركة المصنعة
    public SessionModel()
    {
      // قائمة المحاكاة فارغة
      Simulations = new List<Simulation>();
      // رقم المحاكاة التالية
      NumNextSimulation = 1;
    }
  }
}

ستقوم الفئة [Simulation] في السطرين 9 و 13 بتسجيل معلومات حول محاكاة. ما الذي نحتاج إلى تسجيله؟ يحسب الرابط [Faire la simulation] كشف راتب من النوع [FeuilleSalaire]. يبدو من الطبيعي إدراجها في المحاكاة. علاوة على ذلك، علينا حفظ المعلومات التي أدت إلى إنشاء ورقة الرواتب هذه:

  • الموظف المحدد. يمكن العثور عليه في الحقل [FeuilleSalaire.Employe]. لذا، لا داعي لتخزينه مرة أخرى؛
  • عدد ساعات وأيام العمل. هذه المعلومات غير موجودة في النوع [FeuilleSalaire]. لذا، علينا حفظها.

وأخيرًا، يتم تمييز كل محاكاة برقم. لذا يمكننا البدء بالفئة التالية: [Simulation]:


using Pam.Metier.Entites;

namespace Pam.Web.Models
{
  public class Simulation
  {
    // رقم المحاكاة
    public int Num { get; set; }
    // عدد ساعات العمل
    public double HeuresTravaillées { get; set; }
    // عدد أيام العمل
    public int JoursTravaillés { get; set; }
    // كشوف الرواتب
    public FeuilleSalaire FeuilleSalaire { get; set; }
  }
}

يجب أن تقوم عملية الخادم [FaireSimulation]، بالإضافة إلى حساب كشف الراتب، بإنشاء محاكاة ووضعها في الجلسة. ولهذا الغرض، ستتلقى نموذج الجلسة كمعلمة:


// إجراء محاكاة
    [HttpPost]
    public PartialViewResult FaireSimulation(ApplicationModel application, SessionModel session, FormCollection data)
    {
      // إنشاء نموذج الإجراء
      IndexModel modèle = new IndexModel() { Application = application };
      // نحاول استرداد القيم التي تم إدخالها في النموذج
      TryUpdateModel(modèle, data);
      // هل النموذج صالح؟
      if (!ModelState.IsValid)
      {
        // عرض صفحة الأخطاء
        return PartialView("Erreurs", Static.GetErreursForModel(ModelState));
      }
      // يتم حساب الراتب
      FeuilleSalaire feuilleSalaire = null;
      Exception exception = null;
      try
      {
        // حساب الراتب
        feuilleSalaire = application.PamMetier.GetSalaire(modèle.SS, modèle.HeuresTravaillées, (int)modèle.JoursTravaillés);
      }
      catch (Exception ex)
      {
        exception = ex;
      }
      // خطأ؟
      if (exception != null)
      {
        // يتم عرض صفحة الأخطاء
        return PartialView("Erreurs", Static.GetErreursForException(exception));
      }
      // يتم إنشاء محاكاة ووضعها في الجلسة
      session.Simulation = ...
      // عرض كشف الراتب
      return PartialView("Simulation", feuilleSalaire);
    }
  • السطر 3: تتلقى الإجراء نموذج الجلسة كمعلمة؛

المهمة 1: أكمل كود الإجراء، السطر 34



المهمة 2: باتباع الخطوات الواردة في الفقرة 4.10، قم بما يلزم لضمان تهيئة المعلمة [SessionModel session] الخاصة بالإجراء بشكل صحيح بواسطة إطار العمل. إذا لم يتم القيام بأي شيء، فسيكون لدينا مؤشر null لهذه المعلمة.


9.14. الخطوة 8: تسجيل محاكاة

9.14.1. المشكلة

عندما نقوم بإجراء محاكاة، يمكننا تسجيلها:

Image

تعرض الآن طريقة العرض الجزئية [Simulations.cshtml] قائمة بالمحاكاة التي أجراها المستخدم. نذكر أن كشف الراتب المحسوب هو كشف وهمي.

9.14.2. كتابة إجراء الخادم [EnregistrerSimulation]

يستدعي رابط Ajax [Enregistrer la simulation] إجراء الخادم [EnregistrerSimulation] الذي كان رمزه حتى الآن كما يلي:


    [HttpPost]
    public PartialViewResult EnregistrerSimulation()
    {
      return PartialView("Simulations");
}

وقد تطور كالتالي:


    // تسجيل محاكاة
    [HttpPost]
    public PartialViewResult EnregistrerSimulation(SessionModel session)
    {
      // يتم تسجيل آخر محاكاة تم إجراؤها في قائمة محاكاة الجلسة
      ...
      // يتم زيادة رقم المحاكاة التالية في الجلسة
      ...
      // عرض قائمة المحاكاة
      ...
}
  • السطر 1: تحتاج الإجراء [EnregistrerSimulation] إلى الوصول إلى الجلسة. ولهذا السبب فإن معلمته هي نموذج الجلسة.

المهمة: استكمال الإجراء الخادم [EnregistrerSimulation].


9.14.3. كتابة العرض الجزئي [Simulations.cshtml]

تقوم الإجراء السابق [EnregistrerSimulation] بعرض العرض الجزئي [Simulations.cshtml] باستخدام قائمة المحاكاة التي أجراها المستخدم كنموذج. ورمزها هو التالي:


@model IEnumerable<Simulation>

@using Pam.Web.Models

@if (Model.Count() == 0)
{
  <h2>Votre liste de simulations est vide</h2>
}
@if (Model.Count() != 0)
{
  <h2>Liste des simulations</h2>
...
}

المهمة 1: أكمل كود العرض الجزئي [Simulations.cshtml]. سنستخدم جدول HTML لعرض عمليات المحاكاة. يمكن الاستعانة بالأمثلة الواردة في الفقرة 5.4.


ملاحظة: سيكون الرابط [retirer] لكل محاكاة في الجدول HTML رابطًا جافا سكريبت بالشكل التالي:

<a href="javascript:retirerSimulation(N)">retirer</a>

حيث N هو رقم المحاكاة.


المهمة 2: اختبر تطبيقك عن طريق إجراء عمليات محاكاة. لإجراء هذه المحاكاة، قم بتكرار التسلسل التالي: 1) قم بتحميل صفحة التطبيق عبر [F5]، 2) قم بإجراء محاكاة، 3) قم بحفظها. ستتراكم المحاكاة في الجلسة، وهو ما ينبغي أن ينعكس في العرض [Simulations.cshtml].



المهمة 3: قم بتحسين العرض الجزئي [Simulations.cshtml] بحيث تتناوب ألوان خطوط الجدول HTML.


Image

سيتم إعطاء الأسطر <tr> في الجدول HTML، فئات CSS و [pair] و [impair] المحددة في ورقة الأنماط [/Content/Site.css] بالتناوب:


.impair {
  background-color: beige;
}

.pair {
  background-color: lightsteelblue;
}

9.15. الخطوة 9: العودة إلى نموذج الإدخال

9.15.1. المشكلة

عندما نحصل على قائمة المحاكاة، يمكننا العودة إلى نموذج الإدخال، وهو ما لم نتمكن من فعله منذ فترة:

Image

Image

9.15.2. كتابة إجراء الخادم [Formulaire]

يستدعي رابط Ajax [Retour au formulaire de simulation] إجراء الخادم [Formulaire] الذي كان رمزه حتى الآن كما يلي:


    [HttpPost]
    public PartialViewResult Formulaire()
    {
      return PartialView("Formulaire");
}

تنتظر طريقة العرض الجزئية [Formulaire] التي تعرضها نموذجًا [IndexModel] (السطر 1 أدناه):


@model Pam.Web.Models.IndexModel

@using (Html.BeginForm("FaireSimulation", "Pam", FormMethod.Post, new { id = "formulaire" }))
{
...
}
<div id="simulation" />

ولهذا السبب لم يعد الرابط [Retour au formulaire de simulation] يعمل.


المهمة: كتابة النسخة الجديدة من إجراء الخادم [Formulaire] (سطران يجب إعادة كتابتهما) ثم إجراء الاختبارات.


9.15.3. تعديل وظيفة جافا سكريبت [retourFormulaire]

بعد إجراء التعديل السابق، يمكننا الآن العودة إلى النموذج ولكن تظهر مشكلة:

  • في [1]، نعود إلى نموذج الإدخال؛
  • في [2]، نقوم بإجراء محاكاة باستخدام إدخالات خاطئة. نكتشف عندئذٍ أن أدوات التحقق من صحة البيانات من جانب العميل لم تعد تعمل. هنا، تم استدعاء الخادم وأرسل صفحة أخطاء بفضل العمل الذي تم في الفقرة 9.12.4.

تم تحديد هذه المشكلة ومعالجتها في الفقرة 7.6.7.


المهمة: باتباع الخطوات الواردة في الفقرة 7.6.7، قم بتصحيح دالة جافا سكريبت [retourFormulaire] ثم قم بإجراء اختبارات للتحقق من أن أدوات التحقق من صحة البيانات على جانب العميل تعمل مرة أخرى.


9.16. الخطوة 10: انظر قائمة المحاكاة

9.16.1. المشكلة

عند العمل مع نموذج المحاكاة، يمكن عرض قائمة المحاكاة التي تم إجراؤها:

9.16.2. كتابة إجراء الخادم [VoirSimulations]

يستدعي رابط Ajax [Voir les simulations] إجراء الخادم [VoirSimulations] الذي كان رمزه حتى الآن كما يلي:


    // عرض المحاكاة
    [HttpPost]
    public PartialViewResult VoirSimulations()
    {
      return PartialView("Simulations");
}

تنتظر طريقة العرض الجزئية [Simulations] التي تعرضها نموذجًا [IEnumerable<Simulation>] (السطر 1 أدناه):


@model IEnumerable<Simulation>

@using Pam.Web.Models

@if (Model.Count() == 0)
{
  <h2>Votre liste de simulations est vide</h2>
}
@if (Model.Count() != 0)
{
  <h2>Liste des simulations</h2>
...
}

ولهذا السبب لم يعد الرابط [Voir les simulations] يعمل.


المهمة: كتابة النسخة الجديدة من إجراء الخادم [VoirSimulations] (سطران يجب إعادة كتابتهما) ثم إجراء الاختبارات.


9.17. الخطوة 11: إنهاء الجلسة

9.17.1. المشكلة

يمكن إنهاء جلسة عمل المستخدم في أي وقت باستخدام الرابط [Ajax] [Terminer la session]. يؤدي ذلك إلى إنهاء الجلسة الحالية وبدء جلسة جديدة. علاوة على ذلك، يتم العودة إلى عرض النموذج:

  • في [1]، أجرينا محاكاة مرتين ثم أنهينا الجلسة؛
  • في [2]، عدنا إلى نموذج الإدخال. نريد رؤية عمليات المحاكاة؛
  • في [3]، بسبب تغيير الجلسة، أصبحت قائمة عمليات المحاكاة فارغة الآن.

9.17.2. كتابة إجراء الخادم [TerminerSession]

يستدعي رابط Ajax [Terminer la session] إجراء الخادم [TerminerSession] الذي كان رمزه حتى الآن كما يلي:


    // إنهاء الجلسة
    [HttpPost]
    public PartialViewResult TerminerSession()
    {
      return PartialView("Formulaire");
}

تنتظر طريقة العرض الجزئية [Formulaire] التي تعرضها نموذجًا [IndexModel] (السطر 1 أدناه):


@model Pam.Web.Models.IndexModel

@using (Html.BeginForm("FaireSimulation", "Pam", FormMethod.Post, new { id = "formulaire" }))
{
...
}
<div id="simulation" />

ولهذا السبب لم يعد الرابط [Terminer la session] يعمل.


المهمة: كتابة النسخة الجديدة من إجراء الخادم [TerminerSession] (سطران يجب إعادة كتابتهما) ثم إجراء الاختبارات.


ملاحظة: لإلغاء الجلسة في الإجراء، نكتب:

Session.Abandon() ;

9.17.3. تعديل وظيفة جافا سكريبت [terminerSession]

مع التعديل الذي تم إجراؤه سابقًا، يمكننا الآن العودة إلى النموذج ولكن تظهر عندئذٍ مشكلة، وهي تلك التي تم وصفها سابقًا في الفقرة 9.15.3.


المهمة: باتباع الخطوات التي اتبعتها في الفقرة 9.15.3، قم بتصحيح دالة جافا سكريبت [terminerSession] ثم قم بإجراء الاختبارات للتحقق من أن أدوات التحقق من صحة البيانات من جانب العميل تعمل مرة أخرى.


9.18. الخطوة 12: مسح المحاكاة

9.18.1. المشكلة

عند إجراء محاكاة، يمكن مسحها باستخدام رابط جافا سكريبت [Effacer la simulation]:

9.18.2. كتابة إجراء العميل [effacerSimulation]

تحتوي وظيفة جافا سكريبت [effacerSimulation] حاليًا على الكود التالي:


function effacerSimulation() {
  // يتم مسح الإدخالات من النموذج
  // ...
  // إخفاء المحاكاة إن وجدت
  $("#simulation").hide();
  // القائمة
  setMenu([lnkFaireSimulation, lnkTerminerSession, lnkVoirSimulations]);
}

المهمة: أكمل هذا الرمز. يمكن الاستعانة بالمثال الوارد في الفقرة 7.6.6


9.19. الخطوة 13: إزالة محاكاة

9.19.1. المشكلة

عندما تكون صفحة المحاكاة مفتوحة، يمكن حذف بعضها باستخدام رابط جافا سكريبت [retirer]:

Image

Image

9.19.2. كتابة إجراء العميل [retirerSimulation]

تتخذ روابط [retirer] الشكل التالي: HTML

<a href="javascript:retirerSimulation(N)">retirer</a>

حيث N هو رقم المحاكاة.


المهمة: باتباع الخطوات الواردة في الفقرات 9.9.3، اكتب الدالة JS [retirerSimulation]. ستقوم هذه الدالة بإرسال استدعاء Ajax من النوع POST إلى الإجراء [/Pam/RetirerSimulation]. وسترسل البيانات N بالشكل num=N.


ملاحظة: الدالة JS [retirerSimulation] مشابهة للدوال الأخرى JS التي قمت بكتابتها والتي تقوم بإجراء استدعاء Ajax إلى الخادم. الشيء الجديد الوحيد هنا هو POST لقيمة غير موجودة في نموذج. نعلم أن القيم المرسلة يتم تجميعها في سلسلة أحرف بالشكل:

param1=val1&param2=val2&....

وبالتالي، ستكون الدالة JS [retirerSimulation] بالشكل التالي:


function retirerSimulation(N) {
  // إجراء استدعاء Ajax يدويًا
  $.ajax({
    url: '/Pam/RetirerSimulation',
...
    data:"num="+N,
...
  });
  // القائمة
  setMenu([lnkRetourFormulaire, lnkTerminerSession]);
}
  • السطر 6: تمثل الخاصية [data] لاستدعاء Ajax JQuery السلسلة التي تم إرسالها إلى الخادم.

9.19.3. كتابة إجراء الخادم [RetirerSimulation]

إجراء الخادم [RetirerSimulation]:

  • تستقبل معلمة مرسلة تسمى [num] وهي رقم محاكاة؛
  • يجب أن تحذف من قائمة المحاكاة المسجلة في الجلسة، المحاكاة التي تحمل هذا الرقم؛
  • يجب أن تعرض بعد ذلك قائمة المحاكاة الجديدة.

المهمة: اكتب إجراء الخادم [RetirerSimulation]. راجع الفقرة 4.1 لمعرفة كيفية استرداد المعلمة المرسلة المسماة [num].


9.20. الخطوة 14: تحسين طريقة تهيئة التطبيق

لقد اكتمل تطبيق الويب الخاص بنا. وهو يعمل مع فئة [métier] محاكاة. دعونا نستعرض البنية التي قمنا بتطويرها:

لا تزال هناك بعض التفاصيل التي يجب تسويتها قبل الانتقال إلى التنفيذ الفعلي لطبقة [métier]، ويحدث ذلك في طريقة تهيئة التطبيق: الطريقة [Application_Start] في [Global.asax]:

يتم تنفيذ الطريقة [Application_Start] في [Global.asax] مرة واحدة فقط عند بدء تشغيل التطبيق. وهنا يمكن استخدام ملف التكوين [Web.config]. في الوقت الحالي، تبدو طريقتنا [Application_Start] كما يلي:


// تطبيق
    protected void Application_Start()
    {
      // ----------تم إنشاؤه تلقائيًا
      AreaRegistration.RegisterAllAreas();
      WebApiConfig.Register(GlobalConfiguration.Configuration);
      FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
      RouteConfig.RegisterRoutes(RouteTable.Routes);
      BundleConfig.RegisterBundles(BundleTable.Bundles);
      // -------------------------------------------------------------------
      // ---------- تكوين محدد
      // -------------------------------------------------------------------
      // بيانات نطاق التطبيق
      ApplicationModel application = new ApplicationModel();
      Application["data"] = application;
      // تجسيد الطبقة [métier]
      application.PamMetier = new PamMetier();
...
      // مربط النماذج
...
}

في السطر 17، يتم إنشاء مثيل لطبقة الأعمال بواسطة عامل new. علاوة على ذلك، يتم تعريف نموذج التطبيق على النحو التالي:


  public class ApplicationModel
  {
    // --- بيانات نطاق التطبيق ---
    public Employe[] Employes { get; set; }
    public IPamMetier PamMetier { get; set; }
    public SelectListItem[] EmployesItems { get; set; }
}

في السطر 5 أعلاه، نرى أن نوع الخاصية [PamMetier] هو نوع واجهة [IPamMetier]. وهذا يعني أن هذه الخاصية يمكن تهيئتها بواسطة أي كائن ينفذ هذه الواجهة. ولكن في السطر 17 من [Application_Start]، قمنا بكتابة اسم فئة تنفيذ لـ [IPamMetier] بشكل ثابت. لذا، إذا تم تنفيذ الطبقة [métier] باستخدام فئة جديدة تنفذ [IPamMetier]، فسيكون من الضروري تغيير هذا السطر. هذا ليس مهمًا جدًا، ولكن يمكن تجنبه. يمكن نقل تعريف فئة تنفيذ الواجهة [IPamMetier] إلى ملف تكوين. لتغيير التنفيذ، يتم تغيير محتوى ملف التكوين هذا. لا يلزم تغيير الكود .NET.

سنستخدم هنا حاوية حقن التبعيات [Spring.net]. توجد أطر عمل أخرى .NET للقيام بنفس الشيء، ربما بشكل أفضل وأبسط.

تتطور بنية المشروع على النحو التالي:

  • في [A]، ستطلب طريقة تهيئة الطبقة [ASP.NET MVC] من [Spring.net] مرجعًا للطبقة [métier] المحاكاة؛
  • في [B]، ستقوم [Spring.net] بإنشاء الطبقة [métier] المحاكاة باستخدام ملف التكوين الخاص بها لمعرفة الفئة التي يجب عليها إنشاء مثيل لها؛
  • في [C]، سيقوم [Spring.net] بإرجاع مرجع الطبقة [métier] المحاكاة إلى الطبقة [ASP.NET MVC].

تجدر الإشارة إلى أن الكائنات التي يديرها [Spring.net] هي كائنات فردية بشكل افتراضي: فهي موجودة بنسخة واحدة فقط. وبالتالي، إذا طلبت شفرة لاحقًا في مثالنا من [Spring.net] مرجعًا على الطبقة [métier] المحاكاة، فإن [Spring.net] يكتفي بإرجاع المرجع على الكائن الذي تم إنشاؤه في البداية.

9.20.1. إضافة مراجع [Spring] إلى مشروع الويب

سنستخدم [Spring.net]. يأتي هذا الإطار في شكل DLL الذي يجب إضافته إلى مراجع المشروع. يمكننا القيام بذلك على النحو التالي:

في [1]، انقر بزر الماوس الأيمن على فرع [References] للمشروع ثم اختر الخيار [Gérer les packages NuGet]. يلزم وجود اتصال بالإنترنت. بعد ذلك، نتبع نفس الخطوات التي اتبعناها سابقًا للمكتبة JQuery [Globalize]. نبحث عن الكلمة الرئيسية [Spring.core] ونقوم بتثبيت هذه الحزمة. يؤدي التثبيت إلى ظهور اثنين من DLL: [Spring.core] [2] و [Common.Logging] [3]. في الأمثلة التالية، تم استخدام الإصدار 1.3.2 من Spring.

ملاحظة: إذا لم يكن لديك اتصال بالإنترنت، فستجد ملفات DLL هذه في مجلد [lib] الخاص بدعم هذه الدراسة.

9.20.2. تكوين [web.config]

يتم تعريف فئة تنفيذ واجهة [IPamMetier] في الملف [web.config].


<configuration>
  <configSections>
...
    <sectionGroup name="spring">
      <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
      <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
    </sectionGroup>
  </configSections>
  <!-- تكوين Spring -->
  <spring>
    <context>
      <resource uri="config://spring/objects" />
    </context>
    <objects xmlns="http://www.springframework.net">
      <object id="pammetier" type="Pam.Metier.Service.PamMetier, pam-metier-simule"/>
    </objects>
  </spring>
...
  • الأسطر 2-8: حدد العلامة <configSections> في الملف وأدخل فيها الأسطر 4-7؛
  • السطر 4: يشير السمة [name="spring"] إلى معلومات تتعلق بالقسم [spring] في الأسطر 10-17؛
  • السطر 5: يحدد الفئة [Spring.Context.Support.DefaultSectionHandler] الموجودة في DLL [Spring.Core] باعتبارها قادرة على معالجة القسم [objects] من الأسطر 14-16؛
  • السطر 6: يحدد الفئة [Spring.Context.Support.ContextHandler] الموجودة في DLL [Spring.Core] باعتبارها قادرة على معالجة القسم [context] في الأسطر 11-13؛
  • الأسطر 11-13: يقدم هذا القسم المعلومات [<resource uri="config://spring/objects" />] التي تشير إلى أن كائنات Spring موجودة في ملف التكوين في القسم [/spring/objects]، أي في الأسطر 14-16؛
  • الأسطر 14-16: العلامة [objects] تقدم كائنات Spring؛
  • السطر 15: يحدد كائنًا معرّفًا بـ [id="pammetier"] وهو مثيل للفئة [Pam.Metier.Service.PamMetier] الموجودة في DLL [pam-metier-simule]. هنا، يجب ألا تخطئ. بالنسبة للسمة [id]، يمكنك إدخال ما تريد. ستستخدم هذا المعرف في [Global.asax]. الفئة [Pam.Metier.Service.PamMetier] هي فئة الطبقة [métier] التي قمنا بمحاكاتها. يجب الرجوع إلى تعريفها لمعرفة اسمها الكامل:

namespace Pam.Metier.Service
{
  public class PamMetier : IPamMetier
  {
    ...

بالنسبة إلى DLL [pam-metier-simule]، يجب الاطلاع على خصائص مشروع C# [pam-metier-simule]:

يجب استخدام الاسم المحدد في [1].

9.20.3. تعديل [Application_Start]

تتطور الطريقة [Application_Start] على النحو التالي:


using Spring.Context.Support;

// التطبيق
    protected void Application_Start()
    {
      // ----------تم إنشاؤه تلقائيًا
      AreaRegistration.RegisterAllAreas();
      WebApiConfig.Register(GlobalConfiguration.Configuration);
      FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
      RouteConfig.RegisterRoutes(RouteTable.Routes);
      BundleConfig.RegisterBundles(BundleTable.Bundles);
      // -------------------------------------------------------------------
      // ---------- تكوين محدد
      // -------------------------------------------------------------------
      // بيانات نطاق التطبيق
      ApplicationModel application = new ApplicationModel();
      Application["data"] = application;
      // إنشاء مثيل الطبقة [métier]
      application.PamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
...
      // مربط النماذج
...
}
  • السطر 19: يتم استخدام فئة Spring [ContextRegistry] وهي فئة قادرة على استغلال الملف [web.config]. ولذلك، نحتاج إلى استيراد مساحة الأسماء من السطر 1. تسمح الطريقة الثابتة [GetContext] بالحصول على محتوى العلامات [context] التي تشير إلى مكان وجود كائنات Spring. ثم تسمح الطريقة الثابتة [GetObject] بالحصول على كائن معين يتم تحديده بواسطة سمة id الخاصة به. تجدر الإشارة إلى أن اسم فئة تنفيذ واجهة [IPamMetier] لم يعد مكتوبًا بشكل ثابت في الكود. فهو موجود الآن في الملف [web.config].

بعد إجراء جميع هذه التعديلات، اختبر تطبيقك. يجب أن يعمل.

9.20.4. معالجة خطأ في تهيئة التطبيق

في الطريقة [Application_Start]، كتبنا:


application.PamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;

قد تفشل التعليمات الموجودة على يمين علامة =. وهناك أسباب مختلفة لذلك:

  • الأكثر وضوحًا هو وجود خطأ في اسم الكائن المراد إنشاء مثيل له؛
  • والسبب الآخر هو أن إنشاء مثيل الطبقة [métier] لا يسير على ما يرام. لا يمكن أن يكون هذا هو الحال بالنسبة لطبقتنا [métier] المحاكاة، ولكنه قد يكون كذلك بالنسبة لطبقتنا [métier] الحقيقية التي سيتم توصيلها بقاعدة بيانات. قد لا يتم تشغيل SGBD، أو قد تكون المعلومات المتعلقة بقاعدة البيانات المطلوب إدارتها غير صحيحة، إلخ...

سنقوم بمعالجة أي استثناء محتمل في try / catch. يتطور الكود على النحو التالي:


// التطبيق
    protected void Application_Start()
    {
      // ----------تم إنشاؤه تلقائيًا
...
      // -------------------------------------------------------------------
      // ---------- تكوين محدد
      // -------------------------------------------------------------------
      // بيانات نطاق التطبيق
      ApplicationModel application = new ApplicationModel();
      Application["data"] = application;
      application.InitException = null;
      try
      {
        // إنشاء مثيل الطبقة [métier]
        application.PamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
      }
      catch (Exception ex)
      {
        application.InitException = ex;
      }
      //في حالة عدم وجود أخطاء
      if (application.InitException == null)
      {
....
      }
      // مربط النماذج
...
    }
  • السطر 12، نضيف خاصية جديدة باسم [InitException] في نموذج التطبيق:

  public class ApplicationModel
  {
    // --- بيانات نطاق التطبيق ---
    public Employe[] Employes { get; set; }
    public IPamMetier PamMetier { get; set; }
    public SelectListItem[] EmployesItems { get; set; }
    public Exception InitException { get; set; }
}
  • السطر 7 أعلاه، الاستثناء الذي قد يحدث أثناء تهيئة التطبيق؛
  • الأسطر 13-21 من [Application_Start]: يتم الآن إنشاء مثيل لطبقة [métier] في try / catch؛
  • السطر 20: يتم تخزين الاستثناء؛
  • الأسطر 23-26: إذا لم تكن هناك أخطاء، يتم تنفيذ الكود الذي كان موجودًا سابقًا؛
  • السطر 28: يتم إنشاء [ModelBinders] سواء حدث خطأ أم لا. هذا أمر مهم. نريد التأكد من أن نموذج التطبيق [ApplicationModel] سيتم ربطه بالفعل بواسطة إطار العمل.

نعلم أنه عند بدء تشغيل التطبيق، يتم تنفيذ الإجراء الخادم [Index]. في الوقت الحالي، هو كما يلي:


    [HttpGet]
    public ViewResult Index(ApplicationModel application)
    {
      return View(new IndexModel() { Application = application });
}

في السطر 2، تتلقى الإجراء [Index] نموذج التطبيق. وبالتالي، يمكنها معرفة ما إذا كانت عملية التهيئة قد تمت بنجاح أم لا، وعرض صفحة أخطاء إذا فشلت عملية التهيئة بأي شكل من الأشكال. نقوم بتطوير الكود على النحو التالي:


    [HttpGet]
    public ViewResult Index(ApplicationModel application)
    {
      // خطأ في التهيئة؟
      if (application.InitException != null)
      {
        // صفحة الأخطاء بدون قائمة
        return View("InitFailed",Static.GetErreursForException(application.InitException));
      }
      // لا توجد أخطاء
      return View(new IndexModel() { Application = application });
}

السطر 8، في حالة حدوث خطأ في التهيئة، نعرض العرض [InitFailed.cshtml] باستخدام نموذج قائمة رسائل الخطأ الخاصة بالاستثناء الذي حدث أثناء التهيئة. تم عرض وشرح الطريقة [Static.GetErreursForException] في الفقرة 9.12.4. وستكون طريقة العرض [InitFailed.cshtml] كما يلي:

ورمزها هو كما يلي:


@model IEnumerable<string>
@{
  Layout = null;
}
<!DOCTYPE html>
<html>
<head>
  <title>@ViewBag.Title</title>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width" />
  <link rel="stylesheet" href="~/Content/Site.css" />
</head>
<body>
  <table>
    <tbody>
      <tr>
        <td>
          <h2>Simulateur de calcul de paie</h2>
        </td>
    </tbody>
  </table>
  <hr />
  <h2>Les erreurs suivantes se sont produites à l'initialisation de l'application : </h2>
  <ul>
    @foreach (string msg in Model)
    {
      <li>@msg</li>
    }
  </ul>
</body>
</html>
  • السطر 1: نموذج العرض هو قائمة برسائل الخطأ. يتم عرض هذه الرسائل في قائمة HTML في الأسطر 24-29؛
  • السطر 3: لا تستخدم هذه العرض الصفحة الرئيسية [_Layout.cshtml]. في الواقع، لا نريد القائمة التي يجلبها هذا المستند. لذلك نقوم بإنشاء صفحة HTML كاملة (السطور 5-23).

للتجربة، يكفي تعديل مثيل الطبقة [métier] في [Application_Start] على النحو التالي:


      try
      {
        // إنشاء مثيل الطبقة [métier]
        application.PamMetier = ContextRegistry.GetContext().GetObject("xx") as IPamMetier;
      }
      catch (Exception ex)
      {
        application.InitException = ex;
}

في السطر 4، نبحث عن كائن غير موجود في كائنات Spring.

عند التحقق من صحة هذه التعديلات وتشغيل التطبيق، تظهر الصفحة التالية:

 

نحصل على صفحة أخطاء بدون قائمة. لا يمكن للمستخدم فعل أي شيء سوى ملاحظة الخطأ. وهذا ما كنا نريده.

9.21. أين نحن الآن؟

لدينا الآن تطبيق ويب جاهز للعمل يعمل مع طبقة أعمال محاكاة. وتتمثل بنيته في ما يلي:

تعمل الطبقة [ASP.NET MVC] مع الطبقة الوظيفية المحاكاة عبر الواجهة [IPamMetier]. إذا استبدلنا هذه الطبقة الوظيفية المحاكاة بطبقة وظيفية حقيقية تتوافق مع هذه الواجهة، فلن نضطر إلى تعديل كود طبقة الويب. بفضل [Spring.net]، سيكون علينا فقط تغيير فئة تنفيذ واجهة [IPamMetier] في [web.config]. سنمضي في هذا الاتجاه.

وستكون البنية الجديدة كما يلي:

سنقوم بوصف ما يلي بالتتابع:

  • الطبقة [EF5] المتصلة بـ SGBD. سيتم تنفيذها باستخدام Entity Framework 5 (EF5
  • الطبقة [DAO] التي تدير الوصول إلى البيانات عبر الطبقة [EF5]. وهذا يسمح لها بتجاهل وجود SGBD. تقتصر هذه الطبقة على معالجة كيانات التطبيق [Employe, Cotisations, Indemnites
  • الطبقة [métier] التي تنفذ حساب الراتب.

البنية الجديدة هي تلك التي تم عرضها في بداية هذا المستند في الفقرة 1.1، والتي نذكرها الآن:

  • الطبقة [Web] هي الطبقة التي تتعامل مع مستخدم تطبيق الويب. يتفاعل المستخدم مع تطبيق الويب من خلال صفحات الويب التي يتم عرضها بواسطة متصفح. يوجد في هذه الطبقة ASP.NET MVC وفي هذه الطبقة فقط؛
  • تنفذ الطبقة [métier] قواعد إدارة التطبيق، مثل حساب الراتب أو الفاتورة. تستخدم هذه الطبقة البيانات الواردة من المستخدم عبر الطبقة [Web] ومن SGBD عبر الطبقة [DAO
  • تدير الطبقة [DAO] (كائنات الوصول إلى البيانات)، والطبقة [ORM] (مخطط العلاقات بين الكائنات)، والموصل ADO.NET الوصول إلى بيانات SGBD. تشكل الطبقة [ORM] جسرًا بين الكائنات التي تعالجها الطبقة [DAO] والصفوف والأعمدة لبيانات قاعدة البيانات العلائقية. يتم استخدام طبقتين من ORM بشكل شائع في عالم .NET، NHibernate (http://sourceforge.net/projects/nhibernate/ ) و Entity Framework (http://msdn.microsoft.com/en-us/data/ef.aspx
  • يمكن تحقيق تكامل الطبقات بواسطة حاوية حقن التبعيات (Dependency Injection Container) مثل Spring (http://www.springframework.net/

سيتم تنفيذ الطبقات [métier] و [DAO] و [EF5] باستخدام مشاريع C#. من الآن فصاعدًا، سنعمل باستخدام Visual Studio Express 2012 للكمبيوتر المكتبي.

9.22. الخطوة 15: إعداد طبقة Entity Framework 5

إن إنشاء طبقة [EF5] هو مسألة تتعلق بالتكوين أكثر منها بالبرمجة. لفهم كيفية كتابة هذه الطبقة، يمكن قراءة الوثيقة [Introduction à Entity Framework 5 Code First] المتوفرة في URL [http://tahe.developpez.com/dotnet/ef5cf-02/]. إنه مستند ضخم إلى حد ما. توجد الأساسيات في الفصول الأربعة الأولى. سيتم تحديد الفقرات التي يجب قراءتها بشكل خاص. عندما نشير إلى هذا المستند، سنستخدم الترميز [refEF5].

بالإضافة إلى ذلك، سنحتاج أحيانًا إلى مفاهيم C#. عندها سنشير إلى المقرر [Introduction au langage C#] المتاح في URL [http://tahe.developpez.com/dotnet/csharp/] بالرمز [refC#].

9.22.1. قاعدة البيانات

تم عرض قاعدة بيانات التطبيق في الفقرة 9.4. وهي قاعدة بيانات MySQL تسمى [dbpam_ef5] (pam=Paie Assistante Maternelle). لهذه القاعدة مسؤول يدعى root بدون كلمة مرور.

دعونا نستعرض مخطط قاعدة البيانات. تحتوي على ثلاثة جداول:

Image

هناك علاقة مفتاح خارجي بين العمود EMPLOYES(INDEMNITE_ID) والعمود INDEMNITES(ID). يتم تحديد جزء من بنية قاعدة البيانات هذه من خلال استخدامها مع EF5.

النص البرمجي SQL لإنشاء قاعدة البيانات هو كما يلي:


-- phpMyAdmin SQL تفريغ
-- الإصدار 3.5.1
-- http://www.phpmyadmin.net
--
-- العميل: localhost
-- تم إنشاؤه في: الاثنين 4 نوفمبر 2013 الساعة 09:34
-- إصدار الخادم: 5.5.24-log
-- إصدار PHP: 5.4.3

SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+00:00";


/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;

--
-- قاعدة البيانات: `dbpam_ef5`
--

-- --------------------------------------------------------

--
-- هيكل الجدول `cotisations`
--

CREATE TABLE IF NOT EXISTS `cotisations` (
  `ID` bigint(20) NOT NULL AUTO_INCREMENT,
  `SECU` double NOT NULL,
  `RETRAITE` double NOT NULL,
  `CSGD` double NOT NULL,
  `CSGRDS` double NOT NULL,
  `VERSIONING` int(11) NOT NULL,
  PRIMARY KEY (`ID`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=12 ;

--
-- محتوى الجدول `cotisations`
--

INSERT INTO `cotisations` (`ID`, `SECU`, `RETRAITE`, `CSGD`, `CSGRDS`, `VERSIONING`) VALUES
(11, 9.39, 7.88, 6.15, 3.49, 1);

--
-- مشغلات `cotisations`
--
DROP TRIGGER IF EXISTS `INCR_VERSIONING_COTISATIONS`;
DELIMITER //
CREATE TRIGGER `INCR_VERSIONING_COTISATIONS` BEFORE UPDATE ON `cotisations`
 FOR EACH ROW BEGIN
  SET NEW.VERSIONING:=OLD.VERSIONING+1;
END
//
DELIMITER ;
DROP TRIGGER IF EXISTS `START_VERSIONING_COTISATIONS`;
DELIMITER //
CREATE TRIGGER `START_VERSIONING_COTISATIONS` BEFORE INSERT ON `cotisations`
 FOR EACH ROW BEGIN
  SET NEW.VERSIONING:=1;
END
//
DELIMITER ;

-- --------------------------------------------------------

--
-- هيكل الجدول `employes`
--

CREATE TABLE IF NOT EXISTS `employes` (
  `ID` bigint(20) NOT NULL AUTO_INCREMENT,
  `PRENOM` varchar(20) CHARACTER SET latin1 NOT NULL,
  `SS` varchar(15) CHARACTER SET latin1 NOT NULL,
  `ADRESSE` varchar(50) CHARACTER SET latin1 NOT NULL,
  `CP` varchar(5) CHARACTER SET latin1 NOT NULL,
  `VILLE` varchar(30) CHARACTER SET latin1 NOT NULL,
  `NOM` varchar(30) CHARACTER SET latin1 NOT NULL,
  `VERSIONING` int(11) NOT NULL,
  `INDEMNITE_ID` bigint(20) NOT NULL,
  PRIMARY KEY (`ID`),
  UNIQUE KEY `SS` (`SS`),
  KEY `FK_EMPLOYES_INDEMNITE_ID` (`INDEMNITE_ID`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=26 ;

--
-- محتوى الجدول `employes`
--

INSERT INTO `employes` (`ID`, `PRENOM`, `SS`, `ADRESSE`, `CP`, `VILLE`, `NOM`, `VERSIONING`, `INDEMNITE_ID`) VALUES
(24, 'Marie', '254104940426058', '5 rue des oiseaux', '49203', 'St Corentin', 'Jouveinal', 1, 93),
(25, 'Justine', '260124402111742', 'La Brûlerie', '49014', 'St Marcel', 'Laverti', 1, 94);

--
-- مشغلات `employes`
--
DROP TRIGGER IF EXISTS `INCR_VERSIONING_EMPLOYES`;
DELIMITER //
CREATE TRIGGER `INCR_VERSIONING_EMPLOYES` BEFORE UPDATE ON `employes`
 FOR EACH ROW BEGIN
  SET NEW.VERSIONING:=OLD.VERSIONING+1;
END
//
DELIMITER ;
DROP TRIGGER IF EXISTS `START_VERSIONING_EMPLOYES`;
DELIMITER //
CREATE TRIGGER `START_VERSIONING_EMPLOYES` BEFORE INSERT ON `employes`
 FOR EACH ROW BEGIN
  SET NEW.VERSIONING:=1;
END
//
DELIMITER ;

-- --------------------------------------------------------

--
-- هيكل الجدول `indemnites`
--

CREATE TABLE IF NOT EXISTS `indemnites` (
  `ID` bigint(20) NOT NULL AUTO_INCREMENT,
  `ENTRETIEN_JOUR` double NOT NULL,
  `REPAS_JOUR` double NOT NULL,
  `INDICE` int(11) NOT NULL,
  `INDEMNITES_CP` double NOT NULL,
  `BASE_HEURE` double NOT NULL,
  `VERSIONING` int(11) NOT NULL,
  PRIMARY KEY (`ID`),
  UNIQUE KEY `INDICE` (`INDICE`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=95 ;

--
-- محتوى الجدول `indemnites`
--

INSERT INTO `indemnites` (`ID`, `ENTRETIEN_JOUR`, `REPAS_JOUR`, `INDICE`, `INDEMNITES_CP`, `BASE_HEURE`, `VERSIONING`) VALUES
(93, 2.1, 3.1, 2, 15, 2.1, 1),
(94, 2, 3, 1, 12, 1.93, 1);

--
-- مشغلات `indemnites`
--
DROP TRIGGER IF EXISTS `INCR_VERSIONING_INDEMNITES`;
DELIMITER //
CREATE TRIGGER `INCR_VERSIONING_INDEMNITES` BEFORE UPDATE ON `indemnites`
 FOR EACH ROW BEGIN
  SET NEW.VERSIONING:=OLD.VERSIONING+1;
END
//
DELIMITER ;
DROP TRIGGER IF EXISTS `START_VERSIONING_INDEMNITES`;
DELIMITER //
CREATE TRIGGER `START_VERSIONING_INDEMNITES` BEFORE INSERT ON `indemnites`
 FOR EACH ROW BEGIN
  SET NEW.VERSIONING:=1;
END
//
DELIMITER ;

--
-- القيود المفروضة على الجداول المصدرة
--

--
-- القيود المفروضة على الجدول `employes`
--
ALTER TABLE `employes`
  ADD CONSTRAINT `FK_EMPLOYES_INDEMNITE_ID` FOREIGN KEY (`INDEMNITE_ID`) REFERENCES `indemnites` (`ID`);

/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;

تجدر الإشارة إلى النقاط التالية:

  • الأسطر 30 و73 و122: المفاتيح الأساسية للجداول في وضع [AUTO_INCREMENT]. يديرها MySQL وليس EF5؛
  • السطر 83: الرقم SS يخضع لقيود التفرد؛
  • السطر 130: مؤشر الموظف يخضع لقيود التفرد؛
  • السطران 168-169: المفتاح الأجنبي للجدول [employes] إلى الجدول [indemnites
  • السطر 49: مشغل أو [Trigger] هو برنامج نصي SQL مدمج في SGBD ويتم تنفيذه في أوقات معينة؛
  • الأسطر 51-54: يتم تشغيل المشغل [INCR_VERSIONING_COTISATIONS] قبل أي تعديل لسطر في الجدول [cotisations]. ثم يزيد العمود [VERSIONING] بمقدار وحدة واحدة؛
  • السطور 59-62: يتم تشغيل المشغل [START_VERSIONING_COTISATIONS] قبل أي إدراج لسطر جديد في الجدول [cotisations]. ثم يقوم بتعيين العمود [VERSIONING] إلى 1؛
  • في النهاية، تكون قيمة العمود [VERSIONING] هي 1 عند إنشاء سطر في الجدول [cotisations] ثم تزداد بمقدار 1 مع كل تعديل يتم إجراؤه على هذا السطر. تسمح هذه الآلية لـ EF5 بإدارة التنافس على الوصول إلى سطر في الجدول [cotisations] بالطريقة التالية:
    • تقوم عملية P1 بقراءة سطر L من الجدول [cotisations] في الوقت T1. يحتوي السطر على العمود [VERSIONING] V1؛
    • تقوم العملية P2 بقراءة نفس السطر L من الجدول [cotisations] في الوقت T2. يحتوي السطر على العمود [VERSIONING] V1 لأن العملية P1 لم تقم بعد بالتحقق من صحة تعديلها؛
    • يقوم العملية P1 بتعديل السطر L وتثبيت التعديل. ثم يتغير العمود [VERSIONING] في السطر L إلى V1+1 بسبب المشغل [INCR_VERSIONING_COTISATIONS
    • ثم تقوم العملية P2 بنفس الشيء. ثم تطلق EF5 استثناءً لأن العملية P2 تحتوي على سطر به عمود [VERSIONING] له قيمة V1 تختلف عن تلك الموجودة في قاعدة البيانات وهي V1+1. لا يمكن تعديل السطر إلا إذا كانت قيمة [VERSIONING] هي نفسها الموجودة في قاعدة البيانات.

يُطلق على ذلك اسم الإدارة المتفائلة للوصول المتزامن. مع EF5، يجب أن يكون للحقل الذي يؤدي هذا الدور التعليق التوضيحي [ConcurrencyCheck].

  • يتم إنشاء آلية مماثلة للجدول [employes] (الأسطر 98-113) والجدول [indemnites] (الأسطر 144-159).

المهمة: قم بإنشاء قاعدة البيانات MySQL [dbpam_ef5] باستخدام البرنامج النصي SQL السابق. يجب إنشاء قاعدة البيانات [dbpam_ef5] مسبقًا لأن البرنامج النصي لا يقوم بإنشائها. بعد ذلك، سنقوم بتشغيل البرنامج النصي SQL على هذه القاعدة.


9.22.2. مشروع Visual Studio

باستخدام Visual Studio Express 2012 للكمبيوتر المكتبي، نقوم بتحميل الحل [pam-td] المستخدم عند إنشاء الطبقة [web]:

  • في [1]، VS 2012 Express للكمبيوتر المكتبي لا يستطيع تحميل مشروع الويب [pam-web-01]. هذا أمر طبيعي ولا يمثل مشكلة؛
  • في [2]، نضيف مشروعًا جديدًا إلى الحل [pam-td
  • في [3]، المشروع من النوع [console] ويسمى [4] [pam-ef5
  • في [5]، المشروع الذي تم إنشاؤه. اسمه ليس بخط عريض، لذا فهو ليس مشروع بدء الحل؛
  • في [6] و [7]، نحدد المشروع الجديد كمشروع بدء التشغيل.

9.22.3. إضافة المراجع اللازمة للمشروع

لنضع المشروع في سياقه العام:

يحتاج مشروعنا إلى عدد معين من DLL:

  • DLL من Entity Framework
  • DLL لموصل ADO.NET لـ SGBD MySQL.

يشرح الفقرة 4.2 من [refEF5] كيفية تثبيت هذه DLL باستخدام أداة [NuGet]. حاليًا (نوفمبر 2013)، الإصدار المتاح من Entity Framework هو الإصدار 6 (EF6). للأسف، يبدو أن الموصل ADO.NET الخاص بـ SGBD MySQL المتاح (نوفمبر 2013) عبر [NuGet] غير متوافق مع EF6. لذلك تم وضع [lib] و[1] وDLL منEF5 بالإضافة إلى ملفات DLL الأخرى اللازمة لمشروع [pam-ef5]

لقد وضعنا ملفات DLL أخرى في المجلد [lib]. سنستخدمها لاحقًا. في [2]، نضيف ملفات DLL الجديدة هذه إلى المشروع.

  • في [3]، نتصفح نظام الملفات حتى المجلد [lib]؛
  • في [4]، نختار الثلاثة DLL ثم نؤكد مرتين؛
  • في [5]، تمت إضافة الملفات الثلاثة DLL إلى مراجع المشروع.

نحتاج إلى DLL أخرى. سيتم العثور عليها ضمن ملفات إطار العمل .NET الخاصة بالجهاز.

  • في [1]، أضف مرجعًا جديدًا إلى المشروع؛
  • في [2]، حدد [Assemblys
  • في [3]، اكتب [system.component
  • في [4]، حدد التجميع [System.ComponentModel.DataAnnotations
  • في [5]، تمت إضافة المرجع.

نحن الآن جاهزون للبرمجة والتكوين.

9.22.4. كيانات Entity Framework

كيانات Entity Framework هي فئات يتم فيها تغليف صفوف الجداول المختلفة في قاعدة البيانات. دعونا نذكرها:

Image

في الطبقة [web]، استخدمنا كيانات [Employe, Cotisations, Indemnités] (انظر الفقرة 9.7.3، الصفحة 219). لم تكن هذه الكيانات نسخًا طبق الأصل من الجداول. وبالتالي، تم تجاهل أعمدة [ID, VERSIONING]. ولن يكون هذا هو الحال هنا لأنها تُستخدم بواسطة ORM و EF5. لذا سنقوم بإضافة الخصائص المفقودة إليها. نقوم بإنشاء هذه الكيانات في مجلد [Models] بالمشروع:

أصبح الرمز الجديد لها كما يلي:

فئة [Cotisations]


using System;

namespace Pam.EF5.Entites
{
  public class Cotisations
  {
    public int Id { get; set; }
    public double CsgRds { get; set; }
    public double Csgd { get; set; }
    public double Secu { get; set; }
    public double Retraite { get; set; }
    public int Versioning { get; set; }

    // التوقيع
    public override string ToString()
    {
      return string.Format("Cotisations[{0},{1},{2},{3}, {4}, {5}]", Id, Versioning, CsgRds, Csgd, Secu, Retraite);
    }
  }
}
  • السطر 3: تم تكييف مساحة الأسماء مع المشروع الجديد؛
  • تمت إضافة خصائص السطرين 7 و 12 لتعكس بنية الجدول [cotisations
  • السطر 17: تعرض الطريقة [ToString] الآن الحقلين الجديدين.

الفئة [Indemnites]


using System;

namespace Pam.EF5.Entites
{
  public class Indemnites
  {
    public int Id { get; set; }
    public int Indice { get; set; }
    public double BaseHeure { get; set; }
    public double EntretienJour { get; set; }
    public double RepasJour { get; set; }
    public double IndemnitesCp { get; set; }
    public int Versioning { get; set; }

    // توقيع
    public override string ToString()
    {
      return string.Format("Indemnités[{0},{1},{2},{3},{4}, {5}, {6}]", Id, Versioning, Indice, BaseHeure, EntretienJour, RepasJour, IndemnitesCp);
    }
  }
}
  • السطر 3: تم تكييف مساحة الأسماء مع المشروع الجديد؛
  • تمت إضافة خصائص السطرين 7 و 13 لتعكس بنية الجدول [indemnites
  • السطر 18: تعرض الطريقة [ToString] الآن الحقلين الجديدين.

الفئة [Employe]


using System;

namespace Pam.EF5.Entites
{

  public class Employe
  {
    public int Id { get; set; }
    public string SS { get; set; }
    public string Nom { get; set; }
    public string Prenom { get; set; }
    public string Adresse { get; set; }
    public string Ville { get; set; }
    public string CodePostal { get; set; }
    public Indemnites Indemnites { get; set; }
    public int Versioning { get; set; }

    // توقيع
    public override string ToString()
    {
      return string.Format("Employé[{0},{1},{2},{3},{4},{5}, {6}, {7}]", Id, Versioning, SS, Nom, Prenom, Adresse, Ville, CodePostal);
    }
  }
}
  • السطر 3: تم تكييف مساحة الأسماء مع المشروع الجديد؛
  • تمت إضافة خصائص السطرين 8 و 16 لتعكس بنية الجدول [employes
  • السطر 21: تعرض الطريقة [ToString] الآن الحقلين الجديدين.

لكي تكون قابلة للاستخدام بواسطة ORM و EF5، يجب تزيين خصائص هذه الفئات بالتعليقات التوضيحية.


المهمة: بالاستعانة بالفقرة 3.4 من [Création de la base à partir des entités] في [refEF5]، أضف إلى كيانات [Employe, Cotisations, Indemnites] التعليقات التوضيحية اللازمة لـ EF5.


نصائح:

  • يتعلق الأمر فقط بإنشاء تعليقات توضيحية. لا تتبع الجزء [création de base] من الفقرة المشار إليها؛
  • بالنسبة للتعليق التوضيحي [Table]، اتبع المثال MySQL الوارد في الفقرة 4.2 من [refEF5
  • بالنسبة للتعليق [ConcurrencyCheck] على الخاصية [Versioning]، اتبع المثال الخاص بـ Oracle الوارد في الفقرة 5.2 من [refEF5
  • بالنسبة للمفتاح الأجنبي الذي تمتلكه الجدول [employes] في الجدول [indemnités]، اتبع المثال 3.4.2 من [refEF5]. وبذلك ستضيف خاصية جديدة إلى الكيان [Employe]:

    public int IndemniteId { get; set; }

التي ستكون قيمتها هي قيمة العمود [INDEMNITES_ID] في الجدول [employes]. ستضع تعليقات المفتاح الأجنبي على الخصائص [IndemniteId] و [Indemnites] للكيان [Employe]. للقيام بذلك، اتبع المثال 3.4.2 من [refEF5

  • لن تدير العلاقات العكسية للمفاتيح الخارجية؛
  • يتطلب هذا العمل قراءة بعض المعلومات من [refEF5].

9.22.5. تكوين ORM EF5

لنضع المشروع في سياقه العام:

ستقوم الطبقة [EF5] بالوصول إلى قاعدة البيانات عبر الموصل [ADO.NET] الخاص بـ SGBD MySQL. وهي تحتاج إلى عدد معين من المعلومات للوصول إلى هذه القاعدة. وتوجد هذه المعلومات في أماكن مختلفة من المشروع.

علينا أولاً إنشاء سياق قاعدة البيانات. هذا السياق هو فئة مشتقة من فئة النظام [System.Data.Entity.DbContext]. وهي تُستخدم لتعريف صور كائنات جداول قاعدة البيانات. سنضع هذه الفئة في المجلد [Models] للمشروع مع الكيانات EF5:

وستكون الفئة [DbPamContext] كما يلي:


using Pam.EF5.Entites;
using System.Data.Entity;

namespace Pam.Models
{
  public class DbPamContext : DbContext
  {
    public DbSet<Employe> Employes { get; set; }
    public DbSet<Cotisations> Cotisations { get; set; }
    public DbSet<Indemnites> Indemnites { get; set; }
  }
}
  • السطر 6: الفئة [DbPamContext] مشتقة من فئة النظام [DbContext
  • الأسطر 8-10: الصور الكائنات للجدولين الثلاثة في قاعدة البيانات. نوعها هو [DbSet<Entity>] حيث [Entity] هي إحدى كيانات Entity Framework التي حددناها للتو. يمكن النظر إلى النوع [DbSet] على أنه مجموعة من الكيانات. ويمكن الاستعلام عنها باستخدام LINQ (Language INtegrated Query). يُرجى من القارئ الذي لا يعرف LINQ قراءة الفقرة 3.5.4 [Apprentissage de LINQ avec LINQPad] من [refEF5].

سنطلق فيما يلي على الفئة [DbPamContext] اسم سياق ثبات قاعدة البيانات [dbpam_ef5]. وهي مصطلحات شائعة في ORM (Object Relational Mapper). يُعد سياق الاستمرارية هذا صورة كائن لقاعدة البيانات. يُشار أيضًا إلى مزامنة سياق الاستمرارية مع قاعدة البيانات: حيث تنعكس التعديلات والإضافات والحذف التي تتم على سياق الاستمرارية على قاعدة البيانات. تتم هذه المزامنة في أوقات محددة: عند إغلاق سياق الاستمرارية، أو في نهاية معاملة، أو قبل استعلام SQL SELECT على قاعدة البيانات.

يتم وضع المعلومات المتعلقة بـ SGBD وقاعدة البيانات في [App.config].

يتم شرح التكوين المطلوب في [app.config] في الفقرات التالية من [refEF5]:

  • 3.4 لـ SGBD SQL Server. وهنا يتم وضع المبادئ العامة لتكوين EF5؛
  • 4.2 بالنسبة لـ SGBD MySQL.

نتبع الفقرة الأخيرة ونقوم بتكوين ملف [app.config] بالطريقة التالية:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
  </startup>
    <!-- التكوين EF5 -->
    <!-- سلسلة اتصال قاعدة البيانات [dbam_ef5] -->
    <connectionStrings>
        <add name="DbPamContext"
         connectionString="Server=localhost;Database=dbpam_ef5;Uid=root;Pwd=;"
         providerName="MySql.Data.MySqlClient" />
    </connectionStrings>
    <!-- مزود المصنع لـ MySQL -->
    <system.data>
        <DbProviderFactories>
            <remove invariant="MySql.Data.MySqlClient"/>
            <add name="MySQL Data Provider" invariant="MySql.Data.MySqlClient" description=".Net Framework Data Provider for MySQL"
          type="MySql.Data.MySqlClient.MySqlClientFactory, MySql.Data, Version=6.5.4.0, Culture=neutral, PublicKeyToken=C5687FC88969C44D"
        />
        </DbProviderFactories>
    </system.data>
</configuration>
  • تمت إضافة الأسطر 6-21. يجب إدراجها في العلامة <configuration> في السطرين 2 و22؛
  • الأسطر 8-12: تحدد سلاسل الاتصال بقواعد البيانات، وهو مفهوم ADO.NET (انظر الفقرة 7.3.5 في [refC#])؛
  • السطور 9-11: تحدد سلسلة الاتصال بقاعدة البيانات MySQL [dbpam_ef5
  • السطر 9: اسم سلسلة الاتصال. هنا، لا يمكن إدخال أي شيء. بشكل افتراضي، يجب إدخال اسم الفئة التي تنفذ سياق قاعدة البيانات:

  public class DbPamContext : DbContext
  {
    public DbSet<Employe> Employes { get; set; }
    public DbSet<Cotisations> Cotisations { get; set; }
    public DbSet<Indemnites> Indemnites { get; set; }
}

تسمى الفئة [DbPamContext]. في السطر 9 من [app.config]، يجب إذن إدخال [name="DbPamContext"]؛

  • السطر 10: سلسلة اتصال خاصة بـ SGBD MySQL:
    • [Server=localhost]: عنوان IP للجهاز الذي يستضيف SGBD. هنا هو الجهاز المحلي [localhost
    • [Database=dbpam_ef5;]: اسم قاعدة البيانات،
    • [Uid=root;]: اسم المستخدم الذي سيتم استخدامه للاتصال بقاعدة البيانات،
    • [Pwd=;]: كلمة مرور هذا اسم المستخدم. هنا لا توجد كلمة مرور؛
  • السطر 10: [providerName="MySql.Data.MySqlClient"] هو اسم الموصل ADO.NET الذي سيتم استخدامه. هذا الاسم هو اسم السمة [invariant] في السطر 17. يمكن إدخال أي شيء طالما تم الالتزام بالقاعدة السابقة ولم يتم تسجيل مزود بنفس الثابت من قبل؛
  • السطور 15-20: تحدد مصنع (factory) لموصلات (provider) ADO.NET. [DbProviderFactory] هو مفهوم غامض بعض الشيء بالنسبة لي. إذا صدقت اسمه، فسيكون فئة قادرة على إنشاء الموصل ADO.NET الذي يتيح الوصول إلى SGBD، وهنا MySQL5. عادةً ما يتم نسخ هذه الأسطر ولصقها. فهي ضرورية. يجب الانتباه إلى السمة [Version=6.5.4.0] في السطر 16. يجب أن يتطابق رقم الإصدار هذا مع رقم إصدار DLL [MySql.Data] الذي أضفته إلى مراجع المشروع:
  • السطر 16 مهم. نظرًا لأنه لا يمكن تثبيت مزودين يحملان نفس الاسم، فإننا نبدأ بحذف أي مزود مثبت قد يحمل اسم المزود الذي نريد تثبيته في السطر 17؛

هذا كل شيء. الأمر معقد وغامض عند القيام به لأول مرة، ثم يصبح بسيطًا بمرور الوقت لأننا نكرر دائمًا نفس الشيء.

9.22.6. اختبار الطبقة [EF5]

نحن جاهزون لاختبار طبقتنا [EF5]. نقوم بذلك باستخدام البرنامج [Program.cs] الموجود بالفعل:

سنعرض محتوى قاعدة البيانات. إذا نجحنا في ذلك، فسيكون هذا مؤشراً أولياً على أن التكوين صحيح. يتوفر مثال على الكود في الفقرة 3.5.3 من [refEF5]. سيكون كود [Program.cs] كما يلي:


using Pam.EF5.Entites;
using Pam.Models;
using System;

namespace Pam
{
  class Program
  {
    static void Main(string[] args)
    {
      try
      {
        using (var context = new DbPamContext())
        {
          // يتم عرض محتوى الجداول
          Console.WriteLine("Liste des employés ----------------------------------------");
          foreach (Employe employe in context.Employes)
          {
            Console.WriteLine(employe);
          }
          Console.WriteLine("Liste des indemnités --------------------------------------");
          foreach (Indemnites indemnite in context.Indemnites)
          {
            Console.WriteLine(indemnite);
          }
          Console.WriteLine("Liste des cotisations -------------------------------------");
          foreach (Cotisations cotisations in context.Cotisations)
          {
            Console.WriteLine(cotisations);
          }
        }
      }
      catch (Exception e)
      {
        Console.WriteLine(e);
        return;
      }
    }
  }
}
  • السطر 13: تتم أي عملية على BD من خلال سياق هذه القاعدة. قمنا بتنفيذ هذا السياق باستخدام الفئة [DbPamContext]. أطلقنا عليه أيضًا اسم سياق استمرارية القاعدة؛
  • السطران 13 و31: تتم العمليات على سياق الاستمرارية في جملة [using]. يتم فتح سياق الاستمرارية في بداية الجملة [using] ويتم إغلاقه تلقائيًا عند الخروج من هذه الجملة. وهذا يعني أن أي تعديل يتم إجراؤه على سياق الاستمرارية في الجملة [using] سينعكس على قاعدة البيانات عند الخروج من الجملة. ثم يتم إرسال سلسلة من الأوامر SQL إلى BD داخل معاملة. وهذا يعني أنه في حالة فشل أمر SQL، يتم إلغاء جميع أوامر SQL التي تم إصدارها مسبقًا. ثم يتم إطلاق استثناء بواسطة EF5؛
  • السطر 17: يشير التعبير [context.Employes] إلى الصورة الموضوعة في الجدول [employes]. تجدر الإشارة إلى أن [Employes] هي خاصية لسياق الاستمرارية [DbPamContext]:

  public class DbPamContext : DbContext
  {
    public DbSet<Employe> Employes { get; set; }
    public DbSet<Cotisations> Cotisations { get; set; }
    public DbSet<Indemnites> Indemnites { get; set; }
}
  • السطر 17: إن قيام [foreach] بتصفح المجموعة [context.Employes] سيؤدي إلى إعادة جميع الموظفين من قاعدة البيانات إلى سياق الاستمرارية. وبالتالي، سيتم إصدار أمر SQL SELECT بواسطة EF5؛
  • الأسطر 17-20: يتم استعراض مجموعة الموظفين وفي السطر 19، يتم استخدام الطريقة [ToString] من الفئة [Employe] لعرض الموظفين على وحدة التحكم؛
  • الأسطر 21-25: نفس الشيء بالنسبة لمجموعة التعويضات؛
  • الأسطر 27-30: نفس الشيء بالنسبة لمجموعة الاشتراكات.

لنعد إلى تعريف الكيان [Employe]:


using System;

namespace Pam.EF5.Entites
{

  public class Employe
  {
    public int Id { get; set; }
    public string SS { get; set; }
    public string Nom { get; set; }
    public string Prenom { get; set; }
    public string Adresse { get; set; }
    public string Ville { get; set; }
    public string CodePostal { get; set; }
    public Indemnites Indemnites { get; set; }
    public int Versioning { get; set; }

    // التوقيع
    public override string ToString()
    {
      return string.Format("Employé[{0},{1},{2},{3},{4},{5}, {6}, {7}]", Id, Versioning, SS, Nom, Prenom, Adresse, Ville, CodePostal);
    }
  }
}
  • السطر 15: الموظف لديه مرجع على تعويض.

عندما نعيد موظفًا إلى سياق الاستمرارية، هل نعيد تعويضه معه؟ الإجابة هي لا بشكل افتراضي. هذا هو مفهوم [Lazy Loading]. لا يتم نقل الكيانات المشار إليها داخل كيان آخر إلى سياق الاستمرارية مع هذا الكيان الآخر. ولا يتم نقلها إلا عندما يطلبها الكود داخل سياق استمرارية مفتوح. إذا كان سياق الاستمرارية مغلقًا، يتم عندئذٍ إطلاق استثناء.

وبالتالي، إذا كانت الطريقة [ToString] قد أشارت إلى الخاصية [Indemnites] كما يلي:


    // التوقيع
    public override string ToString()
    {
      return string.Format("Employé[{0},{1},{2},{3},{4},{5},{6},{7},{8}]", Id, Versioning, SS, Nom, Prenom, Adresse, Ville, CodePostal, Indemnites);
}

فإن العملية التالية في [Program.cs]:


          foreach (Employe employe in context.Employes)
          {
            Console.WriteLine(employe);
}

كانت ستعيد إلى سياق الاستمرارية ليس فقط الموظفين بل أيضًا تعويضاتهم، لأن في السطر 3، يتم استدعاء الطريقة [Employe.ToString] وهي تشير إلى الكيان [Indemnites].

يؤدي تنفيذ [Program.cs] إلى النتائج التالية:

1
2
3
4
5
6
7
8
Liste des employés -----------------------------------------
Employé[24,1,254104940426058,Jouveinal,Marie,5 rue des oiseaux,St Corentin,49203]
Employé[25,1,260124402111742,Laverti,Justine,La Brûlerie,St Marcel,49014]
Liste des indemnités -----------------------------------------
Indemnités[93,1,2,2,1,2,1,3,1,15]
Indemnités[94,1,1,1,93,2,3,12]
Liste des cotisations -----------------------------------------
Cotisations[11,1,3,49,6,15,9,39,7,88]

ماذا تفعل إذا لم ينجح ذلك؟ أنت في مأزق... هناك العديد من مصادر الخطأ المحتملة:

  • تحقق من تكوين EF5 (الفقرة 9.22.5
  • تحقق من كيانات Entity Framework (الفقرة 9.22.4).

9.22.7. DLL من الطبقة [EF5]

نقوم بتحويل مشروعنا إلى مكتبة فئات بحيث يتم إنشاء ملف تجميع .dll بدلاً من ملف .exe عند الإنشاء. يتم ذلك في خصائص المشروع كما رأينا في الفقرة 9.7.6، بالنسبة للطبقة الوظيفية المحاكاة.


المهمة: قم بتحويل نوع المشروع [pam-ef5] إلى مكتبة فئات ثم أعد إنشاء المشروع.


9.23. الخطوة 16: إعداد الطبقة [DAO]

9.23.1. واجهة الطبقة [DAO]

كما فعلنا مع الطبقة [métier] المحاكاة، ستكون الطبقة [DAO] متاحة عبر واجهة. ماذا ستكون هذه الواجهة؟

لنلقِ نظرة على واجهة [IPamMetier] للطبقة [métier] المحاكاة التي أنشأناها:


    public interface IPamMetier {
        // قائمة بجميع هويات الموظفين 
        Employe[] GetAllIdentitesEmployes();

        // ------- حساب الراتب 
        FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés);
}

السطر 3، تُستخدم الطريقة [GetAllIdentitesEmployes] لتغذية القائمة المنسدلة في الصفحة الرئيسية:

يجب البحث عن هؤلاء الموظفين في قاعدة البيانات.

السطر 6، تسمح الطريقة [GetSalaire] بحساب كشف راتب موظف لدينا رقمه SS. لنتذكر تعريف النوع [FeuilleSalaire]:


  public class FeuilleSalaire
  {

    // الخصائص التلقائية 
    public Employe Employe { get; set; }
    public Cotisations Cotisations { get; set; }
    public ElementsSalaire ElementsSalaire { get; set; }
}

ستأتي المعلومات الواردة في السطرين 5 و6 من قاعدة البيانات. تذكر أن الموظف لديه خاصية [Indemnites]. يجب استرجاع هذه المعلومات أيضًا.

لذلك يمكننا البدء بالواجهة التالية للطبقة [DAO]:


    public interface IPamDao {
        // قائمة بجميع هويات الموظفين 
        Employe[] GetAllIdentitesEmployes();
        // موظف معين مع بدلاته 
        Employe GetEmploye(string ss);
        // قائمة بجميع الاشتراكات 
        Cotisations GetCotisations();
}

9.23.2. مشروع Visual Studio


المهمة: أضف إلى الحل [pam-td] مشروعًا جديدًا من النوع [console] يسمى [pam-dao]. اجعله مشروع بدء الحل.


 

9.23.3. إضافة المراجع اللازمة للمشروع

لنضع المشروع في سياقه العام:

يحتاج المشروع [pam-dao] إلى عدد معين من DLL:

  • جميع تلك المشار إليها في المشروع [pam-ef5
  • والمشار إليها في المشروع [pam-ef5] نفسه.

بالإضافة إلى ذلك، سنستخدم [Spring.net] لإنشاء مثيل للطبقة [DAO]. ولذلك، نحتاج إلى DLL و [Spring.core] و [Common.Logging]. توجد هذه المكونات في المجلد [lib] الخاص بدعم دراسة الحالة.


المهمة: أضف هذه المراجع المختلفة إلى المشروع [pam-dao].


9.23.4. تنفيذ الطبقة [DAO]

أعلاه، الفئة [PamException] هي تلك التي تم تعريفها في الفقرة 9.7.4. نقوم ببساطة بتغيير مساحة اسمها (السطر 1 أدناه):


namespace Pam.Dao.Entites
{
  // فئة الاستثناءات
  public class PamException : Exception
  {
....
  }
}

الواجهة [IPamDao] هي التي حددناها للتو في الفقرة 9.23.1:


using Pam.EF5.Entites;

namespace Pam.Dao.Service
{
  public interface IPamDao
  {
    // قائمة بجميع هويات الموظفين 
    Employe[] GetAllIdentitesEmployes();
    // موظف معين مع مستحقاته 
    Employe GetEmploye(string ss);
    // قائمة بجميع الاشتراكات 
    Cotisations GetCotisations();
  }
}

تقوم الفئة [PamDaoEF5] بتنفيذ هذه الواجهة باستخدام ORM EF5. ورمزها هو كما يلي:


using Pam.Dao.Entites;
using Pam.EF5.Entites;
using Pam.Models;
using System;
using System.Linq;

namespace Pam.Dao.Service
{

  public class PamDaoEF5 : IPamDao
  {
    // الحقول الخاصة 
    private Cotisations cotisations;
    private Employe[] employes;

    // الشركة المصنعة
    public PamDaoEF5()
    {
      // الاشتراك
      try
      {
....
      }
      catch (Exception e)
      {
        throw new PamException("Erreur système lors de la construction de la couche [DAO]", e, 1);
      }
    }

    // GetCotisations
    public Cotisations GetCotisations()
    {
      return cotisations;
    }

    // GetAllIdentitesEmploye
    public Employe[] GetAllIdentitesEmployes()
    {
      return employes;
    }

    // GetEmploye
    public Employe GetEmploye(string SS)
    {
      try
      {
....
      catch (Exception e)
      {
        throw new PamException(string.Format("Erreur système lors de la recherche de l'employé [{0}]", SS), e, 2);
      }
    }
  }
}

ملاحظة:

  • السطر 10: الفئة [PamDaoEF5] تنفذ الواجهة [IPamDao
  • يتم تخزين الجداول [cotisations] و [employes] في ذاكرة التخزين المؤقتة في خصائص الأسطر 13-14. الموظفون بدون بدلاتهم؛
  • السطور 17-28: يقوم المنشئ بتهيئة السطور 13-14؛
  • السطور 43-52: تعرض الطريقة [GetEmploye] موظفًا مع بدلاته. تتلقى هذه الطريقة رقم الضمان الاجتماعي لهذا الموظف كمعلمة. إذا لم يكن الموظف موجودًا في قاعدة البيانات، فستُرجع الطريقة المؤشر إلى null.

المهمة: أكمل كود الفئة [PamDaoEF5].


بالنسبة للمنشئ، سنستلهم كود الاختبار للطبقة [EF5] المقدم في الفقرة 9.22.6. بالنسبة للطريقة [GetEmploye]، سنستلهم من المثال الوارد في الفقرة 3.5.7 [Eager and Lazy loading] من [refEF5].

9.23.5. تكوين الطبقة [DAO]

كما تم في الفقرة 9.22.5، يتعين علينا تكوين EF5 في ملف [App.config] الخاص بالمشروع:


المهمة 1: قم بتكوين EF5 في [App.config]. يكفي تكرار ما تم في الملف [App.config] للطبقة [EF5].


سيستخدم برنامج الاختبار الخاص بنا [Spring.net] للحصول على مرجع في الطبقة [DAO].


المهمة 2: بالاستعانة بما تم في الفقرة 9.20.2، قم بتعديل ملف التكوين [app.config] الخاص بالمشروع [pam-dao] بحيث يحدد كائن Spring يسمى [pamdao] مرتبطًا بالفئة [PamDaoEF5] التي أنشأناها للتو. تتمتع الملفات [app.config] و [web.config] بنفس البنية. يجب الانتباه إلى أن تكون العلامة <configSections> هي أول علامة تظهر بعد العلامة الجذرية <configuration>.


9.23.6. اختبار الطبقة [DAO]

نحن جاهزون لاختبار طبقتنا [DAO]. نقوم بذلك باستخدام البرنامج [Program.cs] الموجود بالفعل:

سنقوم باختبار الوظائف المختلفة لواجهة الطبقة [DAO]. سيكون كود [Program.cs] كما يلي:


using Pam.Dao.Service;
using Pam.EF5.Entites;
using Spring.Context.Support;
using System;

namespace Pam.Dao.Tests
{
  public class Program
  {
    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("------------------------------------");
            Employe e = pamDao.GetEmploye("254104940426058");
            Console.WriteLine("employé= {0}, indemnités={1}", e, e.Indemnites);
            Console.WriteLine("------------------------------------");
        // موظف غير موجود 
        Employe employe = pamDao.GetEmploye("xx");
        Console.WriteLine("Employé n° xx");
        Console.WriteLine((employe == null ? "null" : employe.ToString()));
        Console.WriteLine("------------------------------------");
        // قائمة الاشتراكات 
        Cotisations cotisations = pamDao.GetCotisations();
        Console.WriteLine(cotisations.ToString());
      }
      catch (Exception ex)
      {
        // عرض استثناء 
        Console.WriteLine(ex.ToString());
      }
      //استراحة 
      Console.ReadLine();
    }
  }
}
  • السطر 15: نحصل على مرجع للطبقة [DAO] بفضل [Spring.net].

نتائج تنفيذ هذا البرنامج هي كما يلي:

1
2
3
4
5
6
7
8
9
Employé[22,1,254104940426058,Jouveinal,Marie,5 rue des oiseaux,St Corentin,49203]
Employé[23,1,260124402111742,Laverti,Justine,La Brûlerie,St Marcel,49014]
------------------------------------
employé= Employé[22,1,254104940426058,Jouveinal,Marie,5 rue des oiseaux,St Corentin,49203], indemnités=Indemnités[91,1,2,2,1,2,1,3,1,15]
------------------------------------
Employé n° xx
null
------------------------------------
Cotisations[10,1,3,49,6,15,9,39,7,88]

9.23.7. DLL من الطبقة [DAO]


المهمة: قم بتحويل نوع المشروع [pam-dao] إلى مكتبة فئات ثم أعد إنشاء المشروع (كرر ما تم في الفقرة 9.22.7).


9.24. الخطوة 17: إنشاء الطبقة [métier]

9.24.1. واجهة الطبقة [métier]

ستكون واجهة الطبقة [métier] هي واجهة [IPamMetier] للطبقة [métier] المحاكاة التي أنشأناها في الفقرة 9.7.2.


    public interface IPamMetier {
        // قائمة بجميع هويات الموظفين 
        Employe[] GetAllIdentitesEmployes();

        // ------- حساب الراتب 
        FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés);
}

9.24.2. مشروع Visual Studio


المهمة: أضف إلى الحل [pam-td] مشروعًا جديدًا من النوع [console] يسمى [pam-metier]. اجعله مشروع بدء الحل.


 

9.24.3. إضافة المراجع اللازمة للمشروع

لنضع المشروع في سياقه العام:

يحتاج المشروع [pam-metier] إلى عدد معين من DLL:

  • جميع تلك المشار إليها في المشاريع [pam-dao] و [pam-ef5
  • تلك الخاصة بالمشروعين [pam-dao] و [pam-ef5] أنفسهما.

المهمة: أضف هذه المراجع المختلفة إلى المشروع [pam-metier].


 

9.24.4. تنفيذ الطبقة [métier]

فيما سبق، نجد أربعة عناصر مستخدمة بالفعل في الطبقة [métier] المحاكاة (انظر الفقرة 9.7). قد تكون هناك تغييرات في مساحات الأسماء المستوردة بواسطة هذه الفئات المختلفة. قم بإدارتها. تقوم الفئة [PamMetier] بتنفيذ واجهة [IPamMetier] بالطريقة التالية:


using Pam.Dao.Service;
using Pam.EF5.Entites;
using Pam.Metier.Entites;
using System;

namespace Pam.Metier.Service
{

  public class PamMetier : IPamMetier
  {

    // مرجع على الطبقة [DAO] التي تم تهيئتها بواسطة Spring
    public IPamDao PamDao { get; set; }

    // قائمة بجميع هويات الموظفين 
    public Employe[] GetAllIdentitesEmployes()
    {
      ...
    }

    // موظف معين مع مستحقاته 
    public Employe GetEmploye(string ss)
    {
      ...
    }

    // الاشتراكات 
    public Cotisations GetCotisations()
    {
      ...
    }

    // حساب الراتب 
    public FeuilleSalaire GetSalaire(string ss, double heuresTravaillées, int joursTravaillés)
    {
      // SS: رقم الموظف SS 
      // HeuresTravaillées: عدد ساعات العمل 
      // أيام العمل: عدد أيام العمل 
...
  }
}
  • السطر 13: لدينا مرجع إلى الطبقة [DAO]. سيتم تهيئتها بواسطة Spring عند إنشاء مثيل للفئة [PamMetier]. لذلك، عند تنفيذ الطرق المختلفة، يكون السطر 13 قد تم تهيئته بالفعل.

المهمة: أكمل كود الفئة [PamMetier]. إذا اكتشفنا في [GetSalaire] أن الموظف ذو الرقم ss غير موجود، فسنطلق [PamException]. يتم شرح طريقة حساب الراتب في الفقرة 9.5. يجب الانتباه إلى تقريب جميع الحسابات الوسيطة إلى رقمين بعد الفاصلة.


9.24.5. تكوين الطبقة [métier]

كما تم في الفقرة 9.22.5، يتعين علينا تكوين EF5 في ملف [app.config] الخاص بالمشروع:


المهمة 1: قم بتكوين EF5 في [app.config]. يكفي تكرار ما تم في ملف [app.config] للطبقة [EF5].


سيستخدم برنامج الاختبار الخاص بنا [Spring.net] للحصول على مرجع في الطبقة [métier].


المهمة 2: بالاستعانة بما قمت به سابقًا في الفقرة 9.23.5، قم بتعديل ملف التكوين [app.config] الخاص بالمشروع [pam-metier] بحيث يحدد كائن Spring يسمى [pammetier] مرتبطًا بالفئة [PamMetier] التي أنشأناها للتو. أسهل طريقة هي نسخ الملف [app.config] من المشروع [pam-dao] وإضافة ما ينقصه.


هناك صعوبة هنا. لا يتعين فقط إنشاء مثيل للطبقة [métier] باستخدام الفئة [PamMetier]، بل يتعين أيضًا تهيئة خاصيتها [PamDao]:


    // مرجع على الطبقة [DAO] التي تم تهيئتها بواسطة Spring
    public IPamDao PamDao { get; set; }

تكون تكوينات Spring في [app.config] كما يلي:


  <spring>
    <context>
      <resource uri="config://spring/objects" />
    </context>
    <objects xmlns="http://www.springframework.net">
      <object id="pamdao" type=" Pam.Dao.Service.PamDaoEF5, pam-dao"/>
      <object id="pammetier" type="Pam.Metier.Service.PamMetier, pam-metier">
        <property name="PamDao" ref="pamdao" />
      </object>
    </objects>
</spring>
  • السطر 6: يحدد الكائن [pamdao] المرتبط بالفئة [PamDaoEF5
  • السطر 7: يحدد الكائن [pammetier] المرتبط بالفئة [PamMetier
  • السطر 8: تُستخدم العلامة [property] لتهيئة خاصية عامة للفئة [PamMetier]. السمة [name="PamDao"] تتوافق مع اسم الخاصية المراد تهيئتها في الفئة [PamMetier]. يشير السمة [ref="pamdao"] إلى أن الخاصية يتم تهيئتها باستخدام مرجع، وهو مرجع الكائن [pamdao] في السطر 6، وبالتالي باستخدام مرجع الطبقة [DAO]. وهذا ما كنا نريده.

9.24.6. اختبار الطبقة [métier]

نحن جاهزون لاختبار طبقتنا [métier]. نقوم بذلك باستخدام البرنامج [Program.cs] الموجود بالفعل:

سنقوم باختبار الوظائف المختلفة لواجهة الطبقة [métier]. سيكون كود [Program.cs] كما يلي:


using System;
using Pam.Dao.Entites;
using Pam.Metier.Service;
using Spring.Context.Support;
using Pam.EF5.Entites;

namespace Pam.Metier.Tests
{
  public class Program
  {
    public static void Main()
    {
      try
      {
        // إنشاء مثيل الطبقة [métier]
        IPamMetier pamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
        // قائمة بهويات الموظفين
        Console.WriteLine("Employés -----------------------------");
        foreach (Employe Employe in pamMetier.GetAllIdentitesEmployes())
        {
          Console.WriteLine(Employe);
        }

        // حسابات كشوف المرتبات 
        Console.WriteLine("salaires -----------------------------");
        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}, Exception interne : {1}", ex.Message, ex.InnerException == null ? "" : ex.InnerException.Message));
      }
      // استراحة 
      Console.ReadLine();
    }
  }
}
  • السطر 16: نحصل على مرجع للطبقة [métier] بفضل [Spring.net].

نتائج تنفيذ هذا البرنامج هي كما يلي:

1
2
3
4
5
6
7
Employés -----------------------------
Employé[24,1,254104940426058,Jouveinal,Marie,5 rue des oiseaux,St Corentin,49203]
Employé[25,1,260124402111742,Laverti,Justine,La Brûlerie,St Marcel,49014]
salaires -----------------------------
[Employé[25,1,260124402111742,Laverti,Justine,La Brûlerie,St Marcel,49014],Cotisations[11,1,3,49,6,15,9,39,7,88],[64,85 : 17,45 : 10 : 15 : 72,4]]
[Employé[24,1,254104940426058,Jouveinal,Marie,5 rue des oiseaux,St Corentin,49203],Cotisations[11,1,3,49,6,15,9,39,7,88],[362,25 : 97,48 : 42 : 62 : 368,77]]
PamException : L'employé de n° [xx] n'existe pas

9.24.7. DLL من الطبقة [métier]


المهمة: قم بتحويل نوع المشروع [pam-metier] إلى مكتبة فئات ثم أعد إنشاء المشروع (كرر ما تم في الفقرة 9.22.7).


9.25. الخطوة 18: إنشاء الطبقة [web]

نصل إلى الطبقة الأخيرة من بنيتنا، وهي الطبقة [web]:

سنعيد استخدام الطبقة [web] التي قمنا بتطويرها بمساعدة طبقة [métier] محاكاة.

9.25.1. مشروع Visual Studio

نعود إلى Visual Studio Express 2012 للويب من أجل ربط طبقتنا على الويب بطبقات [métier, DAO, EF5] التي قمنا بتطويرها للتو. هناك بشكل أساسي بعض الإعدادات التي يجب القيام بها وبعض التغييرات في مساحات الأسماء.

باستخدام Visual Studio Express 2012 للويب، قم بتحميل الحل [pam-td]:

  • في [1]، والحل [pam-td] في VS Studio للويب. يصبح مشروع الويب [pam-web-01] مرئيًا مرة أخرى. كنا قد فقدناه في VS Studio للكمبيوتر المكتبي.
  • سيتعين تعديل تكوين مشروع الويب [pam-web-01]. بدلاً من تعديل مشروع يعمل، سنقوم بإجراء التعديلات على نسخة من هذا المشروع. أولاً في [2]، نقوم بحذف المشروع من الحل (هذا لا يحذف أي شيء في نظام الملفات).
  • في [3]، باستخدام مستكشف Windows، نقوم بنسخ المجلد [pam-web-01] إلى [pam-web-02
  • في [4]، نقوم بتحميل المشروع [pam-web-02] في الحل [pam-td]. ويظهر باسم [pam-web-01
  • في [5]، قم بتغيير هذا الاسم إلى [pam-web-02] واجعل هذا المشروع هو مشروع البدء؛
  • في [6]، قم بتحميل المشروع القديم [pam-web-01]. لديك الآن جميع مشاريعك. احرص على العمل مع [pam-web-02].

9.25.2. إضافة المراجع اللازمة للمشروع

لنضع المشروع في سياقه العام:

يحتاج المشروع [pam-web-02] إلى عدد معين من DLL:

  • جميع تلك المشار إليها في المشاريع [pam-metier] و [pam-dao] و [pam-ef5
  • تلك الموجودة في المشاريع [pam-metier] و [pam-dao] و [pam-ef5] نفسها.

المهمة: أضف هذه المراجع المختلفة إلى المشروع [pam-web-02]. يجب إزالة المرجع الموجود في المشروع [pam-metier-simule]. نقوم بتغيير الطبقة [métier]. توجد بعض المراجع DLL بالفعل في المراجع. قم بحذفها ثم قم بإضافاتك.


 

9.25.3. تنفيذ الطبقة [web]

قم بإنشاء المشروع [pam-web-02]. ستظهر أخطاء مثل التالية:

تستخدم الفئة [ApplicationModel] النوع [Employe]. مع الطبقة [métier] المحاكاة، تم تعريف هذا النوع في مساحة الأسماء [Pam.Metier.Entites]. وهو الآن في مساحة الأسماء [Pam.EF5.Entites]. قم بتصحيح هذه الأخطاء كما هو موضح أعلاه.

9.25.4. تكوين الطبقة [web]

كما تم في الفقرة 9.24.5، يتعين علينا تكوين EF5 في ملف [web.config] الخاص بالمشروع:


المهمة 1: استبدل كل المحتوى الحالي لـ [web.config] بمحتوى الملف [app.config] من مشروع [pam-metier].


يستخدم الملف [Global.asax] في تطبيق الويب الخاص بنا الملف [Spring.net] لاسترداد مرجع على الطبقة [métier]:


      try
      {
        // إنشاء مثيل الطبقة [métier]
        application.PamMetier = ContextRegistry.GetContext().GetObject("pammetier") as IPamMetier;
      }
      catch (Exception ex)
      {
        application.InitException = ex;
}

في السطر 4، نطلب مرجعًا للكائن Spring المسمى [pammetier]. هذا هو الاسم الذي أُطلق على الطبقة [métier] (تحقق من ذلك في ملفك [web.config]).

9.25.5. اختبار الطبقة [web]

نحن جاهزون لاختبار طبقتنا [web]. سنقوم أولاً بتغيير منفذ العمل الخاص بها. بشكل افتراضي، تتمتع [pam-web-02] بتكوين [pam-web-01] وبالتالي تعمل على نفس المنفذ. تُظهر التجربة أن هذا يمثل مشكلة: حيث يستمر IIS في استخدام أكواد مشروع [pam-web-01]. اتبع الخطوات التالية:

في [4]، قم بتغيير رقم المنفذ، على سبيل المثال عن طريق تغيير رقم الوحدات.

يتم تنفيذ المشروع [pam-web-02] بواسطة [Ctrl-F5]. ثم نحصل على الصفحة الرئيسية التالية:

في [1]، نحصل على الموظفين من قاعدة البيانات [dbpam_ef5]. نلاحظ أنه لم يعد لدينا الموظف [X X] الذي كان لدينا مع الطبقة [métier] المحاكاة. لنقم بمحاكاة:

في [2]، نحصل بالفعل على الراتب الحقيقي وليس راتبًا وهميًا. الآن دعونا نوقف SGBD MySQL5 ونجري محاكاة أخرى:

في [3]، حصلنا على صفحة أخطاء يمكن قراءتها حتى لو كانت بعض الرسائل باللغة الإنجليزية. الآن، دعونا نوقف MySQL مرة أخرى ونعيد تشغيل التطبيق في VS عبر [Ctrl-F5]:

 

نحصل على عرض [initFailed.cshtml] الذي تم إنشاؤه في الفقرة 9.20.4. يعرض هذا العرض رسائل الأخطاء من مكدس الاستثناءات. يُدعى القارئ إلى إجراء اختبارات أخرى.

9.26. الخطوة 19: إتاحة تطبيق ASP.NET على الإنترنت

عند تطوير تطبيق ASP.NET باستخدام Visual Studio، فإن الإعدادات الافتراضية المستخدمة تجعل التطبيق المطور متاحًا فقط على العنوان [localhost]. يتم رفض أي عنوان آخر من قبل الخادم المدمج في Visual Studio الذي يعرض عندئذٍ الخطأ [400 Bad Request].

يمكن رؤية ذلك على النحو التالي؛

  • في نافذة DOS، لاحظ العنوان IP لجهاز التطوير الخاص بك:

Microsoft Windows [version 6.3.9600]
(c) 2013 Microsoft Corporation. Tous droits réservés.

dos>ipconfig

Configuration IP de Windows



Carte Ethernet Connexion au réseau local :

   Suffixe DNS propre à la connexion. . . : ad.univ-angers.fr
   Adresse IPv6 de liaison locale. . . . .: fe80::698b:455a:925:6b13%4
   Adresse IPv4. . . . . . . . . . . . . .: 172.19.81.34
   Masque de sous-réseau. . . . . . . . . : 255.255.0.0
   Passerelle par défaut. . . . . . . . . : 172.19.0.254

Carte réseau sans fil Wi-Fi :

   Statut du média. . . . . . . . . . . . : Média déconnecté
   Suffixe DNS propre à la connexion. . . :

يتم الإشارة إلى العنوان IP هنا في السطر 14. إذا كان لديك اتصال واي فاي، فسيظهر عنوان الواي فاي للجهاز في السطر 20 وما يليه.

  • تحقق من خصائص المشروع [clic droit sur projet / propriétés / onglet web]:
 

سيتم تشغيل التطبيق على المنفذ [65010] للجهاز [localhost].

  • قم بتشغيل مشروعك عبر [Ctrl-F5]

Image

  • استبدل [localhost] بالعنوان IP الخاص بالجهاز:

Image

أرسل الخادم استجابة [400 Bad Request]. لا يقبل خادم IIS Express الذي يستخدمه Visual Studio سوى الاسم [localhost].

لجعل التطبيق المطور متاحًا، على URL من النوع [http://adresseIP/contexte/...]، يجب استخدام خادم آخر غير IIS Express، على سبيل المثال خادم IIS (ليس Express). للتحقق من وجود هذا الخادم (عادةً في إصدارات Windows Pro)، يجب الانتقال إلى لوحة التحكم [Panneau de configuration\Système et sécurité\Outils dadministration]:

Image

لا يتوفر هذا الخيار دائمًا. لذا، يجب الانتقال إلى [ Panneau de configuration \ Programmes] وتثبيت أدوات إدارة الويب.

بمجرد ظهور الخيار [Gestionnaire des services internet (IIS)]، قم بتنشيطه:

يتم تشغيل موقع الويب الافتراضي. وللقيام بذلك، يجب تشغيل الخدمة [Service de publication World Wide Web] مسبقًا:

بعد ذلك، اطلب URL [http://localhost] باستخدام متصفح. تحقق مسبقًا من عدم احتلال خادم ويب آخر للمنفذ 80. إذا كان الأمر كذلك، فقم بإيقاف تشغيله.

لقد رد علينا الخادم IIS. الآن استبدل [localhost] بعنوان IP الخاص بجهازك:

هذا يعمل. لنعد الآن إلى Visual Studio:

  • أولاً، يجب تشغيل Visual Studio في الوضع [administrateur]

بعد ذلك، يجب تغيير تكوين مشروع الويب الذي نريد نشره [clic droit sur projet / propriétés / onglet web]:

يجب اختيار الخادم المحلي IIS كخادم للنشر. يقوم Visual Studio بتحديد URL للتطبيق. يمكن تغييره. قم بتشغيل المشروع عن طريق [Ctrl-F5]:

استبدل الآن [localhost] بعنوان IP الخاص بجهازك:

إذا لم يكن خادم IIS متاحًا، فيمكن استخدام خادم ASP.NET مجاني مثل [Ultidev Web Server Pro] المتاح على URL [http://ultidev.com/Download/ ]. بمجرد التثبيت، هناك طريقتان لتشغيل تطبيق ويب باستخدام هذا الخادم:

الطريقة السريعة

استخدم مستعرض Windows وحدد مجلد تطبيق ASP.NET المراد نشره:

يتم عندئذٍ تشغيل خادم الويب وعرض تطبيق الويب في متصفح:

  • في [3]، يمكن إيقاف/تشغيل خادم الويب؛
  • في [4]، يمكن تغيير منفذ خدمة تطبيق الويب؛

قبل تشغيل الخادم، يجب تشغيل الخدمة [UWS HiPriv Services] أدناه:

بمجرد تشغيل الخادم، تظهر الواجهة على النحو التالي:

يؤدي النقر على الرابط [6] إلى عرض الصفحة الأولى للتطبيق:

يمكن بعد ذلك إدخال عنوان الجهاز IP بدلاً من [localhost]:

لذلك، هنا أيضًا، لا يُقبل سوى الاسم [localhost].

الطريقة الطويلة

قم بتشغيل تطبيق Ultidev Web Explorer

ثم اتبع الخطوات التالية:

  • في [8]، حدد مجلد تطبيق الويب المراد نشره؛
  • بسبب [10-11]، يجب طلب تطبيق الويب باستخدام URL [http://localhost:81/
  • قم بتشغيل خادم الويب باستخدام [14]؛
  • اطلب URL [19] ;
  • في [20]، حصلنا على الصفحة المطلوبة باستخدام العنوان المحلي IP للجهاز بدلاً من الاسم [localhost]. هذا ما كنا نبحث عنه؛

تم تثبيت خادم Ultidev كخدمة Windows يتم تشغيلها تلقائيًا. يمكنك منع التشغيل التلقائي لخادم Ultidev بالطريقة التالية:

  • اختر الخيار [Panneau de configuration\Système et sécurité\Outils dadministration
  • [1, 2]: حدد خصائص الخدمة [Ultidev Web Server Pro
  • [3]: اضبطه على التشغيل اليدوي.

لتشغيل الخادم يدويًا، استخدم على سبيل المثال التطبيق [Ultidev Web Explorer]:

9.27. الخطوة 20: إنشاء تطبيق أصلي لنظام Android

عندما يكون لديك تطبيق ويب من نوع APU (تطبيق ذو صفحة واحدة)، يمكن إنتاج ملف قابل للتنفيذ للأجهزة المحمولة (Android، IoS، Windows 8، ...) باستخدام أداة [Phonegap] [http://phonegap.com/]. هناك طرق أخرى للقيام بذلك، لا سيما باستخدام المنتج مفتوح المصدر Apache Cordova [https://cordova.apache.org/]. تقوم الأداة المتوفرة عبر الإنترنت على موقع Phonegap [http://build.phonegap.com/apps] بـ"تحميل" ملف zip الخاص بالموقع المراد تحويله. يجب أن تسمى الصفحة الرئيسية [index.html] ويجب أن تكون صفحة ثابتة، أي ألا يتم إنشاؤها بواسطة إطار عمل ويب (ASP.NET، JEE، PHP، ...). سنبدأ بإنشاء هذه الصفحة.

9.27.1. بنية التطبيق

يجب أن نتذكر هنا أننا نريد إنشاء تطبيق Android. غالبًا ما يكون لهذا النوع من التطبيقات البنية التالية:

  • في [1]، يستخدم المستخدم جهاز لوحي يعمل بنظام أندرويد يتصل بخدمة أو أكثر من خدمات الويب [2]؛

لنعد إلى النموذج APU:

  • يتم تحميل صفحة أولية في المتصفح (لا يوضح الرسم البياني أعلاه من أين تأتي)؛
  • يتم الحصول على العروض التالية عن طريق استدعاءات Ajax [1-4]. لن يتم تحميل أي صفحة جديدة بواسطة المتصفح؛

قد يتم توفير العرض الأولي أو لا يتم توفيره من نفس الخادم الذي يوفر العروض الأخرى التي يتم الحصول عليها عن طريق استدعاءات Ajax. إذا لم يتم توفيره من نفس الخادم، يجب أن يعرف جافا سكريبت الصفحة الأولية عنوان URL لخادم الويب الذي سيقدم العروض الأخرى. سيكون هذا هو الحال في تطبيق أندرويد الذي سنقوم ببنائه:

  • ستتم تغليف الصفحة الثابتة [index.html] في تطبيق Android أصلي [1] يتمتع بقدرات متصفح، وبالتالي قادر على تنفيذ جافا سكريبت المضمن في الصفحة [index.html
  • ستحصل هذه الصفحة على العروض الأخرى من خلال استدعاءات Ajax إلى الخادم [2]. ولهذا الغرض، تحتاج إلى معرفة عنوان URL لخادم الويب؛

سنقوم بإعادة هيكلة التطبيق [pam-web-02] ليعمل في هذا الوضع. وبالتالي ستكون الصفحة الأولى كما يلي:

  • في [1]، URL للصفحة الأولية للتطبيق. سيتم توفيرها لنا بواسطة خادم Ultidev الذي تمت دراسته في الفقرة 9.26؛
  • في [2]، سيتعين على المستخدم إدخال URL لمحاكي الرواتب. يمكن إدخالها بشكل ثابت في كود جافا سكريبت للصفحة الرئيسية، ولكن هذا من شأنه أن يعقد الاختبارات: بمجرد تغيير محاكي العنوان IP (أو المنفذ)، سيكون من الضروري تغييرها في كود جافا سكريبت؛
  • إلى [3]، والرابط [Connexion] الذي سيبحث عن العرض التالي:
  • يجب ملاحظة أنه في [4]، لم يتغير URL للمتصفح. فهو لا يزال هو نفسه الخاص بالصفحة الأولية وسيبقى كذلك طوال مدة حياة التطبيق.

بمجرد الحصول على هذه الصفحة، يعمل كل شيء كما كان من قبل: يتم الحصول على الصفحات المختلفة عن طريق استدعاءات Ajax. سنرى أنه لا يلزم تعديل سوى القليل جدًا من الكود.

9.27.2. إعادة هيكلة المشروع [pam-web-02]

داخل المجلد [Content] الخاص بالمشروع [pam-web-02]، نقوم بإنشاء المجلد [bootstrap] (الاسم لا يهم) التالي:

لقد قمنا بتضمين الصفحة الثابتة [index.html] وجميع الموارد التي تحتاجها (الملفات CSS و JS). تستمد الصفحة [index.html] كود الصفحة الرئيسية [_Layout.cshtml] من مشروع Visual Studio مع إزالة كل ما هو غير ثابت. وهذا يعطينا الكود التالي:


<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Simulateur de paie</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <link rel="stylesheet" href="Site.css" />
    <script type="text/javascript" src="jquery-1.8.2.min.js"></script>
    <script type="text/javascript" src="jquery.validate.min.js"></script>
    <script type="text/javascript" src="jquery.validate.unobtrusive.min.js"></script>
    <script type="text/javascript" src="globalize.js"></script>
    <script type="text/javascript" src="globalize.culture.fr-FR.js"></script>
    <script type="text/javascript" src="jquery.unobtrusive-ajax.min.js"></script>
    <script type="text/javascript" src="myScripts.js"></script>
</head>
<body>
    <table>
        <tbody>
            <tr>
                <td>
                    <h2>Simulateur de calcul de paie</h2>
                </td>
                <td style="width: 20px">
                    <img id="loading" style="display: none" src="indicator.gif" />
                </td>
                <td>
                    <a id="lnkConnexion" href="javascript:connexion()">
                        | Connexion<br />
                    </a>
                    <a id="lnkFaireSimulation" href="javascript:faireSimulation()">
                        | Faire la simulation<br />
                    </a>
                    <a id="lnkEffacerSimulation" href="javascript:effacerSimulation()">
                        | Effacer la simulation<br />
                    </a>
                    <a id="lnkVoirSimulations" href="javascript:voirSimulations()">
                        | Voir les simulations<br />
                    </a>
                    <a id="lnkRetourFormulaire" href="javascript:retourFormulaire()">
                        | Retour au formulaire de simulation<br />
                    </a>
                    <a id="lnkEnregistrerSimulation" href="javascript:enregistrerSimulation()">
                        | Enregistrer la simulation<br />
                    </a>
                    <a id="lnkTerminerSession" href="javascript:terminerSession()">
                        | Terminer la session<br />
                    </a>
                </td>
        </tbody>
    </table>
    <hr />
    <div id="content">
        <table>
            <tr>
                <td>URL du simulateur</td>
                <td><input type="text" id="urlServiceWeb" name="urlServiceWeb" size="80"></td>
            </tr>
        </table>
        <div id="erreur">
            <h3>Réponse du serveur :</h3>
            <div id="erreur1"></div>
            <div id="erreur2"></div>
        </div>
    </div>
</body>
</html>

لقد أضفنا النقاط التالية:

  • الأسطر 27-29: أضفنا خيار القائمة [Connexion] للسماح بالاتصال بخدمة المحاكاة؛
  • الأسطر 55-56: إدخال URL للمحاكي؛
  • الأسطر 59-63: منطقة خطأ في حالة فشل الاتصال؛

تتم إعادة هيكلة الكود فقط في الكود [myScripts.js] في السطر 14 أعلاه. لا يتغير أي شيء آخر. يتطور الكود على النحو التالي:


// عند تحميل المستند
$(document).ready(function () {
    // يتم استرداد مراجع المكونات المختلفة للصفحة
    loading = $("#loading");
    content = $("#content");
    erreur = $("#erreur");
    erreur1 = $("#erreur1");
    erreur2 = $("#erreur2");
    // روابط القائمة
    lnkConnexion = $("#lnkConnexion");
    lnkFaireSimulation = $("#lnkFaireSimulation");
    lnkEffacerSimulation = $("#lnkEffacerSimulation");
    lnkEnregistrerSimulation = $("#lnkEnregistrerSimulation");
    lnkVoirSimulations = $("#lnkVoirSimulations");
    lnkTerminerSession = $("#lnkTerminerSession");
    lnkRetourFormulaire = $("#lnkRetourFormulaire");
    // يتم وضعها في جدول
    options = [lnkConnexion, lnkFaireSimulation, lnkEffacerSimulation, lnkEnregistrerSimulation, lnkVoirSimulations, lnkTerminerSession, lnkRetourFormulaire];
    // إخفاء بعض عناصر الصفحة
    loading.hide();
    erreur.hide();
    // يتم تثبيت القائمة
    setMenu([lnkConnexion]);
});
  • الأسطر 6-8: معرفات المنطقة التي تعرض أخطاء الاتصال في الصفحة [index.html
  • السطر 10: الرابط الجديد للاتصال بالمحاكي؛
  • السطر 21: منطقة الأخطاء مخفية في البداية؛
  • السطر 23: يتم عرض رابط الاتصال فقط؛

في الصفحة [index.html]، يتم تعريف رابط الاتصال على النحو التالي:


<a id="lnkConnexion" href="javascript:connexion()">
| Connexion<br />
</a>

الوظيفة JS [connexion] (السطر 1) هي كما يلي:


var urlServiceWeb;
var erreur, erreur1, erreur2;


function connexion() {
    // نسترد urlServiceWeb من خدمة الويب
    urlServiceWeb = $("#urlServiceWeb").val();
    // استرداد نموذج الإدخال
    $.ajax({
        url: urlServiceWeb + '/Pam/Formulaire',
        type: 'POST',
        dataType: 'html',
        beforeSend: function () {
            // إشارة الانتظار مضاءة
            loading.show();
        },
        success: function (data) {
            // عرض النتائج
            content.html(data);
            // القائمة
            setMenu([lnkFaireSimulation]);
        },
        error: function (jqXHR) {
            erreur2.html(jqXHR.responseText);
            erreur1.html(jqXHR.getAllResponseHeaders().replace(/\r\n/g, "<br/>").replace(/\r/g, "<br/>").replace(/\n/g, "<br/>"));
            erreur.show();
        },
        complete: function () {
            // إشارة الانتظار مطفأة
            loading.hide();
        }
    });
}
  • السطر 7: يتم استرداد URL الذي أدخله المستخدم. يتم وضعه في المتغير العام للسطر 1. وبالتالي سيكون معروفًا في الوظائف الأخرى للملف؛
  • السطر 10: يتم إجراء استدعاء Ajax إلى URL [/Pam/Formulaire] للمحاكي. يعرض هذا URL عرضًا جزئيًا لإدخال معلومات المحاكاة (الموظفون، ساعات العمل، أيام العمل). في الإصدار الأولي من [pam-web-02]، كان هذا URL كافياً. كان يُسبق تلقائياً بـ URL الذي كان قد جلب الصفحة الأولية. الآن، نفترض أن الصفحة الأولية يمكن توفيرها بواسطة خادم آخر غير الخادم الذي يدعم المحاكي. لذلك، يجب إضافة البادئة URL [/Pam/Formulaire] إلى المتغير [urlServiceWeb] في السطر 1، وهو URL الخاص بالمحاكي (على سبيل المثال، http://172.19.81.34/pam-web-02). يجب القيام بذلك لجميع استدعاءات Ajax في الملف؛
  • الأسطر 17-22: في حالة نجاح الاتصال، يتم عرض العرض الجزئي [Formulaire.cshtml] ويتم عرض قائمة تحتوي على الرابط الوحيد [Faire la simulation] (السطر 21)؛
  • الأسطر 23-27: في حالة فشل الاتصال:
    • في السطر 24، يتم عرض الرد HTML المرسل من خادم الويب (إن وجد)؛
    • في السطر 25، يتم عرض الرؤوس HTTP المرسلة من خادم الويب (إذا كان قد رد)؛

هذا كل شيء. في حالة النجاح، نحصل على الصفحة التالية:

نكون عندئذ في الحالة السابقة حيث يتم الآن الحصول على العروض من خلال استدعاءات Ajax. وهكذا، كما هو موضح أعلاه، سيتم تنفيذ النقر على الرابط [Faire la simulation] بواسطة الكود التالي من الملف [myScripts.js]:


function faireSimulation() {
    // يتم استرداد المراجع
    var simulation = $("#simulation");
    var formulaire = $("#formulaire");
    // هل النموذج صالح؟
    var formValid = formulaire.validate().form();
    if (!formValid) return;
    // يتم إجراء استدعاء Ajax يدويًا
    $.ajax({
        url: urlServiceWeb + '/Pam/FaireSimulation',
        type: 'POST',
        data: formulaire.serialize(),
        dataType: 'html',
        ...
    });
    // القائمة
    setMenu([lnkEffacerSimulation, lnkEnregistrerSimulation, lnkTerminerSession, lnkVoirSimulations]);
}
  • تم إجراء تعديل واحد فقط، وهو في السطر 10 حيث أصبح الرمز السابق URL مسبوقًا الآن برمز المحاكي؛

9.27.3. اختبار المشروع المعاد هيكلته

في الفقرة 9.26، أوضحنا كيفية تثبيت التطبيق [pam-web-02] على خادم Ultidev. سنبدأ من هناك:

  • في [6]، نطلب عرض الصفحة [bootstrap/index.html]. نحصل على العرض التالي:

نكتب URL خاطئة:

  • في [10]، رؤوس HTTP من استجابة الخادم؛
  • إلى [11]، المستند HTML من استجابة الخادم؛

إذا تمت كتابة URL الصحيح:

نحصل على الرد التالي:

9.27.4. إنشاء ملف Android الثنائي

سنقوم بإنشاء ملف Android الثنائي من الموقع الثابت الذي أنشأناه للتو واختبرناه [1]:

نضيف في [2] ملف [config.xml] الذي سيُستخدم لتكوين المكون الإضافي [Phonegap] الذي سيقوم بإنشاء ملف Android الثنائي. وفيما يلي كوده:


<?xml version='1.0' encoding='utf-8'?>
<widget id="android.exemples.pam" version="0.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
    <name>Pam</name>
    <description>
        IstiA - Université d'Angers
    </description>
    <author email="serge.tahe@univ-angers.fr">
      Serge Tahé
    </author>
    <content src="index.html" />
    <access origin="*" />
    <allow-navigation href="*" />
    <allow-intent href="*" />
    <plugin name="cordova-plugin-whitelist" />
</widget>
  • الأسطر 7-9: أدخل بياناتك هنا؛
  • الأسطر 11-13: تسمح هذه الأسطر لـ Javascript المدمج في تطبيق الويب الذي سيتم تنفيذه داخل جهاز Android بطلب ملفات URL خارج هذا الجهاز؛

نقوم بضغط محتويات المجلد [Content/bootstrap]:

 

ثم ننتقل إلى موقع Phonegap [http://build.phonegap.com/apps]:

  • قبل [1]، قد تضطر إلى إنشاء حساب؛
  • في [1]، نبدأ؛
  • في [2]، نختار خطة مجانية تسمح بتطبيق Phonegap واحد فقط؛
  • في [3]، نقوم بتنزيل التطبيق المضغوط [4]؛
  • في [5]، اسم التطبيق؛
  • انقر على الرابط [6] لإنشاء ملفات OS IoS الثنائية، Android و Windows. قد يستغرق ذلك بضع ثوانٍ؛
  • في [7-9]، قم بتنزيل الملف الثنائي لنظام Android؛

قم بتشغيل محاكي [GenyMotion] لجهاز لوحي يعمل بنظام Android (انظر الفقرة 11.1):

 

فيما سبق، قمنا بتشغيل محاكي جهاز لوحي باستخدام API 21 من Android. بمجرد تشغيل المحاكي،

  • قم بإلغاء قفله عن طريق سحب القفل (إن وجد) إلى الجانب ثم تركه؛
  • باستخدام الماوس، اسحب الملف [Pam-debug.apk] الذي قمت بتنزيله وأسقطه على المحاكي. سيتم تثبيته وتشغيله بعد ذلك؛

ضع في [1]، URL الخاص بالمحاكي كما هو موضح في الفقرة 9.27.3. بعد ذلك، قم بتسجيل الدخول إلى المحاكي باستخدام الرابط [2]:

اختبر التطبيق على المحاكي. يجب أن يعمل.