Skip to content

1. الجزء 1

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

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

1.1. مقدمة

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

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

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

  • Visual Studio.NET للتطوير — انظر الملحق، القسم 3.1؛
  • خادم الويب Cassini للتنفيذ — انظر الملحق، القسم 3.2؛
  • Nunit لاختبار الوحدات — انظر الملحق، القسم 3.4؛
  • Spring للتكامل وتكوين طبقات تطبيق الويب — انظر الملحق، القسم 3.3؛

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

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

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

1.2. تطبيق webarticles

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

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

وستكون الشاشات المختلفة التي ستظهر للمستخدم على النحو التالي:

- عرض "القائمة"، الذي يعرض قائمة بالعناصر المعروضة للبيع
- عرض [INFO]، الذي يوفر معلومات إضافية عن المنتج:
  • طريقتا العرض [CART] و [EMPTY CART]، اللتان تعرضان محتويات سلة التسوق الخاصة بالعميل
  • عرض [ERRORS]، الذي يبلغ عن أي أخطاء في التطبيق

Image

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

نريد إنشاء تطبيق بالهيكل ثلاثي المستويات التالي:

  • تتمتع الطبقات الثلاث بالاستقلالية من خلال استخدام الواجهات
  • يتم التعامل مع تكامل الطبقات المختلفة بواسطة Spring
  • لكل طبقة مساحة أسماء خاصة بها: web (طبقة واجهة المستخدم)، domain (طبقة الأعمال)، و 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
);
/* constraints */
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);
/* primary key */
ALTER TABLE ARTICLES ADD CONSTRAINT PK_ARTICLES PRIMARY KEY (ID);
id
المفتاح الأساسي الذي يحدد العنصر بشكل فريد
الاسم
اسم العنصر
السعر
سعره
المخزون الحالي
المخزون الحالي
الحد الأدنى للمخزون
مستوى المخزون الذي يجب عنده إعادة الطلب

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

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

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

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

assembly
المحتوى
role
مقالات الويب-dao
- [IArticlesDao]: الواجهة المستخدمة للوصول إلى طبقة [dao].
هذه هي الواجهة الوحيدة المرئية لطبقة [domain]. ولا ترى أي واجهات أخرى.
- [Article]: فئة تحدد المقالة
- [ArticlesDaoArrayList]: فئة تنفيذ
واجهة [IArticlesDao] باستخدام [ArrayList]
طبقة الوصول إلى البيانات
- تقع بالكامل ضمن
طبقة [DAO] في بنية ثلاثية المستويات
تطبيق الويب
webarticles-domain
- [IArticlesDomain]: الواجهة للوصول إلى طبقة [domain]. هذه هي الواجهة الوحيدة المرئية لطبقة الويب. ولا ترى أي واجهات أخرى.
- [AchatsArticles]: فئة تنفذ [IArticlesDomain]
- [Purchase]: فئة تمثل مشتريات العميل
- [Cart]: فئة تمثل إجمالي مشتريات العميل
تمثل نموذج مشتريات الويب
الويب - موجود بالكامل في
طبقة [domain] في
البنية ثلاثية المستويات لتطبيق الويب

1.4.3. طبقة [DAO]

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

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

  • [Article]: فئة تحدد المقالة

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

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

Image

تعليقات:

  • مشروع [dao] هو من نوع [مكتبة الفئات]
  • تم وضع الفئات في بنية شجرية تبدأ من المجلد [istia]. وهي جميعها موجودة في مساحة الاسم [istia.st.articles.dao].

1.4.3.1. فئة [Article]

الفئة التي تحدد المقالة هي كما يلي:

Imports System

Namespace istia.st.articles.dao

    Public Class Article

        ' private fields
        Private _id As Integer
        Private _nom As String
        Private _prix As Double
        Private _stockactuel As Integer
        Private _stockminimum As Integer

        ' id article
        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

        ' item name
        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

        ' item price
        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

        ' current stock item
        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

        ' minimum stock item
        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

        ' default builder
        Public Sub New()
        End Sub

        ' builder with properties
        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

        ' article identification method
        Public Overrides Function ToString() As String
            Return "[" + id.ToString + "," + nom + "," + prix.ToString + "," + stockactuel.ToString + "," + stockminimum.ToString + "]"
        End Function
    End Class
End Namespace

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

  1. منشئًا لتعيين 5 معلومات عن عنصر ما: [id، name، price، currentStock، minimumStock]
  2. خصائص عامة لقراءة وكتابة المعلومات الخمس.
  3. التحقق من صحة البيانات التي تم إدخالها للعنصر. إذا كانت البيانات غير صالحة، يتم إصدار استثناء.
  4. طريقة toString التي تُرجع قيمة عنصر ما كسلسلة نصية. وغالبًا ما يكون ذلك مفيدًا في تصحيح أخطاء التطبيق.

1.4.3.2. واجهة [IArticlesDao]

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

Imports System
Imports System.Collections

Namespace istia.st.articles.dao

    Public Interface IArticlesDao
        ' list of all items
        Function getAllArticles() As IList
        ' add an article
        Function ajouteArticle(ByVal unArticle As Article) As Integer
        ' deletes an article
        Function supprimeArticle(ByVal idArticle As Integer) As Integer
        ' modify an article
        Function modifieArticle(ByVal unArticle As Article) As Integer
        ' search for an article
        Function getArticleById(ByVal idArticle As Integer) As Article
        ' deletes all articles
        Sub clearAllArticles()
        ' changes the stock of an item
        Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer
    End Interface
End Namespace

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

getAllArticles
تُرجع جميع العناصر من مصدر البيانات
clearAllArticles
مسح مصدر البيانات
getArticleById
إرجاع كائن [Article] المحدد بواسطة مفتاحه الأساسي
addArticle
تسمح لك بإضافة مقال إلى مصدر البيانات
modifyArticle
يسمح لك بتعديل مقال في مصدر البيانات
deleteItem
يسمح لك بحذف عنصر من مصدر البيانات
updateItemStock
يسمح لك بتعديل مخزون عنصر في مصدر البيانات

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

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

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

        ' default builder
        Public Sub New()
            ' we build a few items
            For i As Integer = 1 To nbArticles
                articles.Add(New Article(i, "article" + i.ToString, i * 10, i * 10, i * 10))
            Next
        End Sub

        ' list of all items
        Public Function getAllArticles() As IList Implements IArticlesDao.getAllArticles
            ' returns the list of items
            SyncLock Me
                Return articles
            End SyncLock
        End Function

        ' delete all items
        Public Sub clearAllArticles() Implements IArticlesDao.clearAllArticles
            ' empty the list of items
            SyncLock Me
                articles.Clear()
            End SyncLock
        End Sub

        ' obtain an item identified by its key
        Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDao.getArticleById
            ' search for the item in the list
            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

        ' add an item to the list of items
        Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
            ' add the item to the list of items
            SyncLock Me
                ' we check that it doesn't already exist
                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
                ' we add the article
                articles.Add(unArticle)
                ' we return the result
                Return 1
            End SyncLock
        End Function

        ' modify an article
        Public Function modifieArticle(ByVal articleNouveau As Article) As Integer Implements IArticlesDao.modifieArticle
            ' modify an article
            SyncLock Me
                ' we check that
                Dim ipos As Integer = posArticle(articles, articleNouveau.id)
                ' if it doesn't exist
                If ipos = -1 Then Return 0
                ' it exists - we modify it
                articles(ipos) = articleNouveau
                ' we return the result
                Return 1
            End SyncLock
        End Function

        ' delete an item identified by its key
        Public Function supprimeArticle(ByVal idArticle As Integer) As Integer Implements IArticlesDao.supprimeArticle
            ' article deletion
            SyncLock Me
                ' we check that
                Dim ipos As Integer = posArticle(articles, idArticle)
                ' if it doesn't exist
                If ipos = -1 Then Return 0
                ' it exists - we remove it
                articles.RemoveAt(ipos)
                ' we return the result
                Return 1
            End SyncLock
        End Function

        ' change the stock of an item identified by its key
        Public Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer Implements IArticlesDao.changerStockArticle
            ' change the stock of an item
            SyncLock Me
                ' we check that
                Dim ipos As Integer = posArticle(articles, idArticle)
                ' if it doesn't exist
                If ipos = -1 Then Return 0
                ' it exists - you modify your stock if you can
                Dim unArticle As Article = CType(articles(ipos), Article)
                ' only change stock if it is sufficient
                If unArticle.stockactuel + mouvement >= 0 Then
                    unArticle.stockactuel += mouvement
                    Return 1
                Else
                    Return 0
                End If
            End SyncLock
        End Function

        ' search for an item identified by its key
        Private Function posArticle(ByVal listArticles As ArrayList, ByVal idArticle As Integer) As Integer
            ' returns the position of item [idArticle] in the list or -1 if not found
            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
            ' not found
            Return -1
        End Function
    End Class

End Namespace

تعليقات:

  • يتم محاكاة مصدر البيانات بواسطة الحقل الخاص [articles] من النوع [ArrayList]
  • يقوم منشئ الفئة بإنشاء 4 عناصر في مصدر البيانات بشكل افتراضي.
  • تمت مزامنة جميع طرق الوصول إلى البيانات لمنع مشاكل الوصول المتزامن إلى مصدر البيانات. في أي وقت معين، لا يمكن سوى لخيط واحد الوصول إلى طريقة معينة.
  • تُرجع طريقة [posArticle] الموضع [0..N] في مصدر البيانات [ArrayList] لمقال محدد برقمه. إذا كان المقال غير موجود، تُرجع الطريقة الموضع -1. تُستخدم هذه الطريقة بشكل متكرر من قبل الطرق الأخرى.
  • تضيف طريقة [addArticle] مقالًا إلى قائمة المقالات. وتُرجع عدد المقالات التي تم إدراجها: 1. إذا كان المقال موجودًا بالفعل، يتم إصدار استثناء.
  • تسمح لك الطريقة [modifyItem] بتعديل عنصر موجود. وتُرجع عدد العناصر التي تم تعديلها: 1 إذا كان العنصر موجودًا، و0 في حالة عدم وجوده.
  • تحذف الطريقة [deleteArticle] مقالًا موجودًا. وتُرجع عدد المقالات المحذوفة: 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] هو [مكتبة فئات]
  • تتطلب اختبارات [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
        ' the test object
        Private articlesDao As IArticlesDao

        <SetUp()> _
        Public Sub init()
            ' retrieve an instance of the Spring object manufacturer
            Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config.xml", FileMode.Open))
            ' request instantiation of articlesdao object
            articlesDao = CType(factory.GetObject("articlesdao"), IArticlesDao)
        End Sub

        <Test()> _
        Public Sub testGetAllArticles()
            ' visual check
            listArticles()
        End Sub

        <Test()> _
        Public Sub testClearAllArticles()
            ' delete all articles
            articlesDao.clearAllArticles()
            ' all articles are requested
            Dim articles As IList = articlesDao.getAllArticles
            ' verification: there must be 0
            Assert.AreEqual(0, articles.Count)
        End Sub

        <Test()> _
        Public Sub testAjouteArticle()
            ' delete all items
            articlesDao.clearAllArticles()
            ' check: the item table must be empty
            Dim articles As IList = articlesDao.getAllArticles
            Assert.AreEqual(0, articles.Count)
            ' we add two items
            articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
            articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
            ' check: there must be two items
            articles = articlesDao.getAllArticles
            Assert.AreEqual(2, articles.Count)
            ' visual check
            listArticles()
        End Sub

        <Test()> _
        Public Sub testSupprimeArticle()
            ' delete all items
            articlesDao.clearAllArticles()
            ' check: the item table must be empty
            Dim articles As IList = articlesDao.getAllArticles
            Assert.AreEqual(0, articles.Count)
            ' we add two items
            articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
            articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
            ' check: there must be 2 items
            articles = articlesDao.getAllArticles
            Assert.AreEqual(2, articles.Count)
            ' we delete article 4
            articlesDao.supprimeArticle(4)
            ' check: there must be 1 item left
            articles = articlesDao.getAllArticles
            Assert.AreEqual(1, articles.Count)
            ' visual check
            listArticles()
        End Sub

        <Test()> _
        Public Sub testModifieArticle()
            ' delete all items
            articlesDao.clearAllArticles()
            ' check
            Dim articles As IList = articlesDao.getAllArticles
            Assert.AreEqual(0, articles.Count)
            ' 2 items added
            articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
            articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
            ' check
            articles = articlesDao.getAllArticles
            Assert.AreEqual(2, articles.Count)
            ' article 3 search
            Dim unArticle As Article = articlesDao.getArticleById(3)
            ' check
            Assert.AreEqual(unArticle.nom, "article3")
            ' research article 4
            unArticle = articlesDao.getArticleById(4)
            ' check
            Assert.AreEqual(unArticle.nom, "article4")
            ' modification article 4
            articlesDao.modifieArticle(New Article(4, "article4", 44, 44, 44))
            ' check
            unArticle = articlesDao.getArticleById(4)
            Assert.AreEqual(unArticle.prix, 44, 0.000001)
            ' visual check
            listArticles()
        End Sub

        <Test()> _
        Public Sub testGetArticleById()
            ' article deletion
            articlesDao.clearAllArticles()
            ' check
            Dim articles As IList = articlesDao.getAllArticles
            Assert.AreEqual(0, articles.Count)
            ' 2 items added
            articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
            articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
            ' check
            articles = articlesDao.getAllArticles
            Assert.AreEqual(2, articles.Count)
            ' research article 3
            Dim unArticle As Article = articlesDao.getArticleById(3)
            ' check
            Assert.AreEqual(unArticle.nom, "article3")
            ' research article 4
            unArticle = articlesDao.getArticleById(4)
            ' check
            Assert.AreEqual(unArticle.nom, "article4")
        End Sub

        ' screen listing
        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()
            ' delete all items
            articlesDao.clearAllArticles()
            ' research article 1
            Dim article As article = articlesDao.getArticleById(1)
            ' check
            Assert.IsNull(article)
            ' modification of a non-existent item
            Dim i As Integer = articlesDao.modifieArticle(New article(1, "1", 1, 1, 1))
            ' had to modify no line
            Assert.AreEqual(i, 0)
            ' deletion of non-existent item
            i = articlesDao.supprimeArticle(1)
            ' had to delete no line
            Assert.AreEqual(0, i)
        End Sub

        <Test()> _
        Public Sub testChangerStockArticle()
            ' delete all items
            articlesDao.clearAllArticles()
            ' add an item
            Dim nbArticles As Integer = articlesDao.ajouteArticle(New Article(3, "article3", 30, 101, 3))
            Assert.AreEqual(nbArticles, 1)
            ' add an item
            nbArticles = articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
            Assert.AreEqual(nbArticles, 1)
            ' creation of 100 threads
            Dim taches(99) As Thread
            For i As Integer = 0 To taches.Length - 1
                ' create thread i
                taches(i) = New Thread(New ThreadStart(AddressOf décrémente))
                ' set the thread name
                taches(i).Name = "tache_" & i
                ' start execution of thread i
                taches(i).Start()
            Next
            ' wait for all threads to finish
            For i As Integer = 0 To taches.Length - 1
                taches(i).Join()
            Next
            ' checks - item 3 must have a stock of 1
            Dim unArticle As Article = articlesDao.getArticleById(3)
            Assert.AreEqual(unArticle.nom, "article3")
            Assert.AreEqual(1, unArticle.stockactuel)
            ' item 4 stock is decremented
            Dim erreur As Boolean = False
            Dim nbLignes As Integer = articlesDao.changerStockArticle(4, -100)
            ' check: its stock must not have changed
            Assert.AreEqual(0, nbLignes)
            ' visual check
            listArticles()
        End Sub

        Public Sub décrémente()
            ' thread launched
            System.Console.Out.WriteLine(Thread.CurrentThread.Name + " lancé")
            ' thread decrements stock
            articlesDao.changerStockArticle(3, -1)
            ' thread terminated
            System.Console.Out.WriteLine(Thread.CurrentThread.Name + " terminé")
        End Sub

    End Class
End Namespace

تعليقات:

  • أردنا كتابة برنامج اختبار لواجهة [IArticlesDao] يكون مستقلاً عن فئة التنفيذ الخاصة بها. لذلك، استخدمنا Spring لإخفاء اسم فئة التنفيذ عن برنامج الاختبار.
  • تسترد طريقة <Setup()> مرجعًا إلى كائن [articlesdao] المراد اختباره من Spring. ويتم تعريف ذلك في ملف [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()
            ' delete all items
            articlesDao.clearAllArticles()
            ' add an item
            Dim nbArticles As Integer = articlesDao.ajouteArticle(New Article(3, "article3", 30, 101, 3))
            Assert.AreEqual(nbArticles, 1)
            ' add an item
            nbArticles = articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
            Assert.AreEqual(nbArticles, 1)
            ' creation of 100 threads
            Dim taches(99) As Thread
            For i As Integer = 0 To taches.Length - 1
                ' create thread i
                taches(i) = New Thread(New ThreadStart(AddressOf décrémente))
                ' set the thread name
                taches(i).Name = "tache_" & i
                ' start execution of thread i
                taches(i).Start()
            Next
            ' wait for all threads to finish
            For i As Integer = 0 To taches.Length - 1
                taches(i).Join()
            Next
            ' checks - item 3 must have a stock of 1
            Dim unArticle As Article = articlesDao.getArticleById(3)
            Assert.AreEqual(unArticle.nom, "article3")
            Assert.AreEqual(1, unArticle.stockactuel)
            ' item 4 stock is decremented
            Dim erreur As Boolean = False
            Dim nbLignes As Integer = articlesDao.changerStockArticle(4, -100)
            ' check: its stock must not have changed
            Assert.AreEqual(0, nbLignes)
            ' visual check
            listArticles()
        End Sub

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

        Public Sub décrémente()
            ' thread launched
            System.Console.Out.WriteLine(Thread.CurrentThread.Name + " lancé")
            ' thread decrements stock
            articlesDao.changerStockArticle(3, -1)
            ' thread terminated
            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]

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

  • [ShoppingCart]: فئة تحدد عربة التسوق

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

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

Image

تعليقات:

  • مشروع [domain] هو من نوع [مكتبة الفئات]
  • تم وضع الفئات في بنية شجرية تبدأ من المجلد [istia]. وهي جميعها موجودة في مساحة الاسم [istia.st.articles.domain].
  • تم وضع ملف DLL الخاص بطبقة [dao] في مجلد [bin] الخاص بالمشروع الجديد. بالإضافة إلى ذلك، تمت إضافة ملف DLL هذا كمرجع للمشروع.

1.4.4.1. واجهة [IArticlesDomain]

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

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

Namespace istia.st.articles.domain
    Public Interface IArticlesDomain
        ' methods
        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
الدالة getAllArticles() كـ IList
تُرجع قائمة كائنات [Article] من مصدر البيانات المرتبط
الدالة getArticleById(ByVal idArticle As Integer) As Article
تُرجع كائن [Article] المحدد بواسطة [idArticle]
buy(ByVal cart As Cart)
يعالج سلة التسوق الخاصة بالعميل عن طريق خصم كمية المشتريات من المخزون - قد يفشل إذا كان المخزون غير كافٍ
خاصية ReadOnly errors() As ArrayList
تُرجع قائمة بالأخطاء التي حدثت - تكون فارغة في حالة عدم وجود أخطاء

1.4.4.2. فئة [Purchase]

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

Imports istia.st.articles.dao

Namespace istia.st.articles.domain

    Public Class Achat

        ' private fields
        Private _article As article
        Private _qte As Integer

        ' default builder
        Public Sub New()
        End Sub

        ' builder with parameters
        Public Sub New(ByVal unArticle As article, ByVal qte As Integer)
            ' we go through the properties
            Me.article = unArticle
            Me.qte = qte
        End Sub

        ' item purchased
        Public Property article() As article
            Get
                Return _article
            End Get
            Set(ByVal Value As article)
                _article = Value
            End Set
        End Property

        ' qty purchased
        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

        ' total purchase
        Public ReadOnly Property totalAchat() As Double
            Get
                Return _qte * _article.prix
            End Get
        End Property

        ' identity
        Public Overrides Function ToString() As String
            Return "[" + _article.ToString + "," + _qte.ToString + "]"
        End Function
    End Class

End Namespace

تعليقات:

  • تحتوي فئة [Purchase] على الخصائص والطرق التالية:
خاصية عامة item() كـ item
العنصر الذي تم شراؤه
خاصية عامة qty() كعدد صحيح
الكمية المشتراة
خاصية عامة للقراءة فقط purchaseTotal() كعدد مزدوج
مبلغ الشراء
العام Overrides Function ToString() As String
تمثيل الكائن كسلسلة
  • يحتوي على منشئ يقوم بتهيئة خصائص [item, qty] التي تحدد عملية الشراء.

1.4.4.3. فئة [Cart]

تمثل فئة [Cart] إجمالي مشتريات العميل:

Namespace istia.st.articles.domain

Public Class Panier

    ' private fields
    Private _achats As New ArrayList
        Private _totalPanier As Double = 0

    ' default builder
    Public Sub New()
    End Sub

    ' list of purchases
    Public ReadOnly Property achats() As ArrayList
        Get
            Return _achats
        End Get
    End Property

    ' total purchases
    Public ReadOnly Property totalPanier() As Double
        Get
            Return _totalPanier
        End Get
    End Property

    ' methods
    Public Sub ajouter(ByVal unAchat As Achat)
        ' find out if the purchase already exists
        Dim iAchat As Integer = posAchat(unAchat.article.id)
        If iAchat <> -1 Then
            ' we found
                Dim achatCourant As Achat = CType(_achats(iAchat), Achat)
            achatCourant.qte += unAchat.qte
        Else
            ' we didn't find
            _achats.Add(unAchat)
        End If
        ' increment the basket total
        _totalPanier += unAchat.totalAchat
    End Sub

    ' remove a purchase
    Public Sub enlever(ByVal idAchat As Integer)
        ' we're looking to buy
            Dim iachat As Integer = posAchat(idAchat)
        ' if found, remove
        If iachat <> -1 Then
                Dim achatCourant As Achat = CType(_achats(iachat), Achat)
            ' remove from basket
            _achats.RemoveAt(iachat)
            ' decrement the basket total
            _totalPanier -= achatCourant.totalAchat
        End If
    End Sub

    Private Function posAchat(ByVal idArticle As Integer) As Integer
        ' search for a purchase in the purchase list
        ' returns its position in the list or -1 if not found
        Dim achatCourant As Achat
        Dim trouvé As Boolean = False
        Dim i As Integer = 0
            While Not trouvé AndAlso i < _achats.Count
                ' regular purchase
                achatCourant = CType(_achats(i), Achat)
                ' comparison with article searched
                If achatCourant.article.id = idArticle Then
                    Return i
                End If
                'next purchase
                i += 1
            End While
            ' not found
            Return -1
    End Function

    ' identity function
    Public Overrides Function ToString() As String
        Return _achats.ToString
    End Function
End Class

End Namespace

تعليقات:

  • تحتوي فئة [ShoppingCart] على الخصائص والطرق التالية:
خاصية ReadOnly purchases() كـ ArrayList
قائمة التسوق الخاصة بالعميل - قائمة من الكائنات من النوع [Purchase]
add(ByVal aPurchase As Purchase)
تضيف عملية شراء إلى قائمة عمليات الشراء
remove(ByVal purchaseId As Integer)
يزيل عملية الشراء التي تحمل معرف idPurchase
خاصية للقراءة فقط totalBasket() كرقم مزدوج
المبلغ الإجمالي للمشتريات في سلة التسوق
دالة ToString() كسلسلة
تُرجع السلسلة التي تمثل سلة التسوق
  • طريقة [posAchat] هي طريقة مساعدة تُرجع الموضع في قائمة المشتريات لمشتريات محددة برقم العنصر. تُدار قائمة التسوق بحيث لا يشغل العنصر الذي تم شراؤه عدة مرات سوى موضع واحد في القائمة. وبالتالي، يمكن تحديد المشتريات برقم العنصر. تُرجع طريقة [posAchat] القيمة -1 إذا كانت المشتريات التي يتم البحث عنها غير موجودة.
  • تضيف طريقة [add] عملية شراء جديدة إلى قائمة المشتريات. وهذا إما يضيف إدخالًا جديدًا إلى قائمة المشتريات إذا لم يكن العنصر الذي تم شراؤه موجودًا بالفعل في القائمة، أو يزيد الكمية المشتراة إذا كان موجودًا بالفعل.
  • تسمح لك طريقة [remove] بإزالة عملية شراء محددة برقم من قائمة عمليات الشراء. إذا لم تكن عملية الشراء موجودة، فلن تقوم الطريقة بأي شيء.
  • يتم تحديث المبلغ الإجمالي للمشتريات [totalPanier] مع إضافة العناصر وإزالتها.

1.4.4.4. فئة [PurchaseItems]

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

Imports istia.st.articles.dao

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

        'private fields
        Private _articlesDao As IArticlesDao
        Private _erreurs As ArrayList

        ' manufacturer
        Public Sub New(ByVal articlesDao As IArticlesDao)
            _articlesDao = articlesDao
        End Sub

        ' error list
        Public ReadOnly Property erreurs() As ArrayList Implements IArticlesDomain.erreurs
            Get
                Return _erreurs
            End Get
        End Property

        ' list of items
        Public Function getAllArticles() As IList Implements IArticlesDomain.getAllArticles
            ' list of all items
            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

        ' get an item identified by its number
        Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDomain.getArticleById
            ' a special item
            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


        ' buy a basket
        Public Sub acheter(ByVal panier As Panier) Implements IArticlesDomain.acheter
            ' basket purchase - stocks of purchased items must be decremented
            _erreurs = New ArrayList
            Dim achat As achat
            Dim achats As ArrayList = panier.achats
            For i As Integer = achats.Count - 1 To 0 Step -1
                ' decrement stock item i
                achat = CType(achats(i), achat)
                Try
                    If _articlesDao.changerStockArticle(achat.article.id, -achat.qte) = 0 Then
                        ' we couldn't do the operation
                        _erreurs.Add("L'achat " + achat.ToString + " n'a pu se faire - Vérifiez les stocks")
                    Else
                        ' the transaction has been completed - the purchase is removed from the basket
                        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 كـ IArticlesDao
كائن الوصول إلى البيانات
_errors As ArrayList
قائمة الأخطاء المحتملة. يمكن الوصول إليها عبر الخاصية العامة [errors]
  • لإنشاء مثيل للفئة، يجب توفير الكائن الذي يسمح بالوصول إلى البيانات:
Sub New(ByVal articlesDao As IArticlesDao)
  • تعتمد طريقتا [getAllArticles] و [getArticleById] على الطرق التي تحمل نفس الاسم في طبقة [dao]
  • تقوم طريقة [buy] بالتحقق من صحة عملية شراء سلة التسوق. يتضمن هذا التحقق ببساطة خصم كمية العناصر المشتراة من المخزون. لا يمكن شراء عنصر ما إلا إذا كان هناك مخزون كافٍ منه. إذا لم يكن الأمر كذلك، يتم رفض عملية الشراء: يبقى العنصر في سلة التسوق ويتم الإبلاغ عن خطأ في قائمة [errors]. تتم إزالة عملية الشراء التي تم التحقق من صحتها من السلة، ويتم خصم الكمية المشتراة من مخزون العنصر المقابل.

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

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

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

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

Image

تعليقات:

  • مشروع [tests] هو من نوع [مكتبة الفئات]
  • تتطلب اختبارات [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

        ' the test object
        Private articlesDomain As IArticlesDomain
        Private articlesDao As IArticlesDao

        <SetUp()> _
        Public Sub init()
            ' retrieve an instance of the Spring object manufacturer
            Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config.xml", FileMode.Open))
            ' request instantiation of the articles dao object
            articlesDao = CType(factory.GetObject("articlesdao"), IArticlesDao)
            ' then the articlesdomain aobject
            articlesDomain = CType(factory.GetObject("articlesdomain"), IArticlesDomain)
        End Sub

        <Test()> _
        Public Sub getAllArticles()
            ' visual check
            listArticles()
        End Sub

        <Test()> _
        Public Sub getArticleById()
            ' article deletion
            articlesDao.clearAllArticles()
            ' check
            Dim articles As IList = articlesDomain.getAllArticles
            Assert.AreEqual(0, articles.Count)
            ' 2 items added
            articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
            articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
            ' check
            articles = articlesDomain.getAllArticles
            Assert.AreEqual(2, articles.Count)
            ' research article 3
            Dim unArticle As Article = articlesDomain.getArticleById(3)
            ' check
            Assert.AreEqual(unArticle.nom, "article3")
            ' research article 4
            unArticle = articlesDao.getArticleById(4)
            ' check
            Assert.AreEqual(unArticle.nom, "article4")
        End Sub

        <Test()> _
        Public Sub acheterPanier()
            ' article deletion
            articlesDao.clearAllArticles()
            ' check
            Dim articles As IList = articlesDomain.getAllArticles
            Assert.AreEqual(0, articles.Count)
            ' 2 items added
            articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
            articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
            ' check
            articles = articlesDomain.getAllArticles
            Assert.AreEqual(2, articles.Count)
            ' create a basket with two purchases
            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))
            ' checks
            Assert.AreEqual(700, panier.totalPanier, 0.000001)
            Assert.AreEqual(2, panier.achats.Count)
            ' shopping cart validation
            articlesDomain.acheter(panier)
            ' checks
            Assert.AreEqual(0, articlesDomain.erreurs.Count)
            Assert.AreEqual(0, panier.achats.Count)
            ' research article 3
            Dim unArticle As Article = articlesDomain.getArticleById(3)
            ' check
            Assert.AreEqual(unArticle.stockactuel, 20)
            ' research article 4
            unArticle = articlesDao.getArticleById(4)
            ' check
            Assert.AreEqual(unArticle.stockactuel, 30)
            ' new basket
            panier.ajouter(New Achat(New Article(3, "article3", 30, 30, 3), 100))
            ' shopping cart validation
            articlesDomain.acheter(panier)
            ' checks
            Assert.AreEqual(1, articlesDomain.erreurs.Count)
            ' research article 3
            unArticle = articlesDomain.getArticleById(3)
            ' check
            Assert.AreEqual(unArticle.stockactuel, 20)
        End Sub

        <Test()> _
        Public Sub testRetirerAchats()
            ' delete contents of ARTICLES
            articlesDao.clearAllArticles()
            ' reads the ARTICLES table
            Dim articles As IList = articlesDao.getAllArticles()
            Assert.AreEqual(0, articles.Count)
            ' insertion
            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)
            ' reads the ARTICLES table
            articles = articlesDomain.getAllArticles()
            Assert.AreEqual(2, articles.Count)
            ' create a basket with two purchases
            Dim monPanier As New Panier
            monPanier.ajouter(New Achat(article3, 10))
            monPanier.ajouter(New Achat(article4, 10))
            ' checks
            Assert.AreEqual(700.0, monPanier.totalPanier, 0.000001)
            Assert.AreEqual(2, monPanier.achats.Count)
            ' add a previously purchased item
            monPanier.ajouter(New Achat(article3, 10))
            ' checks
            ' the total must be increased to 1000
            Assert.AreEqual(1000.0, monPanier.totalPanier, 0.000001)
            ' always 2 items in the basket
            Assert.AreEqual(2, monPanier.achats.Count)
            ' qty item 3 increased to 20
            Dim unAchat As Achat = CType(monPanier.achats(0), Achat)
            Assert.AreEqual(20, unAchat.qte)
            ' article 3 is removed from the basket
            monPanier.enlever(3)
            ' checks
            ' the total must be increased to 400
            Assert.AreEqual(400.0, monPanier.totalPanier, 0.000001)
            ' 1 item only in basket
            Assert.AreEqual(1, monPanier.achats.Count)
            ' this must be article no. 4
            Assert.AreEqual(4, CType(monPanier.achats(0), Achat).article.id)
        End Sub

        ' screen listing
        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()> مرجعًا إلى كائني [articlesdomain] و[articlesdao] المراد اختبارهما من Spring. وهما معرّفان في ملف [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. سننظر أولاً في طريقة مقدمة في الوثيقة [تطوير الويب باستخدام ASP.NET 1.1]

  • يتم تنفيذ وحدة التحكم C بواسطة ملفين [global.asax، main.aspx]
  • يتم التعامل مع طرق العرض V بواسطة صفحات aspx

1.5. طبقة [web]

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

M = النموذج
فئات الأعمال [domain]، وفئات الوصول إلى البيانات [DAO]، ومصدر البيانات
V = طرق العرض
صفحات ASPX
C = وحدة التحكم
تمر جميع طلبات عملاء HTTP عبر وحدتي التحكم التاليتين:
global.asax: تتعامل مع الأحداث المتعلقة بالتشغيل الأولي للتطبيق
main.aspx: تعالج طلبات كل عميل على حدة

1.5.1. طرق العرض

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

القائمة
list.aspx
توجد طرق العرض في مجلد [views] الخاص بالتطبيق
معلومات
info.aspx
سلة التسوق
cart.aspx
إفراغ سلة التسوق
empty-cart.aspx
أخطاء
errors.aspx

1.5.2. وحدات التحكم

كما ذكرنا سابقًا، تتكون وحدة التحكم من عنصرين:

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

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

request
المعنى
إجراء وحدة التحكم
الاستجابات المحتملة
الإجراء=قائمة
يريد العميل قائمة
العناصر
- يطلب قائمة العناصر من الطبقة
المهنة
- [LIST]
- [أخطاء]
action=info
يطلب العميل
معلومات عن أحد
العناصر المعروضة في العرض
[LIST]
- يطلب العنصر من طبقة الأعمال
- [INFO]
- [ERRORS]
action=purchase
يقوم العميل بشراء عنصر
- يطلب المنتج من طبقة الأعمال
ويضيفه إلى سلة التسوق الخاصة به
- [معلومات] في حالة وجود خطأ في الكمية
- [LIST] في حالة عدم وجود خطأ
action=إزالة الشراء
يريد العميل إزالة
عنصر من سلة التسوق
- استرجاع سلة التسوق من الجلسة
وتعديلها
- [سلة التسوق]
- [إفراغ سلة التسوق]
- [أخطاء]
action=cart
يريد العميل عرض
عربة التسوق
- استرداد سلة التسوق من الجلسة
- [عربة التسوق]
- [سلة التسوق فارغة]
- [أخطاء]
action=validate-cart
لقد انتهى العميل من التسوق
ويشرع في إتمام عملية الدفع
- تحديث قاعدة البيانات بمستويات المخزون
المنتجات التي تم شراؤها
- يفرغ سلة التسوق الخاصة بالعميل من العناصر
التي تم تأكيد شرائها
- [قائمة]
- [أخطاء]

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

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

  1. التغييرات في عناوين URL لمختلف طرق العرض
  2. التغييرات في الفئات التي تنفذ واجهات [IArticlesDao] و [IArticlesDomain]
  3. التغييرات في نظام إدارة قواعد البيانات (DBMS) أو قاعدة البيانات أو جدول المقالات

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>

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

1.5.3.3. التغييرات المتعلقة بنظام إدارة قواعد البيانات (DBMS) أو قاعدة البيانات

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

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

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

Image

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

تذكر أن صفحة ASPX تحتوي على طبقة عرض HTML وطبقة كود .NET تقوم بإعداد البيانات التي يجب أن تعرضها طبقة العرض. يمكن أن يكون هذان الجزءان في نفس ملف [aspx] (حل WebMatrix) أو في ملفين: [aspx] للعرض، و[aspx.vb] للكود. الحل الأخير هو الذي يستخدمه Visual Studio. ولزيادة الأمور تعقيدًا، يمكن أن تحتوي طبقة العرض HTML أيضًا على كود .NET، مما يؤدي إلى طمس الفصل بين طبقتي [التحكم] و[العرض] في العرض. لا يُنصح بهذا النهج بشكل عام. تتطلب إزالة كل الكود من قسم [العرض] إنشاء مكتبات علامات. تعمل هذه المكتبات على "إخفاء" الكود تحت ستار علامات مشابهة لعلامات HTML. نقدم حلين ممكنين لصفحة [ERRORS].

يستخدم الحل الأول كود .NET في قسم [العرض] من الصفحة. تسترد صفحة ASPX قائمة الأخطاء الموجودة في الطلب في قسم وحدة التحكم [errors.aspx.vb]:

        Protected erreurs As ArrayList

        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
            ' error recovery
            erreurs = CType(context.Items("erreurs"), ArrayList)
        End Sub

ثم عرضها في قسم [presentation, errors.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 لعرض [ERRORS] السابق كما يلي:

        <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 الذي سيتم عرضه بعد عرض عناصر مصدر البيانات

يتم ربط مصدر البيانات بالعلامة، عادةً في قسم [controller] من الصفحة:

        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
..
            ' link errors to rptErreurs
            With rptErreurs
                .DataSource = context.Items("erreurs")
                .DataBind()
            End With
        End Sub

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

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

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

تطبيق الويب عبارة عن لغز مكون من العديد من القطع. وعادةً ما يؤدي تزويده بهيكل MVC إلى زيادة عدد هذه القطع. وفيما يلي هيكل تطبيق [webarticles] في [Visual Studio]:

  

تعليقات:

  • مشروع [الويب] هو من نوع [مكتبة الفئات] وليس من نوع [تطبيق ويب ASP.NET]، كما قد يتوقع المرء منطقياً. يتطلب نوع [تطبيق ويب ASP.NET] وجود خادم الويب IIS على جهاز التطوير أو على جهاز بعيد. لا يتم تضمين خادم IIS بشكل افتراضي على أجهزة Windows XP Home Edition. ومع ذلك، يتم بيع العديد من أجهزة الكمبيوتر الشخصية مع هذا الإصدار. للسماح للقراء الذين يستخدمون Windows XP بتنفيذ التطبيق قيد الدراسة، سنستخدم خادم الويب Cassini (انظر الملحق)، المتاح مجانًا من Microsoft، وسنستبدل مشروع [تطبيق ويب ASP.NET] بمشروع [مكتبة فئات]. ينطوي هذا على بعض العيوب، والتي يتم شرحها في الملاحق.
  • ملفات 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 المرتبط بخيار القائمة
  • رابط: نص القائمة

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

رقم
النوع
الاسم
الدور
1
مكرر
rptMenu
مصدر البيانات: مصفوفة من القواميس
مع مفتاحين: href، link
عرض خيارات القائمة

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

<%@ 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("lien") %>
                            </a>
                        </td>
                    </ItemTemplate>
                </asp:Repeater>
            </tr>
        </table>
        <hr>

تعليقات:

  • يتم تعريف مكون [repeater] في الأسطر 6–14
  • كل عنصر في مصدر البيانات المرتبط بـ repeater هو قاموس يحتوي على مفتاحين: href (السطر 9) و link (السطر 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())
                ' associate the action table with its component
                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. في وقت التشغيل، ستقوم هذه العلامة بتضمين الكود من صفحة [entete.ascx] في صفحة ASPX التي تحتوي عليها. سيتولى كود عنصر التحكم [erreurs.aspx.vb] تهيئة هذا المكون. ويمكنه القيام بذلك على النحو التالي:
    Public Class ErreursWebarticles
        Inherits System.Web.UI.Page

        ' page components
...
        Protected WithEvents entete As New EnteteWebArticles

        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
            ' link menu options to 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. مقدمة

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

يتم عرضها بعد إرسال طلب إلى /main?action=list أو /main?action=cartvalidation. عناصر طلب وحدة التحكم هي كما يلي:

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

يحتوي كل رابط [Info] في جدول المقالات بتنسيق HTML على عنوان URL بالصيغة [?action=info&id=ID]، حيث يمثل ID حقل المعرف للمقال المعروض.

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

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

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

Image

  • استخدم رابط [AutoFormat] أعلاه لإدارة تخطيط الشبكة المعروضة
  • والرابط [Property Generator] لإدارة محتواها
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

    ' manages the item list display page
    Public Class ListeWebarticles
        Inherits System.Web.UI.Page

        ' page components
        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
            ' prepare the [list] view using context information
            ' link menu options to rptmenu
            entete.actions = CType(context.Items("options"), Hashtable())
            ' retrieve items from a datatable
            Dim articles As ArrayList = CType(context.Items("articles"), ArrayList)
            ' we link them to the [DataGrid] component of the page
            With DataGridArticles
                .DataSource = articles
                .DataBind()
            End With
            ' the message
            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. مقدمة

تعرض طريقة العرض هذه معلومات حول عنصر ما وتسمح أيضًا بشرائه:

Image

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

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

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

Image

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

1.5.6.3.2. مكونات الصفحة
رقم
النوع
الاسم
الدور
1
مكون المستخدم
رأس
عرض العنوان
2
حرف
رقم العنصر
عرض رقم العنصر
3 إلى 6
DataGrid
DataGridArticle
3 - العمود ذو الصلة: العنوان: الاسم، الحقل: name
4 - العمود ذو الصلة: العنوان: السعر، الحقل: السعر
5 - العمود ذو الصلة: العنوان: المخزون الحالي، الحقل: currentStock
6 - العمود ذو الصلة: العنوان: الحد الأدنى للمخزون، الحقل: stockMinimum
عرض عنصر
7
إرسال HTML
 
إرسال النموذج
8
إدخال HTML
runat=server
txtQty
أدخل الكمية المشتراة
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

    ' manages an article information page
    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

        ' the URL where the form will be posted
        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

        ' page display
        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            ' retrieve query info
            Dim unArticle As Article = CType(Session.Item("article"), Article)
            ' link menu options to rptmenu
            entete.actions = CType(context.Items("options"), Hashtable())
            ' we link the article to [DataGrid]
            Dim articles As New ArrayList
            articles.Add(unArticle)
            With DataGridArticle
                .DataSource = articles
                .DataBind()
            End With
            ' the id label
            litId.Text = unArticle.id.ToString
            ' the error message
            lblMsgQte.Text = context.Items("msg").ToString
            ' the previous qty
            txtQte.Value = context.Items("qte").ToString
            ' the URL action
            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. مقدمة

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

Image

يتم عرضه استجابة لطلب مثل /main?action=cart أو /main?action=cancelpurchase&id=ID. معلمات طلب وحدة التحكم هي كما يلي:

actions
كائن Hashtable() - مصفوفة خيارات القائمة
cart
كائن من النوع [Cart] - سلة التسوق المراد عرضها

يحتوي كل رابط [إزالة] في جدول HTML لعناصر سلة التسوق على عنوان URL بالصيغة [?action=removeitem&id=ID]، حيث يمثل ID حقل [id] للعنصر المراد إزالته من السلة.

1.5.6.4.2. مكونات الصفحة
رقم
النوع
الاسم
الدور
1
مكون المستخدم
رأس
عرض العنوان
2
DataGrid
DataGridPurchases
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
    ' manages the basket display page
    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
            ' link menu options to rptmenu
            entete.actions = CType(context.Items("options"), Hashtable())
            ' we pick up the basket
            Dim unPanier As Panier = CType(Session.Item("panier"), Panier)
            ' transfer purchases to a table of purchase lines
            Dim achats(unPanier.achats.Count - 1) As LigneAchat
            ' change the type of ArrayList elements
            For i As Integer = 0 To achats.Length - 1
                achats(i) = New LigneAchat(CType(unPanier.achats(i), Achat))
            Next
            ' link the data to the [DataGrid] components of the page
            With DataGridAchats
                .DataSource = achats
                .DataBind()
            End With
            ' total payable is displayed
            lblTotal.Text = unPanier.totalPanier.ToString
        End Sub

        ' purchase line built from a Purchase object
        Private Class LigneAchat
            Inherits Achat

            ' builder receives a purchase
            Public Sub New(ByVal unAchat As Achat)
                Me.article = unAchat.article
                Me.qte = unAchat.qte
            End Sub

            ' id: returns the id of the item purchased
            Public ReadOnly Property id() As Integer
                Get
                    Return article.id
                End Get
            End Property

            ' name: name of item purchased
            Public ReadOnly Property nom() As String
                Get
                    Return article.nom
                End Get
            End Property

            ' price of item purchased
            Public ReadOnly Property prix() As Double
                Get
                    Return article.prix
                End Get
            End Property

        End Class
    End Class
End Namespace

تعليقات:

  • يتم تعريف مكونات الصفحة في الأسطر 11–13
  • تكون تهيئة مكون [header] مطابقة لتلك الموجودة في الصفحات التي تمت دراستها سابقًا — السطر 17
  • يتم استرداد عربة التسوق المراد عرضها من الجلسة — السطر 19
  • يُشكل عرض عربة التسوق هذه باستخدام مكون [DataGridAchats] مشكلة. تنبع الصعوبة من تهيئة المكون. دعونا نستعرض أعمدة المكون:
    • عمود [Article] المرتبط بحقل [name] في مصدر البيانات
    • عمود [Qty] المرتبط بحقل [qty] في مصدر البيانات
    • عمود [Price] المرتبط بحقل [price] في مصدر البيانات
    • عمود [Total] المرتبط بحقل [total] في مصدر البيانات

مصدر البيانات الذي لدينا هو عربة التسوق وقائمة التسوق الخاصة بها. وستكون هذه الأخيرة بمثابة مصدر البيانات لـ [DataGrid]. ومع ذلك، فإن كائنات [Purchase] التي ستملأ صفوف [DataGrid] لا تحتوي على الخصائص [name, qty, price, total] التي يتوقعها [DataGrid]. لذلك، نقوم هنا، خصيصًا لـ [DataGrid]، بإنشاء مصدر بيانات تحتوي عناصره على الخصائص التي يتوقعها [DataGrid]. ستكون هذه العناصر من النوع [PurchaseLine]، وهي فئة تم إنشاؤها لهذا الغرض ومشتقة من فئة [Purchase] — الأسطر 36–66

  • بمجرد تعريف فئة [PurchaseLine]، يتم إنشاء مصدر البيانات لـ [DataGridPurchases] من سلة التسوق الموجودة في الجلسة — الأسطر 20–30
  • يتم عرض إجمالي مبلغ الشراء باستخدام خاصية [totalPanier] للفئة [Panier] — السطر 32

1.5.6.5. طريقة العرض [emptyCart.aspx]

1.5.6.5.1. مقدمة

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

Image

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

actions
كائن Hashtable() - مصفوفة خيارات القائمة
1.5.6.5.2. مكونات الصفحة
رقم
النوع
الاسم
الدور
1
مكون المستخدم
رأس
عرض العنوان
1.5.6.5.3. كود العرض [emptycart.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
    ' manages the empty basket display page
    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
            ' prepare the [emptybasket] view using context information
            ' link menu options to rptmenu
            entete.actions = CType(context.Items("options"), Hashtable())
        End Sub
    End Class
End Namespace

تعليقات:

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

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

1.5.6.6.1. مقدمة

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

Image

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

الإجراءات
كائن Hashtable() - مصفوفة خيارات القائمة
الأخطاء
مجموعة ArrayList من كائنات [String] تمثل رسائل الخطأ المراد عرضها
1.5.6.6.2. مكونات الصفحة
رقم
النوع
الاسم
الدور
1
مكون المستخدم
رأس
عرض العنوان
2
المكرر
rptErrors
عرض قائمة الأخطاء
1.5.6.6.3. كود العرض [errors.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
  • يتم تعريف المكون [rptErrors] في الأسطر 13–19. ويأتي محتواه من مصدر بيانات من النوع [ArrayList] يحتوي على كائنات [String].
1.5.6.6.4. كود عنصر التحكم [errors.aspx.vb]
Namespace istia.st.articles.web

    ' manages the error page
    Public Class ErreursWebarticles
        Inherits System.Web.UI.Page

        ' page components
        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
            ' prepare the [errors] view using context information
            ' link menu options to rptmenu
            entete.actions = CType(context.Items("options"), Hashtable())
            ' link errors to rptErreurs
            With rptErreurs
                .DataSource = context.Items("erreurs")
                .DataBind()
            End With
        End Sub
    End Class

End Namespace

تعليقات:

  • يتم تهيئة المكون [header] كالمعتاد، في السطرين 9 و 14
  • يتم تهيئة مكون [rptErrors] باستخدام قائمة الأخطاء [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 errors]. ستكون هذه القائمة فارغة في حالة عدم وجود أخطاء، ولكنها ستظل موجودة على أي حال.
  • في حالة وجود أخطاء، تتوقف طريقة [Application_Start] عند هذا الحد. وإلا، فإنه يطلب مرجعًا إلى عنصر فريد من نوع [IArticlesDomain]، والذي سيكون كائن الأعمال الذي سيستخدمه وحدة التحكم لتلبية احتياجاته. كما هو موضح في 1.5.3.2، ستطلب وحدة التحكم هذا العنصر الفريد من إطار عمل Spring. قد تؤدي عملية إنشاء المثيل هذه إلى أخطاء متنوعة. إذا كان الأمر كذلك، فسيتم تخزين هذه الأخطاء مرة أخرى في كائن [errors] لسياق التطبيق.

يحتوي وحدة التحكم [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

        ' init application
        Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)

            ' local data
            Dim parameters() As String = {"urlMain", "urlErreurs", "urlInfos", "urlListe", "urlPanier", "urlPanierVide"}
            Dim erreurs As New ArrayList

            ' retrieve application initialization parameters
            Dim param As String
            For i As Integer = 0 To parameters.Length - 1
                ' read from conf file
                param = ConfigurationSettings.AppSettings(parameters(i))
                If param Is Nothing Then
                    ' we note the error
                    erreurs.Add("Paramètre [" + parameters(i) + "] absent dans le fichier [web.config]")
                Else
                    ' the parameter is stored in the application
                    Application.Item(parameters(i)) = param
                End If
            Next
            ' mistakes?
            If erreurs.Count = 0 Then
                ' create a IArticlesDomain business layer access object
                Dim contexte As IApplicationContext = CType(ConfigurationSettings.GetConfig("spring/context"), IApplicationContext)
                Dim articlesDomain As IArticlesDomain
                Try
                    articlesDomain = CType(contexte.GetObject("articlesDomain"), IArticlesDomain)
                    ' the object is stored in the application
                    Application.Item("articlesDomain") = articlesDomain
                Catch ex As Exception
                    ' we memorize the error
                    erreurs.Add("Erreur lors de la construction de l'objet d'accès à la couche métier [" + ex.ToString + "]")
                End Try
            End If
            ' errors are placed in the application
            Application.Item("erreurs") = erreurs
            ' it's over if there were mistakes
            If erreurs.Count <> 0 Then Return
            ' build an array of menu options
            Dim options As New Hashtable
            ' retrieve the URL from the controller
            Dim urlMain As String = CType(Application.Item("urlMain"), String)
            Dim uneOption As Hashtable
            ' list of items
            uneOption = New Hashtable
            uneOption.Add("href", urlMain + "?action=liste")
            uneOption.Add("lien", "Liste des articles")
            options.Add("liste", uneOption)
            ' basket
            uneOption = New Hashtable
            uneOption.Add("href", urlMain + "?action=panier")
            uneOption.Add("lien", "Voir le panier")
            options.Add("panier", uneOption)
            ' shopping cart validation
            uneOption = New Hashtable
            uneOption.Add("href", urlMain + "?action=validationpanier")
            uneOption.Add("lien", "Valider le panier")
            options.Add("validationpanier", uneOption)
            ' set menu options in the application
            Application.Item("options") = options
            Return
        End Sub

        ' init session
        Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
            ' create a basket for the customer
            Session.Item("panier") = New Panier
        End Sub
    End Class
End Namespace

تعليقات:

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

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

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

  • يتم فحص الكائن [errors] في سياق التطبيق. إذا لم يكن فارغًا، فهذا يعني حدوث أخطاء أثناء تهيئة التطبيق وأن التطبيق لا يمكن تشغيله. استجابةً لذلك، يتم إرسال عرض [ERRORS].
  • يتم استرداد المعلمة [action] للطلب وفحصها. إذا لم تتطابق مع إجراء معروف، يتم إرسال عرض [ERRORS] مع رسالة خطأ مناسبة.
  • إذا كانت المعلمة [action] صالحة، يتم تمرير طلب العميل إلى إجراء خاص بهذا الإجراء للمعالجة:
method
الطلب
المعالجة
الاستجابات المحتملة
doList
GET /main?action=list
- طلب قائمة العناصر
من فئة الأعمال
- عرضها
[LIST] أو [ERRORS]
doInfo
GET /main?action=info&id=ID
- استرداد العنصر ذي المعرف id=ID من
فئة الأعمال
- عرضه
[INFO] أو [ERRORS]
doPurchase
POST /main?action=purchase&id=ID
- الكمية المشتراة مضمنة في المعلمات المرسلة
- طلب العنصر ذي الرقم التعريفي id=ID من
فئة الأعمال
- أضفه إلى سلة التسوق في
جلسة عمل العميل
[LIST] أو [INFO] أو [ERRORS]
doRetirePurchase
GET /main?action=removePurchase&id=ID
- إزالة العنصر ذي المعرف id=ID من
قائمة التسوق في
جلسة عمل العميل
[CART]
doCart
GET /main?action=cart
- عرض
جلسة العميل
[CART] أو [EMPTY_CART]
doCartValidation
GET /main?action=cartvalidation
- خفض
مستويات المخزون لجميع العناصر
في
[LIST] أو [ERRORS]
[LIST] أو [ERRORS]

قد يبدو قالب وحدة التحكم [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

    ' web application controller class
    Public Class MainWebArticles
        Inherits System.Web.UI.Page

        ' private fields
        Private articlesDomain As IArticlesDomain
        Private options As Hashtable

        ' page loading
        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
....
        End Sub

        ' stock processing methods

        ' list of items
        Public Sub doListe()
...
        End Sub

        ' article info
        Public Sub doInfos()
...
        End Sub

        ' purchase an item
        Public Sub doAchat()
...
        End Sub

        ' delete a purchase
        Public Sub doRetirerAchat()
...
        End Sub

        ' view basket
        Public Sub doPanier()
...
        End Sub

        ' bUY BASKET
        Public Sub doValidationPanier()
...
        End Sub

    End Class

End Namespace

تعليقات:

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

1.5.7.3. طريقة [Page_Load]

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

        ' page loading
        Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            ' check that the application has started correctly
            Dim erreurs As ArrayList = CType(Application.Item("erreurs"), ArrayList)
            ' if there are errors, we send the view [errors]
            If erreurs.Count <> 0 Then
                context.Items("erreurs") = erreurs
                context.Items("options") = New Hashtable() {}
                Server.Transfer(CType(Application("urlErreurs"), String))
            End If
            ' retrieve the business class access object
            articlesDomain = CType(Application.Item("articlesDomain"), IArticlesDomain)
            ' and menu options
            options = CType(Application.Item("options"), Hashtable)
            ' retrieve the action to be performed
            Dim action As String = Request.QueryString("action")
            If action Is Nothing Then
                action = "liste"
            End If
            ' execute the action
            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
  • إذا لم تكن هذه القائمة فارغة، فإننا نعرض عرض [ERRORS] — الأسطر 6–10
  • نسترد العنصر الفردي [articlesDomain] الذي وضعه [global.asax] في سياق التطبيق ونخزنه في الحقل الخاص [articlesDomain] بحيث يكون متاحًا لمختلف أساليب الفئة — السطر 12
  • نقوم بعملية مماثلة مع مصفوفة خيارات القائمة - السطر 14
  • استرداد المعلمة [action] من الطلب - السطر 16
  • نقوم بتنفيذ الطريقة المطابقة للإجراء المطلوب. يتم التعامل مع الإجراء غير المحدد على أنه الإجراء [list] — الأسطر 16-36

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

يتضمن ذلك عرض قائمة العناصر:

Image

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

        ' list of items
        Public Sub doListe()
            ' error management
            Dim erreurs As New ArrayList
            ' the list of items is requested
            Dim articles As IList
            Try
                articles = articlesDomain.getAllArticles
            Catch ex As Exception
                ' we note the error
                erreurs.Add(ex.ToString)
            End Try
            ' mistakes?
            If erreurs.Count <> 0 Then
                ' display view [errors]
                context.Items("erreurs") = erreurs
                context.Items("options") = New Hashtable() {CType(options("liste"), Hashtable)}
                Server.Transfer(CType(Application.Item("urlErreurs"), String))
            End If
            ' display view [list]
            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
  • في حالة وجود أخطاء، يتم عرض [ERRORS] - الأسطر 13-19
  • وإلا، يتم إرجاع عرض [LIST] - الأسطر 20-24

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

طلب العميل معلومات حول عنصر معين:

Image

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

        ' article info
        Public Sub doInfos()
            ' error management
            Dim erreurs As New ArrayList
            ' retrieve the id of the requested item
            Dim strId As String = Request.QueryString("id")
            ' do we have anything?
            If strId Is Nothing Then
                ' not normal - sends error page
                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
            ' do we have an integer?
            Dim id As Integer
            Try
                id = Integer.Parse(strId)
            Catch ex As Exception
                ' not normal - sends error page
                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
            ' the key item id is requested
            Dim unArticle As Article
            Try
                unArticle = articlesDomain.getArticleById(id)
            Catch ex As Exception
                ' data access issues
                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
            ' has an item been recovered?
            If unArticle Is Nothing Then
                ' article does not exist
                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
            ' we have the article - we put it in the current session
            Session.Item("article") = unArticle
            ' prepare your display
            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
  • يتم التحقق من صحة هذا المعرف. يجب أن يكون موجودًا ويجب أن يكون عددًا صحيحًا. إذا لم يكن كذلك، يتم عرض طريقة العرض [ERRORS] مع رسالة الخطأ المناسبة - الأسطر 7-27
  • بمجرد التحقق من المعرف، يتم طلب العنصر من العنصر الفردي [articlesDomain]. في حالة حدوث استثناء، يتم إرسال عرض [ERRORS] - الأسطر 29-39
  • إذا لم يتم العثور على العنصر، يتم إرسال عرض [ERRORS] — الأسطر 41–48
  • إذا تم العثور على العنصر، يتم وضعه في جلسة عمل المستخدم ثم عرضه في عرض [INFO] - الأسطر 50-56

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

قام العميل بشراء العنصر المعروض في عرض [INFO].

Image

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

        ' purchase an item
        Public Sub doAchat()
            ' purchase an item
            ' we retrieve the article that was in the session
            Dim unArticle As Article = CType(Session.Item("article"), Article)
            ' do we have anything?
            If unArticle Is Nothing Then
                ' not normal - item list is displayed
                doListe()
            End If
            ' we retrieve the posted qty
            Dim strQte As String = Request.Form("txtQte")
            ' do we have anything?
            If strQte Is Nothing Then
                ' not normal - we send the list of items
                doListe()
            End If
            ' do we have an integer?
            Dim qte As Integer
            Try
                qte = Integer.Parse(strQte)
                If (qte <= 0) Then Throw New Exception
            Catch ex As Exception
                ' not normal - send info page with error message
                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
            ' all's well - we put the purchase in the customer's shopping cart
            Dim unPanier As Panier = CType(Session.Item("panier"), Panier)
            unPanier.ajouter(New Achat(unArticle, qte))
            ' displays the list of items
            doListe()
        End Sub

تعليقات:

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

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

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

Image

والكود كالتالي:

        ' view basket
        Public Sub doPanier()
            'the basket is retrieved from the session
            Dim unPanier As Panier = CType(Session.Item("panier"), Panier)
            ' empty basket?
            If unPanier.achats.Count = 0 Then
                ' empty basket display
                context.Items("options") = New Hashtable() {CType(options("liste"), Hashtable)}
                Server.Transfer(CType(Application.Item("urlPanierVide"), String))
            End If
            ' display basket not empty
            context.Items("options") = New Hashtable() {CType(options("liste"), Hashtable), CType(options("validationpanier"), Hashtable)}
            Server.Transfer(CType(Application.Item("urlPanier"), String))
        End Sub

تعليقات:

  • نسترد سلة التسوق من الجلسة - السطر 4. لا نتحقق هنا للتأكد من أننا نسترد شيئًا بالفعل. يجب أن نفعل ذلك لأن الجلسة قد تكون انتهت صلاحيتها.
  • إذا كانت سلة التسوق فارغة، نرسل عرض [EMPTY CART] - الأسطر 6-10
  • وإلا، نرسل عرض [SHOPPINGCART] — الأسطر 11–14

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

يريد العميل إزالة عملية شراء من سلة التسوق الخاصة به:

Image

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

        ' delete a purchase
        Public Sub doRetirerAchat()
            'the basket is retrieved from the session
            Dim unPanier As Panier = CType(Session.Item("panier"), Panier)
            ' we remove the purchase
            Try
                ' retrieve the id of the removed item
                Dim idArticle As Integer = Integer.Parse(Request.QueryString("id"))
                ' take it out of the basket
                unPanier.enlever(idArticle)
            Catch ex As Exception
                ' displays the list of items
                doListe()
            End Try
            ' the basket is displayed
            doPanier()
        End Sub

تعليقات:

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

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

يريد العميل تأكيد سلة التسوق الخاصة به:

Image

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

        ' bUY BASKET
        Public Sub doValidationPanier()
            ' at the start, no mistakes
            Dim erreurs As ArrayList
            'the basket is retrieved from the session
            Dim unPanier As Panier = CType(Session.Item("panier"), Panier)
            ' we try to validate the basket
            Try
                articlesDomain.acheter(unPanier)
                ' note any purchasing errors
                erreurs = articlesDomain.erreurs
            Catch ex As Exception
                ' we note the error
                erreurs = New ArrayList
                erreurs.Add(String.Format("Erreur lors de la validation du panier [{0}]", ex.Message))
            End Try
            ' if errors then error page
            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
            ' all is well - the list of items is displayed with a success message
            context.Items("message") = "Votre panier a été validé"
            doListe()
        End Sub

تعليقات:

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

1.6. الخلاصة

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