1. الجزء 1
ملف PDF الخاص بالوثيقة متاح |HERE|.
الأمثلة الواردة في المستند متاحة |HERE|.
1.1. Introduction
أهداف المقالة:
- كتابة تطبيق ويب ثلاثي الطبقات [interface utilisateur, métier, accès aux données]
- تكوين التطبيق باستخدام Spring IOC
- كتابة إصدارات مختلفة عن طريق تغيير تنفيذ إحدى الطبقات الثلاث.
الأدوات المستخدمة:
- Visual Studio.net للتطوير - انظر الفقرة 3.1 في الملحق؛
- خادم الويب Cassini للتنفيذ - انظر الفقرة 3.2 في الملحق؛
- Nunit للاختبارات الفردية – انظر الفقرة 3.4 في الملحق؛
- Spring للتكامل وتكوين طبقات تطبيق الويب - انظر الفقرة 3.3 في الملحق؛
على مقياس المبتدئ - المتوسط - المتقدم، يقع هذا المستند في الجزء [intermédiaire-avancé]. ويتطلب فهمه عدة متطلبات مسبقة. يمكن اكتساب بعضها من المستندات التي قمت بكتابتها. وفي هذه الحالة، أقوم بالإشارة إليها. ومن الواضح تمامًا أن هذا مجرد اقتراح وأن القارئ يمكنه استخدام مستنداته المفضلة.
- لغة VB.net: [مقدمة إلى لغة VB.NET من خلال الأمثلة (2004)]؛
- البرمجة على الويب بلغة VB.net: [تطوير الويب باستخدام ASP.NET 1.1 (2004)]؛
- استخدام مكون Spring المسمى IoC: [Spring IoC لـ .NET (2005)]؛
- التوثيق Spring.net: [Spring.NET | Homepage ]
يستند هذا المستند إلى الخطوط العريضة لمستند مكتوب بلغة Java [[البنى ثلاثية المستويات وبنى MVC باستخدام Struts وSpring و(2005)] . نقوم في VB.NET ببناء تطبيق الويب MVC ذي الطبقات الثلاث والمكتوب بلغة Java. الفكرة التي نريد توضيحها هنا هي أن منصتي التطوير Java و.NET متقاربتان بما يكفي بحيث يمكن إعادة استخدام المهارات المكتسبة في أحد هذين المجالين في المجال الآخر.
لا يبدو أن هناك حلاً معترفًا به على نطاق واسع لتطوير MVC وASP.NET. الحل التالي يعتمد على الطريقة التي تم تقديمها في الوثيقة [تطوير الويب باستخدام ASP.NET 1.1 (2004)]. ورغم أن هذه الطريقة تتميز باستخدام مفاهيم شائعة في تطوير J2EE، إلا أنه لا ينبغي النظر إليها إلا على أنها ما هي عليه، c.a.d، أي طريقة من بين طرق تطوير MVC الأخرى. وبمجرد أن تحظى إحدى طرق التطوير MVC في ASP.NET بقبول واسع النطاق، سيكون من الضروري عندئذٍ اعتماد هذه الطريقة. وقد تكون نسخة Spring .NET، التي لا تزال قيد التطوير، الحل الأول في هذا الصدد.
1.2. تطبيق webarticles
نقدم هنا عناصر تطبيق ويب مبسط للتجارة الإلكترونية. سيسمح هذا التطبيق لعملاء الويب بما يلي:
- الاطلاع على قائمة بالمنتجات المستمدة من قاعدة بيانات
- إضافة بعضها إلى سلة التسوق الإلكترونية
- الموافقة على سلة التسوق. وسيكون التأثير الوحيد لهذه الموافقة هو تحديث مخزون المنتجات المشتراة في قاعدة البيانات.
وستكون العروض المختلفة المعروضة للمستخدم كما يلي:
- العرض "LISTE" الذي يعرض قائمة بالمنتجات المعروضة للبيع ![]() | - العرض [INFOS] الذي يقدم معلومات إضافية عن أحد المنتجات: ![]() |
- طريقتا العرض [PANIER] و [PANIERVIDE] اللتان تعرضان محتويات سلة التسوق الخاصة بالعميل
![]() | ![]() |
- عرض [ERREURS] الذي يُبلغ عن أي خطأ في التطبيق

1.3. البنية العامة للتطبيق
نرغب في إنشاء تطبيق بهيكل ثلاثي الطبقات على النحو التالي:
![]() |
- تتمتع الطبقات الثلاث بالاستقلالية بفضل استخدام الواجهات
- ويتم تكامل الطبقات المختلفة بواسطة Spring
- يتم تخصيص مساحات أسماء منفصلة لكل طبقة: web (طبقة UI)، domain (طبقة الأعمال) و dao (طبقة الوصول إلى البيانات).
سيتبع التطبيق بنية MVC (النموذج - العرض - وحدة التحكم). إذا عدنا إلى مخطط الطبقات أعلاه، فإن بنية MVC تندمج فيه على النحو التالي:
![]() |
تتم معالجة طلب العميل وفقًا للخطوات التالية:
- يقدم العميل طلبًا إلى وحدة التحكم. وستكون وحدة التحكم هذه في هذه الحالة صفحة .aspx سيُعطى لها دور خاص. فهي تستقبل جميع طلبات العملاء. وهي بمثابة بوابة الدخول إلى التطبيق. وهي تمثل الحرف C في MVC.
- يقوم وحدة التحكم بمعالجة هذا الطلب. وللقيام بذلك، قد يحتاج إلى مساعدة من طبقة الأعمال، وهو ما يُسمى بالنموذج M في بنية MVC.
- يتلقى وحدة التحكم ردًا من طبقة الأعمال. وقد تمت معالجة طلب العميل. وقد يؤدي ذلك إلى عدة ردود محتملة. ومن الأمثلة الشائعة على ذلك
- صفحة أخطاء إذا تعذر معالجة الطلب بشكل صحيح
- صفحة تأكيد في الحالات الأخرى
- يختار وحدة التحكم الرد (= العرض) الذي سيتم إرساله إلى العميل. وغالبًا ما يكون هذا الرد عبارة عن صفحة تحتوي على عناصر ديناميكية. وتقوم وحدة التحكم بتزويد العرض بهذه العناصر.
- يتم إرسال العرض إلى العميل. وهذا هو الحرف V في MVC.
1.4. النموذج
ندرس هنا الحرف M في MVC. يتكون النموذج هنا من العناصر التالية:
- فئات الأعمال
- فئات الوصول إلى البيانات
- قاعدة البيانات
1.4.1. قاعدة البيانات
لا تحتوي قاعدة البيانات سوى على جدول واحد يسمى ARTICLES. وقد تم إنشاء هذا الجدول باستخدام الأوامر التالية لـ SQL:
CREATE TABLE ARTICLES (
ID INTEGER NOT NULL,
NOM VARCHAR(20) NOT NULL,
PRIX NUMERIC(15,2) NOT NULL,
STOCKACTUEL INTEGER NOT NULL,
STOCKMINIMUM INTEGER NOT NULL
);
/* القيود */
ALTER TABLE ARTICLES ADD CONSTRAINT CHK_ID check (ID>0);
ALTER TABLE ARTICLES ADD CONSTRAINT CHK_PRIX check (PRIX>=0);
ALTER TABLE ARTICLES ADD CONSTRAINT CHK_STOCKACTUEL check (STOCKACTUEL>=0);
ALTER TABLE ARTICLES ADD CONSTRAINT CHK_STOCKMINIMUM check (STOCKMINIMUM>=0);
ALTER TABLE ARTICLES ADD CONSTRAINT CHK_NOM check (NOM<>'');
ALTER TABLE ARTICLES ADD CONSTRAINT UNQ_NOM UNIQUE (NOM);
/* المفتاح الأساسي */
ALTER TABLE ARTICLES ADD CONSTRAINT PK_ARTICLES PRIMARY KEY (ID);
المفتاح الأساسي الذي يحدد هوية المنتج بشكل فريد | |
اسم المنتج | |
سعره | |
المخزون الحالي | |
المخزون الذي يجب عنده تقديم طلب إعادة التزويد |
1.4.2. مساحات أسماء النموذج
يتم توفير النموذج M هنا في شكل مساحتين للأسماء:
- istia.st.articles.dao: يحتوي على فئات الوصول إلى بيانات الطبقة [dao]
- istia.st.articles.domain: يحتوي على فئات الأعمال الخاصة بالطبقة [domain]
سيتم إنشاء كل من مساحات الأسماء هذه ضمن ملف «تجميع» خاص بها:
contenu | rôle | |
- [IArticlesDao]: واجهة الوصول إلى الطبقة [dao]. هذه هي الواجهة الوحيدة التي تراها الطبقة [domain]. ولا ترى أي واجهة أخرى. - [Article]: فئة تُعرّف عنصرًا - [ArticlesDaoArrayList]: فئة تنفيذ الواجهة [IArticlesDao] باستخدام فئة [ArrayList] | طبقة الوصول إلى البيانات - تقع بالكامل في الطبقة [dao] في بنية ثلاثية المستويات تطبيق الويب | |
- [IArticlesDomain]: واجهة الوصول إلى الطبقة [domain]. وهي الواجهة الوحيدة التي تراها طبقة الويب. ولا ترى أي واجهة أخرى. - [AchatsArticles]: فئة تُنفِّذ [IArticlesDomain] - [Achat]: فئة تمثل عملية شراء أحد العملاء - [Panier]: فئة تمثل مجموع مشتريات العميل | تمثل نموذج عمليات الشراء على الويب - موجود بالكامل في طبقة [domain] في البنية نموذج التطبيق الويب ثلاثي المستويات |
1.4.3. الطبقة [dao]
تحتوي الطبقة [dao] على العناصر التالية:
-
[IArticlesDao]: واجهة الوصول إلى الطبقة [dao]
-
[Article]: فئة تُعرّف عنصرًا
-
[ArticlesDaoArrayList]: فئة تنفيذ واجهة [IArticlesDao] باستخدام فئة [ArrayList]
هيكل المشروع [Visual Studio] التابع للطبقة [dao] هو كما يلي:

تعليقات:
- المشروع [dao] هو من النوع [bibliothèque de classes]
- تم وضع الفئات في شجرة مجلدات تبدأ بالمجلد الجذر [istia]. وهي جميعها موجودة في مساحة الأسماء [istia.st.articles.dao].
1.4.3.1. الفئة [Article]
الفئة التي تُعرّف المقالة هي التالية:
Imports System
Namespace istia.st.articles.dao
Public Class Article
' الحقول الخاصة
Private _id As Integer
Private _nom As String
Private _prix As Double
Private _stockactuel As Integer
Private _stockminimum As Integer
' معرف العنصر
Public Property id() As Integer
Get
Return _id
End Get
Set(ByVal Value As Integer)
If Value <= 0 Then
Throw New Exception("Le champ id [" + Value.ToString + "] est invalide")
End If
Me._id = Value
End Set
End Property
' اسم المنتج
Public Property nom() As String
Get
Return _nom
End Get
Set(ByVal Value As String)
If Value Is Nothing OrElse Value.Trim.Equals("") Then
Throw New Exception("Le champ nom [" + Value + "] est invalide")
End If
Me._nom = Value
End Set
End Property
' سعر المنتج
Public Property prix() As Double
Get
Return _prix
End Get
Set(ByVal Value As Double)
If Value < 0 Then
Throw New Exception("Le champ prix [" + Value.ToString + "] est invalide")
End If
Me._prix = Value
End Set
End Property
' المخزون الحالي للمنتج
Public Property stockactuel() As Integer
Get
Return _stockactuel
End Get
Set(ByVal Value As Integer)
If Value < 0 Then
Throw New Exception("Le champ stockActuel [" + Value.ToString + "] est invalide")
End If
Me._stockactuel = Value
End Set
End Property
' الحد الأدنى لمخزون المنتج
Public Property stockminimum() As Integer
Get
Return _stockminimum
End Get
Set(ByVal Value As Integer)
If Value < 0 Then
Throw New Exception("Le champ stockMinimum [" + Value.ToString + "] est invalide")
End If
Me._stockminimum = Value
End Set
End Property
' الشركة المصنعة الافتراضية
Public Sub New()
End Sub
' الشركة المصنعة مع الخصائص
Public Sub New(ByVal id As Integer, ByVal nom As String, ByVal prix As Double, ByVal stockactuel As Integer, ByVal stockminimum As Integer)
Me.id = id
Me.nom = nom
Me.prix = prix
Me.stockactuel = stockactuel
Me.stockminimum = stockminimum
End Sub
' طريقة تحديد المنتج
Public Overrides Function ToString() As String
Return "[" + id.ToString + "," + nom + "," + prix.ToString + "," + stockactuel.ToString + "," + stockminimum.ToString + "]"
End Function
End Class
End Namespace
توفر هذه الفئة:
- منشئًا يسمح بتحديد المعلومات الخمس الخاصة بالمقالة: [id, nom, prix, stockactuel, stockminimum]
- خصائص عامة تتيح قراءة وكتابة المعلومات الخمس.
- التحقق من صحة البيانات التي يتم إدخالها في المقالة. في حالة وجود بيانات خاطئة، يتم إلقاء استثناء.
- طريقة toString تتيح الحصول على قيمة عنصر ما في شكل سلسلة أحرف. وغالبًا ما يكون ذلك مفيدًا لتصحيح أخطاء التطبيق.
1.4.3.2. الواجهة [IArticlesDao]
يتم تعريف الواجهة [IArticlesDao] على النحو التالي:
Imports System
Imports System.Collections
Namespace istia.st.articles.dao
Public Interface IArticlesDao
' قائمة بجميع المنتجات
Function getAllArticles() As IList
' إضافة منتج
Function ajouteArticle(ByVal unArticle As Article) As Integer
' حذف عنصر
Function supprimeArticle(ByVal idArticle As Integer) As Integer
' تعديل منتج
Function modifieArticle(ByVal unArticle As Article) As Integer
' البحث عن منتج
Function getArticleById(ByVal idArticle As Integer) As Article
' حذف جميع المنتجات
Sub clearAllArticles()
' يغير مخزون أحد المنتجات
Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer
End Interface
End Namespace
دور الطرق المختلفة للواجهة هو كما يلي:
تُرجع جميع المقالات الموجودة في مصدر البيانات | |
إفراغ مصدر البيانات | |
يُرجع الكائن [Article] المُعرَّف بمفتاحه الأساسي | |
يسمح بإضافة عنصر إلى مصدر البيانات | |
يسمح بتعديل عنصر من مصدر البيانات | |
يسمح بحذف عنصر من مصدر البيانات | |
يسمح بتعديل مخزون عنصر من مصدر البيانات |
توفر الواجهة لبرامج العملاء عددًا معينًا من الطرق المحددة فقط من خلال توقيعاتها. ولا تهتم بالكيفية التي سيتم بها تنفيذ هذه الطرق فعليًا. وهذا يوفر مرونة في التطبيق. حيث يقوم برنامج العميل باستدعاءات على الواجهة وليس على تنفيذ محدد لها.
![]() |
سيتم اختيار تنفيذ محدد عن طريق ملف تكوين Spring. ولإثبات أن واجهة الوصول إلى البيانات هي وحدها التي تهم عند اختبار تطبيق الويب، وليس فئة تنفيذها، سنقوم أولاً بتنفيذ مصدر البيانات باستخدام كائن بسيط [ArrayList]. وفي وقت لاحق، سنقدم حلاً يستند إلى SGBD.
1.4.3.3. فئة التنفيذ [ArticlesDaoArrayList]
يتم تعريف فئة التنفيذ [ArticlesDaoArrayList] على النحو التالي:
Imports System
Imports System.Collections
Namespace istia.st.articles.dao
Public Class ArticlesDaoArrayList
Implements istia.st.articles.dao.IArticlesDao
Private articles As New ArrayList
Private Const nbArticles As Integer = 4
' الشركة المصنعة الافتراضية
Public Sub New()
' يتم إنشاء بعض المقالات
For i As Integer = 1 To nbArticles
articles.Add(New Article(i, "article" + i.ToString, i * 10, i * 10, i * 10))
Next
End Sub
' قائمة بجميع المقالات
Public Function getAllArticles() As IList Implements IArticlesDao.getAllArticles
' يتم إرجاع قائمة المقالات
SyncLock Me
Return articles
End SyncLock
End Function
' حذف جميع المقالات
Public Sub clearAllArticles() Implements IArticlesDao.clearAllArticles
' إفراغ قائمة المقالات
SyncLock Me
articles.Clear()
End SyncLock
End Sub
' استرداد عنصر محدد بمفتاحه
Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDao.getArticleById
' البحث عن العنصر في القائمة
SyncLock Me
Dim ipos As Integer = posArticle(articles, idArticle)
If ipos <> -1 Then
Return CType(articles(ipos), Article)
Else
Return Nothing
End If
End SyncLock
End Function
' إضافة عنصر إلى قائمة العناصر
Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
' إضافة العنصر إلى قائمة العناصر
SyncLock Me
' التحقق من عدم وجوده مسبقًا
Dim ipos As Integer = posArticle(articles, unArticle.id)
If ipos <> -1 Then
Throw New Exception("L'article d'id [" + unArticle.id.ToString + "] existe déjà")
End If
' يتم إضافة العنصر
articles.Add(unArticle)
' إرجاع النتيجة
Return 1
End SyncLock
End Function
' تعديل عنصر
Public Function modifieArticle(ByVal articleNouveau As Article) As Integer Implements IArticlesDao.modifieArticle
' يتم تعديل مقال
SyncLock Me
' نتحقق من وجوده
Dim ipos As Integer = posArticle(articles, articleNouveau.id)
' في حالة عدم وجوده
If ipos = -1 Then Return 0
' إنه موجود - نقوم بتعديله
articles(ipos) = articleNouveau
' إرجاع النتيجة
Return 1
End SyncLock
End Function
' حذف مقال محدد بمفتاحه
Public Function supprimeArticle(ByVal idArticle As Integer) As Integer Implements IArticlesDao.supprimeArticle
' حذف عنصر
SyncLock Me
' نتحقق من وجوده
Dim ipos As Integer = posArticle(articles, idArticle)
' في حالة عدم وجوده
If ipos = -1 Then Return 0
' إنه موجود - يتم حذفه
articles.RemoveAt(ipos)
' إرجاع النتيجة
Return 1
End SyncLock
End Function
' تغيير مخزون منتج محدد بمفتاحه
Public Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer Implements IArticlesDao.changerStockArticle
' تغيير مخزون أحد الأصناف
SyncLock Me
' التحقق من وجوده
Dim ipos As Integer = posArticle(articles, idArticle)
' في حالة عدم وجوده
If ipos = -1 Then Return 0
' يوجد - يتم تعديل مخزونه إن أمكن
Dim unArticle As Article = CType(articles(ipos), Article)
' لا يتم تغيير المخزون إلا إذا كان كافياً
If unArticle.stockactuel + mouvement >= 0 Then
unArticle.stockactuel += mouvement
Return 1
Else
Return 0
End If
End SyncLock
End Function
' البحث عن عنصر محدد بمفتاحه
Private Function posArticle(ByVal listArticles As ArrayList, ByVal idArticle As Integer) As Integer
' يعرض موضع المنتج [idArticle] في القائمة أو -1 إذا لم يتم العثور عليه
Dim unArticle As Article
For i As Integer = 0 To listArticles.Count - 1
unArticle = CType(listArticles(i), Article)
If unArticle.id = idArticle Then
Return i
End If
Next
' لم يتم العثور عليه
Return -1
End Function
End Class
End Namespace
تعليقات:
- يتم محاكاة مصدر البيانات بواسطة الحقل الخاص [articles] من النوع [ArrayList]
- يقوم منشئ الفئة بإنشاء 4 عناصر افتراضيًا في مصدر البيانات.
- تمت مزامنة جميع طرق الوصول إلى البيانات لتجنب مشاكل الوصول المتزامن إلى مصدر البيانات. في أي لحظة معينة، لا يمكن إلا لخيط واحد الوصول إلى طريقة معينة.
- تسمح الطريقة [posArticle] بمعرفة الموضع [0..N] في مصدر البيانات [ArrayList] لعنصر محدد برقمه. إذا لم يكن العنصر موجودًا، فإن الطريقة تُرجع الموضع -1. تُستخدم هذه الطريقة بشكل متكرر من قبل الطرق الأخرى.
- تسمح الطريقة [ajouteArticle] بإضافة مادة إلى قائمة المواد. وتُرجع عدد المواد التي تم إدراجها: 1. إذا كانت المادة موجودة بالفعل، يتم إصدار استثناء.
- تسمح الطريقة [modifieArticle] بتعديل عنصر موجود. وتُرجع عدد العناصر التي تم تعديلها: 1 إذا كان العنصر موجودًا، و0 إذا لم يكن موجودًا.
- تسمح الطريقة [supprimeArticle] بحذف عنصر موجود. وتُرجع عدد العناصر المحذوفة: 1 إذا كان العنصر موجودًا، و0 إذا لم يكن موجودًا.
- تُرجع الطريقة [getAllArticles] قائمة بجميع المقالات
- تسمح الطريقة [getArticleById] بالحصول على عنصر محدد برقمه. يتم الحصول على القيمة [nothing] إذا كان العنصر غير موجود.
- لا ينطوي الكود على أي صعوبة حقيقية. نترك للقارئ حرية الاطلاع عليه وفهمه.
1.4.3.4. إنشاء تجميع طبقة [dao]
تم تكوين مشروع Visual Studio لإنشاء ملف التجميع [webarticles-dao.dll]. ويتم إنشاء هذا الملف في المجلد [bin] التابع للمشروع:
![]() | ![]() |
1.4.3.5. اختبارات Nunit للطبقة [dao]
في لغة Java، يتم اختبار الفئات باستخدام إطار العمل [Junit]. في .NET، يوفر إطار عمل Nunit نفس إمكانيات الاختبارات الوحدوية:

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

تعليقات:
- مشروع [tests] هو من النوع [bibliothèque de classes]
- تتطلب الاختبارات [NUnit] مرجعًا إلى التجميع [nunit.framework.dll]
- تسترد فئة الاختبار [NUnit] مثيلًا للكائن المراد اختباره عبر Spring. ولذلك نجد
- في المجلد [bin]، ملفات فئات Spring
- وفي [References]، إشارة إلى التجميع [Spring-Core.dll] من المجلد [bin]
- وفي [bin]، ملف تكوين لـ Spring
- تحتاج فئة الاختبار إلى التجميع [webarticles-dao.dll] من الطبقة [dao]. وقد تم وضع هذا التجميع في المجلد [bin] وإضافة مرجع له إلى مراجع المشروع.
تتطلب فئة الاختبار [NUnit] الوصول إلى الفئات الموجودة في مساحة الأسماء [NUnit.Framework]. ولذلك، توجد فيها تعليمات الاستيراد التالية:
يوجد مساحة الاسم [NUnit.Framework] في «التجميع» [nunit.framework.dll] الذي يجب إضافته إلى مراجع المشروع:
![]() | ![]() |
يجب أن يكون التجميع [nunit.framework.dll] موجودًا في القائمة المقترحة إذا تم تثبيت [Nunit]. يكفي النقر مرتين على التجميع لإضافته إلى المشروع:

قد تكون فئة الاختبار [NUnit] الخاصة بالطبقة [dao] كما يلي:
Imports System
Imports System.Collections
Imports NUnit.Framework
Imports istia.st.articles.dao
Imports System.Threading
Imports Spring.Objects.Factory.Xml
Imports System.IO
Namespace istia.st.articles.tests
<TestFixture()> _
Public Class NunitTestArticlesArrayList
' الكائن المراد اختباره
Private articlesDao As IArticlesDao
<SetUp()> _
Public Sub init()
' يتم استرداد مثيل من مُنشئ الكائنات Spring
Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config.xml", FileMode.Open))
' يُطلب إنشاء مثيل للكائن articlesdao
articlesDao = CType(factory.GetObject("articlesdao"), IArticlesDao)
End Sub
<Test()> _
Public Sub testGetAllArticles()
' التحقق البصري
listArticles()
End Sub
<Test()> _
Public Sub testClearAllArticles()
' حذف جميع المقالات
articlesDao.clearAllArticles()
' يتم طلب جميع المقالات
Dim articles As IList = articlesDao.getAllArticles
' التحقق: يجب أن يكون العدد 0
Assert.AreEqual(0, articles.Count)
End Sub
<Test()> _
Public Sub testAjouteArticle()
' حذف جميع العناصر
articlesDao.clearAllArticles()
' التحقق: يجب أن تكون جدول العناصر فارغة
Dim articles As IList = articlesDao.getAllArticles
Assert.AreEqual(0, articles.Count)
' إضافة عنصرين
articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
' التحقق: يجب أن يكون هناك عنصران
articles = articlesDao.getAllArticles
Assert.AreEqual(2, articles.Count)
' التحقق البصري
listArticles()
End Sub
<Test()> _
Public Sub testSupprimeArticle()
' حذف جميع العناصر
articlesDao.clearAllArticles()
' التحقق: يجب أن تكون جدول العناصر فارغة
Dim articles As IList = articlesDao.getAllArticles
Assert.AreEqual(0, articles.Count)
' يتم إضافة عنصرين
articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
' التحقق: يجب أن يكون هناك 2 عنصر
articles = articlesDao.getAllArticles
Assert.AreEqual(2, articles.Count)
' حذف المادة 4
articlesDao.supprimeArticle(4)
' التحقق: يجب أن يتبقى عنصر واحد
articles = articlesDao.getAllArticles
Assert.AreEqual(1, articles.Count)
' التحقق البصري
listArticles()
End Sub
<Test()> _
Public Sub testModifieArticle()
' حذف جميع البنود
articlesDao.clearAllArticles()
' التحقق
Dim articles As IList = articlesDao.getAllArticles
Assert.AreEqual(0, articles.Count)
' إضافة 2 مقالات
articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
' التحقق
articles = articlesDao.getAllArticles
Assert.AreEqual(2, articles.Count)
' البحث عن المادة 3
Dim unArticle As Article = articlesDao.getArticleById(3)
' التحقق
Assert.AreEqual(unArticle.nom, "article3")
' البحث عن المادة 4
unArticle = articlesDao.getArticleById(4)
' التحقق
Assert.AreEqual(unArticle.nom, "article4")
' تعديل المادة 4
articlesDao.modifieArticle(New Article(4, "article4", 44, 44, 44))
' التحقق
unArticle = articlesDao.getArticleById(4)
Assert.AreEqual(unArticle.prix, 44, 0.000001)
' التحقق البصري
listArticles()
End Sub
<Test()> _
Public Sub testGetArticleById()
' حذف البنود
articlesDao.clearAllArticles()
' التحقق
Dim articles As IList = articlesDao.getAllArticles
Assert.AreEqual(0, articles.Count)
' إضافة مادتين
articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
' التحقق
articles = articlesDao.getAllArticles
Assert.AreEqual(2, articles.Count)
' البحث عن المادة 3
Dim unArticle As Article = articlesDao.getArticleById(3)
' التحقق
Assert.AreEqual(unArticle.nom, "article3")
' البحث عن المادة 4
unArticle = articlesDao.getArticleById(4)
' التحقق
Assert.AreEqual(unArticle.nom, "article4")
End Sub
' عرض قائمة على الشاشة
Private Sub listArticles()
Dim articles As IList = articlesDao.getAllArticles
For i As Integer = 0 To articles.Count - 1
Console.WriteLine(CType(articles(i), Article).ToString)
Next
End Sub
<Test()> _
Public Sub testArticleAbsent()
' حذف جميع المواد
articlesDao.clearAllArticles()
' البحث عن المادة 1
Dim article As article = articlesDao.getArticleById(1)
' التحقق
Assert.IsNull(article)
' تعديل عنصر غير موجود
Dim i As Integer = articlesDao.modifieArticle(New article(1, "1", 1, 1, 1))
' لم يتم تعديل أي سطر
Assert.AreEqual(i, 0)
' حذف بند غير موجود
i = articlesDao.supprimeArticle(1)
' لم يتم حذف أي سطر
Assert.AreEqual(0, i)
End Sub
<Test()> _
Public Sub testChangerStockArticle()
' حذف جميع المقالات
articlesDao.clearAllArticles()
' إضافة مقال
Dim nbArticles As Integer = articlesDao.ajouteArticle(New Article(3, "article3", 30, 101, 3))
Assert.AreEqual(nbArticles, 1)
' إضافة مقال
nbArticles = articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
Assert.AreEqual(nbArticles, 1)
' إنشاء 100 موضوع
Dim taches(99) As Thread
For i As Integer = 0 To taches.Length - 1
' يتم إنشاء الموضوع i
taches(i) = New Thread(New ThreadStart(AddressOf décrémente))
' يتم تحديد اسم الخيط
taches(i).Name = "tache_" & i
' يتم تشغيل مؤشر الترابط i
taches(i).Start()
Next
' الانتظار حتى انتهاء جميع الخيوط
For i As Integer = 0 To taches.Length - 1
taches(i).Join()
Next
' التحقق - يجب أن يكون مخزون المادة 3 هو 1
Dim unArticle As Article = articlesDao.getArticleById(3)
Assert.AreEqual(unArticle.nom, "article3")
Assert.AreEqual(1, unArticle.stockactuel)
' يتم خصم الكمية من مخزون المادة 4
Dim erreur As Boolean = False
Dim nbLignes As Integer = articlesDao.changerStockArticle(4, -100)
' التحقق: لا ينبغي أن يكون مخزونه قد تغير
Assert.AreEqual(0, nbLignes)
' التحقق البصري
listArticles()
End Sub
Public Sub décrémente()
' تم تشغيل مؤشر الترابط
System.Console.Out.WriteLine(Thread.CurrentThread.Name + " lancé")
' الخيط يقوم بتخفيض المخزون
articlesDao.changerStockArticle(3, -1)
' انتهى الخيط
System.Console.Out.WriteLine(Thread.CurrentThread.Name + " terminé")
End Sub
End Class
End Namespace
تعليقات:
- أردنا كتابة برنامج لاختبار واجهة [IArticlesDao] بحيث يكون مستقلاً عن فئة التنفيذ الخاصة بها. ولذلك استخدمنا Spring لإخفاء اسم فئة التنفيذ عن برنامج الاختبار.
- تسترد طريقة السمة <Setup()> من Spring مرجعًا للكائن [articlesdao] المراد اختباره. ويتم تعريف هذا الكائن في الملف [spring-config.xml] التالي:
<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">
<objects>
<object id="articlesdao" type="istia.st.articles.dao.ArticlesDaoArrayList, webarticles-dao"/>
</objects>
يحدد هذا الملف اسم فئة التنفيذ [istia.st.articles.dao.ArticlesDaoArrayList] للواجهة [IArticlesDao] ومكان العثور عليها [ webarticles-dao.dll]. ونظرًا لأن إنشاء المثيل لا يتطلب معلمات، لم يتم تعريف أي معلمات هنا.
- معظم الاختبارات سهلة الفهم. يُرجى من القارئ الاطلاع على التعليقات.
- تتطلب الطريقة [testChangerStockArticle] بعض التوضيحات. فهي تنشئ 100 مؤشر ترابط مكلفة بخفض مخزون منتج معين.
<Test()> _
Public Sub testChangerStockArticle()
' حذف جميع الأصناف
articlesDao.clearAllArticles()
' إضافة عنصر
Dim nbArticles As Integer = articlesDao.ajouteArticle(New Article(3, "article3", 30, 101, 3))
Assert.AreEqual(nbArticles, 1)
' إضافة عنصر
nbArticles = articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
Assert.AreEqual(nbArticles, 1)
' إنشاء 100 مؤشر ترابط
Dim taches(99) As Thread
For i As Integer = 0 To taches.Length - 1
' يتم إنشاء الموضوع i
taches(i) = New Thread(New ThreadStart(AddressOf décrémente))
' تحديد اسم الخيط
taches(i).Name = "tache_" & i
' بدء تشغيل مؤشر الترابط i
taches(i).Start()
Next
' الانتظار حتى انتهاء جميع الخيوط
For i As Integer = 0 To taches.Length - 1
taches(i).Join()
Next
' التحقق - يجب أن يكون مخزون المادة 3 هو 1
Dim unArticle As Article = articlesDao.getArticleById(3)
Assert.AreEqual(unArticle.nom, "article3")
Assert.AreEqual(1, unArticle.stockactuel)
' يتم خصم الكمية من مخزون المادة 4
Dim erreur As Boolean = False
Dim nbLignes As Integer = articlesDao.changerStockArticle(4, -100)
' التحقق: لا ينبغي أن يكون مخزونه قد تغير
Assert.AreEqual(0, nbLignes)
' التحقق البصري
listArticles()
End Sub
يهدف هذا الاختبار إلى اختبار عمليات الوصول المتزامنة إلى مصدر البيانات. الطريقة المسؤولة عن تحديث المخزون هي التالية:
Public Sub décrémente()
' تم تشغيل مؤشر الترابط
System.Console.Out.WriteLine(Thread.CurrentThread.Name + " lancé")
' يقوم الخيط بخفض المخزون
articlesDao.changerStockArticle(3, -1)
' انتهى الخيط
System.Console.Out.WriteLine(Thread.CurrentThread.Name + " terminé")
End Sub
تقلل مخزون المادة رقم 3 بمقدار وحدة واحدة. وبالرجوع إلى رمز الطريقة [testChangerStockArticle]، نلاحظ أن:
- يتم تعيين مخزون المادة رقم 3 مبدئيًا على 101
- ستقوم الخيوط المائة بخفض هذا المخزون بمقدار وحدة واحدة لكل منها
- وبالتالي يجب أن يصبح المخزون 1 عند انتهاء تنفيذ جميع الخيوط
من ناحية أخرى، وفي هذه الطريقة نفسها، نحاول جعل مخزون المادة رقم 4 يساوي قيمة سالبة. يجب أن تفشل هذه المحاولة.
لاختبار الطبقة [dao]، نقوم بإنشاء DLL و[tests-webarticles-dao.dll] في المجلد [bin] التابع للمشروع [tests]:
![]() | ![]() |
ثم، باستخدام التطبيق [Nunit-Gui]، نقوم بتحميل ملف DLL هذا ونجري الاختبارات:

في النافذة اليسرى، نرى قائمة بالطرق التي تم اختبارها. يشير لون النقطة التي تسبق اسم كل طريقة إلى نجاح (أخضر) أو فشل (أحمر) الطريقة. سيتمكن القارئ الذي يعرض هذا المستند على الشاشة من رؤية أن جميع الاختبارات قد نجحت. وبالتالي، سنعتبر أن لدينا طبقة [dao] جاهزة للعمل.
1.4.4. الطبقة [domain]
تحتوي الطبقة [domain] على العناصر التالية:
-
[IArticlesDomain]: واجهة الوصول إلى الطبقة [domain]
-
[Achat]: فئة تحدد عملية شراء
-
[Panier]: فئة تُعرّف سلة التسوق
-
[AchatsArticles]: فئة تنفيذ واجهة [IArticlesDomain]
هيكل الحل [Visual Studio] للطبقة [domain] هو كما يلي:

تعليقات:
- المشروع [domain] هو من النوع [bibliothèque de classes]
- تم وضع الفئات في شجرة مجلدات تبدأ بالمجلد الجذر [istia]. وهي جميعها موجودة في مساحة الأسماء [istia.st.articles.domain].
- تم وضع DLL من الطبقة [dao] في المجلد [bin] التابع للمشروع الجديد. كما تمت إضافة DLL كمرجع للمشروع.
1.4.4.1. الواجهة [IArticlesDomain]
تقوم الواجهة [IArticlesDomain] بفصل الطبقة [métier] عن الطبقة [web]. وتقوم هذه الطبقة بالوصول إلى الطبقة [métier/domain] عبر هذه الواجهة دون الاهتمام بالفئة التي تقوم بتنفيذها فعليًّا. تحدد الواجهة الإجراءات التالية للوصول إلى طبقة الأعمال:
Imports Article = istia.st.articles.dao.Article
Namespace istia.st.articles.domain
Public Interface IArticlesDomain
' الأساليب
Sub acheter(ByVal panier As Panier)
Function getAllArticles() As IList
Function getArticleById(ByVal idArticle As Integer) As Article
ReadOnly Property erreurs() As ArrayList
End Interface
End Namespace
يعرض قائمة الكائنات [Article] من مصدر البيانات المرتبط | |
يعرض الكائن [Article] المحدد بواسطة [idArticle] | |
تقوم بالتحقق من صحة سلة التسوق الخاصة بالعميل عن طريق خصم الكمية المشتراة من مخزون الأصناف المشتراة - قد تفشل العملية في حالة عدم كفاية المخزون | |
يعرض قائمة بالأخطاء التي حدثت - فارغ في حالة عدم وجود أخطاء |
1.4.4.2. الفئة [Achat]
تمثل الفئة [Achat] عملية شراء قام بها العميل:
Imports istia.st.articles.dao
Namespace istia.st.articles.domain
Public Class Achat
' الحقول الخاصة
Private _article As article
Private _qte As Integer
' منشئ افتراضي
Public Sub New()
End Sub
' منشئ مع معلمات
Public Sub New(ByVal unArticle As article, ByVal qte As Integer)
' نمر عبر الخصائص
Me.article = unArticle
Me.qte = qte
End Sub
' المنتج الذي تم شراؤه
Public Property article() As article
Get
Return _article
End Get
Set(ByVal Value As article)
_article = Value
End Set
End Property
' الكمية المشتراة
Public Property qte() As Integer
Get
Return _qte
End Get
Set(ByVal Value As Integer)
If Value < 0 Then
Throw New Exception("Quantité [" + Value.ToString + "] invalide")
End If
_qte = Value
End Set
End Property
' إجمالي الشراء
Public ReadOnly Property totalAchat() As Double
Get
Return _qte * _article.prix
End Get
End Property
' الهوية
Public Overrides Function ToString() As String
Return "[" + _article.ToString + "," + _qte.ToString + "]"
End Function
End Class
End Namespace
تعليقات:
- تحتوي الفئة [Achat] على الخصائص والطرق التالية:
المنتج الذي تم شراؤه | |
الكمية المشتراة | |
مبلغ الشراء | |
سلسلة هوية الكائن |
- يحتوي على منشئ يسمح بتهيئة الخصائص [article, qte] التي تحدد عملية الشراء.
1.4.4.3. الفئة [Panier]
تمثل الفئة [Panier] مجموعة عمليات الشراء الخاصة بالعميل:
Namespace istia.st.articles.domain
Public Class Panier
' الحقول الخاصة
Private _achats As New ArrayList
Private _totalPanier As Double = 0
' الشركة المصنعة الافتراضية
Public Sub New()
End Sub
' قائمة المشتريات
Public ReadOnly Property achats() As ArrayList
Get
Return _achats
End Get
End Property
' إجمالي المشتريات
Public ReadOnly Property totalPanier() As Double
Get
Return _totalPanier
End Get
End Property
' الطرق
Public Sub ajouter(ByVal unAchat As Achat)
' يتم البحث عما إذا كان المشتريات موجودًا بالفعل
Dim iAchat As Integer = posAchat(unAchat.article.id)
If iAchat <> -1 Then
' تم العثور عليه
Dim achatCourant As Achat = CType(_achats(iAchat), Achat)
achatCourant.qte += unAchat.qte
Else
' لم يتم العثور عليه
_achats.Add(unAchat)
End If
' زيادة إجمالي سلة التسوق
_totalPanier += unAchat.totalAchat
End Sub
' إزالة منتج من سلة التسوق
Public Sub enlever(ByVal idAchat As Integer)
' البحث عن المنتج
Dim iachat As Integer = posAchat(idAchat)
' إذا تم العثور عليه، يتم حذفه
If iachat <> -1 Then
Dim achatCourant As Achat = CType(_achats(iachat), Achat)
' يتم حذف المنتج من سلة التسوق
_achats.RemoveAt(iachat)
' يتم خصم المبلغ من إجمالي سلة التسوق
_totalPanier -= achatCourant.totalAchat
End If
End Sub
Private Function posAchat(ByVal idArticle As Integer) As Integer
' البحث عن عملية شراء في قائمة المشتريات
' إرجاع ترتيبه في القائمة أو -1 في حالة عدم العثور عليه
Dim achatCourant As Achat
Dim trouvé As Boolean = False
Dim i As Integer = 0
While Not trouvé AndAlso i < _achats.Count
' العملية الحالية
achatCourant = CType(_achats(i), Achat)
' مقارنة مع المنتج المطلوب
If achatCourant.article.id = idArticle Then
Return i
End If
'عملية الشراء التالية
i += 1
End While
' لم يتم العثور عليه
Return -1
End Function
' وظيفة الهوية
Public Overrides Function ToString() As String
Return _achats.ToString
End Function
End Class
End Namespace
تعليقات:
- تحتوي الفئة [Panier] على الخصائص والطرق التالية:
قائمة مشتريات العميل - قائمة العناصر من النوع [Achat] | |
تضيف عملية شراء إلى قائمة المشتريات | |
حذف عملية الشراء الخاصة بالمنتج idAchat | |
المبلغ الإجمالي لمشتريات سلة التسوق | |
إرجاع سلسلة معرف سلة التسوق |
- الطريقة [posAchat] هي طريقة مساعدة تسمح بالحصول على الموضع في قائمة المشتريات لمشتريات محددة برقم المنتج الذي تم شراؤه. تُدار قائمة المشتريات بحيث لا يشغل المنتج الذي تم شراؤه عدة مرات سوى موضع واحد في القائمة. وبالتالي، يمكن تحديد عملية الشراء برقم المنتج الذي تم شراؤه. تُرجع الطريقة [posAchat] القيمة -1 إذا كانت عملية الشراء المطلوبة غير موجودة.
- تقوم الطريقة [ajouter] بإضافة عملية شراء جديدة إلى قائمة المشتريات. وهذا يعني إما إضافة إدخال جديد إلى قائمة المشتريات إذا لم يكن المنتج المشترى موجودًا بالفعل في القائمة، أو زيادة الكمية المشتراة إذا كان موجودًا بالفعل.
- تسمح الطريقة [enlever] بإزالة عملية شراء محددة برقم من قائمة المشتريات. إذا لم تكن عملية الشراء موجودة، فإن الطريقة لا تصدر أي رسالة ولا تقوم بأي إجراء.
- يتم الحفاظ على مبلغ المشتريات [totalPanier] مع عمليات إضافة وحذف المشتريات.
1.4.4.4. الفئة [AchatsArticles]
سيتم تنفيذ الواجهة [IArticlesDomain] بواسطة الفئة [AchatsArticles] التالية:
Imports istia.st.articles.dao
Namespace istia.st.articles.domain
Public Class AchatsArticles
Implements IArticlesDomain
'الحقول الخاصة
Private _articlesDao As IArticlesDao
Private _erreurs As ArrayList
' المنشئ
Public Sub New(ByVal articlesDao As IArticlesDao)
_articlesDao = articlesDao
End Sub
' قائمة الأخطاء
Public ReadOnly Property erreurs() As ArrayList Implements IArticlesDomain.erreurs
Get
Return _erreurs
End Get
End Property
' قائمة المقالات
Public Function getAllArticles() As IList Implements IArticlesDomain.getAllArticles
' قائمة بجميع المقالات
Try
Return _articlesDao.getAllArticles
Catch ex As Exception
_erreurs = New ArrayList
_erreurs.Add("Erreur d'accès aux données : " + ex.Message)
End Try
End Function
' الحصول على مادة محددة برقمها
Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDomain.getArticleById
' منتج معين
Try
Return _articlesDao.getArticleById(idArticle)
Catch ex As Exception
_erreurs = New ArrayList
_erreurs.Add("Erreur d'accès aux données : " + ex.Message)
End Try
End Function
' شراء سلة التسوق
Public Sub acheter(ByVal panier As Panier) Implements IArticlesDomain.acheter
' شراء سلة التسوق - يجب خصم كميات المنتجات المشتراة من المخزون
_erreurs = New ArrayList
Dim achat As achat
Dim achats As ArrayList = panier.achats
For i As Integer = achats.Count - 1 To 0 Step -1
' تخفيض مخزون المنتج i
achat = CType(achats(i), achat)
Try
If _articlesDao.changerStockArticle(achat.article.id, -achat.qte) = 0 Then
' تعذر تنفيذ العملية
_erreurs.Add("L'achat " + achat.ToString + " n'a pu se faire - Vérifiez les stocks")
Else
' تم تنفيذ العملية - يتم إزالة عملية الشراء من سلة التسوق
panier.enlever(achat.article.id)
End If
Catch ex As Exception
_erreurs = New ArrayList
_erreurs.Add("Erreur d'accès aux données : " + ex.Message)
End Try
Next
End Sub
End Class
End Namespace
تعليقات:
- تنفذ هذه الفئة الطرق الأربع للواجهة [IArticlesDomain]. وتحتوي على حقلين خاصين:
كائن الوصول إلى البيانات | |
قائمة الأخطاء المحتملة. يمكن الوصول إليها عبر الخاصية العامة [erreurs] |
- ولإنشاء مثيل للفئة، يجب توفير الكائن الذي يتيح الوصول إلى البيانات:
- تعتمد الطريقتان [getAllArticles] و [getArticleById] على الطرق التي تحمل نفس الاسم في الطبقة [dao]
- تقوم الطريقة [acheter] بالتحقق من صحة عملية شراء سلة التسوق. ويتمثل هذا التحقق ببساطة في خصم كميات المنتجات المشتراة من المخزون. لا يمكن شراء أي منتج إلا إذا سمح مخزونه بذلك. إذا لم يكن الأمر كذلك، يتم رفض الشراء: يبقى المنتج في سلة التسوق ويتم الإبلاغ عن خطأ في القائمة [erreurs]. يتم سحب الشراء الذي تمت الموافقة عليه من سلة التسوق ويتم خصم الكمية المشتراة من مخزون المنتج المعني.
1.4.4.5. إنشاء تجميع الطبقة [domain]
تم تكوين مشروع Visual Studio لإنشاء التجميع [webarticles-domain.dll]. ويتم إنشاء هذا التجميع في المجلد [bin] الخاص بالمشروع:
![]() | ![]() |
1.4.4.6. اختبارات NUnit للطبقة [domain]
هيكل مشروع Visual Studio للاختبار هو كما يلي:

تعليقات:
- المشروع [tests] هو من النوع [bibliothèque de classes]
- تتطلب الاختبارات [NUnit] مرجعًا إلى التجميع [nunit.framework.dll]
- تسترد فئة الاختبار [NUnit] مثيلًا للكائن المراد اختباره عبر Spring. ولذلك نجد
- في المجلد [bin]، ملفات فئات Spring
- وفي [References]، إشارة إلى التجميع [Spring-Core.dll] من المجلد [bin]
- وفي [bin]، ملف تكوين لـ Spring
- تحتاج فئة الاختبار إلى التجميع [webarticles-dao.dll] من الطبقة [dao] وإلى التجميع [webarticles-domain.dll] من الطبقة [domain]. وقد تم وضع هذين التجميعين في المجلد [bin] وإضافة إشاراتهما إلى إشارات المشروع.
قد تبدو فئة الاختبار NUnit التابعة للطبقة [domain] كما يلي:
Imports NUnit.Framework
Imports istia.st.articles.dao
Imports istia.st.articles.domain
Imports Spring.Objects.Factory.Xml
Imports System.IO
Namespace istia.st.articles.tests
<TestFixture()> _
Public Class NunitTestArticlesDomain
' الكائن المراد اختباره
Private articlesDomain As IArticlesDomain
Private articlesDao As IArticlesDao
<SetUp()> _
Public Sub init()
' استرداد مثيل من مُنشئ الكائنات Spring
Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config.xml", FileMode.Open))
' يُطلب إنشاء مثيل للكائن articles dao
articlesDao = CType(factory.GetObject("articlesdao"), IArticlesDao)
' ثم مثيل الكائن articlesdomain
articlesDomain = CType(factory.GetObject("articlesdomain"), IArticlesDomain)
End Sub
<Test()> _
Public Sub getAllArticles()
' التحقق البصري
listArticles()
End Sub
<Test()> _
Public Sub getArticleById()
' حذف العناصر
articlesDao.clearAllArticles()
' التحقق
Dim articles As IList = articlesDomain.getAllArticles
Assert.AreEqual(0, articles.Count)
' إضافة مادتين
articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
' التحقق
articles = articlesDomain.getAllArticles
Assert.AreEqual(2, articles.Count)
' البحث عن المادة 3
Dim unArticle As Article = articlesDomain.getArticleById(3)
' التحقق
Assert.AreEqual(unArticle.nom, "article3")
' البحث عن المادة 4
unArticle = articlesDao.getArticleById(4)
' التحقق
Assert.AreEqual(unArticle.nom, "article4")
End Sub
<Test()> _
Public Sub acheterPanier()
' حذف البنود
articlesDao.clearAllArticles()
' التحقق
Dim articles As IList = articlesDomain.getAllArticles
Assert.AreEqual(0, articles.Count)
' إضافة مادتين
articlesDao.ajouteArticle(New Article(3, "article3", 30, 30, 3))
articlesDao.ajouteArticle(New Article(4, "article4", 40, 40, 4))
' التحقق
articles = articlesDomain.getAllArticles
Assert.AreEqual(2, articles.Count)
' إنشاء سلة تسوق تحتوي على منتجين
Dim panier As New panier
panier.ajouter(New Achat(New Article(3, "article3", 30, 30, 3), 10))
panier.ajouter(New Achat(New Article(4, "article4", 40, 40, 4), 10))
' عمليات التحقق
Assert.AreEqual(700, panier.totalPanier, 0.000001)
Assert.AreEqual(2, panier.achats.Count)
' التحقق من سلة التسوق
articlesDomain.acheter(panier)
' التحقق
Assert.AreEqual(0, articlesDomain.erreurs.Count)
Assert.AreEqual(0, panier.achats.Count)
' البحث عن المنتج 3
Dim unArticle As Article = articlesDomain.getArticleById(3)
' التحقق
Assert.AreEqual(unArticle.stockactuel, 20)
' البحث عن المنتج 4
unArticle = articlesDao.getArticleById(4)
' التحقق
Assert.AreEqual(unArticle.stockactuel, 30)
' سلة تسوق جديدة
panier.ajouter(New Achat(New Article(3, "article3", 30, 30, 3), 100))
' تأكيد سلة التسوق
articlesDomain.acheter(panier)
' عمليات التحقق
Assert.AreEqual(1, articlesDomain.erreurs.Count)
' البحث عن المنتج 3
unArticle = articlesDomain.getArticleById(3)
' التحقق
Assert.AreEqual(unArticle.stockactuel, 20)
End Sub
<Test()> _
Public Sub testRetirerAchats()
' حذف محتوى ARTICLES
articlesDao.clearAllArticles()
' قراءة الجدول ARTICLES
Dim articles As IList = articlesDao.getAllArticles()
Assert.AreEqual(0, articles.Count)
' إدراج
Dim article3 As New Article(3, "article3", 30, 30, 3)
articlesDao.ajouteArticle(article3)
Dim article4 As New Article(4, "article4", 40, 40, 4)
articlesDao.ajouteArticle(article4)
' قراءة الجدول ARTICLES
articles = articlesDomain.getAllArticles()
Assert.AreEqual(2, articles.Count)
' إنشاء سلة تسوق تحتوي على منتجين
Dim monPanier As New Panier
monPanier.ajouter(New Achat(article3, 10))
monPanier.ajouter(New Achat(article4, 10))
' عمليات التحقق
Assert.AreEqual(700.0, monPanier.totalPanier, 0.000001)
Assert.AreEqual(2, monPanier.achats.Count)
' إضافة منتج تم شراؤه مسبقًا
monPanier.ajouter(New Achat(article3, 10))
' عمليات التحقق
' يجب تعديل المجموع إلى 1000
Assert.AreEqual(1000.0, monPanier.totalPanier, 0.000001)
' لا يزال هناك منتجان في سلة التسوق
Assert.AreEqual(2, monPanier.achats.Count)
' يجب أن تكون كمية المنتج 3 قد تغيرت إلى 20
Dim unAchat As Achat = CType(monPanier.achats(0), Achat)
Assert.AreEqual(20, unAchat.qte)
' إزالة المنتج رقم 3 من سلة التسوق
monPanier.enlever(3)
' عمليات التحقق
' يجب أن يرتفع المجموع إلى 400
Assert.AreEqual(400.0, monPanier.totalPanier, 0.000001)
' يجب أن يكون هناك منتج واحد فقط في سلة التسوق
Assert.AreEqual(1, monPanier.achats.Count)
' يجب أن يكون هذا المنتج رقم 4
Assert.AreEqual(4, CType(monPanier.achats(0), Achat).article.id)
End Sub
' قائمة الشاشة
Private Sub listArticles()
Dim articles As IList = articlesDomain.getAllArticles
For i As Integer = 0 To articles.Count - 1
Console.WriteLine(CType(articles(i), Article).ToString)
Next
End Sub
End Class
End Namespace
تعليقات:
- أردنا كتابة برنامج لاختبار واجهة [IArticlesDomain] بحيث يكون مستقلاً عن فئة التنفيذ الخاصة بها. ولذلك استخدمنا Spring لإخفاء اسم فئة التنفيذ عن برنامج الاختبار.
- تسترد طريقة السمة <Setup()> من Spring مرجعًا إلى الكائنات [articlesdomain] و [articlesdao] المراد اختبارها. وقد تم تعريف هذين الكائنين في الملف [spring-config.xml] التالي:
<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">
<objects>
<object id="articlesdao" type="istia.st.articles.dao.ArticlesDaoArrayList, webarticles-dao" />
<object id="articlesdomain" type="istia.st.articles.domain.AchatsArticles, webarticles-domain">
<constructor-arg index="0">
<ref object="articlesdao" />
</constructor-arg>
</object>
</objects>
يُشير هذا الملف إلى:
- (تابع)
- بالنسبة للسينجلتون [articlesdao]، اسم فئة التنفيذ [istia.st.articles.dao.ArticlesDaoArrayList] ومكان العثور عليها [ webarticles-dao.dll]. وبما أن إنشاء المثيل لا يتطلب معلمات، فلم يتم تعريف أي منها هنا.
- بالنسبة للسينجلتون [articlesdomain]، اسم فئة التنفيذ هو [istia.st.articles.domain.AchatsArticles]، وموقعها هو [ webarticles-domain.dll]. تحتوي الفئة [AchatsArticles] على منشئ بمعلمة واحدة: الفئة الفردية التي تدير الوصول إلى الطبقة [dao]. وهنا، تم تعريف هذه الطبقة على أنها الفئة الفردية [articlesdao] التي تم تعريفها سابقًا.
- تحصل فئة الاختبار على مثيل للفئة المراد اختبارها [articlesdomain] بالإضافة إلى مثيل لفئة الوصول إلى البيانات [articlesdao]. هذه النقطة الأخيرة مثيرة للجدل. من الناحية النظرية، لا ينبغي أن تحتاج فئة الاختبار إلى الوصول إلى الطبقة [dao] التي لا يُفترض بها حتى أن تعرفها. هنا، تجاوزنا هذه «القاعدة» التي كانت ستجبرنا، لو تم الالتزام بها، على إنشاء طرق جديدة في واجهتنا [IArticlesDomain].
لاختبار الطبقة [domain]، نقوم بإنشاء DLL و[tests-webarticles-domain.dll] في المجلد [bin] التابع للمشروع [tests]:
![]() | ![]() |
ثم، باستخدام التطبيق [Nunit-Gui]، نقوم بتحميل ملف DLL هذا ونجري الاختبارات:

سيتمكن القارئ الذي يعرض هذا المستند على الشاشة من رؤية أن جميع الاختبارات قد نجحت. بعد ذلك، سنعتبر أن لدينا طبقة [domain] جاهزة للعمل.
1.4.5. الخلاصة
تجدر الإشارة إلى أننا نرغب في بناء تطبيق الويب ذي الطبقات الثلاث التالي:
![]() |
تم الآن كتابة النموذج M لتطبيقنا MVC واختباره. ويتم توفيره لنا في ملفين هما DLL و[webarticles-dao.dll, webarticles-domain.dll]. يمكننا الانتقال إلى الطبقة الأخيرة، وهي الطبقة [web] التي تحتوي على وحدة التحكم C وطرق العرض V. سننظر أولاً في طريقة مقدمة في المستند [Développement WEB avec ASP.NET 1.1 ]
- يتم توفير وحدة التحكم C من خلال ملفين هما [global.asax, main.aspx]
- أما العروض V فتتم من خلال صفحات aspx
1.5. الطبقة [web]
ستكون بنية تطبيق الويب MVC كما يلي:
![]() |
فئات الأعمال [domain]، وفئات الوصول إلى البيانات [dao]، ومصدر البيانات | |
الصفحات ASPX | |
جميع طلبات العملاء HTTP تمر عبر وحدة التحكمين التاليتين: global.asax: يدير الأحداث المتعلقة بالتشغيل الأولي للتطبيق main.aspx: يعالج طلب كل عميل على حدة |
1.5.1. الآراء
تتطابق العروض مع تلك التي تم عرضها في بداية الوثيقة:
liste.aspx | تم تجميع العروض في المجلد [vues] الخاص بالتطبيق ![]() | |
infos.aspx | ||
panier.aspx | ||
paniervide.aspx | ||
erreurs.aspx |
1.5.2. أجهزة التحكم
كما سبق ذكره، سيتألف المراقب من عنصرين:
- [global.asax,global.asax.vb]: يُستخدم بشكل أساسي لتهيئة التطبيق ووضع جميع البيانات المراد مشاركتها بين العملاء المختلفين في سياق التطبيق
- [main.aspx, main.aspx.vb]: وحدة التحكم الفعلية، وهي التي تعالج طلبات HTTP الواردة من العملاء.
سيتم توجيه الطلبات المختلفة من العملاء إلى وحدة التحكم [main.aspx] وستحتوي على معلمة تسمى [action] تحدد الإجراء المطلوب من قبل العميل:
الطلب | المعنى | إجراء وحدة التحكم | الردود المحتملة |
يريد العميل قائمة بـ المنتجات | - يطلب قائمة المواد من الطبقة الأعمال | - [LISTE] - [ERREURS] | |
يطلب العميل معلومات عن أحد المنتجات المعروضة في العرض [LISTE] | - يطلب المنتج من طبقة الأعمال | - [INFOS] - [ERREURS] | |
يقوم العميل بشراء منتج | - يطلب المنتج من طبقة الأعمال ويضيفها إلى سلة التسوق الخاصة به | - [INFOS] في حالة وجود خطأ في الكمية - [LISTE] في حالة عدم وجود خطأ | |
يريد العميل حذف من سلة التسوق | - يسترد سلة التسوق من الجلسة ويقوم بتعديلها | - [PANIER] - [PANIERVIDE] - [ERREURS] | |
يرغب العميل في عرض سلة التسوق | - استرداد سلة التسوق من الجلسة | - [PANIER] - [PANIERVIDE] - [ERREURS] | |
أكمل العميل عملية الشراء وينتقل إلى مرحلة الدفع | - يقوم بتحديث المخزون في قاعدة البيانات المنتجات التي تم شراؤها - إفراغ سلة التسوق الخاصة بالعميل من المنتجات التي تمت الموافقة على شرائها | - [LISTE] - [ERREURS] |
1.5.3. تكوين التطبيق
سنسعى إلى تكوين التطبيق بحيث يكون أكثر مرونة قدر الإمكان تجاه التغييرات مثل:
- تغيير URL في العروض المختلفة
- تغيير الفئات التي تنفذ واجهات [IArticlesDao] و [IArticlesDomain]
- تغيير SGBD، وقاعدة البيانات، وجدول المنتجات
1.5.3.1. التغييرات في URL
سيتم وضع أسماء URL الخاصة بالطرق في ملف [web.config] الخاص بتكوين التطبيق مع بعض المعلمات الأخرى:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
..
<appSettings>
<add key="urlMain" value="/webarticles/main.aspx"/>
<add key="urlInfos" value="vues/infos.aspx"/>
<add key="urlErreurs" value="vues/erreurs.aspx"/>
<add key="urlListe" value="vues/liste.aspx"/>
<add key="urlPanier" value="vues/panier.aspx"/>
<add key="urlPanierVide" value="vues/paniervide.aspx"/>
</appSettings>
</configuration>
1.5.3.2. تغيير فئات تنفيذ الواجهات
وفقًا لمبادئ البنى ثلاثية الطبقات، يجب أن تكون الطبقات معزولة عن بعضها البعض. ويتم تحقيق هذا العزل بالطريقة التالية:
- تتواصل الطبقات فيما بينها عبر واجهات وليس عبر فئات ملموسة
- لا تقوم شفرة طبقة ما أبدًا بإنشاء مثيل لفئة من طبقة أخرى لاستخدامها. بل تطلب ببساطة من أداة خارجية، وهي Spring في هذه الحالة، مثيلًا لتنفيذ واجهة الطبقة التي تريد استخدامها. ولهذا الغرض، نعلم أنها لا تحتاج إلى معرفة اسم فئة التنفيذ، بل تحتاج فقط إلى اسم عنصر Spring الفريد (singleton) الذي تريد الحصول على مرجع له.
في تطبيقنا، سيتم تكوين Spring في الملف [web.config] الخاص بالتطبيق الويب بالطريقة التالية:
<?xml version="1.0" encoding="iso-8859-1" ?>
<configuration>
<configSections>
<sectionGroup name="spring">
<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
</sectionGroup>
</configSections>
<spring>
<context type="Spring.Context.Support.XmlApplicationContext, Spring.Core">
<resource uri="config://spring/objects" />
</context>
<objects>
<object id="articlesDao" type="istia.st.articles.dao.ArticlesDaoArrayList, webarticles-dao" />
<object id="articlesDomain" type="istia.st.articles.domain.AchatsArticles, webarticles-domain">
<constructor-arg index="0">
<ref object="articlesDao" />
</constructor-arg>
</object>
</objects>
</spring>
<appSettings>
<add key="urlMain" value="/webarticles/main.aspx"/>
<add key="urlInfos" value="vues/infos.aspx"/>
<add key="urlErreurs" value="vues/erreurs.aspx"/>
<add key="urlListe" value="vues/liste.aspx"/>
<add key="urlPanier" value="vues/panier.aspx"/>
<add key="urlPanierVide" value="vues/paniervide.aspx"/>
</appSettings>
</configuration>
للوصول إلى الطبقة [métier]، يمكن لفئة من الطبقة [web] أن تطلب العنصر الفردي [articlesDomain]. عندئذٍ سيقوم Spring بإنشاء مثيل من النوع [istia.st.articles.domain.AchatsArticles]. ولإجراء عملية الإنشاء هذه، يحتاج إلى كائن من النوع [articlesDao]، أي كائن من النوع [istia.st.articles.dao.ArticlesDaoArrayList]. وبالتالي، سيقوم Spring بإنشاء مثيل لمثل هذا الكائن. في نهاية العملية، تكون الطبقة [web] التي طلبت الكائن الفردي [articlesDomain] قد حصلت على السلسلة الكاملة التي تربطها بمصدر البيانات:
![]() |
1.5.3.3. التغييرات المتعلقة بـ SGBD أو بقاعدة البيانات
سيتم تجاهل هذه النقطة هنا لأننا نتعامل مع تطبيق اختباري لا يحتوي على SGBD. وسنتناول في مرحلة لاحقة تنفيذ طبقة [dao] التي تعتمد على SGBD.
1.5.4. مكتبة العلامات <asp:>
لننظر إلى طريقة العرض [ERREURS] التي تعرض قائمة بالأخطاء:

تتولى طريقة العرض [ERREURS] عرض قائمة بالأخطاء التي وضعها عنصر التحكم [main.aspx] في سياق الطلب تحت الاسم [context.Items("erreurs")]. هناك عدة طرق لكتابة مثل هذه الصفحة. ولكننا سنركز هنا فقط على الجزء المتعلق بعرض الأخطاء.
تجدر الإشارة إلى أن صفحة ASPX تحتوي على جزء العرض HTML وجزء الكود .NET الذي يقوم بإعداد البيانات التي يجب أن يعرضها جزء العرض. يمكن أن يكون هذان الجزءان في ملف واحد هو [aspx] (الحل WebMatrix) أو في ملفين: [aspx] للعرض، و[aspx.vb] للكود. وهذا الحل الأخير هو الحل الذي يتبعه Visual Studio. ولزيادة الأمور تعقيدًا، قد يحتوي الجزء الخاص بالتصميم HTML أيضًا على كود .NET، مما يؤدي إلى إرباك الفصل بين [contrôleur] و [présentation] من وجهة النظر. يُنصح بشدة بعدم اتباع هذا الحل عمومًا. وقد استلزم إزالة كل الكود من الجزء [présentation] إنشاء مكتبات علامات. تعمل هذه المكتبات على «إخفاء» الكود تحت شكل علامات مشابهة لعلامات HTML. نقدم حلين ممكنين للصفحة [ERREURS].
يستخدم الحل الأول كود .NET في الجزء [présentation] من الصفحة. وتسترد الصفحة ASPX قائمة الأخطاء الموجودة في الطلب في جزء وحدة التحكم الخاص بها [erreurs.aspx.vb]:
Protected erreurs As ArrayList
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
' استرداد الأخطاء
erreurs = CType(context.Items("erreurs"), ArrayList)
End Sub
ثم يعرضها في الجزء [présentation, erreurs.aspx]:
<h2>Les erreurs suivantes se sont produites :</h2>
<ul>
<%
for i as integer=0 to erreurs.count-1
response.write("<li>" & erreurs(i).ToString & "</li>")
next
%>
</ul>
يستخدم الحل الثاني العلامة <asp:repeater> من مكتبة علامات <asp:> الخاصة بـ ASP.NET. إذا تم إنشاء صفحة ASPX بطريقة رسومية، فإن هذه العلامة تكون متاحة في شكل مكون خادم يتم سحبه وإفلاته على نموذج التصميم. أما إذا تم إنشاء الكود ASPX يدويًّا، فيمكننا عندئذٍ التحدث عن مكتبة العلامات.
باستخدام مكتبة العلامات <asp:>، يصبح الكود ASPX الخاص بالعرض [ERREURS] السابق كما يلي:
<asp:Repeater id="rptErreurs" runat="server">
<HeaderTemplate>
<h3>Les erreurs suivantes se sont produites :
</h3>
<ul>
</HeaderTemplate>
<ItemTemplate>
<li>
<%# Container.DataItem %>
</li>
</ItemTemplate>
<FooterTemplate>
</ul>
</FooterTemplate>
</asp:Repeater>
العلامة
تُستخدم لتكرار نمط HTML على العناصر المختلفة لمصدر البيانات. وعناصرها المختلفة هي كما يلي:
النمط HTML الذي سيتم عرضه قبل عرض عناصر مصدر البيانات | |
النمط HTML الذي يتكرر لكل عنصر من عناصر مصدر البيانات. يُستخدم التعبير [<%# Container.DataItem %>] لعرض قيمة العنصر الحالي في مصدر البيانات | |
النمط HTML الذي يجب عرضه بعد عرض عناصر مصدر البيانات |
يتم ربط مصدر البيانات بالعلامة عادةً في الجزء [contrôleur] من الصفحة:
Protected WithEvents rptErreurs As System.Web.UI.WebControls.Repeater
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
..
' ربط الأخطاء بـ rptErreurs
With rptErreurs
.DataSource = context.Items("erreurs")
.DataBind()
End With
End Sub
يمكن إجراء هذا الربط أيضًا عند تصميم الصفحة إذا كان مصدر البيانات معروفًا بالفعل، مثل قاعدة بيانات موجودة على سبيل المثال.
سنستخدم في طرق العرض الخاصة بنا علامة أخرى: <asp:datagrid> التي تسمح بعرض مصدر البيانات في شكل جدول.
1.5.5. هيكل حل Visual Studio للتطبيق [webarticles]
تُعدّ تطبيقات الويب بمثابة أحجية تتألف من العديد من العناصر. وعادةً ما يؤدي تزويدها بهيكلية MVC إلى زيادة عدد هذه العناصر. وفيما يلي هيكل تطبيق [webarticles] ضمن [Visual Studio]:
![]() | ![]() | ![]() |
![]() |
تعليقات:
- المشروع [web] هو من النوع [bibliothèque de classes] وليس من النوع [Application web ASP.NET] كما قد يتوقع المرء منطقياً. يتطلب النوع [Application web ASP.NET] وجود خادم الويب IIS على جهاز التطوير أو على جهاز بعيد. لا يتوفر خادم IIS بشكل قياسي على أجهزة Windows XP العائلية. ومع ذلك، تُباع العديد من أجهزة PC مع هذا الإصدار. ولتمكين القراء الذين يستخدمون Windows XP من تنفيذ التطبيق قيد الدراسة، سنستخدم خادم الويب Cassini (انظر الملحق) المتاح مجانًا من Microsoft، وسنستبدل المشروع [Application web ASP.NET] بمشروع [bibliothèque de classes]. وينطوي ذلك على بعض العيوب التي تم شرحها في المرفقات.
- الملفات DLL التي يستخدمها التطبيق هي التالية:
تضم فئات طبقة الوصول إلى البيانات | |
تضم فئات طبقة الأعمال | |
يحتوي على فئات Spring التي تسمح لنا بدمج طبقات الويب والمجال وDAO | |
فئات السجلات - التي يستخدمها Spring |
يتم وضع ملفات DLL هذه في المجلد [bin] وإضافتها إلى مراجع المشروع.
1.5.6. طرق العرض ASPX
كما تمت الإشارة سابقًا، سنستخدم مكتبة العلامات <asp:> في طرق العرض الخاصة بنا ASPX.
1.5.6.1. مكون المستخدم [entete.ascx]
من أجل إضفاء قدر من التناسق على العروض المختلفة، ستشترك هذه العروض في رأس واحد، وهو الذي يعرض اسم التطبيق مع القائمة:
![]() | ![]() |
القائمة ديناميكية ويتم تحديدها بواسطة وحدة التحكم. تقوم وحدة التحكم بإدراج سمة مفتاح "actions" في الطلب المرسَل إلى الصفحة ASPX، حيث تكون القيمة المرتبطة بها عبارة عن مصفوفة من العناصر من نوع Hashtable(). كل عنصر في هذه المصفوفة هو قاموس مخصص لتوليد خيار في قائمة العناوين. يحتوي كل قاموس على مفتاحين:
- href: رابط URL المرتبط بخيار القائمة
- link: نص القائمة
سنقوم بتحويل العنوان إلى عنصر تحكم مستخدم. يقوم عنصر التحكم المستخدم بتغليف جزء من الصفحة (التصميم والكود المرتبط) في مكون قابل لإعادة الاستخدام لاحقًا في صفحات أخرى. هنا، نريد إعادة استخدام المكون [entete] في طرق العرض الأخرى للتطبيق. سيكون كود العرض في [entete.ascx] وكود عنصر التحكم المرتبط به في [entete.ascx.vb]. سيستخدم كود العرض مكون <asp:repeater> لعرض جدول خيارات القائمة:
![]() |
n° | type | nom | rôle |
1 | مكرر | rptMenu مصدر البيانات: جدول من القواميس بمفتاحين: href، رابط | عرض خيارات القائمة |
سيكون كود عرض الصفحة كما يلي:
تعليقات:
- تم تعريف المكون [repeater] في الأسطر 6-14
- كل عنصر من عناصر مصدر البيانات المرتبط بالمكرر هو قاموس ذو مفتاحين: href - السطر 9 و lien - السطر 10
وسيكون كود التحكم المرتبط كما يلي:
تعليقات:
- المكون من النوع [EnteteWebArticles] له خاصية عامة [actions] قابلة للكتابة فقط - السطر 7
- تسمح هذه الخاصية بربط المكون <asp:repeater> المسمى [rptMenu] - السطر 10 - بمصفوفة الخيارات التي يحسبها وحدة التحكم في التطبيق - السطران 11-12.
وستستخدم طرق العرض الأخرى للتطبيق الرأس المحدد بواسطة [entete.ascx]. على سبيل المثال، ستقوم الصفحة [erreurs.aspx] بتضمين الرأس باستخدام الكود التالي:
تعليقات:
- يحدد السطر 1 أن العلامة <WA:entete> يجب أن ترتبط بالمكون المحدد بواسطة الملف [entete.ascx]. أما السمتان [TagPrefix] و [TagName] فهما اختياريتان.
- وبعد ذلك، يتم إدراج المكون في كود عرض الصفحة من خلال السطر 9. عند التنفيذ، سيؤدي هذا العلامة إلى تضمين كود الصفحة ASPX التي تحتوي عليها، كود الصفحة [entete.ascx]. وسيتولى كود التحكم [erreurs.aspx.vb] تهيئة هذا المكون. ويمكنه القيام بذلك بالطريقة التالية:
تعليقات:
- السطر 6 ينشئ كائنًا من النوع [EnteteWebArticles] وهو نوع المكون الذي تم إنشاؤه
- السطر 11 يقوم بتهيئة الخاصية [actions] لهذا الكائن
1.5.6.2. طريقة العرض [liste.aspx]
1.5.6.2.1. Introduction
تعرض هذه العرض قائمة بالمنتجات المتاحة للبيع:
![]() | ![]() |
يتم عرضها بعد استعلام /main?action=liste أو /main?action=validationpanier. عناصر استعلام وحدة التحكم هي كما يلي:
كائن Hashtable() - جدول خيارات القائمة | |
ArrayList من كائنات من النوع [Article] | |
كائن String - الرسالة المراد عرضها أسفل الصفحة |
كل رابط [Infos] في جدول HTML للمقالات له URL بالصيغة [?action=infos&id=ID] حيث ID هو حقل idالمقال المعروض.
1.5.6.2.2. مكونات الصفحة
![]() |
n° | type | nom | rôle |
مكون المستخدم | رأس الصفحة | عرض الرأس | |
DataGrid | DataGridArticles 3 - العمود المرتبط: العنوان: الاسم، الحقل: الاسم 4 - العمود المرتبط: العنوان: السعر، الحقل: السعر 5 - عمود الارتباط التشعبي: النص: معلومات، حقل URL: id، تنسيق URL: /webarticles/main.aspx?action=infos&id={0} | عرض المنتجات المعروضة للبيع | |
التسمية | lblMessage | عرض رسالة |
دعونا نستعرض كيفية تعريف هذه الخصائص:
- في Visual Studio، نختار [DataGrid] للوصول إلى ورقة خصائصه:

- استخدم الرابط [Mise en forme automatique] أعلاه لإدارة شكل الجدول المعروض
- والرابط [Générateur de propriétés] لإدارة محتواه
1.5.6.2.3. رمز العرض [liste.aspx]
تعليقات:
- يحدد السطر 9 رأس الصفحة
- تحدد الأسطر من 12 إلى 24 خصائص [DataGrid]
- السطر 26 يحدد التسمية [lblMessage]
1.5.6.2.4. رمز وحدة التحكم [liste.aspx.vb]
تعليقات:
- تظهر مكونات الصفحة في الأسطر 13-15. تجدر الإشارة إلى أنه كان من الضروري إنشاء كائن [EnteteWebArticles] باستخدام عامل [new]، في حين أن ذلك لم يكن ضروريًا مع المكونات الأخرى. وبدون هذا الإنشاء الصريح، كنا نواجه خطأً في التنفيذ يشير إلى أن الكائن [entete] لا يشير إلى أي شيء. هذه النقطة تستحق المزيد من التعمق، لكن لم يتم ذلك.
- يتم أخذ جدول خيارات قائمة الرأس من السياق لتهيئة المكون [entete] للصفحة - السطر 20
- يتم أخذ قائمة العناصر من السياق - السطر 22
- لتهيئة المكون [DataGridArticles] - الأسطر 24-27
- تم تهيئة المكون [lblMessage] برسالة موجودة في السياق - السطر 29
1.5.6.3. طريقة العرض [infos.aspx]
1.5.6.3.1. Introduction
تعرض هذه الواجهة معلومات عن أحد المنتجات وتتيح أيضًا شرائه:

يتم عرضها بعد استعلام /main?action=infos&id=ID أو استعلام /main?action=achat&id=ID عندما تكون الكمية المشتراة خاطئة. عناصر استعلام وحدة التحكم هي كما يلي:
كائن Hashtable() - جدول خيارات القائمة | |
كائن من النوع [Article] - العنصر المراد عرضه | |
كائن String - الرسالة المراد عرضها في حالة حدوث خطأ في الكمية | |
كائن String - القيمة المراد عرضها في حقل الإدخال [Qte] |
يتم استخدام الحقول [msg] و [qte] في حالة حدوث خطأ في إدخال الكمية:

تحتوي هذه الصفحة على نموذج يتم إرساله عبر الزر [Acheter]. القيمة المستهدفة لـ URL في POST هي [?action=achat&id=ID]، حيث ID هو معرّف المنتج الذي تم شراؤه.
1.5.6.3.2. مكونات الصفحة
![]() |
n° | type | nom | rôle |
مكون المستخدم | رأس الصفحة | عرض الرأس | |
القيمة الحرفية | litID | عرض رقم المادة | |
DataGrid | DataGridArticle 3 - العمود ذو الصلة: العنوان: الاسم، الحقل: الاسم 4 - العمود المرتبط: العنوان: السعر، الحقل: السعر 5 - العمود المرتبط: العنوان: المخزون الحالي، الحقل: stockActuel 6 - العمود المرتبط: العنوان: الحد الأدنى للمخزون، الحقل: stockMinimum | عرض منتج | |
HTML إرسال | إرسال النموذج | ||
HTML الإدخال runat=server | txtQte | أدخل الكمية المشتراة | |
label | lblMsgQte | رسالة خطأ محتملة |
1.5.6.3.3. رمز العرض [infos.aspx]
تعليقات:
- الرأس مضمن في الصفحة - السطر 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]
تعليقات:
- يتم تعريف مكونات الصفحة في الأسطر 10-14
- تُعرّف الفئة خاصية عامة [strAction] تُستخدم لتحديد هدف POST في النموذج - الأسطر 17-25
- يتم استرداد المقالة المراد عرضها من سياق التطبيق - السطر 30
- يتم أخذ جدول خيارات قائمة الرأس من السياق لتهيئة مكون الصفحة [entete] - السطر 32
- الأسطر 33-39، يتم ربط المكون [DataGridArticle] بمصدر بيانات من النوع [ArrayList] لا يحتوي إلا على العنصر الذي تم استرداده في السطر 30
- يتم تهيئة المكونات [lblMsgQte, txtQte] بمعلومات مأخوذة من السياق - الأسطر 42-45
- يتم أيضًا تهيئة الخاصية [straction] بمعلومات مأخوذة من السياق - السطر 47. تُستخدم هذه المتغير لتوليد السمة [action] للنموذج HTML الموجود في الصفحة:
1.5.6.4. عرض [panier.aspx]
1.5.6.4.1. Introduction
تعرض هذه النظرة محتويات سلة التسوق:

يتم عرضها بعد إرسال طلب /main?action=panier أو /main?action=retirerachat&id=ID. عناصر طلب وحدة التحكم هي كما يلي:
كائن Hashtable() - جدول خيارات القائمة | |
كائن من النوع [Panier] - سلة التسوق المراد عرضها |
كل رابط [Retirer] في جدول HTML الخاص بمشتريات سلة التسوق له URL بالصيغة [?action=retirerachat&id=ID] حيث ID هو الحقل [id] الخاص بالمنتج الذي نريد إزالته من سلة التسوق.
1.5.6.4.2. مكونات الصفحة
![]() |
n° | type | nom | rôle |
مكون المستخدم | رأس الصفحة | عرض الرأس | |
DataGrid | DataGridAchats 3- العمود المرتبط - العنوان: المادة، الحقل: الاسم 4 - عمود مرتبط - العنوان: الكمية، الحقل: الكمية 5 - عمود مرتبط - العنوان: السعر، الحقل: السعر 6 - عمود مرتبط - العنوان: الإجمالي، الحقل: الإجمالي، التنسيق {0:C} 7 - عمود ارتباط - النص: سحب، عنوان URL: id، تنسيق URL: /webarticles/main.aspx?action=retirerachat&id={0} | عرض قائمة المنتجات المشتراة | |
التسمية | lblTotal | عرض المبلغ المستحق الدفع |
1.5.6.4.3. رمز العرض [panier.aspx]
تعليقات
- السطر 9 يتضمن العنوان
- في الأسطر 12-27، تم تعريف المكون [DataGridAchats]
- في السطر 29، تم تعريف المكون [lblTotal]
1.5.6.4.4. رمز التحكم [panier.aspx.vb]
تعليقات:
- تم إعلان مكونات الصفحة في الأسطر 11-13
- تكون عملية تهيئة المكون [entete] مماثلة لتلك الموجودة في الصفحات التي تمت دراستها سابقًا - السطر 17
- يتم استرداد سلة التسوق المراد عرضها من الجلسة - السطر 19
- يواجه عرض سلة التسوق هذه باستخدام المكون [DataGridAchats] بعض المشاكل. وتكمن الصعوبة في تهيئة المكون. ولنتذكر أعمدة هذا المكون:
- العمود [Article] المرتبط بالحقل [nom] في مصدر البيانات
- العمود [Qté] المرتبط بالحقل [qte] في مصدر البيانات
- العمود [Prix] المرتبط بحقل [prix] في مصدر البيانات
- العمود [Total] المرتبط بالحقل [total] في مصدر البيانات
مصدر البيانات المتاح لدينا هو سلة التسوق وقائمة المشتريات الخاصة بها. وستكون هذه القائمة هي مصدر البيانات لـ [DataGrid]. فقط الكائنات [Achat] التي ستغذي أسطر [DataGrid] لا تمتلك الخصائص [nom, qte, prix, total] التي يتوقعها [DataGrid]. لذلك، نقوم هنا، خصيصًا لـ [DataGrid]، بإنشاء مصدر بيانات تحتوي عناصره على الخصائص التي يتوقعها [DataGrid]. وستكون هذه العناصر من النوع [LigneAchat]، وهي فئة تم إنشاؤها خصيصًا لهذه الغاية ومشتقة من الفئة [Achat] - الأسطر 36-66
- بعد تعريف الفئة [LigneAchat)، يتم إنشاء مصدر البيانات الخاص بـ [DataGridAchats] استنادًا إلى سلة التسوق الموجودة في الجلسة - الأسطر 20-30
- يتم عرض مبلغ المشتريات بفضل الخاصية [totalPanier] للفئة [Panier] - السطر 32
1.5.6.5. عرض [paniervide.aspx]
1.5.6.5.1. Introduction
تعرض هذه العرض المعلومات التي تشير إلى أن سلة التسوق فارغة:

يتم عرضها بعد استعلام /main?action=panier أو /main?action=retirerachat&id=ID. عناصر استعلام وحدة التحكم هي كما يلي:
كائن Hashtable() - جدول خيارات القائمة |
1.5.6.5.2. مكونات الصفحة
![]() |
n° | type | nom | rôle |
مكون المستخدم | رأس الصفحة | عرض الرأس |
1.5.6.5.3. رمز العرض [paniervide.aspx]
تعليقات:
- الرأس مدرج في السطر 9
1.5.6.5.4. رمز التحكم [paniervide.aspx.vb]
تعليقات:
- يكتفى بتهيئة المكون الديناميكي الوحيد في الصفحة - السطر 10
1.5.6.6. طريقة العرض [erreurs.aspx]
1.5.6.6.1. Introduction
يتم عرض هذه الشاشة في حالة حدوث أخطاء:

يتم عرضها بعد أي طلب يؤدي إلى حدوث خطأ، باستثناء عملية الشراء بكمية خاطئة، والتي يتم معالجتها من خلال العرض [INFOS]. عناصر طلب وحدة التحكم هي كما يلي:
كائن Hashtable() - جدول خيارات القائمة | |
ArrayList من كائنات [String] التي تمثل رسائل الأخطاء المطلوب عرضها |
1.5.6.6.2. مكونات الصفحة
![]() |
n° | type | nom | rôle |
مكون المستخدم | رأس الصفحة | عرض الرأس | |
المكرر | rptErreurs | عرض قائمة الأخطاء |
1.5.6.6.3. رمز العرض [erreurs.aspx]
تعليقات:
- تم تعريف الرأس في السطر 9
- المكون [rptErreurs] محدد في الأسطر 13-19. ويأتي محتواه من مصدر بيانات سيكون من النوع [ArrayList] من الكائنات [String].
1.5.6.6.4. رمز التحكم [erreurs.aspx.vb]
تعليقات:
- يتم تهيئة المكون [entete] كالمعتاد، في السطرين 9 و14
- يتم تهيئة المكون [rptErreurs] باستخدام قائمة الأخطاء من النوع [ArrayList] الموجودة في السياق - الأسطر 16-19
1.5.7. وحدات التحكم global.asax و main.aspx
يبقى الآن كتابة جوهر تطبيق الويب الخاص بنا، وهو وحدة التحكم. وتتمثل مهمتها في:
- استلام الطلب من العميل،
- معالجة الإجراء المطلوب من قبل العميل باستخدام فئات الأعمال،
- إرسال العرض المناسب كرد.
1.5.7.1. وحدة التحكم [global.asax.vb]
عندما يتلقى التطبيق طلبه الأول على الإطلاق، يتم تنفيذ الإجراء [Application_Start] الموجود في الملف [global.asax.vb]. وستكون هذه هي المرة الوحيدة. تهدف الإجراء [Application_Start] إلى تهيئة الكائنات الضرورية للتطبيق الويب والتي سيتم مشاركتها في وضع القراءة فقط من قبل جميع خيوط العملاء. يمكن وضع هذه الكائنات المشتركة في مكانين:
- الحقول الخاصة بوحدة التحكم
- سياق تشغيل التطبيق (Application)
ستقوم الطريقة [Application_Start] الخاصة بالتطبيق [global.asax.vb] بالخطوات التالية:
- التحقق من وجود المعلمات اللازمة للتشغيل السليم للتطبيق في الملف [web.config]. وقد تم وصف هذه المعلمات في الفقرة 1.5.3.
- ستضع في سياق التطبيق قائمة بالأخطاء المحتملة في شكل كائن [ArrayList erreurs]. ستكون هذه القائمة فارغة في حالة عدم وجود أخطاء، لكنها ستظل موجودة مع ذلك.
- في حالة وجود أخطاء، تتوقف الطريقة [Application_Start] عند هذا الحد. وإلا، فإنها تطلب مرجعًا لسينجلتون من النوع [IArticlesDomain] الذي سيكون الكائن الوظيفي الذي سيستخدمه المتحكم لتلبية احتياجاته. وكما تم شرحه في 1.5.3.2، سيطلب وحدة التحكم هذا الكائن الفردي من إطار عمل Spring. وقد تؤدي عملية إنشاء المثيل هذه إلى حدوث أخطاء مختلفة. وإذا كان الأمر كذلك، فسيتم تخزين هذه الأخطاء أيضًا في الكائن [erreurs] الخاص بسياق التطبيق.
يحتوي وحدة التحكم [global.asax.vb] على إجراء [Session_Start] يتم تنفيذه في كل مرة يصل فيها عميل جديد. في هذا الإجراء، سيتم إنشاء سلة تسوق فارغة للعميل. وسيتم الاحتفاظ بهذه السلة على مدار طلبات هذا العميل بالذات. قد يكون الكود كما يلي:
تعليقات:
- يتم تعريف المعلمات المتوقعة في [web.config] في مصفوفة - السطر 18
- يتم البحث عنها في [web.config]. إذا كانت موجودة، يتم تخزينها في سياق التطبيق، وإلا يتم تسجيل خطأ في قائمة الأخطاء [erreurs] - الأسطر 21-33
- إذا لم تكن هناك أخطاء، يُطلب من Spring مرجعًا للسينجلتون [articlesDomain] الذي يدير الوصول إلى طبقة [domain] للتطبيق - الأسطر 35-47. يتم تسجيل الأخطاء المحتملة في [erreurs].
- يتم تسجيل الأخطاء في سياق التطبيق - السطر 49
- يتم الخروج من الإجراء في حالة وجود أخطاء - السطر 51
- يتم إنشاء مصفوفة مكونة من ثلاثة قواميس. يحتوي كل منها على مفتاحين: href و lien. تمثل هذه المصفوفة الخيارات الثلاثة الممكنة في القائمة - الأسطر 52-71
- يتم تخزين هذا المصفوف في سياق التطبيق - السطر 73
- عند كل عميل جديد، يتم تنفيذ الإجراء [Session_Start]. ويتم فيه إنشاء سلة تسوق فارغة في جلسة عمل العميل - الأسطر 78-81
1.5.7.2. وحدة التحكم [main.aspx.vb]
تستقبل وحدة التحكم [main.aspx.vb] جميع طلبات العملاء. ففي الواقع، تأتي جميع هذه الطلبات بالصيغة [/webarticles/main.aspx?action=XX]. ويتم معالجة الطلب بالطريقة التالية:
- سيتم التحقق من الكائن [erreurs] في سياق التطبيق. إذا كان غير فارغ، فهذا يعني أن أخطاء قد حدثت أثناء تهيئة التطبيق وأنه لا يمكن تشغيله. عندئذٍ سيتم إرسال العرض [ERREURS] كرد.
- سيتم استرداد المعلمة [action] الخاصة بالطلب والتحقق منها. إذا لم تتطابق مع إجراء معروف، يتم إرسال العرض [ERREURS] مع رسالة خطأ مناسبة.
- إذا كان المعلمة [action] صالحة، يتم تمرير طلب العميل إلى إجراء خاص بالإجراء المحدد للمعالجة:
الطريقة | الطلب | المعالجة | الردود المحتملة |
GET /main?action=liste | - طلب قائمة المقالات من فئة الأعمال - عرضه | [LISTE] أو [ERREURS] | |
GET /main?action=infos&id=ID | - طلب المقالة ذات الرقم التعريفي id=ID من فئة الأعمال - عرضه | [INFOS] أو [ERREURS] | |
POST /main?action=achat&id=ID - الكمية المشتراة جزء من المعلمات المرسلة | - طلب المنتج ذي الرقم التعريفي id=ID من فئة الأعمال - إدراجها في سلة التسوق في جلسة عمل العميل | [LISTE] أو [INFOS] أو [ERREURS] | |
GET /main?action=retirerachat&id=ID | - سحب المنتج ذي الرقم التعريفي id=ID من قائمة مشتريات سلة جلسة عمل العميل | [PANIER] | |
GET /main?action=panier | - عرض سلة التسوق الخاصة بـ جلسة العميل | [PANIER] أو [PANIERVIDE] | |
GET /main?action=validationpanier | - تخفيض مخزون جميع المنتجات الموجودة في سلة العميل | [LISTE] أو [ERREURS] |
قد يكون الهيكل الأساسي لوحدة التحكم [main.aspx.vb] كما يلي:
تعليقات:
- تحتوي الفئة على حقلين خاصين سيتم مشاركتهما بين الطرق - السطور 15-16:
- articlesDomain: العنصر الفردي للوصول إلى الطبقة [domain]
- الخيارات: مصفوفة قواميس خيارات القائمة
- الإجراء [Page_Load]:
- ستقوم بتهيئة الحقلين الخاصين للفئة
- سيسترد المعلمة [action] من الاستعلام ويقوم بتنفيذ الأسلوب الخاص بمعالجة هذا الإجراء.
1.5.7.3. الطريقة [Page_Load]
هذا الحدث هو أول ما يحدث على الصفحة. والرمز هو كما يلي:
تعليقات:
- عند كل عملية تحميل للصفحة، نتأكد من أن تهيئة التطبيق التي تتم بواسطة [global.asax] قد تمت بنجاح.
- ولذلك، يتم استرداد قائمة الأخطاء الموجودة في سياق التطبيق، والتي تم وضعها هناك بواسطة [global.asax] - السطر 4
- إذا كانت هذه القائمة غير فارغة، يتم عرض العرض [ERREURS] - الأسطر 6-10
- نسترد العنصر الفريد [articlesDomain] الذي وضعته [global.asax] في سياق التطبيق ونخزنه في الحقل الخاص [articlesDomain] حتى يكون متاحًا لمختلف أساليب الفئة - السطر 12
- نقوم بعمل مماثل مع مصفوفة خيارات القائمة - السطر 14
- يتم استرداد المعلمة [action] من الطلب - السطر 16
- نقوم بتنفيذ الطريقة المطابقة للإجراء المطلوب. ويُعتبر الإجراء غير المتوقع هو الإجراء [liste] - الأسطر 16-36
1.5.7.4. معالجة الإجراء [liste]
يتعلق الأمر بعرض قائمة المواد:

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

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

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

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

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

والرمز هو كما يلي:
تعليقات:
- نسترد سلة التسوق من الجلسة - السطر 6. لا نتحقق هنا من أننا استردنا شيئًا بالفعل. يجب القيام بذلك لأن الجلسة قد تكون انتهت صلاحيتها.
- في حالة انتهاء صلاحية الجلسة، سيكون لدينا مؤشر [nothing] لسلة التسوق، وستقوم الطريقة [acheter] - السطر 9 - بإطلاق استثناء وسيتم إرسال العرض [ERREURS]. إلا أن رسالة الخطأ لن تكون واضحة تمامًا للمستخدم.
- في الأسطر 8-16، نحاول التحقق من صحة سلة التسوق المستردة من الجلسة. قد لا يتم تأكيد بعض عمليات الشراء إذا تجاوزت الكمية المطلوبة المخزون المتوفر من المنتج المطلوب. يتم تسجيل هذه الحالات بواسطة الأسلوب [acheter] في قائمة أخطاء يتم استردادها في السطر 11.
- في حالة وجود أخطاء، يتم إرسال العرض [ERREURS] - الأسطر 18-23
- وإلا يتم إرسال عرض [LISTE] - الأسطر 25-26
1.6. Conclusion
لقد قمنا هنا بتطوير تطبيق وفقًا لنموذج MVC. لا يبدو أن هناك (أبريل 2005) «أطر عمل» احترافية لتطوير MVC في ASP.NET مثل تلك الموجودة في Java (Struts، Spring، ...). ومن المتوقع أن يقدم مشروع [Spring.net] إطارًا من هذا القبيل قريبًا. وفي انتظار ظهور ذلك، تتيح الطريقة السابقة تطويرًا عمليًّا لـ MVC للتطبيقات متوسطة الحجم.































