Skip to content

1. الجزء 1

ملف PDF الخاص بالوثيقة متاح |HERE|.

الأمثلة الواردة في المستند متاحة |HERE|.

1.1. Introduction

أهداف المقالة:

  • كتابة تطبيق ويب ثلاثي الطبقات [interface utilisateur, métier, accès aux données]
  • تكوين التطبيق باستخدام Spring IOC
  • كتابة إصدارات مختلفة عن طريق تغيير تنفيذ إحدى الطبقات الثلاث.

الأدوات المستخدمة:

  • Visual Studio.net للتطوير - انظر الفقرة 3.1 في الملحق؛
  • خادم الويب Cassini للتنفيذ - انظر الفقرة 3.2 في الملحق؛
  • Nunit للاختبارات الفردية – انظر الفقرة 3.4 في الملحق؛
  • Spring للتكامل وتكوين طبقات تطبيق الويب - انظر الفقرة 3.3 في الملحق؛

على مقياس المبتدئ - المتوسط - المتقدم، يقع هذا المستند في الجزء [intermédiaire-avancé]. ويتطلب فهمه عدة متطلبات مسبقة. يمكن اكتساب بعضها من المستندات التي قمت بكتابتها. وفي هذه الحالة، أقوم بالإشارة إليها. ومن الواضح تمامًا أن هذا مجرد اقتراح وأن القارئ يمكنه استخدام مستنداته المفضلة.

يستند هذا المستند إلى الخطوط العريضة لمستند مكتوب بلغة Java [[البنى ثلاثية المستويات وبنى MVC باستخدام Struts وSpring و(2005)] . نقوم في VB.NET ببناء تطبيق الويب MVC ذي الطبقات الثلاث والمكتوب بلغة Java. الفكرة التي نريد توضيحها هنا هي أن منصتي التطوير Java و.NET متقاربتان بما يكفي بحيث يمكن إعادة استخدام المهارات المكتسبة في أحد هذين المجالين في المجال الآخر.

لا يبدو أن هناك حلاً معترفًا به على نطاق واسع لتطوير MVC وASP.NET. الحل التالي يعتمد على الطريقة التي تم تقديمها في الوثيقة [تطوير الويب باستخدام ASP.NET 1.1 (2004)]. ورغم أن هذه الطريقة تتميز باستخدام مفاهيم شائعة في تطوير J2EE، إلا أنه لا ينبغي النظر إليها إلا على أنها ما هي عليه، c.a.d، أي طريقة من بين طرق تطوير MVC الأخرى. وبمجرد أن تحظى إحدى طرق التطوير MVC في ASP.NET بقبول واسع النطاق، سيكون من الضروري عندئذٍ اعتماد هذه الطريقة. وقد تكون نسخة Spring .NET، التي لا تزال قيد التطوير، الحل الأول في هذا الصدد.

1.2. تطبيق webarticles

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

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

وستكون العروض المختلفة المعروضة للمستخدم كما يلي:

- العرض "LISTE" الذي يعرض قائمة بالمنتجات المعروضة للبيع
- العرض [INFOS] الذي يقدم معلومات إضافية عن أحد المنتجات:
  • طريقتا العرض [PANIER] و [PANIERVIDE] اللتان تعرضان محتويات سلة التسوق الخاصة بالعميل
  • عرض [ERREURS] الذي يُبلغ عن أي خطأ في التطبيق

Image

1.3. البنية العامة للتطبيق

نرغب في إنشاء تطبيق بهيكل ثلاثي الطبقات على النحو التالي:

  • تتمتع الطبقات الثلاث بالاستقلالية بفضل استخدام الواجهات
  • ويتم تكامل الطبقات المختلفة بواسطة Spring
  • يتم تخصيص مساحات أسماء منفصلة لكل طبقة: web (طبقة UIdomain (طبقة الأعمال) و dao (طبقة الوصول إلى البيانات).

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

تتم معالجة طلب العميل وفقًا للخطوات التالية:

  1. يقدم العميل طلبًا إلى وحدة التحكم. وستكون وحدة التحكم هذه في هذه الحالة صفحة .aspx سيُعطى لها دور خاص. فهي تستقبل جميع طلبات العملاء. وهي بمثابة بوابة الدخول إلى التطبيق. وهي تمثل الحرف C في MVC.
  1. يقوم وحدة التحكم بمعالجة هذا الطلب. وللقيام بذلك، قد يحتاج إلى مساعدة من طبقة الأعمال، وهو ما يُسمى بالنموذج M في بنية MVC.
  2. يتلقى وحدة التحكم ردًا من طبقة الأعمال. وقد تمت معالجة طلب العميل. وقد يؤدي ذلك إلى عدة ردود محتملة. ومن الأمثلة الشائعة على ذلك
    • صفحة أخطاء إذا تعذر معالجة الطلب بشكل صحيح
    • صفحة تأكيد في الحالات الأخرى
  3. يختار وحدة التحكم الرد (= العرض) الذي سيتم إرساله إلى العميل. وغالبًا ما يكون هذا الرد عبارة عن صفحة تحتوي على عناصر ديناميكية. وتقوم وحدة التحكم بتزويد العرض بهذه العناصر.
  4. يتم إرسال العرض إلى العميل. وهذا هو الحرف V في MVC.

1.4. النموذج

ندرس هنا الحرف M في MVC. يتكون النموذج هنا من العناصر التالية:

  1. فئات الأعمال
  2. فئات الوصول إلى البيانات
  3. قاعدة البيانات

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

لا تحتوي قاعدة البيانات سوى على جدول واحد يسمى ARTICLES. وقد تم إنشاء هذا الجدول باستخدام الأوامر التالية لـ SQL:

CREATE TABLE ARTICLES (
    ID            INTEGER NOT NULL,
    NOM           VARCHAR(20) NOT NULL,
    PRIX          NUMERIC(15,2) NOT NULL,
    STOCKACTUEL   INTEGER NOT NULL,
    STOCKMINIMUM  INTEGER NOT NULL
);
/* القيود */
ALTER TABLE ARTICLES ADD CONSTRAINT CHK_ID check (ID>0);
ALTER TABLE ARTICLES ADD CONSTRAINT CHK_PRIX check (PRIX>=0);
ALTER TABLE ARTICLES ADD CONSTRAINT CHK_STOCKACTUEL check (STOCKACTUEL>=0);
ALTER TABLE ARTICLES ADD CONSTRAINT CHK_STOCKMINIMUM check (STOCKMINIMUM>=0);
ALTER TABLE ARTICLES ADD CONSTRAINT CHK_NOM check (NOM<>'');
ALTER TABLE ARTICLES ADD CONSTRAINT UNQ_NOM UNIQUE (NOM);
/* المفتاح الأساسي */
ALTER TABLE ARTICLES ADD CONSTRAINT PK_ARTICLES PRIMARY KEY (ID);
id
المفتاح الأساسي الذي يحدد هوية المنتج بشكل فريد
nom
اسم المنتج
prix
سعره
stockactuel
المخزون الحالي
stockminimum
المخزون الذي يجب عنده تقديم طلب إعادة التزويد

1.4.2. مساحات أسماء النموذج

يتم توفير النموذج M هنا في شكل مساحتين للأسماء:

  • istia.st.articles.dao: يحتوي على فئات الوصول إلى بيانات الطبقة [dao]
  • istia.st.articles.domain: يحتوي على فئات الأعمال الخاصة بالطبقة [domain]

سيتم إنشاء كل من مساحات الأسماء هذه ضمن ملف «تجميع» خاص بها:

assembly
contenu
rôle
webarticles-dao
- [IArticlesDao]: واجهة الوصول إلى الطبقة [dao].
هذه هي الواجهة الوحيدة التي تراها الطبقة [domain]. ولا ترى أي واجهة أخرى.
- [Article]: فئة تُعرّف عنصرًا
- [ArticlesDaoArrayList]: فئة تنفيذ
الواجهة [IArticlesDao] باستخدام فئة [ArrayList]
طبقة الوصول إلى البيانات
- تقع بالكامل في الطبقة
[dao] في بنية ثلاثية المستويات
تطبيق الويب
webarticles-domain
- [IArticlesDomain]: واجهة الوصول إلى الطبقة [domain]. وهي الواجهة الوحيدة التي تراها طبقة الويب. ولا ترى أي واجهة أخرى.
- [AchatsArticles]: فئة تُنفِّذ [IArticlesDomain]
- [Achat]: فئة تمثل عملية شراء أحد العملاء
- [Panier]: فئة تمثل مجموع مشتريات العميل
تمثل نموذج عمليات الشراء على
الويب - موجود بالكامل في
طبقة [domain] في البنية
نموذج التطبيق الويب ثلاثي المستويات

1.4.3. الطبقة [dao]

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

  • [IArticlesDao]: واجهة الوصول إلى الطبقة [dao]

  • [Article]: فئة تُعرّف عنصرًا

  • [ArticlesDaoArrayList]: فئة تنفيذ واجهة [IArticlesDao] باستخدام فئة [ArrayList]

هيكل المشروع [Visual Studio] التابع للطبقة [dao] هو كما يلي:

Image

تعليقات:

  • المشروع [dao] هو من النوع [bibliothèque de classes]
  • تم وضع الفئات في شجرة مجلدات تبدأ بالمجلد الجذر [istia]. وهي جميعها موجودة في مساحة الأسماء [istia.st.articles.dao].

1.4.3.1. الفئة [Article]

الفئة التي تُعرّف المقالة هي التالية:

Imports System

Namespace istia.st.articles.dao

    Public Class Article

         ' الحقول الخاصة
        Private _id As Integer
        Private _nom As String
        Private _prix As Double
        Private _stockactuel As Integer
        Private _stockminimum As Integer

         ' معرف العنصر
        Public Property id() As Integer
            Get
                Return _id
            End Get
            Set(ByVal Value As Integer)
                If Value <= 0 Then
                    Throw New Exception("Le champ id [" + Value.ToString + "] est invalide")
                End If
                Me._id = Value
            End Set
        End Property

         ' اسم المنتج
        Public Property nom() As String
            Get
                Return _nom
            End Get
            Set(ByVal Value As String)
                If Value Is Nothing OrElse Value.Trim.Equals("") Then
                    Throw New Exception("Le champ nom [" + Value + "] est invalide")
                End If
                Me._nom = Value
            End Set
        End Property

         ' سعر المنتج
        Public Property prix() As Double
            Get
                Return _prix
            End Get
            Set(ByVal Value As Double)
                If Value < 0 Then
                    Throw New Exception("Le champ prix [" + Value.ToString + "] est invalide")
                End If
                Me._prix = Value
            End Set
        End Property

         ' المخزون الحالي للمنتج
        Public Property stockactuel() As Integer
            Get
                Return _stockactuel
            End Get
            Set(ByVal Value As Integer)
                If Value < 0 Then
                    Throw New Exception("Le champ stockActuel [" + Value.ToString + "] est invalide")
                End If
                Me._stockactuel = Value
            End Set
        End Property

         ' الحد الأدنى لمخزون المنتج
        Public Property stockminimum() As Integer
            Get
                Return _stockminimum
            End Get
            Set(ByVal Value As Integer)
                If Value < 0 Then
                    Throw New Exception("Le champ stockMinimum [" + Value.ToString + "] est invalide")
                End If
                Me._stockminimum = Value
            End Set
        End Property

         ' الشركة المصنعة الافتراضية
        Public Sub New()
        End Sub

         ' الشركة المصنعة مع الخصائص
        Public Sub New(ByVal id As Integer, ByVal nom As String, ByVal prix As Double, ByVal stockactuel As Integer, ByVal stockminimum As Integer)
            Me.id = id
            Me.nom = nom
            Me.prix = prix
            Me.stockactuel = stockactuel
            Me.stockminimum = stockminimum
        End Sub

         ' طريقة تحديد المنتج
        Public Overrides Function ToString() As String
            Return "[" + id.ToString + "," + nom + "," + prix.ToString + "," + stockactuel.ToString + "," + stockminimum.ToString + "]"
        End Function
    End Class
End Namespace

توفر هذه الفئة:

  1. منشئًا يسمح بتحديد المعلومات الخمس الخاصة بالمقالة: [id, nom, prix, stockactuel, stockminimum]
  2. خصائص عامة تتيح قراءة وكتابة المعلومات الخمس.
  3. التحقق من صحة البيانات التي يتم إدخالها في المقالة. في حالة وجود بيانات خاطئة، يتم إلقاء استثناء.
  4. طريقة toString تتيح الحصول على قيمة عنصر ما في شكل سلسلة أحرف. وغالبًا ما يكون ذلك مفيدًا لتصحيح أخطاء التطبيق.

1.4.3.2. الواجهة [IArticlesDao]

يتم تعريف الواجهة [IArticlesDao] على النحو التالي:

Imports System
Imports System.Collections

Namespace istia.st.articles.dao

    Public Interface IArticlesDao
         ' قائمة بجميع المنتجات
        Function getAllArticles() As IList
         ' إضافة منتج
        Function ajouteArticle(ByVal unArticle As Article) As Integer
         ' حذف عنصر
        Function supprimeArticle(ByVal idArticle As Integer) As Integer
         ' تعديل منتج
        Function modifieArticle(ByVal unArticle As Article) As Integer
         ' البحث عن منتج
        Function getArticleById(ByVal idArticle As Integer) As Article
         ' حذف جميع المنتجات
        Sub clearAllArticles()
         ' يغير مخزون أحد المنتجات
        Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer
    End Interface
End Namespace

دور الطرق المختلفة للواجهة هو كما يلي:

getAllArticles
تُرجع جميع المقالات الموجودة في مصدر البيانات
clearAllArticles
إفراغ مصدر البيانات
getArticleById
يُرجع الكائن [Article] المُعرَّف بمفتاحه الأساسي
ajouteArticle
يسمح بإضافة عنصر إلى مصدر البيانات
modifieArticle
يسمح بتعديل عنصر من مصدر البيانات
supprimerArticle
يسمح بحذف عنصر من مصدر البيانات
changerStockArticle
يسمح بتعديل مخزون عنصر من مصدر البيانات

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

سيتم اختيار تنفيذ محدد عن طريق ملف تكوين Spring. ولإثبات أن واجهة الوصول إلى البيانات هي وحدها التي تهم عند اختبار تطبيق الويب، وليس فئة تنفيذها، سنقوم أولاً بتنفيذ مصدر البيانات باستخدام كائن بسيط [ArrayList]. وفي وقت لاحق، سنقدم حلاً يستند إلى SGBD.

1.4.3.3. فئة التنفيذ [ArticlesDaoArrayList]

يتم تعريف فئة التنفيذ [ArticlesDaoArrayList] على النحو التالي:

Imports System
Imports System.Collections

Namespace istia.st.articles.dao

    Public Class ArticlesDaoArrayList
        Implements istia.st.articles.dao.IArticlesDao

        Private articles As New ArrayList
        Private Const nbArticles As Integer = 4

         ' الشركة المصنعة الافتراضية
        Public Sub New()
             ' يتم إنشاء بعض المقالات
            For i As Integer = 1 To nbArticles
                articles.Add(New Article(i, "article" + i.ToString, i * 10, i * 10, i * 10))
            Next
        End Sub

         ' قائمة بجميع المقالات
        Public Function getAllArticles() As IList Implements IArticlesDao.getAllArticles
             ' يتم إرجاع قائمة المقالات
            SyncLock Me
                Return articles
            End SyncLock
        End Function

         ' حذف جميع المقالات
        Public Sub clearAllArticles() Implements IArticlesDao.clearAllArticles
             ' إفراغ قائمة المقالات
            SyncLock Me
                articles.Clear()
            End SyncLock
        End Sub

         ' استرداد عنصر محدد بمفتاحه
        Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDao.getArticleById
             ' البحث عن العنصر في القائمة
            SyncLock Me
                Dim ipos As Integer = posArticle(articles, idArticle)
                If ipos <> -1 Then
                    Return CType(articles(ipos), Article)
                Else
                    Return Nothing
                End If
            End SyncLock
        End Function

         ' إضافة عنصر إلى قائمة العناصر
        Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
             ' إضافة العنصر إلى قائمة العناصر
            SyncLock Me
                 ' التحقق من عدم وجوده مسبقًا
                Dim ipos As Integer = posArticle(articles, unArticle.id)
                If ipos <> -1 Then
                    Throw New Exception("L'article d'id [" + unArticle.id.ToString + "] existe déjà")
                End If
                 ' يتم إضافة العنصر
                articles.Add(unArticle)
                 ' إرجاع النتيجة
                Return 1
            End SyncLock
        End Function

         ' تعديل عنصر
        Public Function modifieArticle(ByVal articleNouveau As Article) As Integer Implements IArticlesDao.modifieArticle
             ' يتم تعديل مقال
            SyncLock Me
                 ' نتحقق من وجوده
                Dim ipos As Integer = posArticle(articles, articleNouveau.id)
                 ' في حالة عدم وجوده
                If ipos = -1 Then Return 0
                 ' إنه موجود - نقوم بتعديله
                articles(ipos) = articleNouveau
                 ' إرجاع النتيجة
                Return 1
            End SyncLock
        End Function

         ' حذف مقال محدد بمفتاحه
        Public Function supprimeArticle(ByVal idArticle As Integer) As Integer Implements IArticlesDao.supprimeArticle
             ' حذف عنصر
            SyncLock Me
                 ' نتحقق من وجوده
                Dim ipos As Integer = posArticle(articles, idArticle)
                 ' في حالة عدم وجوده
                If ipos = -1 Then Return 0
                 ' إنه موجود - يتم حذفه
                articles.RemoveAt(ipos)
                 ' إرجاع النتيجة
                Return 1
            End SyncLock
        End Function

         ' تغيير مخزون منتج محدد بمفتاحه
        Public Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer Implements IArticlesDao.changerStockArticle
             ' تغيير مخزون أحد الأصناف
            SyncLock Me
                 ' التحقق من وجوده
                Dim ipos As Integer = posArticle(articles, idArticle)
                 ' في حالة عدم وجوده
                If ipos = -1 Then Return 0
                 ' يوجد - يتم تعديل مخزونه إن أمكن
                Dim unArticle As Article = CType(articles(ipos), Article)
                 ' لا يتم تغيير المخزون إلا إذا كان كافياً
                If unArticle.stockactuel + mouvement >= 0 Then
                    unArticle.stockactuel += mouvement
                    Return 1
                Else
                    Return 0
                End If
            End SyncLock
        End Function

         ' البحث عن عنصر محدد بمفتاحه
        Private Function posArticle(ByVal listArticles As ArrayList, ByVal idArticle As Integer) As Integer
             ' يعرض موضع المنتج [idArticle] في القائمة أو -1 إذا لم يتم العثور عليه
            Dim unArticle As Article
            For i As Integer = 0 To listArticles.Count - 1
                unArticle = CType(listArticles(i), Article)
                If unArticle.id = idArticle Then
                    Return i
                End If
            Next
             ' لم يتم العثور عليه
            Return -1
        End Function
    End Class

End Namespace

تعليقات:

  • يتم محاكاة مصدر البيانات بواسطة الحقل الخاص [articles] من النوع [ArrayList]
  • يقوم منشئ الفئة بإنشاء 4 عناصر افتراضيًا في مصدر البيانات.
  • تمت مزامنة جميع طرق الوصول إلى البيانات لتجنب مشاكل الوصول المتزامن إلى مصدر البيانات. في أي لحظة معينة، لا يمكن إلا لخيط واحد الوصول إلى طريقة معينة.
  • تسمح الطريقة [posArticle] بمعرفة الموضع [0..N] في مصدر البيانات [ArrayList] لعنصر محدد برقمه. إذا لم يكن العنصر موجودًا، فإن الطريقة تُرجع الموضع -1. تُستخدم هذه الطريقة بشكل متكرر من قبل الطرق الأخرى.
  • تسمح الطريقة [ajouteArticle] بإضافة مادة إلى قائمة المواد. وتُرجع عدد المواد التي تم إدراجها: 1. إذا كانت المادة موجودة بالفعل، يتم إصدار استثناء.
  • تسمح الطريقة [modifieArticle] بتعديل عنصر موجود. وتُرجع عدد العناصر التي تم تعديلها: 1 إذا كان العنصر موجودًا، و0 إذا لم يكن موجودًا.
  • تسمح الطريقة [supprimeArticle] بحذف عنصر موجود. وتُرجع عدد العناصر المحذوفة: 1 إذا كان العنصر موجودًا، و0 إذا لم يكن موجودًا.
  • تُرجع الطريقة [getAllArticles] قائمة بجميع المقالات
  • تسمح الطريقة [getArticleById] بالحصول على عنصر محدد برقمه. يتم الحصول على القيمة [nothing] إذا كان العنصر غير موجود.
  • لا ينطوي الكود على أي صعوبة حقيقية. نترك للقارئ حرية الاطلاع عليه وفهمه.

1.4.3.4. إنشاء تجميع طبقة [dao]

تم تكوين مشروع Visual Studio لإنشاء ملف التجميع [webarticles-dao.dll]. ويتم إنشاء هذا الملف في المجلد [bin] التابع للمشروع:

1.4.3.5. اختبارات Nunit للطبقة [dao]

في لغة Java، يتم اختبار الفئات باستخدام إطار العمل [Junit]. في .NET، يوفر إطار عمل Nunit نفس إمكانيات الاختبارات الوحدوية:

Image

هيكل مشروع Visual Studio الخاص بالاختبار هو كما يلي:

Image

تعليقات:

  • مشروع [tests] هو من النوع [bibliothèque de classes]
  • تتطلب الاختبارات [NUnit] مرجعًا إلى التجميع [nunit.framework.dll]
  • تسترد فئة الاختبار [NUnit] مثيلًا للكائن المراد اختباره عبر Spring. ولذلك نجد
    • في المجلد [bin]، ملفات فئات Spring
    • وفي [References]، إشارة إلى التجميع [Spring-Core.dll] من المجلد [bin]
    • وفي [bin]، ملف تكوين لـ Spring
  • تحتاج فئة الاختبار إلى التجميع [webarticles-dao.dll] من الطبقة [dao]. وقد تم وضع هذا التجميع في المجلد [bin] وإضافة مرجع له إلى مراجع المشروع.

تتطلب فئة الاختبار [NUnit] الوصول إلى الفئات الموجودة في مساحة الأسماء [NUnit.Framework]. ولذلك، توجد فيها تعليمات الاستيراد التالية:

Imports NUnit.Framework

يوجد مساحة الاسم [NUnit.Framework] في «التجميع» [nunit.framework.dll] الذي يجب إضافته إلى مراجع المشروع:

يجب أن يكون التجميع [nunit.framework.dll] موجودًا في القائمة المقترحة إذا تم تثبيت [Nunit]. يكفي النقر مرتين على التجميع لإضافته إلى المشروع:

Image

قد تكون فئة الاختبار [NUnit] الخاصة بالطبقة [dao] كما يلي:

Imports System
Imports System.Collections
Imports NUnit.Framework
Imports istia.st.articles.dao
Imports System.Threading
Imports Spring.Objects.Factory.Xml
Imports System.IO

Namespace istia.st.articles.tests

    <TestFixture()> _
    Public Class NunitTestArticlesArrayList
         ' الكائن المراد اختباره
        Private articlesDao As IArticlesDao

        <SetUp()> _
        Public Sub init()
             ' يتم استرداد مثيل من مُنشئ الكائنات Spring
            Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config.xml", FileMode.Open))
             ' يُطلب إنشاء مثيل للكائن articlesdao
            articlesDao = CType(factory.GetObject("articlesdao"), IArticlesDao)
        End Sub

        <Test()> _
        Public Sub testGetAllArticles()
             ' التحقق البصري
            listArticles()
        End Sub

        <Test()> _
        Public Sub testClearAllArticles()
             ' حذف جميع المقالات
            articlesDao.clearAllArticles()
             ' يتم طلب جميع المقالات
            Dim articles As IList = articlesDao.getAllArticles
             ' التحقق: يجب أن يكون العدد 0
            Assert.AreEqual(0, articles.Count)
        End Sub

        <Test()> _
        Public Sub testAjouteArticle()
             ' حذف جميع العناصر
            articlesDao.clearAllArticles()
             ' التحقق: يجب أن تكون جدول العناصر فارغة
            Dim articles As IList = articlesDao.getAllArticles
            Assert.AreEqual(0, articles.Count)
             ' إضافة عنصرين
            articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
            articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
             ' التحقق: يجب أن يكون هناك عنصران
            articles = articlesDao.getAllArticles
            Assert.AreEqual(2, articles.Count)
             ' التحقق البصري
            listArticles()
        End Sub

        <Test()> _
        Public Sub testSupprimeArticle()
             ' حذف جميع العناصر
            articlesDao.clearAllArticles()
             ' التحقق: يجب أن تكون جدول العناصر فارغة
            Dim articles As IList = articlesDao.getAllArticles
            Assert.AreEqual(0, articles.Count)
             ' يتم إضافة عنصرين
            articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
            articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
             ' التحقق: يجب أن يكون هناك 2 عنصر
            articles = articlesDao.getAllArticles
            Assert.AreEqual(2, articles.Count)
             ' حذف المادة 4
            articlesDao.supprimeArticle(4)
             ' التحقق: يجب أن يتبقى عنصر واحد
            articles = articlesDao.getAllArticles
            Assert.AreEqual(1, articles.Count)
             ' التحقق البصري
            listArticles()
        End Sub

        <Test()> _
        Public Sub testModifieArticle()
             ' حذف جميع البنود
            articlesDao.clearAllArticles()
             ' التحقق
            Dim articles As IList = articlesDao.getAllArticles
            Assert.AreEqual(0, articles.Count)
             ' إضافة 2 مقالات
            articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
            articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
             ' التحقق
            articles = articlesDao.getAllArticles
            Assert.AreEqual(2, articles.Count)
             ' البحث عن المادة 3
            Dim unArticle As Article = articlesDao.getArticleById(3)
             ' التحقق
            Assert.AreEqual(unArticle.nom, "article3")
             ' البحث عن المادة 4
            unArticle = articlesDao.getArticleById(4)
             ' التحقق
            Assert.AreEqual(unArticle.nom, "article4")
             ' تعديل المادة 4
            articlesDao.modifieArticle(New Article(4, "article4", 44, 44, 44))
             ' التحقق
            unArticle = articlesDao.getArticleById(4)
            Assert.AreEqual(unArticle.prix, 44, 0.000001)
             ' التحقق البصري
            listArticles()
        End Sub

        <Test()> _
        Public Sub testGetArticleById()
             ' حذف البنود
            articlesDao.clearAllArticles()
             ' التحقق
            Dim articles As IList = articlesDao.getAllArticles
            Assert.AreEqual(0, articles.Count)
             ' إضافة مادتين
            articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
            articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
             ' التحقق
            articles = articlesDao.getAllArticles
            Assert.AreEqual(2, articles.Count)
             ' البحث عن المادة 3
            Dim unArticle As Article = articlesDao.getArticleById(3)
             ' التحقق
            Assert.AreEqual(unArticle.nom, "article3")
             ' البحث عن المادة 4
            unArticle = articlesDao.getArticleById(4)
             ' التحقق
            Assert.AreEqual(unArticle.nom, "article4")
        End Sub

         ' عرض قائمة على الشاشة
        Private Sub listArticles()
            Dim articles As IList = articlesDao.getAllArticles
            For i As Integer = 0 To articles.Count - 1
                Console.WriteLine(CType(articles(i), Article).ToString)
            Next
        End Sub

        <Test()> _
        Public Sub testArticleAbsent()
             ' حذف جميع المواد
            articlesDao.clearAllArticles()
             ' البحث عن المادة 1
            Dim article As article = articlesDao.getArticleById(1)
             ' التحقق
            Assert.IsNull(article)
             ' تعديل عنصر غير موجود
            Dim i As Integer = articlesDao.modifieArticle(New article(1, "1", 1, 1, 1))
             ' لم يتم تعديل أي سطر
            Assert.AreEqual(i, 0)
             ' حذف بند غير موجود
            i = articlesDao.supprimeArticle(1)
             ' لم يتم حذف أي سطر
            Assert.AreEqual(0, i)
        End Sub

        <Test()> _
        Public Sub testChangerStockArticle()
             ' حذف جميع المقالات
            articlesDao.clearAllArticles()
             ' إضافة مقال
            Dim nbArticles As Integer = articlesDao.ajouteArticle(New Article(3, "article3", 30, 101, 3))
            Assert.AreEqual(nbArticles, 1)
             ' إضافة مقال
            nbArticles = articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
            Assert.AreEqual(nbArticles, 1)
             ' إنشاء 100 موضوع
            Dim taches(99) As Thread
            For i As Integer = 0 To taches.Length - 1
                 ' يتم إنشاء الموضوع i
                taches(i) = New Thread(New ThreadStart(AddressOf décrémente))
                 ' يتم تحديد اسم الخيط
                taches(i).Name = "tache_" & i
                 ' يتم تشغيل مؤشر الترابط i
                taches(i).Start()
            Next
             ' الانتظار حتى انتهاء جميع الخيوط
            For i As Integer = 0 To taches.Length - 1
                taches(i).Join()
            Next
             ' التحقق - يجب أن يكون مخزون المادة 3 هو 1
            Dim unArticle As Article = articlesDao.getArticleById(3)
            Assert.AreEqual(unArticle.nom, "article3")
            Assert.AreEqual(1, unArticle.stockactuel)
             ' يتم خصم الكمية من مخزون المادة 4
            Dim erreur As Boolean = False
            Dim nbLignes As Integer = articlesDao.changerStockArticle(4, -100)
             ' التحقق: لا ينبغي أن يكون مخزونه قد تغير
            Assert.AreEqual(0, nbLignes)
             ' التحقق البصري
            listArticles()
        End Sub

        Public Sub décrémente()
             ' تم تشغيل مؤشر الترابط
            System.Console.Out.WriteLine(Thread.CurrentThread.Name + " lancé")
             ' الخيط يقوم بتخفيض المخزون
            articlesDao.changerStockArticle(3, -1)
             ' انتهى الخيط
            System.Console.Out.WriteLine(Thread.CurrentThread.Name + " terminé")
        End Sub

    End Class
End Namespace

تعليقات:

  • أردنا كتابة برنامج لاختبار واجهة [IArticlesDao] بحيث يكون مستقلاً عن فئة التنفيذ الخاصة بها. ولذلك استخدمنا Spring لإخفاء اسم فئة التنفيذ عن برنامج الاختبار.
  • تسترد طريقة السمة <Setup()> من Spring مرجعًا للكائن [articlesdao] المراد اختباره. ويتم تعريف هذا الكائن في الملف [spring-config.xml] التالي:
<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">

<objects>
    <object id="articlesdao" type="istia.st.articles.dao.ArticlesDaoArrayList, webarticles-dao"/>
</objects>

يحدد هذا الملف اسم فئة التنفيذ [istia.st.articles.dao.ArticlesDaoArrayList] للواجهة [IArticlesDao] ومكان العثور عليها [ webarticles-dao.dll]. ونظرًا لأن إنشاء المثيل لا يتطلب معلمات، لم يتم تعريف أي معلمات هنا.

  • معظم الاختبارات سهلة الفهم. يُرجى من القارئ الاطلاع على التعليقات.
  • تتطلب الطريقة [testChangerStockArticle] بعض التوضيحات. فهي تنشئ 100 مؤشر ترابط مكلفة بخفض مخزون منتج معين.
        <Test()> _
        Public Sub testChangerStockArticle()
             ' حذف جميع الأصناف
            articlesDao.clearAllArticles()
             ' إضافة عنصر
            Dim nbArticles As Integer = articlesDao.ajouteArticle(New Article(3, "article3", 30, 101, 3))
            Assert.AreEqual(nbArticles, 1)
             ' إضافة عنصر
            nbArticles = articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
            Assert.AreEqual(nbArticles, 1)
             ' إنشاء 100 مؤشر ترابط
            Dim taches(99) As Thread
            For i As Integer = 0 To taches.Length - 1
                 ' يتم إنشاء الموضوع i
                taches(i) = New Thread(New ThreadStart(AddressOf décrémente))
                 ' تحديد اسم الخيط
                taches(i).Name = "tache_" & i
                 ' بدء تشغيل مؤشر الترابط i
                taches(i).Start()
            Next
             ' الانتظار حتى انتهاء جميع الخيوط
            For i As Integer = 0 To taches.Length - 1
                taches(i).Join()
            Next
             ' التحقق - يجب أن يكون مخزون المادة 3 هو 1
            Dim unArticle As Article = articlesDao.getArticleById(3)
            Assert.AreEqual(unArticle.nom, "article3")
            Assert.AreEqual(1, unArticle.stockactuel)
             ' يتم خصم الكمية من مخزون المادة 4
            Dim erreur As Boolean = False
            Dim nbLignes As Integer = articlesDao.changerStockArticle(4, -100)
             ' التحقق: لا ينبغي أن يكون مخزونه قد تغير
            Assert.AreEqual(0, nbLignes)
             ' التحقق البصري
            listArticles()
        End Sub

يهدف هذا الاختبار إلى اختبار عمليات الوصول المتزامنة إلى مصدر البيانات. الطريقة المسؤولة عن تحديث المخزون هي التالية:

        Public Sub décrémente()
             ' تم تشغيل مؤشر الترابط
            System.Console.Out.WriteLine(Thread.CurrentThread.Name + " lancé")
             ' يقوم الخيط بخفض المخزون
            articlesDao.changerStockArticle(3, -1)
             ' انتهى الخيط
            System.Console.Out.WriteLine(Thread.CurrentThread.Name + " terminé")
        End Sub

تقلل مخزون المادة رقم 3 بمقدار وحدة واحدة. وبالرجوع إلى رمز الطريقة [testChangerStockArticle]، نلاحظ أن:

  • يتم تعيين مخزون المادة رقم 3 مبدئيًا على 101
  • ستقوم الخيوط المائة بخفض هذا المخزون بمقدار وحدة واحدة لكل منها
  • وبالتالي يجب أن يصبح المخزون 1 عند انتهاء تنفيذ جميع الخيوط

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

لاختبار الطبقة [dao]، نقوم بإنشاء DLL و[tests-webarticles-dao.dll] في المجلد [bin] التابع للمشروع [tests]:

ثم، باستخدام التطبيق [Nunit-Gui]، نقوم بتحميل ملف DLL هذا ونجري الاختبارات:

Image

في النافذة اليسرى، نرى قائمة بالطرق التي تم اختبارها. يشير لون النقطة التي تسبق اسم كل طريقة إلى نجاح (أخضر) أو فشل (أحمر) الطريقة. سيتمكن القارئ الذي يعرض هذا المستند على الشاشة من رؤية أن جميع الاختبارات قد نجحت. وبالتالي، سنعتبر أن لدينا طبقة [dao] جاهزة للعمل.

1.4.4. الطبقة [domain]

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

  • [IArticlesDomain]: واجهة الوصول إلى الطبقة [domain]

  • [Achat]: فئة تحدد عملية شراء

  • [Panier]: فئة تُعرّف سلة التسوق

  • [AchatsArticles]: فئة تنفيذ واجهة [IArticlesDomain]

هيكل الحل [Visual Studio] للطبقة [domain] هو كما يلي:

Image

تعليقات:

  • المشروع [domain] هو من النوع [bibliothèque de classes]
  • تم وضع الفئات في شجرة مجلدات تبدأ بالمجلد الجذر [istia]. وهي جميعها موجودة في مساحة الأسماء [istia.st.articles.domain].
  • تم وضع DLL من الطبقة [dao] في المجلد [bin] التابع للمشروع الجديد. كما تمت إضافة DLL كمرجع للمشروع.

1.4.4.1. الواجهة [IArticlesDomain]

تقوم الواجهة [IArticlesDomain] بفصل الطبقة [métier] عن الطبقة [web]. وتقوم هذه الطبقة بالوصول إلى الطبقة [métier/domain] عبر هذه الواجهة دون الاهتمام بالفئة التي تقوم بتنفيذها فعليًّا. تحدد الواجهة الإجراءات التالية للوصول إلى طبقة الأعمال:

Imports Article = istia.st.articles.dao.Article

Namespace istia.st.articles.domain
    Public Interface IArticlesDomain
         ' الأساليب
        Sub acheter(ByVal panier As Panier)
        Function getAllArticles() As IList
        Function getArticleById(ByVal idArticle As Integer) As Article
        ReadOnly Property erreurs() As ArrayList
    End Interface
End Namespace
Function getAllArticles() As IList
يعرض قائمة الكائنات [Article] من مصدر البيانات المرتبط
Function getArticleById(ByVal idArticle As Integer) As Article
يعرض الكائن [Article] المحدد بواسطة [idArticle]
acheter(ByVal panier As Panier)
تقوم بالتحقق من صحة سلة التسوق الخاصة بالعميل عن طريق خصم الكمية المشتراة من مخزون الأصناف المشتراة - قد تفشل العملية في حالة عدم كفاية المخزون
ReadOnly Property erreurs() As ArrayList
يعرض قائمة بالأخطاء التي حدثت - فارغ في حالة عدم وجود أخطاء

1.4.4.2. الفئة [Achat]

تمثل الفئة [Achat] عملية شراء قام بها العميل:

Imports istia.st.articles.dao

Namespace istia.st.articles.domain

    Public Class Achat

         ' الحقول الخاصة
        Private _article As article
        Private _qte As Integer

         ' منشئ افتراضي
        Public Sub New()
        End Sub

         ' منشئ مع معلمات
        Public Sub New(ByVal unArticle As article, ByVal qte As Integer)
             ' نمر عبر الخصائص
            Me.article = unArticle
            Me.qte = qte
        End Sub

         ' المنتج الذي تم شراؤه
        Public Property article() As article
            Get
                Return _article
            End Get
            Set(ByVal Value As article)
                _article = Value
            End Set
        End Property

         ' الكمية المشتراة
        Public Property qte() As Integer
            Get
                Return _qte
            End Get
            Set(ByVal Value As Integer)
                If Value < 0 Then
                    Throw New Exception("Quantité [" + Value.ToString + "] invalide")
                End If
                _qte = Value
            End Set
        End Property

         ' إجمالي الشراء
        Public ReadOnly Property totalAchat() As Double
            Get
                Return _qte * _article.prix
            End Get
        End Property

         ' الهوية
        Public Overrides Function ToString() As String
            Return "[" + _article.ToString + "," + _qte.ToString + "]"
        End Function
    End Class

End Namespace

تعليقات:

  • تحتوي الفئة [Achat] على الخصائص والطرق التالية:
Public Property article() As article
المنتج الذي تم شراؤه
Public Property qte() As Integer
الكمية المشتراة
Public ReadOnly Property totalAchat() As Double
مبلغ الشراء
Public Overrides Function ToString() As String
سلسلة هوية الكائن
  • يحتوي على منشئ يسمح بتهيئة الخصائص [article, qte] التي تحدد عملية الشراء.

1.4.4.3. الفئة [Panier]

تمثل الفئة [Panier] مجموعة عمليات الشراء الخاصة بالعميل:

Namespace istia.st.articles.domain

Public Class Panier

     ' الحقول الخاصة
    Private _achats As New ArrayList
        Private _totalPanier As Double = 0

     ' الشركة المصنعة الافتراضية
    Public Sub New()
    End Sub

     ' قائمة المشتريات
    Public ReadOnly Property achats() As ArrayList
        Get
            Return _achats
        End Get
    End Property

     ' إجمالي المشتريات
    Public ReadOnly Property totalPanier() As Double
        Get
            Return _totalPanier
        End Get
    End Property

     ' الطرق
    Public Sub ajouter(ByVal unAchat As Achat)
         ' يتم البحث عما إذا كان المشتريات موجودًا بالفعل
        Dim iAchat As Integer = posAchat(unAchat.article.id)
        If iAchat <> -1 Then
             ' تم العثور عليه
                Dim achatCourant As Achat = CType(_achats(iAchat), Achat)
            achatCourant.qte += unAchat.qte
        Else
             ' لم يتم العثور عليه
            _achats.Add(unAchat)
        End If
         ' زيادة إجمالي سلة التسوق
        _totalPanier += unAchat.totalAchat
    End Sub

     ' إزالة منتج من سلة التسوق
    Public Sub enlever(ByVal idAchat As Integer)
         ' البحث عن المنتج
            Dim iachat As Integer = posAchat(idAchat)
         ' إذا تم العثور عليه، يتم حذفه
        If iachat <> -1 Then
                Dim achatCourant As Achat = CType(_achats(iachat), Achat)
             ' يتم حذف المنتج من سلة التسوق
            _achats.RemoveAt(iachat)
             ' يتم خصم المبلغ من إجمالي سلة التسوق
            _totalPanier -= achatCourant.totalAchat
        End If
    End Sub

    Private Function posAchat(ByVal idArticle As Integer) As Integer
         ' البحث عن عملية شراء في قائمة المشتريات
         ' إرجاع ترتيبه في القائمة أو -1 في حالة عدم العثور عليه
        Dim achatCourant As Achat
        Dim trouvé As Boolean = False
        Dim i As Integer = 0
            While Not trouvé AndAlso i < _achats.Count
                 ' العملية الحالية
                achatCourant = CType(_achats(i), Achat)
                 ' مقارنة مع المنتج المطلوب
                If achatCourant.article.id = idArticle Then
                    Return i
                End If
                 'عملية الشراء التالية
                i += 1
            End While
             ' لم يتم العثور عليه
            Return -1
    End Function

     ' وظيفة الهوية
    Public Overrides Function ToString() As String
        Return _achats.ToString
    End Function
End Class

End Namespace

تعليقات:

  • تحتوي الفئة [Panier] على الخصائص والطرق التالية:
ReadOnly Property achats() As ArrayList
قائمة مشتريات العميل - قائمة العناصر من النوع [Achat]
ajouter(ByVal unAchat As Achat)
تضيف عملية شراء إلى قائمة المشتريات
enlever(ByVal idAchat As Integer)
حذف عملية الشراء الخاصة بالمنتج idAchat
ReadOnly Property totalPanier() As Double
المبلغ الإجمالي لمشتريات سلة التسوق
Function ToString() As String
إرجاع سلسلة معرف سلة التسوق
  • الطريقة [posAchat] هي طريقة مساعدة تسمح بالحصول على الموضع في قائمة المشتريات لمشتريات محددة برقم المنتج الذي تم شراؤه. تُدار قائمة المشتريات بحيث لا يشغل المنتج الذي تم شراؤه عدة مرات سوى موضع واحد في القائمة. وبالتالي، يمكن تحديد عملية الشراء برقم المنتج الذي تم شراؤه. تُرجع الطريقة [posAchat] القيمة -1 إذا كانت عملية الشراء المطلوبة غير موجودة.
  • تقوم الطريقة [ajouter] بإضافة عملية شراء جديدة إلى قائمة المشتريات. وهذا يعني إما إضافة إدخال جديد إلى قائمة المشتريات إذا لم يكن المنتج المشترى موجودًا بالفعل في القائمة، أو زيادة الكمية المشتراة إذا كان موجودًا بالفعل.
  • تسمح الطريقة [enlever] بإزالة عملية شراء محددة برقم من قائمة المشتريات. إذا لم تكن عملية الشراء موجودة، فإن الطريقة لا تصدر أي رسالة ولا تقوم بأي إجراء.
  • يتم الحفاظ على مبلغ المشتريات [totalPanier] مع عمليات إضافة وحذف المشتريات.

1.4.4.4. الفئة [AchatsArticles]

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

Imports istia.st.articles.dao

Namespace istia.st.articles.domain
    Public Class AchatsArticles
        Implements IArticlesDomain

         'الحقول الخاصة
        Private _articlesDao As IArticlesDao
        Private _erreurs As ArrayList

         ' المنشئ
        Public Sub New(ByVal articlesDao As IArticlesDao)
            _articlesDao = articlesDao
        End Sub

         ' قائمة الأخطاء
        Public ReadOnly Property erreurs() As ArrayList Implements IArticlesDomain.erreurs
            Get
                Return _erreurs
            End Get
        End Property

         ' قائمة المقالات
        Public Function getAllArticles() As IList Implements IArticlesDomain.getAllArticles
             ' قائمة بجميع المقالات
            Try
                Return _articlesDao.getAllArticles
            Catch ex As Exception
                _erreurs = New ArrayList
                _erreurs.Add("Erreur d'accès aux données : " + ex.Message)
            End Try
        End Function

         ' الحصول على مادة محددة برقمها
        Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDomain.getArticleById
             ' منتج معين
            Try
                Return _articlesDao.getArticleById(idArticle)
            Catch ex As Exception
                _erreurs = New ArrayList
                _erreurs.Add("Erreur d'accès aux données : " + ex.Message)
            End Try
        End Function


         ' شراء سلة التسوق
        Public Sub acheter(ByVal panier As Panier) Implements IArticlesDomain.acheter
             ' شراء سلة التسوق - يجب خصم كميات المنتجات المشتراة من المخزون
            _erreurs = New ArrayList
            Dim achat As achat
            Dim achats As ArrayList = panier.achats
            For i As Integer = achats.Count - 1 To 0 Step -1
                 ' تخفيض مخزون المنتج i
                achat = CType(achats(i), achat)
                Try
                    If _articlesDao.changerStockArticle(achat.article.id, -achat.qte) = 0 Then
                         ' تعذر تنفيذ العملية
                        _erreurs.Add("L'achat " + achat.ToString + " n'a pu se faire - Vérifiez les stocks")
                    Else
                         ' تم تنفيذ العملية - يتم إزالة عملية الشراء من سلة التسوق
                        panier.enlever(achat.article.id)
                    End If
                Catch ex As Exception
                    _erreurs = New ArrayList
                    _erreurs.Add("Erreur d'accès aux données : " + ex.Message)
                End Try
            Next
        End Sub
    End Class

End Namespace

تعليقات:

  • تنفذ هذه الفئة الطرق الأربع للواجهة [IArticlesDomain]. وتحتوي على حقلين خاصين:
_articlesDao As IArticlesDao
كائن الوصول إلى البيانات
_erreurs As ArrayList
قائمة الأخطاء المحتملة. يمكن الوصول إليها عبر الخاصية العامة [erreurs]
  • ولإنشاء مثيل للفئة، يجب توفير الكائن الذي يتيح الوصول إلى البيانات:
Sub New(ByVal articlesDao As IArticlesDao)
  • تعتمد الطريقتان [getAllArticles] و [getArticleById] على الطرق التي تحمل نفس الاسم في الطبقة [dao]
  • تقوم الطريقة [acheter] بالتحقق من صحة عملية شراء سلة التسوق. ويتمثل هذا التحقق ببساطة في خصم كميات المنتجات المشتراة من المخزون. لا يمكن شراء أي منتج إلا إذا سمح مخزونه بذلك. إذا لم يكن الأمر كذلك، يتم رفض الشراء: يبقى المنتج في سلة التسوق ويتم الإبلاغ عن خطأ في القائمة [erreurs]. يتم سحب الشراء الذي تمت الموافقة عليه من سلة التسوق ويتم خصم الكمية المشتراة من مخزون المنتج المعني.

1.4.4.5. إنشاء تجميع الطبقة [domain]

تم تكوين مشروع Visual Studio لإنشاء التجميع [webarticles-domain.dll]. ويتم إنشاء هذا التجميع في المجلد [bin] الخاص بالمشروع:

1.4.4.6. اختبارات NUnit للطبقة [domain]

هيكل مشروع Visual Studio للاختبار هو كما يلي:

Image

تعليقات:

  • المشروع [tests] هو من النوع [bibliothèque de classes]
  • تتطلب الاختبارات [NUnit] مرجعًا إلى التجميع [nunit.framework.dll]
  • تسترد فئة الاختبار [NUnit] مثيلًا للكائن المراد اختباره عبر Spring. ولذلك نجد
  • في المجلد [bin]، ملفات فئات Spring
  • وفي [References]، إشارة إلى التجميع [Spring-Core.dll] من المجلد [bin]
  • وفي [bin]، ملف تكوين لـ Spring
  • تحتاج فئة الاختبار إلى التجميع [webarticles-dao.dll] من الطبقة [dao] وإلى التجميع [webarticles-domain.dll] من الطبقة [domain]. وقد تم وضع هذين التجميعين في المجلد [bin] وإضافة إشاراتهما إلى إشارات المشروع.

قد تبدو فئة الاختبار NUnit التابعة للطبقة [domain] كما يلي:

Imports NUnit.Framework
Imports istia.st.articles.dao
Imports istia.st.articles.domain
Imports Spring.Objects.Factory.Xml
Imports System.IO

Namespace istia.st.articles.tests

    <TestFixture()> _
    Public Class NunitTestArticlesDomain

         ' الكائن المراد اختباره
        Private articlesDomain As IArticlesDomain
        Private articlesDao As IArticlesDao

        <SetUp()> _
        Public Sub init()
             ' استرداد مثيل من مُنشئ الكائنات Spring
            Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config.xml", FileMode.Open))
             ' يُطلب إنشاء مثيل للكائن articles dao
            articlesDao = CType(factory.GetObject("articlesdao"), IArticlesDao)
             ' ثم مثيل الكائن articlesdomain
            articlesDomain = CType(factory.GetObject("articlesdomain"), IArticlesDomain)
        End Sub

        <Test()> _
        Public Sub getAllArticles()
             ' التحقق البصري
            listArticles()
        End Sub

        <Test()> _
        Public Sub getArticleById()
             ' حذف العناصر
            articlesDao.clearAllArticles()
             ' التحقق
            Dim articles As IList = articlesDomain.getAllArticles
            Assert.AreEqual(0, articles.Count)
             ' إضافة مادتين
            articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
            articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
             ' التحقق
            articles = articlesDomain.getAllArticles
            Assert.AreEqual(2, articles.Count)
             ' البحث عن المادة 3
            Dim unArticle As Article = articlesDomain.getArticleById(3)
             ' التحقق
            Assert.AreEqual(unArticle.nom, "article3")
             ' البحث عن المادة 4
            unArticle = articlesDao.getArticleById(4)
             ' التحقق
            Assert.AreEqual(unArticle.nom, "article4")
        End Sub

        <Test()> _
        Public Sub acheterPanier()
             ' حذف البنود
            articlesDao.clearAllArticles()
             ' التحقق
            Dim articles As IList = articlesDomain.getAllArticles
            Assert.AreEqual(0, articles.Count)
             ' إضافة مادتين
            articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
            articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
             ' التحقق
            articles = articlesDomain.getAllArticles
            Assert.AreEqual(2, articles.Count)
             ' إنشاء سلة تسوق تحتوي على منتجين
            Dim panier As New panier
            panier.ajouter(New Achat(New Article(3, "article3", 30, 30, 3), 10))
            panier.ajouter(New Achat(New Article(4, "article4", 40, 40, 4), 10))
             ' عمليات التحقق
            Assert.AreEqual(700, panier.totalPanier, 0.000001)
            Assert.AreEqual(2, panier.achats.Count)
             ' التحقق من سلة التسوق
            articlesDomain.acheter(panier)
             ' التحقق
            Assert.AreEqual(0, articlesDomain.erreurs.Count)
            Assert.AreEqual(0, panier.achats.Count)
             ' البحث عن المنتج 3
            Dim unArticle As Article = articlesDomain.getArticleById(3)
             ' التحقق
            Assert.AreEqual(unArticle.stockactuel, 20)
             ' البحث عن المنتج 4
            unArticle = articlesDao.getArticleById(4)
             ' التحقق
            Assert.AreEqual(unArticle.stockactuel, 30)
             ' سلة تسوق جديدة
            panier.ajouter(New Achat(New Article(3, "article3", 30, 30, 3), 100))
             ' تأكيد سلة التسوق
            articlesDomain.acheter(panier)
             ' عمليات التحقق
            Assert.AreEqual(1, articlesDomain.erreurs.Count)
             ' البحث عن المنتج 3
            unArticle = articlesDomain.getArticleById(3)
             ' التحقق
            Assert.AreEqual(unArticle.stockactuel, 20)
        End Sub

        <Test()> _
        Public Sub testRetirerAchats()
             ' حذف محتوى ARTICLES
            articlesDao.clearAllArticles()
             ' قراءة الجدول ARTICLES
            Dim articles As IList = articlesDao.getAllArticles()
            Assert.AreEqual(0, articles.Count)
             ' إدراج
            Dim article3 As New Article(3, "article3", 30, 30, 3)
            articlesDao.ajouteArticle(article3)
            Dim article4 As New Article(4, "article4", 40, 40, 4)
            articlesDao.ajouteArticle(article4)
             ' قراءة الجدول ARTICLES
            articles = articlesDomain.getAllArticles()
            Assert.AreEqual(2, articles.Count)
             ' إنشاء سلة تسوق تحتوي على منتجين
            Dim monPanier As New Panier
            monPanier.ajouter(New Achat(article3, 10))
            monPanier.ajouter(New Achat(article4, 10))
             ' عمليات التحقق
            Assert.AreEqual(700.0, monPanier.totalPanier, 0.000001)
            Assert.AreEqual(2, monPanier.achats.Count)
             ' إضافة منتج تم شراؤه مسبقًا
            monPanier.ajouter(New Achat(article3, 10))
             ' عمليات التحقق
             ' يجب تعديل المجموع إلى 1000
            Assert.AreEqual(1000.0, monPanier.totalPanier, 0.000001)
             ' لا يزال هناك منتجان في سلة التسوق
            Assert.AreEqual(2, monPanier.achats.Count)
             ' يجب أن تكون كمية المنتج 3 قد تغيرت إلى 20
            Dim unAchat As Achat = CType(monPanier.achats(0), Achat)
            Assert.AreEqual(20, unAchat.qte)
             ' إزالة المنتج رقم 3 من سلة التسوق
            monPanier.enlever(3)
             ' عمليات التحقق
             ' يجب أن يرتفع المجموع إلى 400
            Assert.AreEqual(400.0, monPanier.totalPanier, 0.000001)
             ' يجب أن يكون هناك منتج واحد فقط في سلة التسوق
            Assert.AreEqual(1, monPanier.achats.Count)
             ' يجب أن يكون هذا المنتج رقم 4
            Assert.AreEqual(4, CType(monPanier.achats(0), Achat).article.id)
        End Sub

         ' قائمة الشاشة
        Private Sub listArticles()
            Dim articles As IList = articlesDomain.getAllArticles
            For i As Integer = 0 To articles.Count - 1
                Console.WriteLine(CType(articles(i), Article).ToString)
            Next
        End Sub

    End Class


End Namespace

تعليقات:

  • أردنا كتابة برنامج لاختبار واجهة [IArticlesDomain] بحيث يكون مستقلاً عن فئة التنفيذ الخاصة بها. ولذلك استخدمنا Spring لإخفاء اسم فئة التنفيذ عن برنامج الاختبار.
  • تسترد طريقة السمة <Setup()> من Spring مرجعًا إلى الكائنات [articlesdomain] و [articlesdao] المراد اختبارها. وقد تم تعريف هذين الكائنين في الملف [spring-config.xml] التالي:
<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">
<objects>
    <object id="articlesdao" type="istia.st.articles.dao.ArticlesDaoArrayList, webarticles-dao" />
    <object id="articlesdomain" type="istia.st.articles.domain.AchatsArticles, webarticles-domain">
        <constructor-arg index="0">
            <ref object="articlesdao" />
        </constructor-arg>
    </object>
</objects>

يُشير هذا الملف إلى:

  • (تابع)
    • بالنسبة للسينجلتون [articlesdao]، اسم فئة التنفيذ [istia.st.articles.dao.ArticlesDaoArrayList] ومكان العثور عليها [ webarticles-dao.dll]. وبما أن إنشاء المثيل لا يتطلب معلمات، فلم يتم تعريف أي منها هنا.
    • بالنسبة للسينجلتون [articlesdomain]، اسم فئة التنفيذ هو [istia.st.articles.domain.AchatsArticles]، وموقعها هو [ webarticles-domain.dll]. تحتوي الفئة [AchatsArticles] على منشئ بمعلمة واحدة: الفئة الفردية التي تدير الوصول إلى الطبقة [dao]. وهنا، تم تعريف هذه الطبقة على أنها الفئة الفردية [articlesdao] التي تم تعريفها سابقًا.
  • تحصل فئة الاختبار على مثيل للفئة المراد اختبارها [articlesdomain] بالإضافة إلى مثيل لفئة الوصول إلى البيانات [articlesdao]. هذه النقطة الأخيرة مثيرة للجدل. من الناحية النظرية، لا ينبغي أن تحتاج فئة الاختبار إلى الوصول إلى الطبقة [dao] التي لا يُفترض بها حتى أن تعرفها. هنا، تجاوزنا هذه «القاعدة» التي كانت ستجبرنا، لو تم الالتزام بها، على إنشاء طرق جديدة في واجهتنا [IArticlesDomain].

لاختبار الطبقة [domain]، نقوم بإنشاء DLL و[tests-webarticles-domain.dll] في المجلد [bin] التابع للمشروع [tests]:

ثم، باستخدام التطبيق [Nunit-Gui]، نقوم بتحميل ملف DLL هذا ونجري الاختبارات:

Image

سيتمكن القارئ الذي يعرض هذا المستند على الشاشة من رؤية أن جميع الاختبارات قد نجحت. بعد ذلك، سنعتبر أن لدينا طبقة [domain] جاهزة للعمل.

1.4.5. الخلاصة

تجدر الإشارة إلى أننا نرغب في بناء تطبيق الويب ذي الطبقات الثلاث التالي:

تم الآن كتابة النموذج M لتطبيقنا MVC واختباره. ويتم توفيره لنا في ملفين هما DLL و[webarticles-dao.dll, webarticles-domain.dll]. يمكننا الانتقال إلى الطبقة الأخيرة، وهي الطبقة [web] التي تحتوي على وحدة التحكم C وطرق العرض V. سننظر أولاً في طريقة مقدمة في المستند [Développement WEB avec ASP.NET 1.1 ]

  • يتم توفير وحدة التحكم C من خلال ملفين هما [global.asax, main.aspx]
  • أما العروض V فتتم من خلال صفحات aspx

1.5. الطبقة [web]

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

M=modèle
فئات الأعمال [domain]، وفئات الوصول إلى البيانات [dao]، ومصدر البيانات
V=vues
الصفحات ASPX
C=contrôleur
جميع طلبات العملاء HTTP تمر عبر وحدة التحكمين التاليتين:
global.asax: يدير الأحداث المتعلقة بالتشغيل الأولي للتطبيق
main.aspx: يعالج طلب كل عميل على حدة

1.5.1. الآراء

تتطابق العروض مع تلك التي تم عرضها في بداية الوثيقة:

LISTE
liste.aspx
تم تجميع العروض في المجلد [vues] الخاص بالتطبيق
INFOS
infos.aspx
PANIER
panier.aspx
PANIERVIDE
paniervide.aspx
ERREURS
erreurs.aspx

1.5.2. أجهزة التحكم

كما سبق ذكره، سيتألف المراقب من عنصرين:

  1. [global.asax,global.asax.vb]: يُستخدم بشكل أساسي لتهيئة التطبيق ووضع جميع البيانات المراد مشاركتها بين العملاء المختلفين في سياق التطبيق
  2. [main.aspx, main.aspx.vb]: وحدة التحكم الفعلية، وهي التي تعالج طلبات HTTP الواردة من العملاء.

سيتم توجيه الطلبات المختلفة من العملاء إلى وحدة التحكم [main.aspx] وستحتوي على معلمة تسمى [action] تحدد الإجراء المطلوب من قبل العميل:

الطلب
المعنى
إجراء وحدة التحكم
الردود المحتملة
action=liste
يريد العميل قائمة بـ
المنتجات
- يطلب قائمة المواد من الطبقة
الأعمال
- [LISTE]
- [ERREURS]
action=infos
يطلب العميل
معلومات عن أحد
المنتجات المعروضة في العرض
[LISTE]
- يطلب المنتج من طبقة الأعمال
- [INFOS]
- [ERREURS]
action=achat
يقوم العميل بشراء منتج
- يطلب المنتج من طبقة الأعمال
ويضيفها إلى سلة التسوق الخاصة به
- [INFOS] في حالة وجود خطأ في الكمية
- [LISTE] في حالة عدم وجود خطأ
action=retirerachat
يريد العميل حذف
من سلة التسوق
- يسترد سلة التسوق من الجلسة
ويقوم بتعديلها
- [PANIER]
- [PANIERVIDE]
- [ERREURS]
action=panier
يرغب العميل في عرض
سلة التسوق
- استرداد سلة التسوق من الجلسة
- [PANIER]
- [PANIERVIDE]
- [ERREURS]
action=validationpanier
أكمل العميل عملية الشراء
وينتقل إلى مرحلة الدفع
- يقوم بتحديث المخزون في قاعدة البيانات
المنتجات التي تم شراؤها
- إفراغ سلة التسوق الخاصة بالعميل من المنتجات
التي تمت الموافقة على شرائها
- [LISTE]
- [ERREURS]

1.5.3. تكوين التطبيق

سنسعى إلى تكوين التطبيق بحيث يكون أكثر مرونة قدر الإمكان تجاه التغييرات مثل:

  1. تغيير URL في العروض المختلفة
  2. تغيير الفئات التي تنفذ واجهات [IArticlesDao] و [IArticlesDomain]
  3. تغيير SGBD، وقاعدة البيانات، وجدول المنتجات

1.5.3.1. التغييرات في URL

سيتم وضع أسماء URL الخاصة بالطرق في ملف [web.config] الخاص بتكوين التطبيق مع بعض المعلمات الأخرى:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
..
    <appSettings>
        <add key="urlMain" value="/webarticles/main.aspx"/>
        <add key="urlInfos" value="vues/infos.aspx"/>
        <add key="urlErreurs" value="vues/erreurs.aspx"/>
        <add key="urlListe" value="vues/liste.aspx"/>
        <add key="urlPanier" value="vues/panier.aspx"/>
        <add key="urlPanierVide" value="vues/paniervide.aspx"/>
    </appSettings>
</configuration>

1.5.3.2. تغيير فئات تنفيذ الواجهات

وفقًا لمبادئ البنى ثلاثية الطبقات، يجب أن تكون الطبقات معزولة عن بعضها البعض. ويتم تحقيق هذا العزل بالطريقة التالية:

  • تتواصل الطبقات فيما بينها عبر واجهات وليس عبر فئات ملموسة
  • لا تقوم شفرة طبقة ما أبدًا بإنشاء مثيل لفئة من طبقة أخرى لاستخدامها. بل تطلب ببساطة من أداة خارجية، وهي Spring في هذه الحالة، مثيلًا لتنفيذ واجهة الطبقة التي تريد استخدامها. ولهذا الغرض، نعلم أنها لا تحتاج إلى معرفة اسم فئة التنفيذ، بل تحتاج فقط إلى اسم عنصر Spring الفريد (singleton) الذي تريد الحصول على مرجع له.

في تطبيقنا، سيتم تكوين Spring في الملف [web.config] الخاص بالتطبيق الويب بالطريقة التالية:

<?xml version="1.0" encoding="iso-8859-1" ?>
<configuration>
    <configSections>
        <sectionGroup name="spring">
            <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
            <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
        </sectionGroup>
    </configSections>
    <spring>
        <context type="Spring.Context.Support.XmlApplicationContext, Spring.Core">
            <resource uri="config://spring/objects" />
        </context>
        <objects>
    <object id="articlesDao" type="istia.st.articles.dao.ArticlesDaoArrayList, webarticles-dao" />
    <object id="articlesDomain" type="istia.st.articles.domain.AchatsArticles, webarticles-domain">
        <constructor-arg index="0">
            <ref object="articlesDao" />
        </constructor-arg>
    </object>
        </objects>
    </spring>
        <appSettings>
        <add key="urlMain" value="/webarticles/main.aspx"/>
        <add key="urlInfos" value="vues/infos.aspx"/>
        <add key="urlErreurs" value="vues/erreurs.aspx"/>
        <add key="urlListe" value="vues/liste.aspx"/>
        <add key="urlPanier" value="vues/panier.aspx"/>
        <add key="urlPanierVide" value="vues/paniervide.aspx"/>
    </appSettings>
</configuration>

للوصول إلى الطبقة [métier]، يمكن لفئة من الطبقة [web] أن تطلب العنصر الفردي [articlesDomain]. عندئذٍ سيقوم Spring بإنشاء مثيل من النوع [istia.st.articles.domain.AchatsArticles]. ولإجراء عملية الإنشاء هذه، يحتاج إلى كائن من النوع [articlesDao]، أي كائن من النوع [istia.st.articles.dao.ArticlesDaoArrayList]. وبالتالي، سيقوم Spring بإنشاء مثيل لمثل هذا الكائن. في نهاية العملية، تكون الطبقة [web] التي طلبت الكائن الفردي [articlesDomain] قد حصلت على السلسلة الكاملة التي تربطها بمصدر البيانات:

1.5.3.3. التغييرات المتعلقة بـ SGBD أو بقاعدة البيانات

سيتم تجاهل هذه النقطة هنا لأننا نتعامل مع تطبيق اختباري لا يحتوي على SGBD. وسنتناول في مرحلة لاحقة تنفيذ طبقة [dao] التي تعتمد على SGBD.

1.5.4. مكتبة العلامات <asp:>

لننظر إلى طريقة العرض [ERREURS] التي تعرض قائمة بالأخطاء:

Image

تتولى طريقة العرض [ERREURS] عرض قائمة بالأخطاء التي وضعها عنصر التحكم [main.aspx] في سياق الطلب تحت الاسم [context.Items("erreurs")]. هناك عدة طرق لكتابة مثل هذه الصفحة. ولكننا سنركز هنا فقط على الجزء المتعلق بعرض الأخطاء.

تجدر الإشارة إلى أن صفحة ASPX تحتوي على جزء العرض HTML وجزء الكود .NET الذي يقوم بإعداد البيانات التي يجب أن يعرضها جزء العرض. يمكن أن يكون هذان الجزءان في ملف واحد هو [aspx] (الحل WebMatrix) أو في ملفين: [aspx] للعرض، و[aspx.vb] للكود. وهذا الحل الأخير هو الحل الذي يتبعه Visual Studio. ولزيادة الأمور تعقيدًا، قد يحتوي الجزء الخاص بالتصميم HTML أيضًا على كود .NET، مما يؤدي إلى إرباك الفصل بين [contrôleur] و [présentation] من وجهة النظر. يُنصح بشدة بعدم اتباع هذا الحل عمومًا. وقد استلزم إزالة كل الكود من الجزء [présentation] إنشاء مكتبات علامات. تعمل هذه المكتبات على «إخفاء» الكود تحت شكل علامات مشابهة لعلامات HTML. نقدم حلين ممكنين للصفحة [ERREURS].

يستخدم الحل الأول كود .NET في الجزء [présentation] من الصفحة. وتسترد الصفحة ASPX قائمة الأخطاء الموجودة في الطلب في جزء وحدة التحكم الخاص بها [erreurs.aspx.vb]:

        Protected erreurs As ArrayList

        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
             ' استرداد الأخطاء
            erreurs = CType(context.Items("erreurs"), ArrayList)
        End Sub

ثم يعرضها في الجزء [présentation, erreurs.aspx]:

                <h2>Les erreurs suivantes se sont produites :</h2>
                <ul>
                <%
                    for i as integer=0 to erreurs.count-1
                        response.write("<li>" & erreurs(i).ToString & "</li>")
                    next
                %>
                </ul>

يستخدم الحل الثاني العلامة <asp:repeater> من مكتبة علامات <asp:> الخاصة بـ ASP.NET. إذا تم إنشاء صفحة ASPX بطريقة رسومية، فإن هذه العلامة تكون متاحة في شكل مكون خادم يتم سحبه وإفلاته على نموذج التصميم. أما إذا تم إنشاء الكود ASPX يدويًّا، فيمكننا عندئذٍ التحدث عن مكتبة العلامات.

باستخدام مكتبة العلامات <asp:>، يصبح الكود ASPX الخاص بالعرض [ERREURS] السابق كما يلي:

        <asp:Repeater id="rptErreurs" runat="server">
            <HeaderTemplate>
                <h3>Les erreurs suivantes se sont produites :
                </h3>
                <ul>
            </HeaderTemplate>
            <ItemTemplate>
                <li>
                    <%# Container.DataItem %>
                </li>
            </ItemTemplate>
            <FooterTemplate>
                </ul>
            </FooterTemplate>
        </asp:Repeater>

العلامة

        <asp:Repeater id="rptErreurs" runat="server">

تُستخدم لتكرار نمط HTML على العناصر المختلفة لمصدر البيانات. وعناصرها المختلفة هي كما يلي:

HeaderTemplate
النمط HTML الذي سيتم عرضه قبل عرض عناصر مصدر البيانات
ItemTemplate
النمط HTML الذي يتكرر لكل عنصر من عناصر مصدر البيانات. يُستخدم التعبير [<%# Container.DataItem %>] لعرض قيمة العنصر الحالي في مصدر البيانات
FooterTemplate
النمط HTML الذي يجب عرضه بعد عرض عناصر مصدر البيانات

يتم ربط مصدر البيانات بالعلامة عادةً في الجزء [contrôleur] من الصفحة:

        Protected WithEvents rptErreurs As System.Web.UI.WebControls.Repeater

        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
..
             ' ربط الأخطاء بـ rptErreurs
            With rptErreurs
                .DataSource = context.Items("erreurs")
                .DataBind()
            End With
        End Sub

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

سنستخدم في طرق العرض الخاصة بنا علامة أخرى: <asp:datagrid> التي تسمح بعرض مصدر البيانات في شكل جدول.

1.5.5. هيكل حل Visual Studio للتطبيق [webarticles]

تُعدّ تطبيقات الويب بمثابة أحجية تتألف من العديد من العناصر. وعادةً ما يؤدي تزويدها بهيكلية MVC إلى زيادة عدد هذه العناصر. وفيما يلي هيكل تطبيق [webarticles] ضمن [Visual Studio]:

  

تعليقات:

  • المشروع [web] هو من النوع [bibliothèque de classes] وليس من النوع [Application web ASP.NET] كما قد يتوقع المرء منطقياً. يتطلب النوع [Application web ASP.NET] وجود خادم الويب IIS على جهاز التطوير أو على جهاز بعيد. لا يتوفر خادم IIS بشكل قياسي على أجهزة Windows XP العائلية. ومع ذلك، تُباع العديد من أجهزة PC مع هذا الإصدار. ولتمكين القراء الذين يستخدمون Windows XP من تنفيذ التطبيق قيد الدراسة، سنستخدم خادم الويب Cassini (انظر الملحق) المتاح مجانًا من Microsoft، وسنستبدل المشروع [Application web ASP.NET] بمشروع [bibliothèque de classes]. وينطوي ذلك على بعض العيوب التي تم شرحها في المرفقات.
  • الملفات DLL التي يستخدمها التطبيق هي التالية:
webarticles-dao.dll
تضم فئات طبقة الوصول إلى البيانات
webarticles-domain.dll
تضم فئات طبقة الأعمال
Spring.Core.dll
يحتوي على فئات Spring التي تسمح لنا بدمج طبقات الويب والمجال وDAO
log4net.dll
فئات السجلات - التي يستخدمها Spring

يتم وضع ملفات DLL هذه في المجلد [bin] وإضافتها إلى مراجع المشروع.

1.5.6. طرق العرض ASPX

كما تمت الإشارة سابقًا، سنستخدم مكتبة العلامات <asp:> في طرق العرض الخاصة بنا ASPX.

1.5.6.1. مكون المستخدم [entete.ascx]

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

القائمة ديناميكية ويتم تحديدها بواسطة وحدة التحكم. تقوم وحدة التحكم بإدراج سمة مفتاح "actions" في الطلب المرسَل إلى الصفحة ASPX، حيث تكون القيمة المرتبطة بها عبارة عن مصفوفة من العناصر من نوع Hashtable(). كل عنصر في هذه المصفوفة هو قاموس مخصص لتوليد خيار في قائمة العناوين. يحتوي كل قاموس على مفتاحين:

  • href: رابط URL المرتبط بخيار القائمة
  • link: نص القائمة

سنقوم بتحويل العنوان إلى عنصر تحكم مستخدم. يقوم عنصر التحكم المستخدم بتغليف جزء من الصفحة (التصميم والكود المرتبط) في مكون قابل لإعادة الاستخدام لاحقًا في صفحات أخرى. هنا، نريد إعادة استخدام المكون [entete] في طرق العرض الأخرى للتطبيق. سيكون كود العرض في [entete.ascx] وكود عنصر التحكم المرتبط به في [entete.ascx.vb]. سيستخدم كود العرض مكون <asp:repeater> لعرض جدول خيارات القائمة:

type
nom
rôle
1
مكرر
rptMenu
مصدر البيانات: جدول من القواميس
بمفتاحين: href، رابط
عرض خيارات القائمة

سيكون كود عرض الصفحة كما يلي:

<%@ Control codebehind="entete.ascx.vb" Language="vb" autoeventwireup="false" inherits="istia.st.articles.web.EnteteWebArticles" %>
        <table>
            <tr>
                <td>
                    <h2>Magasin virtuel</h2></td>
                <asp:Repeater id="rptMenu" runat="server">
                    <ItemTemplate>
                        <td>
                            |<a href='<%# Container.DataItem("href") %>'>
                                <%# Container.DataItem("رابط") %>
                            </a>
                        </td>
                    </ItemTemplate>
                </asp:Repeater>
            </tr>
        </table>
        <hr>

تعليقات:

  • تم تعريف المكون [repeater] في الأسطر 6-14
  • كل عنصر من عناصر مصدر البيانات المرتبط بالمكرر هو قاموس ذو مفتاحين: href - السطر 9 و lien - السطر 10

وسيكون كود التحكم المرتبط كما يلي:

Namespace istia.st.articles.web
    Public Class EnteteWebArticles
        Inherits System.Web.UI.UserControl

        Protected WithEvents rptMenu As System.Web.UI.WebControls.Repeater

        Public WriteOnly Property actions() As Hashtable()
            Set(ByVal Value As Hashtable())
                ' يتم ربط جدول الإجراءات بمكونه
                With rptMenu
                    .DataSource = Value
                    .DataBind()
                End With
            End Set
        End Property
    End Class
End Namespace

تعليقات:

  • المكون من النوع [EnteteWebArticles] له خاصية عامة [actions] قابلة للكتابة فقط - السطر 7
  • تسمح هذه الخاصية بربط المكون <asp:repeater> المسمى [rptMenu] - السطر 10 - بمصفوفة الخيارات التي يحسبها وحدة التحكم في التطبيق - السطران 11-12.

وستستخدم طرق العرض الأخرى للتطبيق الرأس المحدد بواسطة [entete.ascx]. على سبيل المثال، ستقوم الصفحة [erreurs.aspx] بتضمين الرأس باستخدام الكود التالي:

<%@ Register TagPrefix="WA" TagName="entete" Src="entete.ascx" %>
<%@ Page inherits="istia.st.articles.web.ErreursWebarticles" autoeventwireup="false" Language="vb" %>
<HTML>
    <HEAD>
        <TITLE>webarticles</TITLE>
        <META http-equiv="Content-Type" content="text/html; charset=windows-1252">
    </HEAD>
    <body>
        <WA:entete id="entete" runat="server"></WA:entete>
        <h3>Les erreurs suivantes se sont produites :
        </h3>    

تعليقات:

  • يحدد السطر 1 أن العلامة <WA:entete> يجب أن ترتبط بالمكون المحدد بواسطة الملف [entete.ascx]. أما السمتان [TagPrefix] و [TagName] فهما اختياريتان.
  • وبعد ذلك، يتم إدراج المكون في كود عرض الصفحة من خلال السطر 9. عند التنفيذ، سيؤدي هذا العلامة إلى تضمين كود الصفحة ASPX التي تحتوي عليها، كود الصفحة [entete.ascx]. وسيتولى كود التحكم [erreurs.aspx.vb] تهيئة هذا المكون. ويمكنه القيام بذلك بالطريقة التالية:
    Public Class ErreursWebarticles
        Inherits System.Web.UI.Page

         ' مكونات الصفحة
...
        Protected WithEvents entete As New EnteteWebArticles

        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
             ' يتم ربط خيارات القائمة بـ rptmenu
            entete.actions = CType(context.Items("options"), Hashtable())
...
        End Sub
    End Class

تعليقات:

  • السطر 6 ينشئ كائنًا من النوع [EnteteWebArticles] وهو نوع المكون الذي تم إنشاؤه
  • السطر 11 يقوم بتهيئة الخاصية [actions] لهذا الكائن

1.5.6.2. طريقة العرض [liste.aspx]

1.5.6.2.1. Introduction

تعرض هذه العرض قائمة بالمنتجات المتاحة للبيع:

يتم عرضها بعد استعلام /main?action=liste أو /main?action=validationpanier. عناصر استعلام وحدة التحكم هي كما يلي:

actions
كائن Hashtable() - جدول خيارات القائمة
listarticles
ArrayList من كائنات من النوع [Article]
message
كائن String - الرسالة المراد عرضها أسفل الصفحة

كل رابط [Infos] في جدول HTML للمقالات له URL بالصيغة [?action=infos&id=ID] حيث ID هو حقل idالمقال المعروض.

1.5.6.2.2. مكونات الصفحة
type
nom
rôle
1
مكون المستخدم
رأس الصفحة
عرض الرأس
2
DataGrid
DataGridArticles
3 - العمود المرتبط: العنوان: الاسم، الحقل: الاسم
4 - العمود المرتبط: العنوان: السعر، الحقل: السعر
5 - عمود الارتباط التشعبي: النص: معلومات، حقل URL: id،
تنسيق URL: /webarticles/main.aspx?action=infos&id={0}
عرض المنتجات المعروضة للبيع
6
التسمية
lblMessage
عرض رسالة

دعونا نستعرض كيفية تعريف هذه الخصائص:

  • في Visual Studio، نختار [DataGrid] للوصول إلى ورقة خصائصه:

Image

  • استخدم الرابط [Mise en forme automatique] أعلاه لإدارة شكل الجدول المعروض
  • والرابط [Générateur de propriétés] لإدارة محتواه
1.5.6.2.3. رمز العرض [liste.aspx]
<%@ Page codebehind="liste.aspx.vb" inherits="istia.st.articles.web.ListeWebarticles" autoeventwireup="false" Language="vb" %>
<%@ Register TagPrefix="WA" TagName="entete" Src="entete.ascx"%>
<HTML>
    <HEAD>
        <TITLE>webarticles</TITLE>
        <META http-equiv="Content-Type" content="text/html; charset=windows-1252">
    </HEAD>
    <body>
        <WA:entete id="entete" runat="server"></WA:entete>
        <h2>Liste des articles</h2>
        <P>
            <asp:DataGrid id="DataGridArticles" runat="server" ForeColor="Black" BackColor="LightGoldenrodYellow"
                BorderColor="Tan" CellPadding="2" BorderWidth="1px" GridLines="None" AutoGenerateColumns="False">
                <SelectedItemStyle ForeColor="GhostWhite" BackColor="DarkSlateBlue"></SelectedItemStyle>
                <AlternatingItemStyle BackColor="PaleGoldenrod"></AlternatingItemStyle>
                <HeaderStyle Font-Bold="True" BackColor="Tan"></HeaderStyle>
                <FooterStyle BackColor="Tan"></FooterStyle>
                <Columns>
                    <asp:BoundColumn DataField="nom" HeaderText="Nom"></asp:BoundColumn>
                    <asp:BoundColumn DataField="prix" HeaderText="Prix" DataFormatString="{0:C}"></asp:BoundColumn>
                    <asp:HyperLinkColumn Text="Infos" DataNavigateUrlField="id" DataNavigateUrlFormatString="/webarticles/main.aspx?action=infos&amp;id={0}"></asp:HyperLinkColumn>
                </Columns>
                <PagerStyle HorizontalAlign="Center" ForeColor="DarkSlateBlue" BackColor="PaleGoldenrod"></PagerStyle>
            </asp:DataGrid></P>
        <P>
            <asp:Label id="lblMessage" runat="server" BackColor="#FFC080"></asp:Label></P>
    </body>
</HTML>

تعليقات:

  • يحدد السطر 9 رأس الصفحة
  • تحدد الأسطر من 12 إلى 24 خصائص [DataGrid]
  • السطر 26 يحدد التسمية [lblMessage]
1.5.6.2.4. رمز وحدة التحكم [liste.aspx.vb]
Imports System
Imports System.Collections
Imports System.Data
Imports istia.st.articles.dao

Namespace istia.st.articles.web

     ' يدير صفحة عرض قائمة المقالات
    Public Class ListeWebarticles
        Inherits System.Web.UI.Page

         ' مكونات الصفحة
        Protected WithEvents lblMessage As System.Web.UI.WebControls.Label
        Protected WithEvents DataGridArticles As System.Web.UI.WebControls.DataGrid
        Protected WithEvents entete As New EnteteWebArticles

        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
             ' يتم إعداد العرض [liste] استنادًا إلى معلومات السياق
             ' ربط خيارات القائمة بـ rptmenu
            entete.actions = CType(context.Items("options"), Hashtable())
             ' يتم استرداد المقالات في جدول بيانات
            Dim articles As ArrayList = CType(context.Items("articles"), ArrayList)
             ' يتم ربطها بمكون الصفحة [DataGrid]
            With DataGridArticles
                .DataSource = articles
                .DataBind()
            End With
             ' يتم عرض الرسالة
            lblMessage.Text = context.Items("message").ToString
        End Sub
    End Class
End Namespace

تعليقات:

  • تظهر مكونات الصفحة في الأسطر 13-15. تجدر الإشارة إلى أنه كان من الضروري إنشاء كائن [EnteteWebArticles] باستخدام عامل [new]، في حين أن ذلك لم يكن ضروريًا مع المكونات الأخرى. وبدون هذا الإنشاء الصريح، كنا نواجه خطأً في التنفيذ يشير إلى أن الكائن [entete] لا يشير إلى أي شيء. هذه النقطة تستحق المزيد من التعمق، لكن لم يتم ذلك.
  • يتم أخذ جدول خيارات قائمة الرأس من السياق لتهيئة المكون [entete] للصفحة - السطر 20
  • يتم أخذ قائمة العناصر من السياق - السطر 22
  • لتهيئة المكون [DataGridArticles] - الأسطر 24-27
  • تم تهيئة المكون [lblMessage] برسالة موجودة في السياق - السطر 29

1.5.6.3. طريقة العرض [infos.aspx]

1.5.6.3.1. Introduction

تعرض هذه الواجهة معلومات عن أحد المنتجات وتتيح أيضًا شرائه:

Image

يتم عرضها بعد استعلام /main?action=infos&id=ID أو استعلام /main?action=achat&id=ID عندما تكون الكمية المشتراة خاطئة. عناصر استعلام وحدة التحكم هي كما يلي:

actions
كائن Hashtable() - جدول خيارات القائمة
article
كائن من النوع [Article] - العنصر المراد عرضه
msg
كائن String - الرسالة المراد عرضها في حالة حدوث خطأ في الكمية
qte
كائن String - القيمة المراد عرضها في حقل الإدخال [Qte]

يتم استخدام الحقول [msg] و [qte] في حالة حدوث خطأ في إدخال الكمية:

Image

تحتوي هذه الصفحة على نموذج يتم إرساله عبر الزر [Acheter]. القيمة المستهدفة لـ URL في POST هي [?action=achat&id=ID]، حيث ID هو معرّف المنتج الذي تم شراؤه.

1.5.6.3.2. مكونات الصفحة
type
nom
rôle
1
مكون المستخدم
رأس الصفحة
عرض الرأس
2
القيمة الحرفية
litID
عرض رقم المادة
3 à 6
DataGrid
DataGridArticle
3 - العمود ذو الصلة: العنوان: الاسم، الحقل: الاسم
4 - العمود المرتبط: العنوان: السعر، الحقل: السعر
5 - العمود المرتبط: العنوان: المخزون الحالي، الحقل: stockActuel
6 - العمود المرتبط: العنوان: الحد الأدنى للمخزون، الحقل: stockMinimum
عرض منتج
7
HTML إرسال
 
إرسال النموذج
8
HTML الإدخال
runat=server
txtQte
أدخل الكمية المشتراة
9
label
lblMsgQte
رسالة خطأ محتملة
1.5.6.3.3. رمز العرض [infos.aspx]
<%@ Register TagPrefix="WA" TagName="entete" Src="entete.ascx" %>
<%@ Page codebehind="infos.aspx.vb" inherits="istia.st.articles.web.InfosWebarticles" autoeventwireup="false" Language="vb" %>
<HTML>
    <HEAD>
        <TITLE>webarticles</TITLE>
        <META http-equiv="Content-Type" content="text/html; charset=windows-1252">
    </HEAD>
    <body>
        <WA:entete id="entete" runat="server"></WA:entete>
        <h2>Article d'id [<asp:Literal id="litId" runat="server"></asp:Literal>]</h2>
        <P>
            <asp:DataGrid id="DataGridArticle" runat="server" BackColor="White" BorderColor="#E7E7FF" CellPadding="3"
                BorderWidth="1px" BorderStyle="None" GridLines="Horizontal" AutoGenerateColumns="False">
                <SelectedItemStyle Font-Bold="True" ForeColor="#F7F7F7" BackColor="#738A9C"></SelectedItemStyle>
                <AlternatingItemStyle BackColor="#F7F7F7"></AlternatingItemStyle>
                <ItemStyle HorizontalAlign="Center" ForeColor="#4A3C8C" BackColor="#E7E7FF"></ItemStyle>
                <HeaderStyle Font-Bold="True" HorizontalAlign="Center" ForeColor="#F7F7F7" BackColor="#4A3C8C"></HeaderStyle>
                <FooterStyle ForeColor="#4A3C8C" BackColor="#B5C7DE"></FooterStyle>
                <Columns>
                    <asp:BoundColumn DataField="nom" HeaderText="Nom">
                        <HeaderStyle HorizontalAlign="Center"></HeaderStyle>
                    </asp:BoundColumn>
                    <asp:BoundColumn DataField="prix" HeaderText="Prix" DataFormatString="{0:C}">
                        <HeaderStyle HorizontalAlign="Center"></HeaderStyle>
                    </asp:BoundColumn>
                    <asp:BoundColumn DataField="stockactuel" HeaderText="Stock actuel">
                        <HeaderStyle HorizontalAlign="Center"></HeaderStyle>
                    </asp:BoundColumn>
                    <asp:BoundColumn DataField="stockminimum" HeaderText="Stock minimum">
                        <HeaderStyle HorizontalAlign="Center"></HeaderStyle>
                    </asp:BoundColumn>
                </Columns>
                <PagerStyle HorizontalAlign="Right" ForeColor="#4A3C8C" BackColor="#E7E7FF" Mode="NumericPages"></PagerStyle>
            </asp:DataGrid></P>
        <HR width="100%" SIZE="1">
        <form method="post" action="<%=strAction%>">
            <table>
                <tr>
                    <td><input type="submit" value="Acheter"></td>
                    <td>Qté</td>
                    <td><INPUT type="text" maxLength="3" size="3" id="txtQte" runat="server"></td>
                    <td><asp:Label id="lblMsgQte" runat="server" />
                    </td>
                </tr>
            </table>
        </form>
    </body>
</HTML>

تعليقات:

  • الرأس مضمن في الصفحة - السطر 9
  • تم تعريف القيمة الثابتة [litId] في السطر 10
  • تم تعريف DataGrid و [DataGridArticles] في الأسطر 12-34
  • تم تعريف النموذج في الأسطر 36-46. وهو من النوع POST.
  • يتم توفير الهدف لـ POST بواسطة متغير [strAction] - السطر 36. يجب تعريف هذا المتغير بواسطة وحدة التحكم.
  • حقل إدخال الكمية المشتراة محدد في السطر 41. وهو مكون خادم من نوع HTML (runat=server). من ناحية الكود، يمكن الوصول إليه عبر كائن.
  • يحدد السطر 42 التسمية [lblMsgQte] التي ستحتوي على أي رسالة خطأ متعلقة بالكمية المدخلة
1.5.6.3.4. رمز التحكم [infos.aspx.vb]
Imports istia.st.articles.dao
Imports System
Imports System.Collections

Namespace istia.st.articles.web

     ' يدير صفحة المعلومات الخاصة بالمقال
    Public Class InfosWebarticles
        Inherits System.Web.UI.Page
        Protected WithEvents lblMsgQte As System.Web.UI.WebControls.Label
        Protected WithEvents litId As System.Web.UI.WebControls.Literal
        Protected WithEvents txtQte As System.Web.UI.HtmlControls.HtmlInputText
        Protected WithEvents DataGridArticle As System.Web.UI.WebControls.DataGrid
        Protected WithEvents entete As New EnteteWebArticles

         ' URL حيث سيتم إرسال النموذج
        Private _strAction As String
        Public Property strAction() As String
            Get
                Return _strAction
            End Get
            Set(ByVal Value As String)
                _strAction = Value
            End Set
        End Property

         ' عرض الصفحة
        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
             ' يتم استرداد معلومات الطلب
            Dim unArticle As Article = CType(Session.Item("article"), Article)
             ' ربط خيارات القائمة بـ rptmenu
            entete.actions = CType(context.Items("options"), Hashtable())
             ' ربط المقالة بـ [DataGrid]
            Dim articles As New ArrayList
            articles.Add(unArticle)
            With DataGridArticle
                .DataSource = articles
                .DataBind()
            End With
             ' معرف التسمية
            litId.Text = unArticle.id.ToString
             ' رسالة الخطأ
            lblMsgQte.Text = context.Items("msg").ToString
             ' الكمية السابقة
            txtQte.Value = context.Items("qte").ToString
             ' الإجراء
            strAction = "?action=achat&id=" + unArticle.id.ToString
        End Sub
End Namespace

تعليقات:

  • يتم تعريف مكونات الصفحة في الأسطر 10-14
  • تُعرّف الفئة خاصية عامة [strAction] تُستخدم لتحديد هدف POST في النموذج - الأسطر 17-25
  • يتم استرداد المقالة المراد عرضها من سياق التطبيق - السطر 30
  • يتم أخذ جدول خيارات قائمة الرأس من السياق لتهيئة مكون الصفحة [entete] - السطر 32
  • الأسطر 33-39، يتم ربط المكون [DataGridArticle] بمصدر بيانات من النوع [ArrayList] لا يحتوي إلا على العنصر الذي تم استرداده في السطر 30
  • يتم تهيئة المكونات [lblMsgQte, txtQte] بمعلومات مأخوذة من السياق - الأسطر 42-45
  • يتم أيضًا تهيئة الخاصية [straction] بمعلومات مأخوذة من السياق - السطر 47. تُستخدم هذه المتغير لتوليد السمة [action] للنموذج HTML الموجود في الصفحة:
        <form method="post" action="<%=strAction%>">
....
        </form>

1.5.6.4. عرض [panier.aspx]

1.5.6.4.1. Introduction

تعرض هذه النظرة محتويات سلة التسوق:

Image

يتم عرضها بعد إرسال طلب /main?action=panier أو /main?action=retirerachat&id=ID. عناصر طلب وحدة التحكم هي كما يلي:

actions
كائن Hashtable() - جدول خيارات القائمة
panier
كائن من النوع [Panier] - سلة التسوق المراد عرضها

كل رابط [Retirer] في جدول HTML الخاص بمشتريات سلة التسوق له URL بالصيغة [?action=retirerachat&id=ID] حيث ID هو الحقل [id] الخاص بالمنتج الذي نريد إزالته من سلة التسوق.

1.5.6.4.2. مكونات الصفحة
type
nom
rôle
1
مكون المستخدم
رأس الصفحة
عرض الرأس
2
DataGrid
DataGridAchats
3- العمود المرتبط - العنوان: المادة، الحقل: الاسم
4 - عمود مرتبط - العنوان: الكمية، الحقل: الكمية
5 - عمود مرتبط - العنوان: السعر، الحقل: السعر
6 - عمود مرتبط - العنوان: الإجمالي، الحقل: الإجمالي، التنسيق {0:C}
7 - عمود ارتباط - النص: سحب، عنوان URL: id، تنسيق URL: /webarticles/main.aspx?action=retirerachat&id={0}
عرض قائمة المنتجات المشتراة
8
التسمية
lblTotal
عرض المبلغ المستحق الدفع
1.5.6.4.3. رمز العرض [panier.aspx]
<%@ Page codebehind="panier.aspx.vb" inherits="istia.st.articles.web.PanierWebarticles" autoeventwireup="false" Language="vb" %>
<%@ Register TagPrefix="WA" TagName="entete" Src="entete.ascx" %>
<HTML>
    <HEAD>
        <TITLE>webarticles</TITLE>
        <META http-equiv="Content-Type" content="text/html; charset=windows-1252">
    </HEAD>
    <body>
        <WA:entete id="entete" runat="server"></WA:entete>
        <h2>Contenu de votre panier</h2>
        <P>
            <asp:DataGrid id="DataGridAchats" runat="server" BorderWidth="1px" GridLines="Vertical" CellPadding="4"
                BackColor="White" BorderStyle="None" BorderColor="#DEDFDE" ForeColor="Black" AutoGenerateColumns="False">
                <SelectedItemStyle Font-Bold="True" ForeColor="White" BackColor="#CE5D5A"></SelectedItemStyle>
                <AlternatingItemStyle BackColor="White"></AlternatingItemStyle>
                <ItemStyle BackColor="#F7F7DE"></ItemStyle>
                <HeaderStyle Font-Bold="True" ForeColor="White" BackColor="#6B696B"></HeaderStyle>
                <FooterStyle BackColor="#CCCC99"></FooterStyle>
                <Columns>
                    <asp:BoundColumn DataField="nom" HeaderText="Article"></asp:BoundColumn>
                    <asp:BoundColumn DataField="qte" HeaderText="Qt&#233;"></asp:BoundColumn>
                    <asp:BoundColumn DataField="prix" HeaderText="Prix"></asp:BoundColumn>
                    <asp:BoundColumn DataField="totalAchat" HeaderText="Total" DataFormatString="{0:C}"></asp:BoundColumn>
                    <asp:HyperLinkColumn Text="Retirer" DataNavigateUrlField="id" DataNavigateUrlFormatString="/webarticles/main.aspx?action=retirerachat&amp;id={0}"></asp:HyperLinkColumn>
                </Columns>
                <PagerStyle HorizontalAlign="Right" ForeColor="Black" BackColor="#F7F7DE" Mode="NumericPages"></PagerStyle>
            </asp:DataGrid></P>
        <P>Total de la commande :
            <asp:Label id="lblTotal" runat="server"></asp:Label>&nbsp;euros</P>
    </body>
</HTML>

تعليقات

  • السطر 9 يتضمن العنوان
  • في الأسطر 12-27، تم تعريف المكون [DataGridAchats]
  • في السطر 29، تم تعريف المكون [lblTotal]
1.5.6.4.4. رمز التحكم [panier.aspx.vb]
Imports System
Imports System.Collections
Imports System.Data
Imports istia.st.articles.dao
Imports istia.st.articles.domain

Namespace istia.st.articles.web
     ' يدير صفحة عرض سلة التسوق
    Public Class PanierWebarticles
        Inherits System.Web.UI.Page
        Protected WithEvents DataGridAchats As System.Web.UI.WebControls.DataGrid
        Protected WithEvents lblTotal As System.Web.UI.WebControls.Label
        Protected WithEvents entete As New EnteteWebArticles

        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
             ' ربط خيارات القائمة بـ rptmenu
            entete.actions = CType(context.Items("options"), Hashtable())
             ' استرداد سلة التسوق
            Dim unPanier As Panier = CType(Session.Item("panier"), Panier)
             ' ننقل المشتريات إلى جدول سطور المشتريات
            Dim achats(unPanier.achats.Count - 1) As LigneAchat
             ' يتم تغيير نوع عناصر ArrayList
            For i As Integer = 0 To achats.Length - 1
                achats(i) = New LigneAchat(CType(unPanier.achats(i), Achat))
            Next
             ' ربط البيانات بمكونات الصفحة [DataGrid]
            With DataGridAchats
                .DataSource = achats
                .DataBind()
            End With
             ' عرض المبلغ الإجمالي المستحق الدفع
            lblTotal.Text = unPanier.totalPanier.ToString
        End Sub

         ' يتم إنشاء سطر الشراء استنادًا إلى كائن «شراء»
        Private Class LigneAchat
            Inherits Achat

             ' يستقبل المنشئ عملية شراء
            Public Sub New(ByVal unAchat As Achat)
                Me.article = unAchat.article
                Me.qte = unAchat.qte
            End Sub

             ' المعرف: يعرض معرف السلعة المشتراة
            Public ReadOnly Property id() As Integer
                Get
                    Return article.id
                End Get
            End Property

             ' الاسم: اسم السلعة المشتراة
            Public ReadOnly Property nom() As String
                Get
                    Return article.nom
                End Get
            End Property

             ' سعر السلعة المشتراة
            Public ReadOnly Property prix() As Double
                Get
                    Return article.prix
                End Get
            End Property

        End Class
    End Class
End Namespace

تعليقات:

  • تم إعلان مكونات الصفحة في الأسطر 11-13
  • تكون عملية تهيئة المكون [entete] مماثلة لتلك الموجودة في الصفحات التي تمت دراستها سابقًا - السطر 17
  • يتم استرداد سلة التسوق المراد عرضها من الجلسة - السطر 19
  • يواجه عرض سلة التسوق هذه باستخدام المكون [DataGridAchats] بعض المشاكل. وتكمن الصعوبة في تهيئة المكون. ولنتذكر أعمدة هذا المكون:
    • العمود [Article] المرتبط بالحقل [nom] في مصدر البيانات
    • العمود [Qté] المرتبط بالحقل [qte] في مصدر البيانات
    • العمود [Prix] المرتبط بحقل [prix] في مصدر البيانات
    • العمود [Total] المرتبط بالحقل [total] في مصدر البيانات

مصدر البيانات المتاح لدينا هو سلة التسوق وقائمة المشتريات الخاصة بها. وستكون هذه القائمة هي مصدر البيانات لـ [DataGrid]. فقط الكائنات [Achat] التي ستغذي أسطر [DataGrid] لا تمتلك الخصائص [nom, qte, prix, total] التي يتوقعها [DataGrid]. لذلك، نقوم هنا، خصيصًا لـ [DataGrid]، بإنشاء مصدر بيانات تحتوي عناصره على الخصائص التي يتوقعها [DataGrid]. وستكون هذه العناصر من النوع [LigneAchat]، وهي فئة تم إنشاؤها خصيصًا لهذه الغاية ومشتقة من الفئة [Achat] - الأسطر 36-66

  • بعد تعريف الفئة [LigneAchat)، يتم إنشاء مصدر البيانات الخاص بـ [DataGridAchats] استنادًا إلى سلة التسوق الموجودة في الجلسة - الأسطر 20-30
  • يتم عرض مبلغ المشتريات بفضل الخاصية [totalPanier] للفئة [Panier] - السطر 32

1.5.6.5. عرض [paniervide.aspx]

1.5.6.5.1. Introduction

تعرض هذه العرض المعلومات التي تشير إلى أن سلة التسوق فارغة:

Image

يتم عرضها بعد استعلام /main?action=panier أو /main?action=retirerachat&id=ID. عناصر استعلام وحدة التحكم هي كما يلي:

actions
كائن Hashtable() - جدول خيارات القائمة
1.5.6.5.2. مكونات الصفحة
type
nom
rôle
1
مكون المستخدم
رأس الصفحة
عرض الرأس
1.5.6.5.3. رمز العرض [paniervide.aspx]
<%@ Register TagPrefix="WA" TagName="entete" Src="entete.ascx"%>
<%@ Page codebehind="paniervide.aspx.vb" inherits="istia.st.articles.web.PaniervideWebarticles" autoeventwireup="false" Language="vb" %>
<HTML>
    <HEAD>
        <TITLE>webarticles</TITLE>
        <META http-equiv="Content-Type" content="text/html; charset=windows-1252">
    </HEAD>
    <body>
        <WA:entete id="entete" runat="server"></WA:entete>
        <h2>Contenu de votre panier</h2>
        <P>Votre panier est vide</P>
    </body>
</HTML>

تعليقات:

  • الرأس مدرج في السطر 9
1.5.6.5.4. رمز التحكم [paniervide.aspx.vb]
Namespace istia.st.articles.web
     ' يدير صفحة عرض سلة التسوق الفارغة
    Public Class PaniervideWebarticles
        Inherits System.Web.UI.Page
        Protected WithEvents entete As New EnteteWebArticles

        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
             ' يتم إعداد العرض [paniervide] استنادًا إلى معلومات السياق
             ' ربط خيارات القائمة بـ rptmenu
            entete.actions = CType(context.Items("options"), Hashtable())
        End Sub
    End Class
End Namespace

تعليقات:

  • يكتفى بتهيئة المكون الديناميكي الوحيد في الصفحة - السطر 10

1.5.6.6. طريقة العرض [erreurs.aspx]

1.5.6.6.1. Introduction

يتم عرض هذه الشاشة في حالة حدوث أخطاء:

Image

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

actions
كائن Hashtable() - جدول خيارات القائمة
erreurs
ArrayList من كائنات [String] التي تمثل رسائل الأخطاء المطلوب عرضها
1.5.6.6.2. مكونات الصفحة
type
nom
rôle
1
مكون المستخدم
رأس الصفحة
عرض الرأس
2
المكرر
rptErreurs
عرض قائمة الأخطاء
1.5.6.6.3. رمز العرض [erreurs.aspx]
<%@ Register TagPrefix="WA" TagName="entete" Src="entete.ascx" %>
<%@ Page codebehind="erreurs.aspx.vb" inherits="istia.st.articles.web.ErreursWebarticles" autoeventwireup="false" Language="vb" %>
<HTML>
    <HEAD>
        <TITLE>webarticles</TITLE>
        <META http-equiv="Content-Type" content="text/html; charset=windows-1252">
    </HEAD>
    <body>
        <WA:entete id="entete" runat="server"></WA:entete>
        <h3>Les erreurs suivantes se sont produites :
        </h3>
        <ul>
            <asp:Repeater id="rptErreurs" runat="server">
                <ItemTemplate>
                    <li>
                        <%# Container.DataItem %>
                    </li>
                </ItemTemplate>
            </asp:Repeater></ul>
    </body>
</HTML>

تعليقات:

  • تم تعريف الرأس في السطر 9
  • المكون [rptErreurs] محدد في الأسطر 13-19. ويأتي محتواه من مصدر بيانات سيكون من النوع [ArrayList] من الكائنات [String].
1.5.6.6.4. رمز التحكم [erreurs.aspx.vb]
Namespace istia.st.articles.web

     ' يدير صفحة الأخطاء
    Public Class ErreursWebarticles
        Inherits System.Web.UI.Page

         ' مكونات الصفحة
        Protected WithEvents rptErreurs As System.Web.UI.WebControls.Repeater
        Protected WithEvents entete As New EnteteWebArticles

        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
             ' يتم إعداد العرض [erreurs] استنادًا إلى معلومات السياق
             ' ربط خيارات القائمة بـ rptmenu
            entete.actions = CType(context.Items("options"), Hashtable())
             ' ربط الأخطاء بـ rptErreurs
            With rptErreurs
                .DataSource = context.Items("erreurs")
                .DataBind()
            End With
        End Sub
    End Class

End Namespace

تعليقات:

  • يتم تهيئة المكون [entete] كالمعتاد، في السطرين 9 و14
  • يتم تهيئة المكون [rptErreurs] باستخدام قائمة الأخطاء من النوع [ArrayList] الموجودة في السياق - الأسطر 16-19

1.5.7. وحدات التحكم global.asax و main.aspx

يبقى الآن كتابة جوهر تطبيق الويب الخاص بنا، وهو وحدة التحكم. وتتمثل مهمتها في:

  • استلام الطلب من العميل،
  • معالجة الإجراء المطلوب من قبل العميل باستخدام فئات الأعمال،
  • إرسال العرض المناسب كرد.

1.5.7.1. وحدة التحكم [global.asax.vb]

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

  • الحقول الخاصة بوحدة التحكم
  • سياق تشغيل التطبيق (Application)

ستقوم الطريقة [Application_Start] الخاصة بالتطبيق [global.asax.vb] بالخطوات التالية:

  • التحقق من وجود المعلمات اللازمة للتشغيل السليم للتطبيق في الملف [web.config]. وقد تم وصف هذه المعلمات في الفقرة 1.5.3.
  • ستضع في سياق التطبيق قائمة بالأخطاء المحتملة في شكل كائن [ArrayList erreurs]. ستكون هذه القائمة فارغة في حالة عدم وجود أخطاء، لكنها ستظل موجودة مع ذلك.
  • في حالة وجود أخطاء، تتوقف الطريقة [Application_Start] عند هذا الحد. وإلا، فإنها تطلب مرجعًا لسينجلتون من النوع [IArticlesDomain] الذي سيكون الكائن الوظيفي الذي سيستخدمه المتحكم لتلبية احتياجاته. وكما تم شرحه في 1.5.3.2، سيطلب وحدة التحكم هذا الكائن الفردي من إطار عمل Spring. وقد تؤدي عملية إنشاء المثيل هذه إلى حدوث أخطاء مختلفة. وإذا كان الأمر كذلك، فسيتم تخزين هذه الأخطاء أيضًا في الكائن [erreurs] الخاص بسياق التطبيق.

يحتوي وحدة التحكم [global.asax.vb] على إجراء [Session_Start] يتم تنفيذه في كل مرة يصل فيها عميل جديد. في هذا الإجراء، سيتم إنشاء سلة تسوق فارغة للعميل. وسيتم الاحتفاظ بهذه السلة على مدار طلبات هذا العميل بالذات. قد يكون الكود كما يلي:

Imports System
Imports System.Web
Imports System.Web.SessionState
Imports System.Configuration
Imports istia.st.articles.domain
Imports System.Collections
Imports Spring.Context

Namespace istia.st.articles.web

    Public Class GlobalWebArticles
        Inherits System.Web.HttpApplication

         ' تفعيل التطبيق
        Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)

             ' البيانات المحلية
            Dim parameters() As String = {"urlMain", "urlErreurs", "urlInfos", "urlListe", "urlPanier", "urlPanierVide"}
            Dim erreurs As New ArrayList

             ' استرداد معلمات تهيئة التطبيق
            Dim param As String
            For i As Integer = 0 To parameters.Length - 1
                 ' قراءة من ملف التكوين
                param = ConfigurationSettings.AppSettings(parameters(i))
                If param Is Nothing Then
                     ' تسجيل الخطأ
                    erreurs.Add("Paramètre [" + parameters(i) + "] absent dans le fichier [web.config]")
                Else
                     ' يتم تخزين المعلمة في التطبيق
                    Application.Item(parameters(i)) = param
                End If
            Next
             ' هل توجد أخطاء؟
            If erreurs.Count = 0 Then
                 ' يتم إنشاء كائن IArticlesDomain للوصول إلى طبقة الأعمال
                Dim contexte As IApplicationContext = CType(ConfigurationSettings.GetConfig("spring/context"), IApplicationContext)
                Dim articlesDomain As IArticlesDomain
                Try
                    articlesDomain = CType(contexte.GetObject("articlesDomain"), IArticlesDomain)
                     ' يتم تخزين الكائن في التطبيق
                    Application.Item("articlesDomain") = articlesDomain
                Catch ex As Exception
                     ' يتم تخزين الخطأ
                    erreurs.Add("Erreur lors de la construction de l'objet d'accès à la couche métier [" + ex.ToString + "]")
                End Try
            End If
             ' يتم وضع الأخطاء في التطبيق
            Application.Item("erreurs") = erreurs
             ' تنتهي العملية في حالة وجود أخطاء
            If erreurs.Count <> 0 Then Return
             ' يتم إنشاء مصفوفة لخيارات القائمة
            Dim options As New Hashtable
             ' يتم استرداد URL من وحدة التحكم
            Dim urlMain As String = CType(Application.Item("urlMain"), String)
            Dim uneOption As Hashtable
             ' قائمة العناصر
            uneOption = New Hashtable
            uneOption.Add("href", urlMain + "?action=liste")
            uneOption.Add("lien", "Liste des articles")
            options.Add("liste", uneOption)
             ' سلة التسوق
            uneOption = New Hashtable
            uneOption.Add("href", urlMain + "?action=panier")
            uneOption.Add("lien", "Voir le panier")
            options.Add("panier", uneOption)
             ' التحقق من سلة التسوق
            uneOption = New Hashtable
            uneOption.Add("href", urlMain + "?action=validationpanier")
            uneOption.Add("lien", "Valider le panier")
            options.Add("validationpanier", uneOption)
             ' يتم إدراج خيارات القائمة في التطبيق
            Application.Item("options") = options
            Return
        End Sub

         ' بدء الجلسة
        Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
             ' إنشاء سلة التسوق للعميل
            Session.Item("panier") = New Panier
        End Sub
    End Class
End Namespace

تعليقات:

  • يتم تعريف المعلمات المتوقعة في [web.config] في مصفوفة - السطر 18
  • يتم البحث عنها في [web.config]. إذا كانت موجودة، يتم تخزينها في سياق التطبيق، وإلا يتم تسجيل خطأ في قائمة الأخطاء [erreurs] - الأسطر 21-33
  • إذا لم تكن هناك أخطاء، يُطلب من Spring مرجعًا للسينجلتون [articlesDomain] الذي يدير الوصول إلى طبقة [domain] للتطبيق - الأسطر 35-47. يتم تسجيل الأخطاء المحتملة في [erreurs].
  • يتم تسجيل الأخطاء في سياق التطبيق - السطر 49
  • يتم الخروج من الإجراء في حالة وجود أخطاء - السطر 51
  • يتم إنشاء مصفوفة مكونة من ثلاثة قواميس. يحتوي كل منها على مفتاحين: href و lien. تمثل هذه المصفوفة الخيارات الثلاثة الممكنة في القائمة - الأسطر 52-71
  • يتم تخزين هذا المصفوف في سياق التطبيق - السطر 73
  • عند كل عميل جديد، يتم تنفيذ الإجراء [Session_Start]. ويتم فيه إنشاء سلة تسوق فارغة في جلسة عمل العميل - الأسطر 78-81

1.5.7.2. وحدة التحكم [main.aspx.vb]

تستقبل وحدة التحكم [main.aspx.vb] جميع طلبات العملاء. ففي الواقع، تأتي جميع هذه الطلبات بالصيغة [/webarticles/main.aspx?action=XX]. ويتم معالجة الطلب بالطريقة التالية:

  • سيتم التحقق من الكائن [erreurs] في سياق التطبيق. إذا كان غير فارغ، فهذا يعني أن أخطاء قد حدثت أثناء تهيئة التطبيق وأنه لا يمكن تشغيله. عندئذٍ سيتم إرسال العرض [ERREURS] كرد.
  • سيتم استرداد المعلمة [action] الخاصة بالطلب والتحقق منها. إذا لم تتطابق مع إجراء معروف، يتم إرسال العرض [ERREURS] مع رسالة خطأ مناسبة.
  • إذا كان المعلمة [action] صالحة، يتم تمرير طلب العميل إلى إجراء خاص بالإجراء المحدد للمعالجة:
الطريقة
الطلب
المعالجة
الردود المحتملة
doListe
GET /main?action=liste
- طلب قائمة المقالات
من فئة الأعمال
- عرضه
[LISTE] أو [ERREURS]
doInfos
GET /main?action=infos&id=ID
- طلب المقالة ذات الرقم التعريفي id=ID من
فئة الأعمال
- عرضه
[INFOS] أو [ERREURS]
doAchat
POST /main?action=achat&id=ID
- الكمية المشتراة جزء من المعلمات المرسلة
- طلب المنتج ذي الرقم التعريفي id=ID من
فئة الأعمال
- إدراجها في سلة التسوق في
جلسة عمل العميل
[LISTE] أو [INFOS] أو [ERREURS]
doRetirerAchat
GET /main?action=retirerachat&id=ID
- سحب المنتج ذي الرقم التعريفي id=ID من
قائمة مشتريات سلة
جلسة عمل العميل
[PANIER]
doPanier
GET /main?action=panier
- عرض سلة التسوق الخاصة بـ
جلسة العميل
[PANIER] أو [PANIERVIDE]
doValidationPanier
GET /main?action=validationpanier
- تخفيض
مخزون جميع المنتجات
الموجودة في سلة
العميل
[LISTE] أو [ERREURS]

قد يكون الهيكل الأساسي لوحدة التحكم [main.aspx.vb] كما يلي:

Imports System.Collections
Imports System
Imports System.Data

Imports istia.st.articles.dao
Imports istia.st.articles.domain

Namespace istia.st.articles.web

     ' فئة وحدة التحكم في تطبيق الويب
    Public Class MainWebArticles
        Inherits System.Web.UI.Page

         ' الحقول الخاصة
        Private articlesDomain As IArticlesDomain
        Private options As Hashtable

         ' تحميل الصفحة
        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
....
        End Sub

         ' طرق معالجة الإجراءات

         ' قائمة المقالات
        Public Sub doListe()
...
        End Sub

         ' معلومات عن أحد المنتجات
        Public Sub doInfos()
...
        End Sub

         ' شراء منتج
        Public Sub doAchat()
...
        End Sub

         ' إلغاء عملية شراء
        Public Sub doRetirerAchat()
...
        End Sub

         ' عرض سلة التسوق
        Public Sub doPanier()
...
        End Sub

         ' شراء محتويات سلة التسوق
        Public Sub doValidationPanier()
...
        End Sub

    End Class

End Namespace

تعليقات:

  • تحتوي الفئة على حقلين خاصين سيتم مشاركتهما بين الطرق - السطور 15-16:
    • articlesDomain: العنصر الفردي للوصول إلى الطبقة [domain]
    • الخيارات: مصفوفة قواميس خيارات القائمة
  • الإجراء [Page_Load]:
    • ستقوم بتهيئة الحقلين الخاصين للفئة
    • سيسترد المعلمة [action] من الاستعلام ويقوم بتنفيذ الأسلوب الخاص بمعالجة هذا الإجراء.

1.5.7.3. الطريقة [Page_Load]

هذا الحدث هو أول ما يحدث على الصفحة. والرمز هو كما يلي:

         ' تحميل الصفحة
        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
             ' التحقق مما إذا كان التطبيق قد تم تشغيله بشكل صحيح
            Dim erreurs As ArrayList = CType(Application.Item("erreurs"), ArrayList)
             ' في حالة وجود أخطاء، يتم إرسال العرض [erreurs]
            If erreurs.Count <> 0 Then
                context.Items("erreurs") = erreurs
                context.Items("options") = New Hashtable() {}
                Server.Transfer(CType(Application("urlErreurs"), String))
            End If
             ' يتم استرداد كائن الوصول إلى فئة الأعمال
            articlesDomain = CType(Application.Item("articlesDomain"), IArticlesDomain)
             ' بالإضافة إلى خيارات القائمة
            options = CType(Application.Item("options"), Hashtable)
             ' يتم استرداد الإجراء المطلوب تنفيذه
            Dim action As String = Request.QueryString("action")
            If action Is Nothing Then
                action = "liste"
            End If
             ' يتم تنفيذ الإجراء
            Select Case action
                Case "liste"
                    doListe()
                Case "infos"
                    doInfos()
                Case "achat"
                    doAchat()
                Case "panier"
                    doPanier()
                Case "retirerachat"
                    doRetirerAchat()
                Case "validationpanier"
                    doValidationPanier()
                Case Else
                    doListe()
            End Select
        End Sub

تعليقات:

  • عند كل عملية تحميل للصفحة، نتأكد من أن تهيئة التطبيق التي تتم بواسطة [global.asax] قد تمت بنجاح.
  • ولذلك، يتم استرداد قائمة الأخطاء الموجودة في سياق التطبيق، والتي تم وضعها هناك بواسطة [global.asax] - السطر 4
  • إذا كانت هذه القائمة غير فارغة، يتم عرض العرض [ERREURS] - الأسطر 6-10
  • نسترد العنصر الفريد [articlesDomain] الذي وضعته [global.asax] في سياق التطبيق ونخزنه في الحقل الخاص [articlesDomain] حتى يكون متاحًا لمختلف أساليب الفئة - السطر 12
  • نقوم بعمل مماثل مع مصفوفة خيارات القائمة - السطر 14
  • يتم استرداد المعلمة [action] من الطلب - السطر 16
  • نقوم بتنفيذ الطريقة المطابقة للإجراء المطلوب. ويُعتبر الإجراء غير المتوقع هو الإجراء [liste] - الأسطر 16-36

1.5.7.4. معالجة الإجراء [liste]

يتعلق الأمر بعرض قائمة المواد:

Image

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

         ' قائمة العناصر
        Public Sub doListe()
             ' إدارة الأخطاء
            Dim erreurs As New ArrayList
             ' يتم طلب قائمة المقالات
            Dim articles As IList
            Try
                articles = articlesDomain.getAllArticles
            Catch ex As Exception
                 ' تسجيل الخطأ
                erreurs.Add(ex.ToString)
            End Try
             ' هل توجد أخطاء؟
            If erreurs.Count <> 0 Then
                 ' عرض طريقة العرض [erreurs]
                context.Items("erreurs") = erreurs
                context.Items("options") = New Hashtable() {CType(options("liste"), Hashtable)}
                Server.Transfer(CType(Application.Item("urlErreurs"), String))
            End If
             ' عرض طريقة عرض [liste]
            context.Items("articles") = articles
            context.Items("options") = New Hashtable() {CType(options("panier"), Hashtable)}
            If context.Items("message") Is Nothing Then context.Items("message") = ""
            Server.Transfer(CType(Application.Item("urlListe"), String))
        End Sub

تعليقات:

  • يتم وضع الأخطاء المحتملة في [ArrayList] - السطر 4
  • يتم طلب قائمة العناصر من السجل الفردي [articlesDomain] - الأسطر 5-12
  • في حالة وجود أخطاء، يتم إرسال العرض [ERREURS] - الأسطر 13-19
  • وإلا يتم إرسال العرض [LISTE] - الأسطر 20-24

1.5.7.5. معالجة الإجراء [infos]

طلب العميل معلومات عن منتج معين:

Image

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

         ' معلومات عن منتج
        Public Sub doInfos()
             ' إدارة الأخطاء
            Dim erreurs As New ArrayList
             ' استرداد معرّف المقالة المطلوبة
            Dim strId As String = Request.QueryString("id")
             ' هل لدينا أي شيء؟
            If strId Is Nothing Then
                 ' غير طبيعي - يتم إرسال صفحة الأخطاء
                erreurs.Add("action incorrecte (action=infos, id=rien)")
                context.Items("erreurs") = erreurs
                context.Items("options") = New Hashtable() {CType(options("liste"), Hashtable)}
                Server.Transfer(CType(Application.Item("urlErreurs"), String))
                Exit Sub
            End If
             ' هل لدينا عدد صحيح؟
            Dim id As Integer
            Try
                id = Integer.Parse(strId)
            Catch ex As Exception
                 ' غير طبيعي - يتم إرسال صفحة الأخطاء
                erreurs.Add("action incorrecte (action=infos, id[" + strId + "] invalide)")
                context.Items("erreurs") = erreurs
                context.Items("options") = New Hashtable() {CType(options("liste"), Hashtable)}
                Server.Transfer(CType(Application.Item("urlErreurs"), String))
                Exit Sub
            End Try
             ' يتم طلب السجل ذي معرف المفتاح
            Dim unArticle As Article
            Try
                unArticle = articlesDomain.getArticleById(id)
            Catch ex As Exception
                 ' مشكلة في الوصول إلى البيانات
                erreurs.Add("Problème d'accès aux données (" + ex.ToString + ")")
                context.Items("erreurs") = erreurs
                context.Items("options") = New Hashtable() {CType(options("liste"), Hashtable)}
                Server.Transfer(CType(Application.Item("urlErreurs"), String))
                Exit Sub
            End Try
             ' هل تم استرداد السجل؟
            If unArticle Is Nothing Then
                 ' العنصر غير موجود
                erreurs.Add("L'article d'id=" + id.ToString + " n'existe pas")
                context.Items("erreurs") = erreurs
                context.Items("options") = New Hashtable() {CType(options("liste"), Hashtable)}
                Server.Transfer(CType(Application.Item("urlErreurs"), String))
                Exit Sub
            End If
             ' العنصر موجود - يتم إدراجه في الجلسة الحالية
            Session.Item("article") = unArticle
             ' يتم التحضير لعرضه
            context.Items("options") = New Hashtable() {CType(options("liste"), Hashtable)}
            context.Items("msg") = ""
            context.Items("qte") = ""
            Server.Transfer(CType(Application.Item("urlInfos"), String))
        End Sub

تعليقات:

  • يتم وضع الأخطاء المحتملة في [ArrayList] - السطر 4
  • يتم استرداد معرّف المقالة المطلوبة من الاستعلام - السطر 6
  • يتم التحقق من هذا المعرف. يجب أن يكون موجودًا ويجب أن يكون عددًا صحيحًا. إذا لم يكن الأمر كذلك، يتم إرسال العرض [ERREURS] مع رسالة الخطأ المناسبة - الأسطر 7-27
  • بمجرد التحقق من المعرف، يتم طلب العنصر من الكائن الفردي [articlesDomain]. في حالة حدوث استثناء، يتم إرسال العرض [ERREURS] - الأسطر 29-39
  • إذا لم يتم العثور على العنصر، يتم إرسال عرض [ERREURS] - الأسطر 41-48
  • إذا تم العثور على المقالة، يتم وضعها في جلسة عمل المستخدم ثم عرضها في عرض [INFOS] - الأسطر 50-56

1.5.7.6. معالجة الإجراء [achat]

اشترى العميل المادة المعروضة في عرض [INFOS].

Image

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

         ' شراء منتج
        Public Sub doAchat()
             ' شراء منتج
             ' استرداد المنتج الذي كان موجودًا في الجلسة
            Dim unArticle As Article = CType(Session.Item("article"), Article)
             ' هل لدينا أي شيء؟
            If unArticle Is Nothing Then
                 ' غير طبيعي - يتم عرض قائمة العناصر
                doListe()
            End If
             ' استرداد الكمية التي تم نشرها
            Dim strQte As String = Request.Form("txtQte")
             ' هل لدينا أي شيء؟
            If strQte Is Nothing Then
                 ' غير طبيعي - يتم إرسال قائمة الأصناف
                doListe()
            End If
             ' هل لدينا عدد صحيح؟
            Dim qte As Integer
            Try
                qte = Integer.Parse(strQte)
                If (qte <= 0) Then Throw New Exception
            Catch ex As Exception
                 ' غير طبيعي - يتم إرسال صفحة المعلومات مع رسالة الخطأ
                context.Items("options") = New Hashtable() {CType(options("liste"), Hashtable)}
                context.Items("msg") = "Quantité incorrecte"
                context.Items("qte") = strQte
                Server.Transfer(CType(Application.Item("urlInfos"), String))
            End Try
             ' كل شيء على ما يرام - نضيف المشتريات إلى سلة التسوق الخاصة بالعميل
            Dim unPanier As Panier = CType(Session.Item("panier"), Panier)
            unPanier.ajouter(New Achat(unArticle, qte))
             ' يتم عرض قائمة المنتجات
            doListe()
        End Sub

تعليقات:

  • يتم استرداد المنتج المضاف إلى الجلسة - السطر 5
  • وإذا لم تكن موجودة (ربما انتهت صلاحية الجلسة)، يتم عرض العرض [LISTE] - الأسطر 7-10
  • يتم استرداد الكمية المشتراة من الاستعلام - السطر 12
  • يتم التحقق من صلاحيتها - الأسطر 13-29
  • في حالة عدم الصلاحية، يتم إرسال عرض [LISTE] - السطر 16 أو عرض [INFOS] - الأسطر 24-28، حسب الحالة
  • إذا كان كل شيء طبيعيًا، يتم تسجيل الشراء في سلة التسوق - الأسطر 31-32
  • ثم يتم إرسال العرض [LISTE] - السطر 34

1.5.7.7. معالجة الإجراء [panier]

قام العميل بعدة عمليات شراء ويطلب الاطلاع على سلة التسوق:

Image

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

         ' عرض سلة التسوق
        Public Sub doPanier()
             'يتم استرداد سلة التسوق من الجلسة
            Dim unPanier As Panier = CType(Session.Item("panier"), Panier)
             ' سلة التسوق فارغة؟
            If unPanier.achats.Count = 0 Then
                 ' عرض سلة التسوق الفارغة
                context.Items("options") = New Hashtable() {CType(options("liste"), Hashtable)}
                Server.Transfer(CType(Application.Item("urlPanierVide"), String))
            End If
             ' عرض سلة التسوق غير الفارغة
            context.Items("options") = New Hashtable() {CType(options("liste"), Hashtable), CType(options("validationpanier"), Hashtable)}
            Server.Transfer(CType(Application.Item("urlPanier"), String))
        End Sub

تعليقات:

  • يتم استرداد سلة التسوق من الجلسة - السطر 4. لا يتم هنا التحقق من استرداد شيء ما. يجب القيام بذلك لأن الجلسة قد تكون انتهت صلاحيتها.
  • إذا كانت سلة التسوق فارغة، يتم إرسال العرض [PANIERVIDE] - الأسطر 6-10
  • وإلا يتم إرسال العرض [PANIER] - الأسطر 11-14

1.5.7.8. معالجة الإجراء [retirerachat]

يرغب العميل في إزالة منتج من سلة التسوق:

Image

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

         ' حذف عملية شراء
        Public Sub doRetirerAchat()
             'استرداد سلة التسوق من الجلسة
            Dim unPanier As Panier = CType(Session.Item("panier"), Panier)
             ' إزالة المنتج
            Try
                 ' استرداد معرّف المنتج الذي تم سحبه
                Dim idArticle As Integer = Integer.Parse(Request.QueryString("id"))
                 ' إزالته من سلة التسوق
                unPanier.enlever(idArticle)
            Catch ex As Exception
                 ' عرض قائمة المنتجات
                doListe()
            End Try
             ' عرض سلة التسوق
            doPanier()
        End Sub

تعليقات:

  • يتم استرداد سلة التسوق من الجلسة - السطر 4. لا يتم هنا التحقق من استرداد شيء ما بالفعل. يجب القيام بذلك لأن الجلسة قد تكون انتهت صلاحيتها.
  • يتم استرداد المعرف [id] للمنتج المراد إزالته من الاستعلام - السطر 8.
  • يتم حذف عملية الشراء المقابلة من سلة التسوق - السطر 10
  • لم يتم التحقق هنا من صحة معرّف المنتج الذي تم شراؤه. إذا كان نوعه غير صالح، فستحدث استثناء وسيتم معالجته في الأسطر 11-13. إذا كان صالحًا ولكنه غير موجود، فإن الطريقة [panier.enlever] - السطر 10 - لا تقوم بأي شيء.
  • يتم عرض سلة التسوق الجديدة - السطر 16

1.5.7.9. معالجة الإجراء [validerpanier]

يرغب العميل في تأكيد سلة التسوق:

Image

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

         ' شراء سلة التسوق
        Public Sub doValidationPanier()
             ' في البداية، لا توجد أخطاء
            Dim erreurs As ArrayList
             'يتم استرداد سلة التسوق من الجلسة
            Dim unPanier As Panier = CType(Session.Item("panier"), Panier)
             ' محاولة تأكيد سلة التسوق
            Try
                articlesDomain.acheter(unPanier)
                 ' تسجيل الأخطاء المحتملة في عملية الشراء
                erreurs = articlesDomain.erreurs
            Catch ex As Exception
                 ' يتم تسجيل الخطأ
                erreurs = New ArrayList
                erreurs.Add(String.Format("Erreur lors de la validation du panier [{0}]", ex.Message))
            End Try
             ' في حالة وجود أخطاء، يتم عرض صفحة الأخطاء
            If erreurs.Count <> 0 Then
                context.Items("erreurs") = erreurs
                context.Items("options") = New Hashtable() {CType(options("liste"), Hashtable), CType(options("panier"), Hashtable)}
                Server.Transfer(CType(Application.Item("urlErreurs"), String))
                Exit Sub
            End If
             ' كل شيء على ما يرام - يتم عرض قائمة المنتجات مع رسالة تفيد بنجاح العملية
            context.Items("message") = "Votre panier a été validé"
            doListe()
        End Sub

تعليقات:

  • نسترد سلة التسوق من الجلسة - السطر 6. لا نتحقق هنا من أننا استردنا شيئًا بالفعل. يجب القيام بذلك لأن الجلسة قد تكون انتهت صلاحيتها.
  • في حالة انتهاء صلاحية الجلسة، سيكون لدينا مؤشر [nothing] لسلة التسوق، وستقوم الطريقة [acheter] - السطر 9 - بإطلاق استثناء وسيتم إرسال العرض [ERREURS]. إلا أن رسالة الخطأ لن تكون واضحة تمامًا للمستخدم.
  • في الأسطر 8-16، نحاول التحقق من صحة سلة التسوق المستردة من الجلسة. قد لا يتم تأكيد بعض عمليات الشراء إذا تجاوزت الكمية المطلوبة المخزون المتوفر من المنتج المطلوب. يتم تسجيل هذه الحالات بواسطة الأسلوب [acheter] في قائمة أخطاء يتم استردادها في السطر 11.
  • في حالة وجود أخطاء، يتم إرسال العرض [ERREURS] - الأسطر 18-23
  • وإلا يتم إرسال عرض [LISTE] - الأسطر 25-26

1.6. Conclusion

لقد قمنا هنا بتطوير تطبيق وفقًا لنموذج MVC. لا يبدو أن هناك (أبريل 2005) «أطر عمل» احترافية لتطوير MVC في ASP.NET مثل تلك الموجودة في Java (Struts، Spring، ...). ومن المتوقع أن يقدم مشروع [Spring.net] إطارًا من هذا القبيل قريبًا. وفي انتظار ظهور ذلك، تتيح الطريقة السابقة تطويرًا عمليًّا لـ MVC للتطبيقات متوسطة الحجم.