6. أمثلة
نقترح في هذا الفصل توضيح ما سبق ذكره من خلال سلسلة من الأمثلة.
6.1. المثال 1
6.1.1. المشكلة
يجب أن تسمح هذه التطبيق للمستخدم بحساب ضريبته. نضع أنفسنا في حالة مبسطة لمكلف لا يملك سوى راتبه ليصرح به (أرقام 2004 للدخل لعام 2003):
- نحسب عدد حصص الموظف nbParts=nbEnfants/2 +1 إذا كان غير متزوج، nbEnfants/2+2 إذا كان متزوجًا، حيث nbEnfants هو عدد أطفاله.
- إذا كان لديه ثلاثة أطفال على الأقل، يحصل على نصف حصة إضافية
- يتم حساب دخله الخاضع للضريبة R=0.72*S حيث S هو راتبه السنوي
- يتم حساب معامل الأسرة QF=R/nbParts
- نحسب ضريبته I. لننظر إلى الجدول التالي:
4262 | 0 | 0 |
8382 | 0.0683 | 291.09 |
14753 | 0.1914 | 1322.92 |
23888 | 0.2826 | 2668.39 |
38868 | 0.3738 | 4846.98 |
47932 | 0.4262 | 6883.66 |
0 | 0.4809 | 9505.54 |
يحتوي كل سطر على 3 حقول. لحساب الضريبة I، نبحث عن السطر الأول الذي يكون فيه QF<=الحقل 1. على سبيل المثال، إذا كان QF=5000، فسنجد السطر
يكون الضريبة I عندئذٍ مساوية لـ 0.0683*R - 291.09*nbParts. إذا كان QF بحيث لا يتم التحقق من العلاقة QF<=champ1 أبدًا، فسيتم استخدام معاملات السطر الأخير. هنا:
مما يعطي الضريبة I=0.4809*R - 9505.54*nbParts.
6.1.2. هيكل MVC للتطبيق
ستكون بنية MVC للتطبيق كما يلي:

ستقوم الصفحة [main.aspx] بدور وحدة التحكم. ستكون هناك ثلاثة إجراءات ممكنة:
- init: تتوافق مع الطلب الأول للعميل. سيعرض وحدة التحكم العرض [formulaire.aspx]
- calcul: يتوافق مع طلب حساب الضريبة. إذا كانت بيانات نموذج الإدخال صحيحة، يتم حساب الضريبة باستخدام فئة الأعمال [impots]. يعيد وحدة التحكم إلى العميل العرض [formulaire.aspx] كما تم التحقق من صحته مع إضافة الضريبة المحسوبة. إذا كانت بيانات نموذج الإدخال غير صحيحة، فستعيد وحدة التحكم العرض [erreurs.aspx] مع قائمة بالأخطاء ورابط للعودة إلى النموذج.
- العودة: تعني العودة إلى النموذج بعد حدوث خطأ. يعرض المراقب العرض [formulaire.aspx] كما تم التحقق من صحته قبل حدوث الخطأ.
لا يعرف وحدة التحكم [main.aspx] أي شيء عن حساب الضرائب. إنها مسؤولة فقط عن إدارة الحوار بين العميل والخادم وتنفيذ الإجراءات التي يطلبها العميل. بالنسبة للإجراء [calcul]، سيعتمد على فئة الأعمال [impot].
6.1.3. فئة الأعمال
سيتم تعريف فئة الضرائب على النحو التالي:
' مساحات الأسماء المستوردة
Imports System
' فئة
Namespace st.istia.univangers.fr
Public Class impot
Private limites(), coeffR(), coeffN() As Decimal
' المنشئ
Public Sub New(ByRef source As impotsData)
' البيانات اللازمة لحساب الضريبة
' تأتي من مصدر خارجي [source]
' يتم استردادها - قد يكون هناك استثناء
Dim data() As Object = source.getData
limites = CType(data(0), Decimal())
coeffR = CType(data(1), Decimal())
coeffN = CType(data(2), Decimal())
End Sub
' حساب الضريبة
Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Long) As Long
' حساب عدد الحصص
Dim nbParts As Decimal
If marié Then
nbParts = CDec(nbEnfants) / 2 + 2
Else
nbParts = CDec(nbEnfants) / 2 + 1
End If
If nbEnfants >= 3 Then
nbParts += 0.5D
End If
' حساب الدخل الخاضع للضريبة و"الحصة العائلية"
Dim revenu As Decimal = 0.72D * salaire
Dim QF As Decimal = revenu / nbParts
' حساب الضريبة
limites((limites.Length - 1)) = QF + 1
Dim i As Integer = 0
While QF > limites(i)
i += 1
End While
Return CLng(revenu * coeffR(i) - nbParts * coeffN(i))
End Function
End Class
End Namespace
كائن ضريبة من خلال تزويد منشئه بمصدر بيانات من النوع [impotsData]. تحتوي هذه الفئة على طريقة عامة [getData] تسمح بالحصول على الجداول الثلاثة للبيانات اللازمة لحساب الضريبة والتي تم عرضها سابقًا. يمكن لهذه الطريقة معالجة استثناء إذا تعذر الحصول على البيانات أو إذا تبين أنها غير صحيحة. بمجرد إنشاء الكائن [impot]، يمكن استدعاء طريقة calculer الخاصة به بشكل متكرر، والتي تحسب ضريبة المكلف بناءً على حالته الاجتماعية (متزوج أم لا)، وعدد أطفاله، وراتبه السنوي.
6.1.4. فئة الوصول إلى البيانات
فئة [impotsData] هي الفئة التي تسمح بالوصول إلى البيانات. وهي فئة مجردة. يجب إنشاء فئة مشتقة لكل مصدر بيانات جديد محتمل (جداول، ملفات مسطحة، قواعد بيانات، وحدة التحكم، ...). تعريفها هو كما يلي:
Imports System.Collections
Namespace st.istia.univangers.fr
Public MustInherit Class impotsData
Protected limites() As Decimal
Protected coeffr() As Decimal
Protected coeffn() As Decimal
Protected checked As Boolean
Protected valide As Boolean
' طريقة الوصول إلى البيانات
Public MustOverride Function getData() As Object()
' طريقة التحقق من البيانات
Protected Function checkData() As Integer
' التحقق من البيانات المكتسبة
' يجب أن تكون هناك بيانات
valide = Not limites Is Nothing AndAlso Not coeffr Is Nothing AndAlso Not coeffn Is Nothing
If Not valide Then Return 1
' يجب أن يكون لدينا 3 جداول بنفس الحجم
If valide Then valide = limites.Length = coeffr.Length AndAlso limites.Length = coeffn.Length
If Not valide Then Return 2
' يجب ألا تكون الجداول فارغة
valide = limites.Length <> 0
If Not valide Then Return 3
' يجب أن يحتوي كل جدول على عناصر >=0 وبترتيب تصاعدي
valide = check(limites, limites.Length - 1) AndAlso check(coeffr, coeffr.Length) AndAlso check(coeffn, coeffn.Length)
If Not valide Then Return 4
' كل شيء على ما يرام
Return 0
End Function
' تحقق من صحة محتوى المصفوفة
Protected Function check(ByRef tableau() As Decimal, ByVal n As Integer) As Boolean
' يجب أن تكون العناصر n الأولى في المصفوفة >=0 وبترتيب تصاعدي صارم
If tableau(0) < 0 Then Return False
For i As Integer = 1 To n - 1
If tableau(i) <= tableau(i - 1) Then Return False
Next
' صحيح
Return True
End Function
End Class
End Namespace
تحتوي الفئة على السمات المحمية التالية:
جدول حدود شرائح الضرائب | |
جدول المعاملات المطبقة على الدخل الخاضع للضريبة | |
جدول المعاملات المطبقة على عدد الحصص | |
قيمة منطقية تشير إلى ما إذا كانت البيانات (الحدود، المعاملات، المعاملات) قد تم التحقق منها | |
قيمة منطقية تشير إلى ما إذا كانت البيانات (الحدود، coeffr، coeffn) صالحة |
لا تحتوي الفئة على منشئ. وتحتوي على طريقة مجردة [getData] يجب على الفئات المشتقة تنفيذها. وتتمثل وظيفة هذه الطريقة في:
- تعيين قيم للمصفوفات الثلاثة limits و coeffr و coeffn
- إطلاق استثناء إذا تعذر الحصول على البيانات أو إذا تبين أنها غير صالحة.
توفر الفئة الطريقتين المحميتين [checkData] و [check] اللتين تتحققان من صحة السمات (limites، coeffr، coeffn). وهذا يعفي الفئات المشتقة من تنفيذها. سيكون عليها فقط استخدامها.
ستكون الفئة المشتقة الأولى التي سنستخدمها هي التالية:
Imports System.Collections
Imports System
Namespace st.istia.univangers.fr
Public Class impotsArray
Inherits impotsData
' منشئ بدون وسيطة
Public Sub New()
' تهيئة المصفوفات باستخدام الثوابت
limites = New Decimal() {4262D, 8382D, 14753D, 23888D, 38868D, 47932D, 0D}
coeffr = New Decimal() {0D, 0.0683D, 0.1914D, 0.2826D, 0.3738D, 0.4262D, 0.4809D}
coeffn = New Decimal() {0D, 291.09D, 1322.92D, 2668.39D, 4846.98D, 6883.66D, 9505.54D}
checked = True
valide = True
End Sub
' منشئ مع ثلاثة مصفوفات كمدخلات
Public Sub New(ByRef limites() As Decimal, ByRef coeffr() As Decimal, ByRef coeffn() As Decimal)
' يتم تخزين البيانات
Me.limites = limites
Me.coeffr = coeffr
Me.coeffn = coeffn
checked = False
End Sub
Public Overrides Function getData() As Object()
' التحقق من البيانات عند الضرورة
Dim erreur As Integer
If Not checked Then erreur = checkData() : checked = True
' إذا كانت غير صالحة، يتم إثارة استثناء
If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
' وإلا يتم إرجاع المصفوفات الثلاثة
Return New Object() {limites, coeffr, coeffn}
End Function
End Class
End Namespace
تحتوي هذه الفئة المسماة [impotsArray] على منشئين:
- منشئ بدون وسيطة يقوم بتهيئة سمات (الحدود، coeffr، coeffn) للفئة الأساسية باستخدام مصفوفات مبرمجة بشكل ثابت
- منشئ يقوم بتهيئة سمات (limites,coeffr,coeffn) للفئة الأساسية باستخدام مصفوفات يتم تمريرها إليه كمعلمات
تكتفي الطريقة [getData]، التي ستسمح للفئات الخارجية بالحصول على المصفوفات (limites، coeffr، coeffn)، بالتحقق من صحة المصفوفات الثلاث باستخدام الطريقة [checkData] للفئة الأساسية. وتطلق استثناءً إذا كانت البيانات غير صالحة.
6.1.5. اختبارات فئات الأعمال وفئات الوصول إلى البيانات
من المهم ألا تتضمن تطبيقات الويب سوى فئات الأعمال وفئات الوصول إلى البيانات التي تم التحقق من صحتها. وبذلك يمكن أن تركز مرحلة تصحيح أخطاء تطبيق الويب على جزء وحدة التحكم وطرق العرض. قد يكون برنامج الاختبار كما يلي:
' خيارات
Option Strict On
Option Explicit On
' مساحات الأسماء
Imports System
Imports Microsoft.VisualBasic
Namespace st.istia.univangers.fr
Module test
Sub Main()
' برنامج تفاعلي لحساب الضرائب
' يقوم المستخدم بإدخال ثلاث بيانات عبر لوحة المفاتيح: متزوج nbEnfants الراتب
' ثم يعرض البرنامج الضريبة المستحقة
Const syntaxe As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"
' إنشاء كائن ضريبة
Dim objImpôt As impot = Nothing
Try
objImpôt = New impot(New impotsArray)
Catch ex As Exception
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
Environment.Exit(1)
End Try
' حلقة لا نهائية
Dim marié As String
Dim nbEnfants As Integer
Dim salaire As Long
While True
' يُطلب إدخال معلمات حساب الضريبة
Console.Out.Write("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :")
Dim paramètres As String = Console.In.ReadLine().Trim()
' هل هناك ما يجب فعله؟
If paramètres Is Nothing OrElse paramètres = "" Then
Exit While
End If
' التحقق من عدد المعلمات في السطر الذي تم إدخاله
Dim erreur As Boolean = False
Dim args As String() = paramètres.Split(Nothing)
Dim nbParamètres As Integer = args.Length
If nbParamètres <> 3 Then
Console.Error.WriteLine(syntaxe)
erreur = True
End If
' التحقق من صحة المعلمات
If Not erreur Then
' متزوج
marié = args(0).ToLower()
If marié <> "o" And marié <> "n" Then
erreur = True
End If
' nbEnfants
Try
nbEnfants = Integer.Parse(args(1))
If nbEnfants < 0 Then
Throw New Exception
End If
Catch
erreur = True
End Try
' الراتب
Try
salaire = Integer.Parse(args(2))
If salaire < 0 Then
Throw New Exception
End If
Catch
erreur = True
End Try
End If
' إذا كانت المعلمات صحيحة - يتم حساب الضريبة
If Not erreur Then
Console.Out.WriteLine(("impôt=" & objImpôt.calculer(marié = "o", nbEnfants, salaire) & " euro(s)"))
Else
Console.Error.WriteLine(syntaxe)
End If
End While
End Sub
End Module
End Namespace
يطلب التطبيق من المستخدم إدخال المعلومات الثلاث اللازمة لحساب ضريبته عبر لوحة المفاتيح:
- حالته الاجتماعية: o للمتزوج، n لغير المتزوج
- عدد أطفاله
- راتبه السنوي
يتم حساب الضريبة باستخدام كائن من النوع [impot] يتم إنشاؤه فور تشغيل التطبيق:
' إنشاء كائن ضريبة
Dim objImpôt As impot = Nothing
Try
objImpôt = New impot(New impotsArray)
Catch ex As Exception
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
Environment.Exit(1)
End Try
كمصدر للبيانات، يتم استخدام كائن من النوع [impotsArray]. يتم استخدام منشئ هذه الفئة بدون حجج، والذي يوفر الجداول الثلاثة (limites، coeffr، coeffn) بقيم "ثابتة". يمكن أن يؤدي إنشاء كائن [impot] نظريًا إلى حدوث استثناء، لأن الكائن سيطلب البيانات (limites، coeffr، coeffn) من مصدر البيانات الذي تم تمريره كمعلمة، وقد يؤدي الحصول على هذه البيانات إلى حدوث استثناء. ويحدث هنا أن طريقة الحصول على البيانات (الترميز الثابت) لا يمكن أن تسبب استثناءً. ومع ذلك، فقد تركنا إدارة هذا الاستثناء من أجل لفت انتباه القارئ إلى احتمال أن يكون الكائن [impot] قد تم إنشاؤه بشكل خاطئ.
فيما يلي مثال على تنفيذ البرنامج السابق:
dos>dir
05/04/2004 13:28 1 337 impots.vb
21/04/2004 08:23 1 311 impotsArray.vb
21/04/2004 08:26 1 634 impotsData.vb
21/04/2004 08:42 2 490 testimpots1.vb
نقوم بتجميع جميع الفئات [impot, impotsData, impotsArray] في تجميع [impot.dll]:
dos>vbc /t:library /out:impot.dll impotsData.vb impotsArray.vb impots.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4
dos>dir
05/04/2004 13:28 1 337 impots.vb
21/04/2004 08:23 1 311 impotsArray.vb
21/04/2004 08:26 1 634 impotsData.vb
21/04/2004 08:42 2 490 testimpots1.vb
21/04/2004 09:21 5 632 impot.dll
نقوم بتجميع برنامج الاختبار:
dos>dir
05/04/2004 13:28 1 337 impots.vb
21/04/2004 08:23 1 311 impotsArray.vb
21/04/2004 08:26 1 634 impotsData.vb
21/04/2004 08:42 2 490 testimpots1.vb
21/04/2004 09:21 5 632 impot.dll
21/04/2004 09:23 4 608 testimpots1.exe
يمكننا إجراء الاختبارات:
dos>testimpots1
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 60000
impôt=4300 euro(s)
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :n 2 60000
impôt=6872 euro(s)
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :
6.1.6. طرق عرض تطبيق الويب
سيكون للتطبيق عرضان: [formulaire.aspx] و [erreurs.aspx]. لنوضح كيفية عمل التطبيق من خلال لقطات الشاشة. يتم عرض واجهة [formulaire.aspx] عند طلب عنوان URL [main.aspx] لأول مرة:

يقوم المستخدم بملء النموذج:

ويستخدم الزر [Calculer] للحصول على الرد التالي:

قد يخطئ في البيانات التي أدخلها:

يؤدي استخدام الزر [Calculer] إلى إجابة أخرى [erreurs.aspx]:

يمكنه استخدام الرابط [Retour au formulaire] أعلاه لاستعادة العرض [formulaire.aspx] كما تم تأكيده قبل الخطأ:

6.1.7. الطريقة [formulaire.aspx]
ستكون الصفحة [formulaire.aspx] كما يلي:
<%@ page src="formulaire.aspx.vb" inherits="formulaire" AutoEventWireup="false"%>
<html>
<head>
<title>Impôt</title>
</head>
<body>
<P>Calcul de votre impôt</P>
<HR>
<form method="post" action="main.aspx?action=calcul">
<TABLE border="0">
<TR>
<TD>Etes-vous marié(e)</TD>
<TD>
<INPUT type="radio" value="oui" name="rdMarie" <%=rdouichecked%>>Oui
<INPUT type="radio" value="non" name="rdMarie" <%=rdnonchecked%>>Non
</TD>
</TR>
<TR>
<TD>Nombre d'enfants</TD>
<TD><INPUT type="text" size="3" maxLength="3" name="txtEnfants" value="<%=txtEnfants%>"></TD>
</TR>
<TR>
<TD>Salaire annuel (euro)</TD>
<TD><INPUT type="text" maxLength="12" size="12" name="txtSalaire" value="<%=txtSalaire%>"></TD>
</TR>
<TR>
<TD>Impôt à payer :
</TD>
<TD><%=txtImpot%></TD>
</TR>
</TABLE>
<hr>
<P>
<INPUT type="submit" value="Calculer">
</P>
</form>
<form method="post" action="main.aspx?action=effacer">
<INPUT type="submit" value="Effacer">
</form>
</body>
</html>
الحقول الديناميكية في هذه الصفحة هي التالية:
"checked" إذا كان يجب تحديد المربع [oui]، و"" في حالة عدم تحديد المربع | |
نفس الشيء بالنسبة للمربع [non] | |
القيمة التي يجب إدخالها في حقل الإدخال [txtEnfants] | |
القيمة التي يجب إدخالها في حقل الإدخال [txtSalaire] | |
القيمة التي يجب إدخالها في حقل الإدخال [txtImpot] |
تحتوي الصفحة على نموذجين، يحتوي كل منهما على زر [submit]. الزر [Calculer] هو الزر [submit] في النموذج التالي:
<form method="post" action="main.aspx?action=calcul">
...
<P>
<INPUT type="submit" value="Calculer">
</P>
</form>
يمكننا أن نرى أن معلمات النموذج سيتم إرسالها إلى وحدة التحكم باستخدام [action=calcul]. الزر [Effacer] هو الزر [submit] في النموذج التالي:
<form method="post" action="main.aspx?action=effacer">
<INPUT type="submit" value="Effacer">
</form>
نرى أن معلمات النموذج سيتم إرسالها إلى وحدة التحكم باستخدام [action=effacer]. هنا، لا يحتوي النموذج على أي معلمات. لا يهم سوى الإجراء.
يتم حساب حقول [formulaire.aspx] بواسطة [formulaire.aspx.vb]:
Imports System.Collections.Specialized
Public Class formulaire
Inherits System.Web.UI.Page
' حقول الصفحة
Protected rdouichecked As String
Protected rdnonchecked As String
Protected txtEnfants As String
Protected txtSalaire As String
Protected txtImpot As String
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' استرداد الطلب السابق في السياق
Dim form As NameValueCollection = Context.Items("formulaire")
' يتم تجهيز الصفحة للعرض
' أزرار الاختيار
rdouichecked = ""
rdnonchecked = "checked"
If form("rdMarie").ToString = "oui" Then
rdouichecked = "checked"
rdnonchecked = ""
End If
' الباقي
txtEnfants = CType(form("txtEnfants"), String)
txtSalaire = CType(form("txtSalaire"), String)
txtImpot = CType(Context.Items("txtImpot"), String)
End Sub
End Class
يتم حساب حقول [main.aspx] بناءً على معلومتين يضعهما المراقب في سياق الصفحة:
- Context.Items("نموذج"): قاموس من النوع NameValueCollection يحتوي على قيم الحقول HTML [rdmarie,txtEnfants,txtSalaire]
- Context.Items("txtImpot"): قيمة الضريبة
6.1.8. عرض [erreurs.aspx]
عرض [erreurs.aspx] هو الذي يعرض الأخطاء المحتملة التي قد تحدث أثناء تشغيل التطبيق. رمز العرض الخاص به هو التالي:
<%@ page src="erreurs.aspx.vb" inherits="erreurs" AutoEventWireup="false"%>
<HTML>
<HEAD>
<title>Impôt</title>
</HEAD>
<body>
<P>Les erreurs suivantes se sont produites :</P>
<HR>
<ul>
<%=erreursHTML%>
</ul>
<a href="<%=href%>">
<%=lien%>
</a>
</body>
</HTML>
تحتوي الصفحة على ثلاثة حقول ديناميكية:
رمز HTML من قائمة الأخطاء | |
عنوان URL لرابط | |
نص الرابط |
يتم حساب هذه الحقول بواسطة جزء التحكم في الصفحة في [erreurs.aspx.vb]:
Imports System.Collections
Imports Microsoft.VisualBasic
Public Class erreurs
Inherits System.Web.UI.Page
' معلمات الصفحة
Protected erreursHTML As String = ""
Protected href As String
Protected lien As String
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' يتم استرداد عناصر السياق
Dim erreurs As ArrayList = CType(context.Items("erreurs"), ArrayList)
href = context.Items("href").ToString
lien = context.Items("lien").ToString
' يتم إنشاء رمز HTML من القائمة
Dim i As Integer
For i = 0 To erreurs.Count - 1
erreursHTML += "<li> " + erreurs(i).ToString + "</li>" + ControlChars.CrLf
Next
End Sub
End Class
يسترد عنصر التحكم في الصفحة المعلومات التي وضعها عنصر التحكم في التطبيق في سياق الصفحة:
كائن ArrayList الذي يحتوي على قائمة رسائل الأخطاء المطلوب عرضها | |
عنوان URL لرابط | |
نص الرابط |
الآن بعد أن عرفنا ما يراه مستخدم التطبيق، يمكننا الانتقال إلى كتابة وحدة التحكم الخاصة به.
6.1.9. وحدات التحكم [global.asax, main.aspx]
لنتذكر مخطط MVC لتطبيقنا:

العميل منطق التطبيق
يتعين على وحدة التحكم [main.aspx] معالجة ثلاث عمليات:
- init: تتوافق مع الطلب الأول للعميل. يعرض وحدة التحكم العرض [formulaire.aspx]
- calcul: تتوافق مع طلب حساب الضريبة. إذا كانت بيانات نموذج الإدخال صحيحة، يتم حساب الضريبة باستخدام فئة الأعمال [impots]. يعيد وحدة التحكم إلى العميل العرض [formulaire.aspx] كما تم التحقق من صحته مع إضافة الضريبة المحسوبة. إذا كانت بيانات نموذج الإدخال غير صحيحة، تعيد وحدة التحكم العرض [erreurs.aspx] مع قائمة بالأخطاء ورابط للعودة إلى النموذج.
- العودة: تعني العودة إلى النموذج بعد حدوث خطأ. يعرض المراقب العرض [formulaire.aspx] كما تم التحقق من صحته قبل حدوث الخطأ.
من المعروف أيضًا أن أي طلب موجه إلى التطبيق يمر عبر وحدة التحكم [global.asax] إن وجدت. وبذلك يكون لدينا عند مدخل التطبيق سلسلة من وحدتي تحكم:
- [global.asax] الذي يتلقى، بحكم بنية ASP.NET، أي طلب موجه إلى التطبيق
- [main.aspx] الذي يتلقى أيضًا، بناءً على قرار المطور، أي طلب موجه إلى التطبيق
تأتي الحاجة إلى [main.aspx] من حقيقة أننا سنحتاج إلى إدارة جلسة عمل. وقد رأينا أن [global.asax] لا يصلح كوحدة تحكم في هذه الحالة. يمكننا هنا الاستغناء تمامًا عن [global.asax]. ومع ذلك، سنستخدمه لتنفيذ التعليمات البرمجية عند بدء تشغيل التطبيق. يوضح المخطط MVC أعلاه أننا سنحتاج إلى إنشاء كائن [impot] لحساب الضريبة. لا داعي لإنشائه عدة مرات، فمرة واحدة تكفي. لذلك سنقوم بإنشائه عند بدء تشغيل التطبيق أثناء الحدث [Application_Start] الذي يديره وحدة التحكم [global.asax]. ورمزه هو التالي:
[global.asax]
[global.asax.vb]
Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr
Public Class Global
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' يتم إنشاء كائن مصفوفة
Dim objImpot As impot
Try
objImpot = New impot(New impotsArray)
' يتم وضع الكائن في التطبيق
Application("objImpot") = objImpot
' لا توجد أخطاء
Application("erreur") = False
Catch ex As Exception
'حدث خطأ، يتم تسجيله في التطبيق
Application("erreur") = True
End Try
End Sub
End Class
بمجرد إنشائه، يتم وضع الكائن من النوع [impot] في التطبيق. وهنا ستقوم الطلبات المختلفة من العملاء المختلفين بالبحث عنه. نظرًا لأن إنشاء الكائن [impot] قد يفشل، فإننا نتعامل مع الاستثناء المحتمل ونضع مفتاحًا [erreur] في التطبيق للإشارة إلى ما إذا كان قد حدث خطأ أم لا أثناء إنشاء الكائن [impot].
سيكون كود وحدة التحكم [main.aspx, main.aspx.vb] كما يلي:
[main.aspx]
[main.aspx.vb]
Imports System
Imports System.Collections.Specialized
Imports System.Collections
Imports st.istia.univangers.fr
Public Class main
Inherits System.Web.UI.Page
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' قبل كل شيء، نتحقق مما إذا كان التطبيق قد تم تهيئته بشكل صحيح
If CType(Application("erreur"), Boolean) Then
' يتم إعادة التوجيه إلى صفحة الأخطاء
Dim erreurs As New ArrayList
erreurs.Add("Application momentanément indisponible...")
context.Items("erreurs") = erreurs
context.Items("lien") = ""
context.Items("href") = ""
Server.Transfer("erreurs.aspx")
End If
' نسترد الإجراء المطلوب
Dim action As String
If Request.QueryString("action") Is Nothing Then
action = "init"
Else
action = Request.QueryString("action").ToString.ToLower
End If
' نقوم بتنفيذ الإجراء
Select Case action
Case "init"
' تهيئة التطبيق
initAppli()
Case "calcul"
' حساب الضريبة
calculImpot()
Case "retour"
' العودة إلى النموذج
retourFormulaire()
Case "effacer"
' تشغيل التطبيق
initAppli()
Case Else
' إجراء غير معروف = التمهيد
initAppli()
End Select
End Sub
Private Sub initAppli()
' عرض النموذج المملوء مسبقًا
Context.Items("formulaire") = initForm()
Context.Items("txtImpot") = ""
Server.Transfer("formulaire.aspx", True)
End Sub
Private Function initForm() As NameValueCollection
' يتم تهيئة النموذج
Dim form As New NameValueCollection
form.Set("rdMarie", "non")
form.Set("txtEnfants", "")
form.Set("txtSalaire", "")
Return form
End Function
Private Sub calculImpot()
' التحقق من صحة البيانات المدخلة
Dim erreurs As ArrayList = checkData()
' في حالة وجود أخطاء، يتم الإبلاغ عنها
If erreurs.Count <> 0 Then
' يتم حفظ المدخلات
Session.Item("formulaire") = Request.Form
' يتم إعداد صفحة الأخطاء
context.Items("href") = "main.aspx?action=retour"
context.Items("lien") = "Retour au formulaire"
context.Items("erreurs") = erreurs
Server.Transfer("erreurs.aspx")
End If
' لا توجد أخطاء هنا - يتم حساب الضريبة
Dim impot As Long = CType(Application("objImpot"), impot).calculer( _
Request.Form("rdMarie") = "oui", _
CType(Request.Form("txtEnfants"), Integer), _
CType(Request.Form("txtSalaire"), Long))
' يتم عرض صفحة النتائج
context.Items("txtImpot") = impot.ToString + " euro(s)"
context.Items("formulaire") = Request.Form
Server.Transfer("formulaire.aspx", True)
End Sub
Private Sub retourFormulaire()
' يتم عرض النموذج مع القيم المأخوذة من الجلسة
Context.Items("formulaire") = Session.Item("formulaire")
Context.Items("txtImpot") = ""
Server.Transfer("formulaire.aspx", True)
End Sub
Private Function checkData() As ArrayList
' في البداية لا توجد أخطاء
Dim erreurs As New ArrayList
Dim erreur As Boolean = False
' زر الاختيار "متزوج"
Try
Dim rdMarie As String = Request.Form("rdMarie").ToString
If rdMarie <> "oui" And rdMarie <> "non" Then
Throw New Exception
End If
Catch
erreurs.Add("Vous n'avez pas indiqué votre statut marital")
End Try
' عدد الأطفال
Try
Dim txtEnfants As String = Request.Form("txtEnfants").ToString
Dim nbEnfants As Integer = CType(txtEnfants, Integer)
If nbEnfants < 0 Then Throw New Exception
Catch
erreurs.Add("Le nombre d'enfants est incorrect")
End Try
' الراتب
Try
Dim txtSalaire As String = Request.Form("txtSalaire").ToString
Dim salaire As Integer = CType(txtSalaire, Long)
If salaire < 0 Then Throw New Exception
Catch
erreurs.Add("Le salaire annuel est incorrect")
End Try
' نعرض قائمة الأخطاء
Return erreurs
End Function
End Class
يبدأ المراقب بالتحقق من أن التطبيق قد تم تهيئته بشكل صحيح:
' قبل كل شيء، نتحقق مما إذا كان التطبيق قد تم تهيئته بشكل صحيح
If CType(Application("erreur"), Boolean) Then
' يتم إعادة التوجيه إلى صفحة الأخطاء
Dim erreurs As New ArrayList
erreurs.Add("Application momentanément indisponible...")
context.Items("erreurs") = erreurs
context.Items("lien") = ""
context.Items("href") = ""
Server.Transfer("erreurs.aspx")
End If
إذا اكتشف المراقب أن التطبيق لم يتمكن من التهيئة بشكل صحيح (لم يتم إنشاء الكائن [impot] الضروري للحساب)، فإنه يعرض صفحة الأخطاء مع المعلمات المناسبة. هنا، لا داعي لوضع رابط العودة في النموذج لأن التطبيق بأكمله غير متاح. يتم وضع رسالة خطأ عامة في [Context.Items("erreurs")] من النوع [ArrayList].
إذا اكتشف وحدة التحكم أن التطبيق قيد التشغيل، فإنه يقوم عندئذٍ بتحليل الإجراء المطلوب تنفيذه عبر المعلمة [action]. وقد صادفنا هذا النمط من التشغيل مرارًا وتكرارًا حتى الآن. ويتم تفويض معالجة كل نوع من الإجراءات إلى دالة.
6.1.9.1. الإجراءات init و effacer
يجب أن تؤدي هاتان الإجراءان إلى عرض نموذج الإدخال فارغًا. نذكر أن هذا النموذج (انظر العروض) له معلمتان:
- Context.Items("formulaire"): قاموس من النوع [NameValueCollection] يحتوي على قيم الحقول HTML [rdmarie,txtEnfants,txtSalaire]
- Context.Items("txtImpot"): قيمة الضريبة
تقوم الدالة [initAppli] بتهيئة هذين المعلمتين بحيث يتم عرض نموذج فارغ.
6.1.9.2. إجراء الحساب
يجب أن تحسب هذه العملية الضريبة المستحقة بناءً على البيانات المدخلة في النموذج، ثم تعيد النموذج مملوءًا مسبقًا بالقيم المدخلة بالإضافة إلى مبلغ الضريبة المحسوب. تبدأ الدالة [calculImpot] المكلفة بهذه المهمة بالتحقق من صحة بيانات النموذج:
- يجب أن يكون الحقل [rdMarie] موجودًا وأن تكون قيمته [oui] أو [non]
- يجب أن يكون الحقل [txtEnfants] موجودًا وأن يكون عددًا صحيحًا >=0
- يجب أن يكون الحقل [txtSalaire] موجودًا وأن يكون عددًا صحيحًا >=0
إذا تبين أن البيانات المدخلة غير صالحة، يقوم المراقب بعرض العرض [erreurs.aspx] بعد أن يكون قد وضع مسبقًا في السياق القيم المتوقعة من قبله:
- يتم وضع رسائل الخطأ في كائن [ArrayList]، الذي يتم وضعه بعد ذلك في السياق [Context.Items("erreurs")]
- يتم أيضًا وضع عنوان URL لرابط العودة ونص هذا الرابط في السياق.
قبل تمرير الأمر إلى الصفحة [erreurs.aspx] التي سترسل الرد إلى العميل، يتم وضع القيم التي تم إدخالها في النموذج (Request.Form) في الجلسة، مرتبطة بالمفتاح "formulaire". سيسمح هذا باستردادها في طلب لاحق.
قد نتساءل هنا عما إذا كان من المفيد التحقق من وجود الحقول [rdMarie, txtEnfants, txtSalaire] في الطلب المرسل من العميل. هذا غير ضروري إذا كنا متأكدين من أن عميلنا هو متصفح تلقى العرض [formulaire.aspx] الذي يحتوي على هذه الحقول. لا يمكننا أبدًا أن نكون متأكدين من ذلك. سنعرض لاحقًا مثالًا يكون فيه العميل هو التطبيق [curl] الذي سبق أن تناولناه. سنستعلم التطبيق دون إرسال الحقول التي يتوقعها وسنرى كيف سيكون رد فعله. هذه قاعدة تم ذكرها مرارًا وتكرارًا ونذكرها هنا: يجب ألا تفترض التطبيقات أبدًا نوع العميل الذي يستعلم عنها. ولأغراض الأمان، يجب أن تفترض أنه يمكن أن يستعلم عنها تطبيق مبرمج قد يرسل إليها سلاسل معلمات غير متوقعة. ويجب أن تتصرف بشكل صحيح في جميع الحالات.
في حالتنا، تحققنا من وجود الحقول [rdMarie, txtEnfants, txtSalaire] في الطلب، لكننا لم نتحقق من إمكانية احتوائه على حقول أخرى. في هذا التطبيق، سيتم تجاهلها. ومع ذلك، ولأسباب أمنية أيضًا، سيكون من المفيد تسجيل هذا النوع من الطلبات في ملف سجلات وإرسال تنبيه إلى مسؤول التطبيق حتى يعلم أن التطبيق يتلقى طلبات "غريبة". من خلال تحليل هذه الطلبات في ملف السجلات، يمكنه اكتشاف أي هجوم محتمل على التطبيق واتخاذ الإجراءات اللازمة لحمايته.
إذا كانت البيانات المتوقعة صحيحة، يبدأ وحدة التحكم في حساب الضريبة باستخدام الكائن [impot] المخزن في التطبيق. ثم يقوم بتخزين المعلومات المتوقعة من قبل العرض [formulaire.aspx] في السياق:
- Context.Items("نموذج"): قاموس من النوع [NameValueCollection] يحتوي على قيم الحقول HTML [rdmarie,txtEnfants,txtSalaire]، وهنا [Request.Form)]، c.a.d. القيم التي تم إدخالها مسبقًا في النموذج
- Context.Items("txtImpot"): قيمة الضريبة التي تم الحصول عليها للتو
ربما طرح القارئ اليقظ سؤالاً عند قراءة ما سبق: بما أن الكائن [impot] الذي تم إنشاؤه عند بدء تشغيل التطبيق مشترك بين جميع الاستعلامات، ألا يمكن أن يكون هناك تعارضات في الوصول تؤدي إلى تلف بيانات الكائن [impot]. للإجابة على هذا السؤال، علينا العودة إلى كود الفئة [impot]. تستخدم الطلبات الطريقة [impot].calculerImpot للحصول على الضريبة المستحقة. لذا، فإن هذا هو الكود الذي يتعين علينا فحصه:
Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Long) As Long
' حساب عدد الحصص
Dim nbParts As Decimal
If marié Then
nbParts = CDec(nbEnfants) / 2 + 2
Else
nbParts = CDec(nbEnfants) / 2 + 1
End If
If nbEnfants >= 3 Then
nbParts += 0.5D
End If
' حساب الدخل الخاضع للضريبة و"الحصة العائلية"
Dim revenu As Decimal = 0.72D * salaire
Dim QF As Decimal = revenu / nbParts
' حساب الضريبة
limites((limites.Length - 1)) = QF + 1
Dim i As Integer = 0
While QF > limites(i)
i += 1
End While
Dim impot As Long = CLng(revenu * coeffR(i) - nbParts * coeffN(i))
Return impot
End Function
لنفترض أن خيطًا ما يقوم بتنفيذ الطريقة السابقة وتم مقاطعته. عندئذٍ يقوم خيط آخر بتنفيذ الطريقة. ما هي المخاطر؟ لمعرفة ذلك، أضفنا الكود التالي:
Dim impot As Long = CLng(revenu * coeffR(i) - nbParts * coeffN(i))
' ننتظر 10 ثوانٍ
Thread.Sleep(10000)
Return impot
يتم مقاطعة الخيط 1، بعد حساب القيمة [impot1] للمتغير المحلي [impot]. ثم يتم تنفيذ الخيط 2 ويحسب قيمة جديدة [impot2] لنفس المتغير [impot] قبل أن يتم إيقافه. يستعيد الخيط 1 زمام الأمور. ماذا يجد في المتغير المحلي [impot]؟ نظرًا لأن هذه المتغير محلي لأحد الأساليب، يتم تخزينه في بنية ذاكرة تسمى المكدس. هذا المكدس جزء من سياق الخيط الذي يتم حفظه عند توقفه. عندما يبدأ الخيط 2، يتم إعداد سياقه بمكدس جديد وبالتالي متغير محلي جديد [impot]. عندما يتم مقاطعة الخيط 2 بدوره، سيتم حفظ سياقه بدوره. وعندما يتم إعادة تشغيل الخيط 1، يتم استعادة سياقه بما في ذلك مكدسه. فيستعيد عندئذ متغيره المحلي [impot] وليس متغير الخيط 2. نحن إذن في حالة لا يوجد فيها تضارب في الوصول بين الطلبات. أكدت الاختبارات التي أجريت مع فترة التوقف لمدة 10 ثوانٍ المذكورة أعلاه أن الطلبات المتزامنة حصلت بالفعل على النتيجة المتوقعة.
6.1.9.3. إجراء العودة
يتوافق هذا الإجراء مع تنشيط الرابط [Retour vers le formulaire] في العرض [erreurs.aspx] للعودة إلى العرض [formulaire.aspx] المعبأ مسبقًا بالقيم التي تم إدخالها سابقًا وحفظها في الجلسة. تسترد الدالة [retourFormulaire] هذه المعلومات. يتم تهيئة المعلمتين المتوقعين في العرض [formulaire.aspx]:
- Context.Items("formulaire") بالقيم التي تم إدخالها مسبقًا وحفظها في الجلسة
- Context.Items("txtImpot") مع السلسلة الفارغة
6.1.10. اختبار تطبيق الويب
يتم وضع جميع الملفات السابقة في مجلد <application-path>.

في هذا المجلد، يتم إنشاء مجلد فرعي [bin] يتم وضع فيه التجميع [impot.dll] الناتج عن تجميع ملفات فئات الأعمال: [impots.vb, impotsData.vb, impotsArray.vb]. نذكر أدناه أمر التجميع اللازم:
dos>vbc /t:library /out:impot.dll impotsData.vb impotsArray.vb impots.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4
dos>dir
05/04/2004 13:28 1 337 impots.vb
21/04/2004 08:23 1 311 impotsArray.vb
21/04/2004 08:26 1 634 impotsData.vb
21/04/2004 09:21 5 632 impot.dll
يجب وضع الملف [impot.dll] أعلاه في <application-path>\bin حتى يتمكن تطبيق الويب من الوصول إليه. يتم تشغيل خادم Cassini باستخدام المعلمات (<application-path>,/impots1). باستخدام متصفح، نطلب عنوان URL [http://localhost/impots1/main.aspx]:

نقوم بملء النموذج:

ثم نبدأ حساب الضريبة باستخدام الزر [Calculer]. نحصل على الإجابة التالية:

ثم نقوم بإدخال بيانات خاطئة:

يؤدي الضغط على الزر [Calculer] إلى الحصول على النتيجة التالية:

استخدام الرابط [Retour au formulaire] يعيدنا إلى النموذج في الحالة التي كان عليها عند التحقق من صحته:

وأخيرًا، يؤدي استخدام الزر [Effacer] إلى إعادة تعيين الصفحة:

6.1.11. استخدام العميل [curl]
من المهم اختبار تطبيقات الويب باستخدام عملاء آخرين غير المتصفحات. إذا أرسلنا إلى متصفح نموذجًا مع معلمات ليتم إرسالها عند التحقق من صحته، فسيقوم المتصفح بإعادة إرسال قيم هذه المعلمات إلى الخادم. قد لا يقوم عميل آخر بذلك، وبالتالي سيتلقى الخادم طلبًا تفتقد فيه بعض المعلمات. يجب أن يعرف الخادم ما يجب فعله في هذه الحالة. مثال آخر هو عمليات التحقق من الإدخال التي تتم من جانب العميل. إذا كان النموذج يحتوي على بيانات يجب التحقق منها، فيمكن إجراء هذا التحقق من جانب العميل بفضل البرامج النصية المضمنة في المستند الذي يحتوي على النموذج. لن يقوم المتصفح بإرسال النموذج إلا إذا كانت جميع البيانات التي تم التحقق منها من جانب العميل صالحة. قد نميل، من جانب الخادم، إلى افتراض أننا سنستقبل بيانات تم التحقق منها، وبالتالي لا نرغب في إجراء هذا التحقق مرة ثانية. سيكون هذا خطأً. في الواقع، قد يرسل عميل غير المتصفح بيانات غير صالحة إلى الخادم، وبالتالي قد يتصرف تطبيق الويب بشكل غير متوقع. سنوضح هذه النقاط باستخدام العميل [curl].
أولاً، نطلب عنوان URL [http://localhost/impots1/main.aspx]:
dos>curl --include --url http://localhost/impots1/main.aspx
HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Thu, 01 Apr 2004 15:18:10 GMT
Set-Cookie: ASP.NET_SessionId=ivthkl45tjdjrzznevqsf255; path=/
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 982
Connection: Close
<html>
<head>
<title>Impôt</title>
</head>
<body>
<P>Calcul de votre impôt</P>
<HR width="100%" SIZE="1">
<form method="post" action="main.aspx?action=calcul">
<TABLE border="0">
<TR>
<TD>Etes-vous marié(e)</TD>
<TD>
<INPUT type="radio" value="oui" name="rdMarie" >Oui <INPUT type="radio" value="non" name="rdMarie" checked>Non</TD>
</TR>
<TR>
<TD>Nombre d'enfants</TD>
<TD><INPUT type="text" size="3" maxLength="3" name="txtEnfants" value=""></TD>
</TR>
<TR>
<TD>Salaire annuel (euro)</TD>
<TD><INPUT type="text" maxLength="12" size="12" name="txtSalaire" value=""></TD>
</TR>
<TR>
<TD>Impôt à payer :
</TD>
<TD></TD>
</TR>
</TABLE>
<hr>
<P>
<INPUT type="submit" value="Calculer">
</P>
</form>
<form method="post" action="main.aspx?action=effacer">
<INPUT type="submit" value="Effacer">
</form>
</body>
</html>
أرسل لنا الخادم رمز النموذج HTML. في رؤوس HTTP لدينا ملف تعريف ارتباط الجلسة. سنستخدمه في الطلبات التالية من أجل الحفاظ على الجلسة. لنطلب الإجراء [calcul] دون تقديم أي معلمات:
dos>curl --cookie ASP.NET_SessionId=ivthkl45tjdjrzznevqsf255 --include --url http://localhost/impots1/main.aspx?action=calcul
HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Thu, 01 Apr 2004 15:22:42 GMT
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 380
Connection: Close
<HTML>
<HEAD>
<title>Impôt</title>
</HEAD>
<body>
<P>Les erreurs suivantes se sont produites :</P>
<HR>
<ul>
<li> Vous n'avez pas indiqué votre statut marital</li>
<li> Le nombre d'enfants est incorrect</li>
<li> Le salaire annuel est incorrect</li>
</ul>
<a href="main.aspx?action=retour">
Retour au formulaire
</a>
</body>
</HTML>
يمكننا ملاحظة أن تطبيق الويب قد أعاد عرض [erreurs] مع ثلاث رسائل خطأ للمعلمات الثلاثة المفقودة. لنرسل الآن معلمات خاطئة:
dos>curl --cookie ASP.NET_SessionId=ivthkl45tjdjrzznevqsf255 --include --data rdMarie=xx --data txtEnfants=xx --data txtSalaire=xx --url http://localhost/impots1/main.aspx?action=calcul
HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Thu, 01 Apr 2004 15:25:50 GMT
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 380
Connection: Close
<HTML>
<HEAD>
<title>Impôt</title>
</HEAD>
<body>
<P>Les erreurs suivantes se sont produites :</P>
<HR>
<ul>
<li> Vous n'avez pas indiqué votre statut marital</li>
<li> Le nombre d'enfants est incorrect</li>
<li> Le salaire annuel est incorrect</li>
</ul>
<a href="main.aspx?action=retour">
Retour au formulaire
</a>
</body>
</HTML>
تم الكشف عن الأخطاء الثلاثة بشكل صحيح. الآن لنرسل معلمات صالحة:
dos>curl --cookie ASP.NET_SessionId=ivthkl45tjdjrzznevqsf255 --include --data rdMarie=oui --data txtEnfants=2 --data txtSalaire=60000 --url http://localhost/impots1/main.aspx?action=calcul
HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Thu, 01 Apr 2004 15:28:24 GMT
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 1000
Connection: Close
<html>
<head>
<title>Impôt</title>
</head>
<body>
<P>Calcul de votre impôt</P>
<HR width="100%" SIZE="1">
<form method="post" action="main.aspx?action=calcul">
<TABLE border="0">
<TR>
<TD>Etes-vous marié(e)</TD>
<TD>
<INPUT type="radio" value="oui" name="rdMarie" checked>Oui <INPUT type="radio" value="non" name="rdMarie" >Non</TD>
</TR>
<TR>
<TD>Nombre d'enfants</TD>
<TD><INPUT type="text" size="3" maxLength="3" name="txtEnfants" value="2"></TD>
</TR>
<TR>
<TD>Salaire annuel (euro)</TD>
<TD><INPUT type="text" maxLength="12" size="12" name="txtSalaire" value="60000"></TD>
</TR>
<TR>
<TD>Impôt à payer :
</TD>
<TD>4300 euro(s)</TD>
</TR>
</TABLE>
<hr>
<P>
<INPUT type="submit" value="Calculer">
</P>
</form>
<form method="post" action="main.aspx?action=effacer">
<INPUT type="submit" value="Effacer">
</form>
</body>
</html>
لقد استرددنا الضريبة المستحقة: 4300 يورو. سنستخلص من هذا المثال أنه لا يجب أن ننخدع بحقيقة أننا نكتب تطبيق ويب مخصص للعملاء الذين يستخدمون متصفحات. تطبيق الويب هو خدمة TCP/IP، ولا يسمح هذا البروتوكول الشبكي بتحديد طبيعة تطبيق العميل الخاص بخدمة ما. لذلك، لا يمكننا معرفة ما إذا كان عميل تطبيق الويب هو متصفح أم لا. لذا، نتبع قاعدتين:
- عند استلام طلب من عميل، لا نفترض أي شيء بشأن العميل ونتحقق من أن المعلمات المتوقعة في الطلب موجودة وصالحة
- نقوم بإعداد استجابة موجهة للمتصفحات، وبالتالي تكون بشكل عام مستندات HTML
يمكن إنشاء تطبيق ويب لخدمة عملاء مختلفين في وقت واحد، على سبيل المثال المتصفحات والهواتف المحمولة. يمكن عندئذٍ تضمين معلمة جديدة في كل طلب تشير إلى نوع العميل. وهكذا سيطلب المتصفح حساب الضريبة عن طريق طلب إلى عنوان url http://machine/impots/main.aspx?client=navigateur&action=calcul بينما سيقوم الهاتف المحمول بإرسال طلب إلى عنوان URL http://machine/impots/main.aspx?client=mobile&action=calcul. تسهل بنية MVC كتابة مثل هذا التطبيق. وتصبح كما يلي:

لا يتغير الكتلة [Classes métier, Classes d'accès aux données]. فهي في الواقع جزء لا يهم العميل. تتغير الكتلة [Contrôleur] قليلاً ولكن يجب أن تأخذ في الاعتبار معلمة جديدة في الطلب، وهي المعلمة [client] التي تشير إلى نوع العميل الذي تتعامل معه. يجب أن يولد الكتلة [vues] طرق عرض لكل نوع من أنواع العملاء. قد يكون من المفيد أخذ وجود المعلمة [client] في الاعتبار منذ مرحلة تصميم التطبيق، حتى لو كان هدفنا على المدى القصير أو المتوسط يقتصر على المتصفحات فقط. إذا كان على التطبيق إدارة نوع جديد من العملاء لاحقًا، فسيكون من الضروري كتابة طرق عرض ملائمة لهذا النوع فقط.
6.2. مثال 2
6.2.1. المشكلة
نقترح هنا معالجة نفس المشكلة السابقة ولكن مع تعديل مصدر بيانات الكائن [impot] الذي أنشأته تطبيق الويب. في الإصدار السابق، كان المصدر المستخدم يقدم قيم الجداول المكتوبة بشكل "ثابت" في الكود. هذه المرة، سيأخذها مصدر البيانات الجديد من مصدر بيانات ODBC المرتبط بقاعدة بيانات MySQL.
6.2.2. مصدر البيانات ODBC
ستوجد البيانات في جدول يسمى [IMPOTS] في قاعدة بيانات MySQL تسمى [dbimpots]. سيكون محتوى هذا الجدول كما يلي:

مالك قاعدة البيانات هو المستخدم [admimpots] بكلمة مرور [mdpimpots]. نقوم بربط مصدر بيانات ODBC بهذه القاعدة. قبل القيام بذلك، دعونا نستعرض أولاً الطرق المختلفة للوصول إلى قاعدة البيانات باستخدام منصة .NET.
توجد العديد من قواعد البيانات لمنصات ويندوز. للوصول إليها، تمر التطبيقات عبر برامج تسمى برامج التشغيل (drivers).

في المخطط أعلاه، يقدم برنامج التشغيل واجهتين:
- واجهة I1 المعروضة للتطبيق
- واجهة I2 الموجهة إلى قاعدة البيانات
من أجل تجنب الحاجة إلى إعادة كتابة تطبيق مكتوب لقاعدة بيانات B1 في حالة الترحيل إلى قاعدة بيانات B2 مختلفة، تم بذل جهد لتوحيد واجهة I1. في حالة استخدام قواعد بيانات تستخدم برامج تشغيل "معيارية"، سيتم توفير قاعدة البيانات B1 مع برنامج تشغيل P1، وقاعدة البيانات B2 مع برنامج تشغيل P2، وستكون واجهة I1 لهذين البرنامجين متطابقة. وبالتالي لن يكون هناك حاجة لإعادة كتابة التطبيق. وبذلك يمكن، على سبيل المثال، ترحيل قاعدة بيانات ACCESS إلى قاعدة بيانات MySQL دون تغيير التطبيق.
يوجد نوعان من برامج التشغيل القياسية:
- برامج التشغيل ODBC (Open DataBase Connectivity)
- برامج التشغيل OLE و DB (ربط الكائنات وتضمينها DataBase)
تسمح برامج التشغيل ODBC بالوصول إلى قواعد البيانات. مصادر البيانات لبرامج التشغيل OLE و DB أكثر تنوعًا: قواعد البيانات، والبريد الإلكتروني، والدلائل، ... ولا يوجد حدود. يمكن أن تكون أي مصدر بيانات موضوعًا لبرامج تشغيل Ole DB إذا قرر الناشر ذلك. الفائدة كبيرة بالطبع: لدينا وصول موحد إلى مجموعة متنوعة من البيانات.
تأتي منصة .NET 1.1 مزودة بثلاثة أنواع من فئات الوصول إلى البيانات:
- فئات SQL و Server.NET، للوصول إلى قواعد بيانات Microsoft SQL Server
- فئات Ole Db.NET، للوصول إلى قواعد بيانات SGBD التي توفر برنامج تشغيل OLE و DB
- فئات odbc.net، للوصول إلى قواعد بيانات SGBD التي توفر برنامج تشغيل ODBC
تتوفر لفئة SGBD MySQL منذ فترة طويلة برنامج تشغيل ODBC. وهذا هو البرنامج الذي نستخدمه الآن. في نظام Windows، نختار الخيار [Menu Démarrer/Panneau de configuration/Outils d'administration/Sources ODBC 32 bits]. قد يختلف هذا المسار قليلاً حسب إصدار Windows. نحصل على التطبيق التالي الذي سيسمح لنا بإنشاء مصدرنا ODBC:

سنقوم بإنشاء مصدر بيانات نظام، c.a.d. مصدر بيانات يمكن لأي مستخدم للجهاز الوصول إليه utiliser.Aussi، أعلاه نختار علامة التبويب [Source de données système]. تحتوي الصفحة المعروضة على زر [Ajouter] الذي نستخدمه لإنشاء مصدر بيانات جديد ODBC:

يطلب المعالج تحديد برنامج التشغيل ODBC المراد استخدامه. يأتي Windows مزودًا بعدد من برامج التشغيل ODBC المثبتة مسبقًا. برنامج التشغيل ODBC الخاص بـ MySQL ليس جزءًا من المجموعة. لذلك يجب تثبيته مسبقًا. يمكن العثور عليه على الإنترنت عن طريق كتابة سلسلة الكلمات الرئيسية "MySQL ODBC" أو "MyODBC" في محرك بحث. هنا، قمنا بتثبيت برنامج التشغيل [MySQL ODBC 3.51]. نختاره ونقوم بـ [Terminer]:

يجب تقديم عدد من المعلومات:
الاسم الذي سيشير إلى مصدر البيانات ODBC. سيتمكن أي تطبيق ويندوز من الوصول إلى المصدر عبر هذا الاسم | |
نص تعسفي يصف مصدر البيانات | |
اسم الجهاز الذي يستضيف SGBD MySQL. هنا هو الجهاز المحلي. قد يكون جهازًا بعيدًا. وهذا سيسمح لتطبيق ويندوز بالوصول إلى قاعدة بيانات بعيدة دون أي ترميز خاص. وهذا هو أحد المزايا الهامة لمصدر ODBC. | |
يمكن لـ SGBD MySQL إدارة عدة قواعد بيانات. هنا نحدد القاعدة التي نريد إدارتها: dbimpots | |
اسم مستخدم مسجل في SGBD MySQL. سيتم الوصول إلى مصدر البيانات باستخدام هذا الاسم. هنا: admimpots | |
كلمة مرور هذا المستخدم. هنا: mdpimpots | |
منفذ عمل SGBD MySQL. بشكل افتراضي، هو المنفذ 3306. لم نقم بتغييره |
بعد ذلك، نختبر صحة معلمات الاتصال الخاصة بنا باستخدام الزر [Test Data Source]:

بعد ذلك، نكون متأكدين من مصدر البيانات ODBC. يمكننا الآن استخدامه. نكرر [OK] عدة مرات حسب الحاجة للخروج من المساعد ODBC.
إذا لم يكن لدى القارئ SGBD mySQL، فيمكنه الحصول عليه مجانًا من الرابط [http://www.mysql.com]. نقدم أدناه الخطوات اللازمة لإنشاء مصدر ODBC باستخدام Access. الخطوات الأولى مطابقة لما تم وصفه سابقًا. نضيف مصدر بيانات نظام جديد:

سيكون برنامج التشغيل المحدد هو [Microsoft Access Driver]. نقوم بـ [Terminer] للانتقال إلى تعريف المصدر ODBC:

المعلومات المطلوبة هي التالية:
الاسم الذي سيشير إلى مصدر البيانات ODBC. سيتمكن أي تطبيق ويندوز من الوصول إلى المصدر عبر هذا الاسم | |
نص تعسفي يصف مصدر البيانات | |
الاسم الكامل لملف ACCESS المراد استخدامه |
6.2.3. فئة جديدة للوصول إلى البيانات
لنعد إلى هيكل MVC لتطبيقنا:

في المخطط أعلاه، تتولى الفئة [impotsData] مهمة استرداد البيانات. وسيتعين عليها القيام بذلك هنا من قاعدة البيانات MySQL [dbimpots]. نعلم من الإصدار السابق لهذا التطبيق أن [impotsData] هي فئة مجردة يجب اشتقاقها في كل مرة نريد تكييفها مع مصدر بيانات جديد. لنتذكر بنية هذه الفئة المجردة:
Imports System.Collections
Namespace st.istia.univangers.fr
Public MustInherit Class impotsData
Protected limites() As Decimal
Protected coeffr() As Decimal
Protected coeffn() As Decimal
Protected checked As Boolean
Protected valide As Boolean
' طريقة الوصول إلى البيانات
Public MustOverride Function getData() As Object()
' طريقة التحقق من البيانات
Protected Function checkData() As Integer
' يتحقق من البيانات المكتسبة
...
End Function
' التحقق من صحة محتوى الجدول
Protected Function check(ByRef tableau() As Decimal, ByVal n As Integer) As Boolean
...
End Function
End Class
End Namespace
يجب أن تنفذ الفئة المشتقة من [impotsData] طريقتين:
- منشئ إذا كان المنشئ بدون حجج لـ [impotsData] لا يناسبها
- طريقة [getData] التي تعرض المصفوفات الثلاثة (limites,coeffr,coeffn)
نقوم بإنشاء الفئة [impotsODBC] التي ستقوم باسترداد البيانات (limites,coeffr,coeffn) من مصدر ODBC الذي سنطلق عليه الاسم:
Imports System.Data.Odbc
Imports System.Data
Imports System.Collections
Imports System
Namespace st.istia.univangers.fr
Public Class impotsODBC
Inherits impotsData
' متغيرات المثيل
Protected DSNimpots As String
' المنشئ
Public Sub New(ByVal DSNimpots As String)
' يتم تدوين المعلومات الثلاثة
Me.DSNimpots = DSNimpots
End Sub
Public Overrides Function getdata() As Object()
' تقوم بتهيئة الجداول الثلاثة limits و coeffr و coeffn انطلاقًا من
' محتوى الجدول [impots] في قاعدة البيانات ODBC DSNimpots
' الحدود، coeffr، coeffn هي الأعمدة الثلاثة في هذا الجدول
' يمكن أن تطلق استثناءات متنوعة
Dim connectString As String = "DSN=" + DSNimpots + ";" ' chaîne de connexion à la base
Dim impotsConn As OdbcConnection = Nothing ' la connexion
Dim sqlCommand As OdbcCommand = Nothing ' la commande SQL
' الاستعلام SELECT
Dim selectCommand As String = "select limites,coeffr,coeffn from impots"
' جداول لاسترداد البيانات
Dim aLimites As New ArrayList
Dim aCoeffR As New ArrayList
Dim aCoeffN As New ArrayList
Try
' يتم محاولة الوصول إلى قاعدة البيانات
impotsConn = New OdbcConnection(connectString)
impotsConn.Open()
' يتم إنشاء كائن أمر
sqlCommand = New OdbcCommand(selectCommand, impotsConn)
' يتم تنفيذ الاستعلام
Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader()
' استغلال الجدول المسترد
While myReader.Read()
' يتم وضع بيانات السطر الحالي في الجداول
aLimites.Add(myReader("limites"))
aCoeffR.Add(myReader("coeffr"))
aCoeffN.Add(myReader("coeffn"))
End While
' تحرير الموارد
myReader.Close()
impotsConn.Close()
Catch e As Exception
Throw New Exception("Erreur d'accès à la base de données (" + e.Message + ")")
End Try
' يتم وضع الجداول الديناميكية في جداول ثابتة
Me.limites = New Decimal(aLimites.Count - 1) {}
Me.coeffr = New Decimal(aLimites.Count - 1) {}
Me.coeffn = New Decimal(aLimites.Count - 1) {}
Dim i As Integer
For i = 0 To aLimites.Count - 1
limites(i) = Decimal.Parse(aLimites(i).ToString())
coeffR(i) = Decimal.Parse(aCoeffR(i).ToString())
coeffN(i) = Decimal.Parse(aCoeffN(i).ToString())
Next i
' يتم التحقق من البيانات المكتسبة
Dim erreur As Integer = checkData()
' إذا كانت البيانات غير صالحة، يتم إصدار استثناء
If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
' وإلا يتم إرجاع الجداول الثلاثة
Return New Object() {limites, coeffr, coeffn}
End Function
End Class
End Namespace
لنلقِ نظرة على الشركة المصنعة:
' المنشئ
Public Sub New(ByVal DSNimpots As String)
' يتم تدوين المعلومات الثلاثة
Me.DSNimpots = DSNimpots
End Sub
يتلقى كمعلمة اسم المصدر ODBC الذي توجد فيه البيانات المطلوب الحصول عليها. يكتفي المُنشئ بتخزين هذا الاسم. تتولى الطريقة [getData] قراءة البيانات من الجدول [impots] ووضعها في ثلاثة جداول (limites، coeffr، coeffn). لنعلق على كودها:
- تم تعريف معلمات الاتصال بمصدر البيانات ODBC ولكن هذا المصدر غير مفتوح
' سلسلة الاتصال بقاعدة البيانات
Dim connectString As String = "DSN=" + DSNimpots + ";"
' نقوم بإنشاء كائن اتصال بقاعدة البيانات - هذا الاتصال غير مفتوح
Dim impotsConn As OdbcConnection = New OdbcConnection(connectString)
- يتم تعريف ثلاثة كائنات [ArrayList] لاسترداد البيانات من الجدول [impots]:
' جداول لاسترداد البيانات
Dim aLimites As New ArrayList
Dim aCoeffR As New ArrayList
Dim aCoeffN As New ArrayList
- يتم وضع كل كود الوصول إلى قاعدة البيانات بين try/catch للتعامل مع أي خطأ محتمل في الوصول. نفتح الاتصال بقاعدة البيانات:
' محاولة الوصول إلى قاعدة البيانات
impotsConn = New OdbcConnection(connectString)
impotsConn.Open()
- نقوم بتنفيذ الأمر [select] على الاتصال المفتوح. نحصل على كائن [OdbcDataReader] الذي سيسمح لنا بتصفح صفوف الجدول الناتج عن الأمر select:
' يتم إنشاء كائن أمر
Dim sqlCommand As OdbcCommand = New OdbcCommand(selectCommand, impotsConn)
' يتم تنفيذ الاستعلام
Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader()
- نقوم بتصفح جدول النتائج، سطراً سطراً. وللقيام بذلك، نستخدم الطريقة [Read] الخاصة بالكائن [OdbcDataReader] الذي تم الحصول عليه سابقاً. تقوم هذه الطريقة بأمرين:
- تتحرك خطوة واحدة للأمام في الجدول. في البداية، يكون الموضع قبل السطر الأول
- تُرجع القيمة المنطقية [true] إذا تمكنت من التقدم، و [false] في حالة عدم تمكنها من ذلك، حيث تشير الحالة الأخيرة إلى أنه تم استغلال جميع الأسطر.
يتم الحصول على أعمدة السطر الحالي للكائن [OdbcDataReader] بواسطة OdbcDataReader. نحصل على كائن يمثل قيمة العمود. نستعرض الجدول بالكامل لوضع محتواه في الكائنات الثلاثة [ArrayList]:
' استغلال الجدول المسترد
While myReader.Read()
' يتم وضع بيانات السطر الحالي في الجداول
aLimites.Add(myReader("limites"))
aCoeffR.Add(myReader("coeffr"))
aCoeffN.Add(myReader("coeffn"))
- بعد ذلك، نقوم بتحرير الموارد المرتبطة بالاتصال:
- يتم نقل محتوى الكائنات الثلاثة [ArrayList] إلى ثلاثة جداول تقليدية:
' يتم وضع الجداول الديناميكية في جداول ثابتة
limites = New Decimal(aLimites.Count - 1) {}
coeffr = New Decimal(aLimites.Count - 1) {}
coeffn = New Decimal(aLimites.Count - 1) {}
Dim i As Integer
For i = 0 To aLimites.Count - 1
limites(i) = CType(aLimites(i), Decimal)
coeffR(i) = CType(aCoeffR(i), Decimal)
coeffN(i) = CType(aCoeffN(i), Decimal)
Next i
- بمجرد وصول بيانات الجدول [impots] إلى الجداول الثلاثة، لم يتبق سوى التحقق من محتواها باستخدام الطريقة [checkData] من الفئة الأساسية [impotsData]:
' يتم التحقق من البيانات المكتسبة
Dim erreur As Integer = checkData()
' إذا كانت البيانات غير صالحة، يتم إصدار استثناء
If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
' وإلا يتم إرجاع الجداول الثلاثة
Return New Object() {limites, coeffr, coeffn}
6.2.4. اختبارات فئة الوصول إلى البيانات
قد يكون برنامج الاختبار كما يلي:
Option Explicit On
Option Strict On
' مساحات الأسماء
Imports System
Imports Microsoft.VisualBasic
Namespace st.istia.univangers.fr
' صفحة اختبار
Module testimpots
Sub Main(ByVal arguments() As String)
' برنامج تفاعلي لحساب الضرائب
' يقوم المستخدم بإدخال ثلاث بيانات عبر لوحة المفاتيح: متزوج nbEnfants الراتب
' ثم يعرض البرنامج الضريبة المستحقة
Const syntaxe1 As String = "pg DSNimpots"
Const syntaxe2 As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"
' التحقق من إعدادات البرنامج
If arguments.Length <> 1 Then
' رسالة خطأ
Console.Error.WriteLine(syntaxe1)
' نهاية
Environment.Exit(1)
End If
' يتم استرداد الحجج
Dim DSNimpots As String = arguments(0)
' إنشاء كائن ضريبة
Dim objImpot As impot = Nothing
Try
objImpot = New impot(New impotsODBC(DSNimpots))
Catch ex As Exception
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
Environment.Exit(2)
End Try
' حلقة لا نهائية
While True
' في البداية لا توجد أخطاء
Dim erreur As Boolean = False
' يتم طلب معلمات حساب الضريبة
Console.Out.Write("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :")
Dim paramètres As String = Console.In.ReadLine().Trim()
' هل هناك ما يجب فعله؟
If paramètres Is Nothing Or paramètres = "" Then
Exit While
End If
' التحقق من عدد المعلمات في السطر الذي تم إدخاله
Dim args As String() = paramètres.Split(Nothing)
Dim nbParamètres As Integer = args.Length
If nbParamètres <> 3 Then
Console.Error.WriteLine(syntaxe2)
erreur = True
End If
Dim marié As String
Dim nbEnfants As Integer
Dim salaire As Integer
If Not erreur Then
' التحقق من صحة المعلمات
' متزوج
marié = args(0).ToLower()
If marié <> "o" And marié <> "n" Then
Console.Error.WriteLine((syntaxe2 + ControlChars.Lf + "Argument marié incorrect : tapez o ou n"))
erreur = True
End If
' nbEnfants
nbEnfants = 0
Try
nbEnfants = Integer.Parse(args(1))
If nbEnfants < 0 Then
Throw New Exception
End If
Catch
Console.Error.WriteLine(syntaxe2 + "\nArgument nbEnfants incorrect : tapez un entier positif ou nul")
erreur = True
End Try
' الراتب
salaire = 0
Try
salaire = Integer.Parse(args(2))
If salaire < 0 Then
Throw New Exception
End If
Catch
Console.Error.WriteLine(syntaxe2 + "\nArgument salaire incorrect : tapez un entier positif ou nul")
erreur = True
End Try
End If
If Not erreur Then
' المعلمات صحيحة - يتم حساب الضريبة
Console.Out.WriteLine(("impôt=" & objImpot.calculer(marié = "o", nbEnfants, salaire).ToString + " euro(s)"))
End If
End While
End Sub
End Module
End Namespace
يتم تشغيل التطبيق بمعلمة واحدة:
- DSNimpots: اسم مصدر البيانات ODBC المراد استخدامه
يتم حساب الضريبة باستخدام كائن من النوع [impot] يتم إنشاؤه فور تشغيل التطبيق:
' إنشاء كائن ضريبة
Dim objImpôt As impot = Nothing
Try
objImpot = New impot(New impotsODBC(DSNimpots))
Catch ex As Exception
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
Environment.Exit(1)
End Try
بمجرد بدء التشغيل، يطلب التطبيق من المستخدم بشكل متكرر إدخال المعلومات الثلاث اللازمة لحساب ضريبته عبر لوحة المفاتيح:
- حالته الاجتماعية: o للمتزوج، n لغير المتزوج
- عدد أطفاله
- راتبه السنوي
يتم تجميع جميع الفئات:
dos>vbc /r:system.dll /r:system.data.dll /t:library /out:impot.dll impots.vb impotsArray.vb impotsData.vb impotsODBC.vb
dos>dir
01/04/2004 19:34 7 168 impot.dll
01/04/2004 19:31 1 360 impots.vb
21/04/2004 08:23 1 311 impotsArray.vb
21/04/2004 08:26 1 634 impotsData.vb
01/04/2004 19:34 2 735 impotsODBC.vb
01/04/2004 19:32 3 210 testimpots.vb
يتم تجميع برنامج الاختبار بدوره:
dir>dir
01/04/2004 19:34 7 168 impot.dll
01/04/2004 19:31 1 360 impots.vb
21/04/2004 08:23 1 311 impotsArray.vb
21/04/2004 08:26 1 634 impotsData.vb
01/04/2004 19:34 2 735 impotsODBC.vb
01/04/2004 19:34 6 144 testimpots.exe
01/04/2004 19:32 3 210 testimpots.vb
يتم تنفيذ برنامج الاختبار أولاً باستخدام مصدر البيانات ODBC MySQL:
dos>testimpots odbc-mysql-dbimpots
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 60000
impôt=4300 euro(s)
يتم تغيير المصدر ODBC لاستخدام مصدر Access:
dos>testimpots odbc-access-dbimpots
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 60000
impôt=4300 F
6.2.5. طرق عرض تطبيق الويب
وهي نفس واجهات التطبيق السابق: formulaire.aspx و erreurs.aspx
6.2.6. وحدات التحكم في التطبيق [global.asax, main.aspx]
يجب تعديل وحدة التحكم [global.asax] فقط. فهي مسؤولة عن إنشاء الكائن [impot] عند بدء تشغيل التطبيق. المعلمة الوحيدة لمنشئ هذا الكائن هي الكائن من النوع [impotsData] المسؤول عن استرداد البيانات. وبالتالي، تتغير هذه المعلمة مع كل نوع جديد من مصادر البيانات. يصبح وحدة التحكم [global.asax.vb] كما يلي:
Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr
Imports System.Configuration
Public Class Global
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' يتم إنشاء كائن ضريبة
Dim objImpot As impot
Try
objImpot = New impot(New impotsODBC(ConfigurationSettings.AppSettings("DSNimpots")))
' يتم وضع الكائن في التطبيق
Application("objImpot") = objImpot
' لا توجد أخطاء
Application("erreur") = False
Catch ex As Exception
'حدث خطأ، يتم تسجيله في التطبيق
Application("erreur") = True
Application("message") = ex.Message
End Try
End Sub
End Class
أصبح مصدر بيانات الكائن [impot] الآن كائنًا [impotODBC]. ويحتوي هذا الأخير على المعلمة DSN الخاصة بمصدر البيانات ODBC المراد استخدامه. بدلاً من كتابة هذا الاسم بشكل ثابت في الكود، يتم وضعه في ملف التكوين [web.config] للتطبيق:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="DSNimpots" value="odbc-mysql-dbimpots" />
</appSettings>
</configuration>
من المعروف أن قيمة مفتاح C في القسم <appSettings> من الملف [web.config] يتم الحصول عليها في كود التطبيق بواسطة [ConfigurationSettings.AppSettings(C)].
لمعرفة سبب الاستثناء، يتم تسجيل رسالة الاستثناء في التطبيق بحيث تظل متاحة للاستعلامات. سيقوم عنصر التحكم [main.aspx.vb] بتضمين هذه الرسالة في قائمة الأخطاء الخاصة به:
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' قبل كل شيء، نتحقق مما إذا كان التطبيق قد تم تهيئته بشكل صحيح
If CType(Application("erreur"), Boolean) Then
' يتم إعادة التوجيه إلى صفحة الأخطاء
Dim erreurs As New ArrayList
erreurs.Add("Application momentanément indisponible...(" + Application("message").ToString + ")")
context.Items("erreurs") = erreurs
context.Items("lien") = ""
context.Items("href") = ""
Server.Transfer("erreurs.aspx")
End If
' نسترد الإجراء المطلوب
...
6.2.7. ملخص التغييرات
التطبيق جاهز للاختبار. دعونا نسرد التعديلات التي أُدخلت على الإصدار السابق:
- تم إنشاء فئة جديدة للوصول إلى البيانات
- تم تعديل وحدة التحكم [global.asax.vb] في موضعين: إنشاء الكائن [impot] وتسجيل الرسالة المرتبطة بالاستثناء المحتمل في التطبيق
- تم تعديل وحدة التحكم [main.aspx.vb] في مكان واحد لعرض رسالة الاستثناء السابقة
- تمت إضافة ملف [web.config]
تمت أعمال التعديل بشكل أساسي في 1، c.a.d. خارج التطبيق الويب. وقد أصبح ذلك ممكنًا بفضل بنية التطبيق MVC التي تفصل وحدة التحكم عن فئات الأعمال. وهنا يكمن الفائدة الكبرى لهذه البنية. يمكن إثبات أنه باستخدام ملف تكوين مناسب، كان من الممكن تجنب أي تعديل على وحدة التحكم في التطبيق. من الممكن إدخال اسم فئة الوصول إلى البيانات المراد إنشاء مثيل لها ديناميكيًا في الملف، بالإضافة إلى المعلمات المختلفة اللازمة لعملية الإنشاء هذه. باستخدام هذه المعلومات، يمكن لـ [global.asax] إنشاء مثيل لكائن الوصول إلى البيانات. وبالتالي، فإن تغيير مصدر البيانات يعود إلى:
- إنشاء فئة الوصول إلى هذا المصدر إذا لم تكن موجودة بعد
- تعديل الملف [web.config] للسماح بإنشاء مثيل ديناميكي لهذه الفئة في [global.asax]
6.2.8. اختبار تطبيق الويب
يتم وضع جميع الملفات السابقة في مجلد <application-path>.

في هذا المجلد، يتم إنشاء مجلد فرعي [bin] يتم وضع فيه التجميع [impot.dll] الناتج عن تجميع ملفات فئات الأعمال: [impots.vb, impotsData.vb, impotsArray.vb, impotsODBC.vb]. نذكر أدناه أمر التجميع اللازم:
dos>vbc /r:system.dll /r:system.data.dll /t:library /out:impot.dll impots.vb impotsArray.vb impotsData.vb impotsODBC.vb
dos>dir
01/04/2004 19:34 7 168 impot.dll
01/04/2004 19:31 1 360 impots.vb
21/04/2004 08:23 1 311 impotsArray.vb
21/04/2004 08:26 1 634 impotsData.vb
01/04/2004 19:34 2 735 impotsODBC.vb
01/04/2004 19:32 3 210 testimpots.vb
يجب وضع الملف [impot.dll] أعلاه في <application-path>\bin حتى يتمكن تطبيق الويب من الوصول إليه. يتم تشغيل خادم Cassini باستخدام المعلمات (<application-path>,/impots2). تعطي الاختبارات نفس النتائج التي تم الحصول عليها في الإصدار السابق، حيث يكون وجود قاعدة البيانات غير مرئي للمستخدم. ولكن لتوضيح هذا الوجود، نحرص على عدم توفر المصدر ODBC عن طريق إيقاف SGBD وMySQL ونطلب عنوان URL [http://localhost/impots2/main.aspx]. ونحصل على الرد التالي:

6.3. المثال 3
6.3.1. المشكلة
نقترح هنا معالجة نفس المشكلة عن طريق تعديل مصدر بيانات الكائن [impot] الذي أنشأته تطبيق الويب مرة أخرى. هذه المرة، سيكون مصدر البيانات الجديد قاعدة بيانات ACCESS التي سيتم الوصول إليها عبر برنامج تشغيل OLEDB. هدفنا هو إظهار طريقة أخرى للوصول إلى قاعدة البيانات.
6.3.2. مصدر البيانات OLEDB
ستوجد البيانات في جدول يسمى [IMPOTS] في قاعدة بيانات ACCESS. سيكون محتوى هذا الجدول كما يلي:

6.3.3. فئة الوصول إلى البيانات
لنعد إلى البنية MVC لتطبيقنا:

- في المخطط أعلاه، تتولى فئة [impotsData] مهمة استرداد البيانات. وسيتعين عليها القيام بذلك هذه المرة من مصدر OLEDB.
نقوم بإنشاء الفئة [impotsOLEDB] التي ستقوم باسترداد البيانات (limites,coeffr,coeffn) من مصدر ODBC الذي سنطلق عليه الاسم:
Imports System.Data
Imports System.Collections
Imports System
Imports System.Xml
Imports System.Data.OleDb
Namespace st.istia.univangers.fr
Public Class impotsOLEDB
Inherits impotsData
' متغيرات المثيل
Protected chaineConnexion As String
' المنشئ
Public Sub New(ByVal chaineConnexion As String)
' يتم تدوين المعلومات الثلاثة
Me.chaineConnexion = chaineConnexion
End Sub
Public Overrides Function getData() As Object()
' تتم تهيئة الجداول الثلاثة limits و coeffr و coeffn انطلاقًا من
' محتوى الجدول [impots] في قاعدة البيانات OLEDB [chaineConnexion]
' الحدود، coeffr، coeffn هي الأعمدة الثلاثة في هذا الجدول
' يمكن أن تطلق استثناءات متنوعة
' يتم إنشاء كائن DataAdapter لقراءة البيانات من المصدر OLEDB
Dim adaptateur As New OleDbDataAdapter("select limites,coeffr,coeffn from impots", chaineConnexion)
' يتم إنشاء صورة في الذاكرة لنتيجة عملية الاختيار
Dim contenu As New DataTable("impots")
Try
adaptateur.Fill(contenu)
Catch e As Exception
Throw New Exception("Erreur d'accès à la base de données (" + e.Message + ")")
End Try
' يتم استرداد محتوى الجدول impots
Dim lignesImpots As DataRowCollection = contenu.Rows
' يتم تحديد حجم الجداول المستقبلة
Me.limites = New Decimal(lignesImpots.Count - 1) {}
Me.coeffr = New Decimal(lignesImpots.Count - 1) {}
Me.coeffn = New Decimal(lignesImpots.Count - 1) {}
' ننقل محتوى الجدول impots إلى المصفوفات
Dim i As Integer
Dim ligne As DataRow
Try
For i = 0 To lignesImpots.Count - 1
' السطر i من الجدول
ligne = lignesImpots.Item(i)
' يتم استرداد محتوى السطر
limites(i) = CType(ligne.Item(0), Decimal)
coeffr(i) = CType(ligne.Item(1), Decimal)
coeffn(i) = CType(ligne.Item(2), Decimal)
Next
Catch
Throw New Exception("Les données des tranches d'impôts n'ont pas le bon type")
End Try
' التحقق من البيانات التي تم الحصول عليها
Dim erreur As Integer = checkData()
' إذا كانت البيانات غير صالحة، يتم إصدار استثناء
If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
' وإلا يتم إرجاع الجداول الثلاثة
Return New Object() {limites, coeffr, coeffn}
End Function
End Class
End Namespace
لنلقِ نظرة على الشركة المصنعة:
' المنشئ
Public Sub New(ByVal chaineConnexion As String)
' يتم تدوين المعلومات الثلاثة
Me.chaineConnexion = chaineConnexion
End Sub
يتلقى كمعلمة سلسلة الاتصال للمصدر OLEDB الذي توجد فيه البيانات المطلوب الحصول عليها. ويكتفي المُصمم بتخزينها. تحتوي سلسلة الاتصال على جميع المعلمات اللازمة لبرنامج التشغيل OLEDB للاتصال بالمصدر OLEDB. وهي عادةً ما تكون معقدة إلى حد ما. لاكتشاف سلسلة اتصال قواعد البيانات ACCESS، يمكن الاستعانة بالأداة [WebMatrix]. يتم تشغيل هذه الأداة. وهي توفر نافذة تسمح بالاتصال بمصدر بيانات:
![]() ![]() | ![]() ![]() |
بفضل الرمز المشار إليه بالسهم أعلاه، يمكن إنشاء اتصال بنوعين من قواعد بيانات Microsoft: SQL Server و ACCESS. لنختر ACCESS:

استخدمنا الزر [...] لتعيين قاعدة البيانات ACCESS. نؤكد المعالج. في علامة التبويب [Data]، ترمز الرموز إلى الاتصال:

الآن، لنقم بإنشاء ملف .aspx جديد بواسطة [Files/New File]:

نحصل على ورقة فارغة يمكننا رسم واجهة الويب الخاصة بنا عليها:

لنقم بسحب الجدول [impots] من علامة التبويب [Data] إلى الورقة أعلاه. نحصل على النتيجة التالية:

لنضغط بزر الفأرة الأيمن على الكائن [AccessDataSourceControl] أدناه للوصول إلى هذه الخصائص:

يتم تحديد سلسلة الاتصال OLEDB بقاعدة البيانات ACCESS من خلال الخاصية [ConnectionString] أعلاه:
Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=D:\data\serge\devel\aspnet\poly\chap5\impots\3\impots.mdb
نلاحظ أن هذه السلسلة تتكون من جزء ثابت وجزء متغير وهو ببساطة اسم الملف ACCESS. سنستخدم هذه المعلومة لتوليد سلسلة الاتصال بمصدر البيانات الخاص بنا OLEDB.
لنعد الآن إلى فئتنا [impotsOLEDB]. تتولى الطريقة [getData] قراءة البيانات من الجدول [impots] ووضعها في ثلاثة مصفوفات (limites، coeffr، coeffn). لنعلق على كودها:
- نحدد الكائن [DataAdapter] الذي سيسمح لنا بنقل نتيجة استعلام SQL select إلى الذاكرة. للقيام بذلك، نحدد الاستعلام [select] المراد تنفيذه ونربطه بالكائن [DataAdapter]. يتطلب منشئ هذا الكائن أيضًا سلسلة الاتصال التي سيستخدمها للاتصال بالمصدر OLEDB
' يتم إنشاء كائن DataAdapter لقراءة البيانات من المصدر OLEDB
Dim adaptateur As New OleDbDataAdapter("select limites,coeffr,coeffn from impots", chaineConnexion)
- يتم تنفيذ الأمر [select] باستخدام الطريقة [Fill] للكائن [DataAdapter]. يتم إدخال نتيجة [select] في كائن [DataTable] تم إنشاؤه لهذه الغاية. كائن [DataTable] هو صورة في الذاكرة لجدول قاعدة بيانات، c.a.d. مجموعة من الصفوف والأعمدة. نقوم بمعالجة استثناء قد يحدث إذا كانت سلسلة الاتصال غير صحيحة على سبيل المثال.
' يتم إنشاء صورة في الذاكرة لنتيجة عملية الاختيار
Dim contenu As New DataTable("impots")
Try
adaptateur.Fill(contenu)
Catch e As Exception
Throw New Exception("Erreur d'accès à la base de données (" + e.Message + ")")
End Try
- في [contenu]، لدينا الجدول [impots] الذي تم إحضاره بواسطة [select]. الكائن [DataTable] هو جدول، أي مجموعة من الأسطر. يمكن الوصول إلى هذه الأسطر عبر الخاصية [rows] الخاصة بـ [datatable]:
' يتم استرداد محتوى الجدول impots
Dim lignesImpots As DataRowCollection = contenu.Rows
- كل عنصر في المجموعة [lignesImpots] هو كائن من النوع [DataRow] يمثل سطرًا في الجدول. يحتوي هذا الجدول على أعمدة يمكن الوصول إليها عبر الكائن [DataRow] من خلال خاصيته [Item]. [DataRow].[Item(i)] هو العمود رقم i للصف [DataRow]. من خلال تصفح مجموعة الصفوف (المجموعة DataRows من lignesImpots)، ومجموعات الأعمدة لكل صف، يمكننا الحصول على الجدول بالكامل:
' يتم تحديد حجم الجداول المستقبلة
Me.limites = New Decimal(lignesImpots.Count - 1) {}
Me.coeffr = New Decimal(lignesImpots.Count - 1) {}
Me.coeffn = New Decimal(lignesImpots.Count - 1) {}
' ننقل محتوى الجدول impots إلى المصفوفات
Dim i As Integer
Dim ligne As DataRow
Try
For i = 0 To lignesImpots.Count - 1
' السطر i من الجدول
ligne = lignesImpots.Item(i)
' يتم استرداد محتوى السطر
limites(i) = CType(ligne.Item(0), Decimal)
coeffr(i) = CType(ligne.Item(1), Decimal)
coeffn(i) = CType(ligne.Item(2), Decimal)
Next
Catch
Throw New Exception("Les données des tranches d'impôts n'ont pas le bon type")
End Try
- بمجرد وصول بيانات الجدول [impots] إلى الجداول الثلاثة، لم يتبق سوى التحقق من محتواها باستخدام الطريقة [checkData] من الفئة الأساسية [impotsData]:
' التحقق من البيانات التي تم الحصول عليها
Dim erreur As Integer = checkData()
' إذا كانت البيانات غير صالحة، يتم إصدار استثناء
If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
' وإلا يتم إرجاع الجداول الثلاثة
Return New Object() {limites, coeffr, coeffn}
6.3.4. اختبارات فئة الوصول إلى البيانات
قد يكون برنامج الاختبار كما يلي:
Option Explicit On
Option Strict On
' مساحات الأسماء
Imports System
Imports Microsoft.VisualBasic
Namespace st.istia.univangers.fr
' صفحة اختبار
Module testimpots
Sub Main(ByVal arguments() As String)
' برنامج تفاعلي لحساب الضرائب
' يقوم المستخدم بإدخال ثلاث بيانات عبر لوحة المفاتيح: متزوج nbEnfants الراتب
' ثم يعرض البرنامج الضريبة المستحقة
Const syntaxe1 As String = "pg bdACCESS"
Const syntaxe2 As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"
' التحقق من إعدادات البرنامج
If arguments.Length <> 1 Then
' رسالة خطأ
Console.Error.WriteLine(syntaxe1)
' نهاية
Environment.Exit(1)
End If
' يتم استرداد الحجج
Dim chemin As String = arguments(0)
' يتم إعداد سلسلة الاتصال
Dim chaineConnexion As String = "Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=" + chemin
' إنشاء كائن ضريبة
Dim objImpot As impot = Nothing
Try
objImpot = New impot(New impotsOLEDB(chaineConnexion))
Catch ex As Exception
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
Environment.Exit(2)
End Try
' حلقة لا نهائية
While True
' في البداية لا توجد أخطاء
Dim erreur As Boolean = False
' مطلوب معلمات حساب الضريبة
Console.Out.Write("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :")
Dim paramètres As String = Console.In.ReadLine().Trim()
' هل هناك ما يجب فعله؟
If paramètres Is Nothing Or paramètres = "" Then
Exit While
End If
' التحقق من عدد المعلمات في السطر الذي تم إدخاله
Dim args As String() = paramètres.Split(Nothing)
Dim nbParamètres As Integer = args.Length
If nbParamètres <> 3 Then
Console.Error.WriteLine(syntaxe2)
erreur = True
End If
Dim marié As String
Dim nbEnfants As Integer
Dim salaire As Integer
If Not erreur Then
' التحقق من صحة المعلمات
' متزوج
marié = args(0).ToLower()
If marié <> "o" And marié <> "n" Then
Console.Error.WriteLine((syntaxe2 + ControlChars.Lf + "Argument marié incorrect : tapez o ou n"))
erreur = True
End If
' nbEnfants
nbEnfants = 0
Try
nbEnfants = Integer.Parse(args(1))
If nbEnfants < 0 Then
Throw New Exception
End If
Catch
Console.Error.WriteLine(syntaxe2 + "\nArgument nbEnfants incorrect : tapez un entier positif ou nul")
erreur = True
End Try
' الراتب
salaire = 0
Try
salaire = Integer.Parse(args(2))
If salaire < 0 Then
Throw New Exception
End If
Catch
Console.Error.WriteLine(syntaxe2 + "\nArgument salaire incorrect : tapez un entier positif ou nul")
erreur = True
End Try
End If
If Not erreur Then
' المعلمات صحيحة - يتم حساب الضريبة
Console.Out.WriteLine(("impôt=" & objImpot.calculer(marié = "o", nbEnfants, salaire).ToString + " euro(s)"))
End If
End While
End Sub
End Module
End Namespace
يتم تشغيل التطبيق بمعلمة:
- bdACCESS: اسم الملف ACCESS المراد استخدامه
يتم حساب الضريبة باستخدام كائن من النوع [impot] يتم إنشاؤه فور تشغيل التطبيق:
' يتم استرداد الحجج
Dim chemin As String = arguments(0)
' يتم إعداد سلسلة الاتصال
Dim chaineConnexion As String = "Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=" + chemin
' إنشاء كائن ضريبة
Dim objImpot As impot = Nothing
Try
objImpot = New impot(New impotsOLEDB(chaineConnexion))
Catch ex As Exception
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
Environment.Exit(2)
End Try
تم إنشاء سلسلة الاتصال بالمصدر OLEDB استنادًا إلى المعلومات التي تم الحصول عليها باستخدام [WebMatrix].
بمجرد بدء التشغيل، يطلب التطبيق من المستخدم بشكل متكرر إدخال المعلومات الثلاث اللازمة لحساب ضريبته عبر لوحة المفاتيح:
- حالته الاجتماعية: o للمتزوج، n لغير المتزوج
- عدد أطفاله
- راتبه السنوي
يتم تجميع جميع الفئات:
dos>vbc /r:system.dll /r:system.data.dll /t:library /out:impot.dll impots.vb impotsArray.vb impotsData.vb impotsOLEDB.vb
يتم وضع الملف [impots.mdb] في مجلد تطبيق الاختبار ويتم تشغيله على النحو التالي:
dos>testimpots impots.mdb
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 60000
impôt=4300 euro(s)
يمكن تشغيل التطبيق باستخدام ملف ACCESS غير صحيح:
dos>testimpots xx
L'erreur suivante s'est produite : Erreur d'accès à la base de données (Ficher 'D:\data\serge\devel\aspnet\poly\chap5\impots\3\xx' introuvable.)
6.3.5. طرق عرض تطبيق الويب
وهي نفس واجهات التطبيق السابق: formulaire.aspx و erreurs.aspx
6.3.6. وحدات التحكم في التطبيق [global.asax, main.aspx]
يجب تعديل وحدة التحكم [global.asax] فقط. فهي مسؤولة عن إنشاء الكائن [impot] عند بدء تشغيل التطبيق. المعلمة الوحيدة لمنشئ هذا الكائن هي الكائن من النوع [impotsData] المسؤول عن استرداد البيانات. وبالتالي، تتغير هذه المعلمة لأننا نغير مصادر البيانات. يصبح وحدة التحكم [global.asax.vb] كما يلي:
Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr
Imports System.Configuration
Public Class Global
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' يتم إنشاء كائن ضريبة
Dim objImpot As impot
Try
objImpot = New impot(New impotsOLEDB(ConfigurationSettings.AppSettings("chaineConnexion")))
' وضع الكائن في التطبيق
Application("objImpot") = objImpot
' لا توجد أخطاء
Application("erreur") = False
Catch ex As Exception
'حدث خطأ، يتم تسجيله في التطبيق
Application("erreur") = True
Application("message") = ex.Message
End Try
End Sub
End Class
أصبح مصدر بيانات الكائن [impot] الآن كائن [impotOLEDB]. يحتوي هذا الأخير على معلمة سلسلة الاتصال لمصدر البيانات OLEDB المراد استخدامه. يتم وضع هذه السلسلة في ملف التكوين [web.config] للتطبيق:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="chaineConnexion"
value="Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=D:\data\serge\devel\aspnet\poly\chap5\impots2\impots.mdb" />
</appSettings>
</configuration>
لا يتغير جهاز التحكم [main.aspx].
6.3.7. ملخص التغييرات
التطبيق جاهز للاختبار. فيما يلي قائمة بالتعديلات التي أُدخلت على الإصدار السابق:
- تم إنشاء فئة جديدة للوصول إلى البيانات
- تم تعديل وحدة التحكم [global.asax.vb] في مكان واحد: إنشاء الكائن [impot]
- تمت إضافة ملف [web.config]
6.3.8. اختبار تطبيق الويب
يتم وضع جميع الملفات السابقة في مجلد <application-path>.

في هذا المجلد، يتم إنشاء مجلد فرعي [bin] يتم وضع فيه التجميع [impot.dll] الناتج عن تجميع ملفات فئات الأعمال: [impots.vb, impotsData.vb, impotsArray.vb, impotsOLEDB.vb]. نذكر أدناه أمر التجميع اللازم:
dos>vbc /r:system.dll /r:system.data.dll /t:library /out:impot.dll impots.vb impotsArray.vb impotsData.vb impotsOLEDB.vb
يجب وضع الملف [impot.dll] الناتج عن هذا الأمر في <application-path>\bin حتى يتسنى للتطبيق الويب الوصول إليه. يتم تشغيل خادم Cassini باستخدام المعلمات (<application-path>,/impots3). تعطي الاختبارات نفس النتائج التي تم الحصول عليها في الإصدار السابق.
6.4. مثال 4
6.4.1. المشكلة
نقترح الآن تحويل تطبيقنا إلى تطبيق لمحاكاة حسابات الضرائب. سيتمكن المستخدم من إجراء حسابات متتالية للضرائب وستُعرض عليه هذه الحسابات في شاشة جديدة تشبه ما يلي:

6.4.2. هيكل MVC للتطبيق
تصبح بنية التطبيق MVC كما يلي:

تظهر شاشة جديدة [simulations.aspx] التي قدمنا للتو لقطة شاشة لها. ستكون فئة الوصول إلى البيانات هي الفئة [impotsODBC] من المثال 2.
6.4.3. طرق عرض تطبيق الويب
لا تتغير طريقة العرض [erreurs.aspx]. تتغير طريقة العرض [formulaire.aspx] بشكل طفيف. في الواقع، لم يعد مبلغ الضريبة يظهر في طريقة العرض هذه. وهو موجود الآن في طريقة العرض [simulations.aspx]. وبالتالي، عند بدء التشغيل، تكون الصفحة المعروضة للمستخدم هي التالية:

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

رمز العرض هو التالي:
<%@ page src="formulaire.aspx.vb" inherits="formulaire" AutoEventWireup="false"%>
<html>
<head>
<title>Impôt</title>
<script language="javascript">
function calculer(){
// التحقق من المعلمات قبل إرسالها إلى الخادم
with(document.frmImpots){
//عدد العناصر الفرعية
champs=/^\s*(\d+)\s*$/.exec(txtEnfants.value);
if(champs==null){
// لم يتم التحقق من النموذج
alert("Le nombre d'enfants n'a pas été donné ou est incorrect");
txtEnfants.focus();
return;
}//if
//الراتب
champs=/^\s*(\d+)\s*$/.exec(txtSalaire.value);
if(champs==null){
// لم يتم التحقق من النموذج
alert("Le salaire n'a pas été donné ou est incorrect");
txtSalaire.focus();
return;
}//إذا
// حسناً - يتم إرسال النموذج إلى الخادم
submit();
}//مع
}//حساب
</script>
</head>
<body>
<P>Calcul de votre impôt</P>
<HR width="100%" SIZE="1">
<form name="frmImpots" method="post" action="main.aspx?action=calcul">
<TABLE border="0">
<TR>
<TD>Etes-vous marié(e)</TD>
<TD>
<INPUT type="radio" value="oui" name="rdMarie" <%=rdouichecked%>>Oui
<INPUT type="radio" value="non" name="rdMarie" <%=rdnonchecked%>>Non</TD>
</TR>
<TR>
<TD>Nombre d'enfants</TD>
<TD><INPUT type="text" size="3" maxLength="3" name="txtEnfants" value="<%=txtEnfants%>"></TD>
</TR>
<TR>
<TD>Salaire annuel (euro)</TD>
<TD><INPUT type="text" maxLength="12" size="12" name="txtSalaire" value="<%=txtSalaire%>"></TD>
</TR>
</TABLE>
<hr>
<P>
<INPUT type="button" value="Calculer" onclick="calculer()">
</P>
</form>
<form method="post" action="main.aspx?action=effacer">
<INPUT type="submit" value="Effacer">
</form>
</body>
</html>
الحقول الديناميكية للصفحة هي نفسها الموجودة في الإصدارات السابقة. وقد اختفى الحقل الديناميكي الخاص بمبلغ الضريبة. لم يعد الزر [Calculer] من النوع [submit]. إنه من النوع [button] وعند النقر عليه، يتم تنفيذ وظيفة جافا سكريبت [calculer()]:
<INPUT type="button" value="Calculer" onclick="calculer()">
تم تسمية النموذج باسم [frmImpots] حتى يمكن الإشارة إليه في البرنامج النصي [calculer]:
<form name="frmImpots" method="post" action="main.aspx?action=calcul">
تستخدم وظيفة جافا سكريبت [calculer] تعبيرات منتظمة للتحقق من صحة حقول النموذجين [document.frmImpots.txtEnfants] و [document.frmImpots.txtSalaire]. إذا كانت القيم المدخلة صحيحة، يتم إرسالها إلى الخادم بواسطة [document.frmImpots.submit()].
تحصل صفحة العرض على حقولها الديناميكية من وحدة التحكم [formulaire.aspx.vb] التالية:
Imports System.Collections.Specialized
Public Class formulaire
Inherits System.Web.UI.Page
' حقول الصفحة
Protected rdouichecked As String
Protected rdnonchecked As String
Protected txtEnfants As String
Protected txtSalaire As String
Protected txtImpot As String
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' نسترد الطلب السابق في السياق
Dim form As NameValueCollection = Context.Items("formulaire")
' نقوم بإعداد الصفحة لعرضها
' أزرار الاختيار
rdouichecked = ""
rdnonchecked = "checked"
If form("rdMarie").ToString = "oui" Then
rdouichecked = "checked"
rdnonchecked = ""
End If
' الباقي
txtEnfants = CType(form("txtEnfants"), String)
txtSalaire = CType(form("txtSalaire"), String)
End Sub
End Class
وحدة التحكم [formulaire.aspx.vb] مطابقة للإصدارات السابقة باستثناء أنها لم تعد بحاجة إلى استرداد الحقل [txtImpot] من السياق، حيث اختفى هذا الحقل من الصفحة.
تظهر طريقة العرض [simulations.aspx] بصريًا على النحو التالي:

وتتوافق مع كود العرض التالي:
<%@ page src="simulations.aspx.vb" inherits="simulations" autoeventwireup="false" %>
<HTML>
<HEAD>
<title>simulations</title>
</HEAD>
<body>
<P>Résultats des simulations</P>
<HR width="100%" SIZE="1">
<table>
<tr>
<th>
Marié</th>
<th>
Enfants</th>
<th>
Salaire annuel (euro)</th>
<th>
Impôt à payer (euro)</th>
</tr>
<%=simulationsHTML%>
</table>
<p></p>
<a href="<%=href%>">
<%=lien%>
</a>
</body>
</HTML>
يحتوي هذا الرمز على ثلاثة حقول ديناميكية:
رمز HTML لقائمة محاكاة في شكل أسطر جدول HTML | |
رابط URL | |
نص الرابط |
يتم إنشاؤها بواسطة وحدة التحكم [simulations.aspx.vb]:
Imports System.Collections
Imports Microsoft.VisualBasic
Public Class simulations
Inherits System.Web.UI.Page
Protected simulationsHTML As String = ""
Protected href As String
Protected lien As String
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
'يتم استرداد المحاكاة في السياق
Dim simulations As ArrayList = CType(context.Items("simulations"), ArrayList)
' كل محاكاة عبارة عن مصفوفة مكونة من 4 عناصر من نوع string
Dim simulation() As String
Dim i, j As Integer
For i = 0 To simulations.Count - 1
simulation = CType(simulations(i), String())
simulationsHTML += "<tr>"
For j = 0 To simulation.Length - 1
simulationsHTML += "<td>" + simulation(j) + "</td>"
Next
simulationsHTML += "</tr>" + ControlChars.CrLf
Next
' نسترد العناصر الأخرى من السياق
href = context.Items("href").ToString
lien = context.Items("lien").ToString
End Sub
End Class
يسترد عنصر التحكم في الصفحة المعلومات التي وضعها عنصر التحكم في التطبيق في سياق الصفحة:
كائن ArrayList الذي يحتوي على قائمة المحاكاة المطلوب عرضها. كل عنصر عبارة عن مصفوفة من 4 سلاسل أحرف تمثل معلومات (متزوج، أطفال، راتب، ضريبة) المحاكاة. | |
رابط URL | |
نص الرابط |
6.4.4. عناصر التحكم [global.asax, main.aspx]
لنتذكر مخطط MVC لتطبيقنا:

يتعين على وحدة التحكم [main.aspx] معالجة ثلاث إجراءات:
- init: يتوافق مع الطلب الأول للعميل. يعرض وحدة التحكم العرض [formulaire.aspx]
- calcul: تتوافق مع طلب حساب الضريبة. إذا كانت بيانات نموذج الإدخال صحيحة، يتم حساب الضريبة باستخدام فئة الأعمال [impotsODBC]. يعيد وحدة التحكم إلى العميل العرض [simulations.aspx] مع نتيجة المحاكاة الحالية بالإضافة إلى جميع المحاكاة السابقة. إذا كانت بيانات نموذج الإدخال غير صحيحة، تعيد وحدة التحكم العرض [erreurs.aspx] مع قائمة الأخطاء ورابط للعودة إلى النموذج.
- العودة: تعني العودة إلى النموذج بعد حدوث خطأ. يعرض وحدة التحكم العرض [formulaire.aspx] كما تم التحقق من صحته قبل حدوث الخطأ.
في هذه النسخة الجديدة، لم يتغير سوى الإجراء [calcul]. في الواقع، إذا كانت البيانات صالحة، فيجب أن تؤدي إلى العرض [simulations.aspx] بينما كانت تؤدي سابقًا إلى العرض [formulaire.aspx]. يصبح المراقب [main.aspx.vb] كما يلي:
Imports System
...
Public Class main
Inherits System.Web.UI.Page
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' قبل كل شيء، نتحقق مما إذا كان التطبيق قد تم تهيئته بشكل صحيح
...
' يتم تنفيذ الإجراء
Select Case action
Case "init"
' تهيئة التطبيق
initAppli()
Case "calcul"
' حساب الضريبة
calculImpot()
Case "retour"
' العودة إلى النموذج
retourFormulaire()
Case "effacer"
' تهيئة التطبيق
initAppli()
Case Else
' إجراء غير معروف = بدء التشغيل
initAppli()
End Select
End Sub
...
Private Sub calculImpot()
' حفظ الإدخالات
Session.Item("formulaire") = Request.Form
' التحقق من صحة البيانات المدخلة
Dim erreurs As ArrayList = checkData()
' في حالة وجود أخطاء، يتم الإبلاغ عنها
If erreurs.Count <> 0 Then
' يتم إعداد صفحة الأخطاء
context.Items("href") = "main.aspx?action=retour"
context.Items("lien") = "Retour au formulaire"
context.Items("erreurs") = erreurs
Server.Transfer("erreurs.aspx")
End If
' لا توجد أخطاء هنا - يتم حساب الضريبة
Dim impot As Long = CType(Application("objImpot"), impot).calculer( _
Request.Form("rdMarie") = "oui", _
CType(Request.Form("txtEnfants"), Integer), _
CType(Request.Form("txtSalaire"), Long))
' يتم إضافة النتيجة إلى المحاكاة الحالية
Dim simulations As ArrayList
If Not Session.Item("simulations") Is Nothing Then
simulations = CType(Session.Item("simulations"), ArrayList)
Else
simulations = New ArrayList
End If
' إضافة المحاكاة الحالية
Dim simulation() As String = New String() {Request.Form("rdMarie").ToString, _
Request.Form("txtEnfants").ToString, Request.Form("txtSalaire").ToString, _
impot.ToString}
simulations.Add(simulation)
' وضع المحاكاة في الجلسة والسياق
context.Items("simulations") = simulations
Session.Item("simulations") = simulations
' عرض صفحة النتائج
context.Items("href") = "main.aspx?action=retour"
context.Items("lien") = "Retour au formulaire"
Server.Transfer("simulations.aspx", True)
End Sub
...
End Class
لم نحتفظ أعلاه إلا بما هو ضروري لفهم التعديلات الموجودة حصريًا في الدالة [calculImpots]:
- أولاً، تقوم الوظيفة بحفظ النموذج [Request.Form] في الجلسة، وذلك من أجل إعادة إنشاء النموذج في الحالة التي تم فيها التحقق من صحته. يجب القيام بذلك في جميع الحالات، حيث إن العملية تؤدي إلى الرد [erreurs.aspx] أو الرد [simulations.aspx]، ونعود إلى النموذج عبر الرابط [Retour au formulaire]. لاستعادة النموذج بشكل صحيح، يجب حفظ قيمه مسبقًا في الجلسة.
- إذا كانت البيانات المدخلة صحيحة، تضع الدالة المحاكاة الحالية (متزوج، أطفال، راتب، ضريبة) في قائمة المحاكاة. توجد هذه القائمة في الجلسة المرتبطة بالمفتاح "simulations".
- يتم إعادة وضع قائمة المحاكاة في الجلسة لاستخدامها في المستقبل. كما يتم وضعها في السياق الحالي لأن هذا هو المكان الذي تنتظر فيه طريقة العرض [simulations.aspx]
- يتم عرض العرض [simulations.aspx] بمجرد وضع المعلومات الأخرى التي ينتظرها في السياق
6.4.5. ملخص التغييرات
التطبيق جاهز للاختبار. دعونا نسرد التعديلات التي تم إجراؤها على الإصدارات السابقة:
- تم إنشاء عرض جديد
- تم تعديل وحدة التحكم [main.aspx.vb] في مكان واحد: معالجة الإجراء [calcul]
6.4.6. اختبار تطبيق الويب
يُطلب من القارئ إجراء الاختبارات. نذكر الخطوات مرة أخرى. يتم وضع جميع ملفات التطبيق في مجلد <application-path>. في هذا المجلد، يتم إنشاء مجلد فرعي [bin] يتم وضع فيه التجميع [impot.dll] الناتج عن تجميع ملفات فئات الأعمال: [impots.vb، impotsData.vb، impotsArray.vb، impotsODBC.vb. يجب وضع الملف [impot.dll] الناتج عن هذا الأمر في <application-path>\bin حتى يتمكن تطبيق الويب من الوصول إليه. يتم تشغيل خادم Cassini باستخدام المعلمات (<application-path>,/impots4).
6.5. Conclusion
أظهرت الأمثلة السابقة، من خلال حالة ملموسة، الآليات المستخدمة بشكل شائع في تطوير الويب. استخدمنا بشكل منهجي بنية MVC لفائدتها التعليمية. كان من الممكن معالجة هذه الأمثلة نفسها بطريقة مختلفة وربما أبسط بدون هذه البنية. لكن هذه البنية توفر مزايا كبيرة بمجرد أن يصبح التطبيق معقدًا بعض الشيء مع وجود صفحات متعددة.
يمكننا متابعة أمثلةنا بطرق مختلفة. وإليك بعض منها:
- قد يرغب المستخدم في الاحتفاظ بمحاكياته بمرور الوقت. قد يقوم بإجراء محاكيات في اليوم J ويمكنه العثور عليها في اليوم J+3 على سبيل المثال. أحد الحلول الممكنة لهذه المشكلة هو استخدام ملفات تعريف الارتباط. نحن نعلم أن رمز الجلسة بين الخادم والعميل يتم نقله عبر هذه الآلية. يمكننا أيضًا استخدام هذه الآلية لنقل المحاكيات بين العميل والخادم.
- في نفس الوقت الذي يرسل فيه الخادم صفحة نتائج المحاكاة، يرسل في رؤوسه HTTP ملف تعريف ارتباط يحتوي على سلسلة أحرف تمثل المحاكاة. ونظرًا لأن هذه المحاكاة موجودة في كائن [ArrayList]، فإنه يتعين إجراء عملية تحويل هذا الكائن إلى [String]. ويحدد الخادم مدة صلاحية ملف تعريف الارتباط، على سبيل المثال 30 يومًا.
- يقوم متصفح العميل بتخزين ملفات تعريف الارتباط المستلمة في ملف وإعادة إرسالها في كل مرة يقوم فيها بإرسال طلب إلى الخادم الذي أرسلها إليه إذا كانت لا تزال صالحة (لم تتجاوز مدة صلاحيتها). سيتلقى الخادم لسلسلة المحاكاة سلسلة أحرف [String] التي سيتعين عليه تحويلها إلى كائن [ArrayList].
تتم إدارة ملفات تعريف الارتباط بواسطة [Response.Cookies] عند إرسالها إلى العميل وبواسطة [Request.Cookies] عند استلامها على الخادم.
- قد تصبح الآلية السابقة ثقيلة إلى حد ما إذا كان هناك عدد كبير من عمليات المحاكاة. علاوة على ذلك، غالبًا ما يقوم المستخدم بمسح ملفات تعريف الارتباط بشكل دوري عن طريق حذفها جميعًا حتى لو كان قد سمح لمتصفحه باستخدامها. وبالتالي، في يوم من الأيام سنفقد ملف تعريف الارتباط الخاص بعمليات المحاكاة. قد نرغب عندئذٍ في تخزينها على الخادم بدلاً من العميل، في قاعدة بيانات على سبيل المثال. لربط المحاكاة بمستخدم معين، يمكن أن تبدأ التطبيق بمرحلة تحديد الهوية التي تتطلب اسم مستخدم وكلمة مرور مخزنين بدورهما في قاعدة بيانات أو أي نوع آخر من مخازن البيانات.
- قد نرغب أيضًا في تأمين تشغيل تطبيقنا. يفترض التطبيق حاليًا أمرين:
- يمر المستخدم دائمًا عبر وحدة التحكم [main.aspx]
- وفي هذه الحالة، يستخدم دائمًا الإجراءات المقترحة في الصفحة التي تم إرسالها إليه
ماذا يحدث، على سبيل المثال، إذا طلب المستخدم مباشرةً عنوان URL [http://localhost/impots4/formulaire.aspx]؟ هذه الحالة غير محتملة لأن المستخدم لا يعرف بوجود هذا الرابط. ومع ذلك، يجب توقعها. يمكن إدارتها بواسطة وحدة التحكم في التطبيق [global.asax] التي ترى جميع الطلبات الموجهة إلى التطبيق. وبذلك يمكنها التحقق من أن المورد المطلوب هو بالفعل [main.aspx].
الحالة الأكثر احتمالاً هي أن المستخدم لا يستخدم الإجراءات الموجودة على الصفحة التي أرسلها له الخادم. على سبيل المثال، ماذا يحدث إذا طلب المستخدم عنوان URL [http://localhost/impots4/main.aspx?action=retour] مباشرةً دون ملء النموذج مسبقاً؟ لنجرب ذلك. نحصل على الرد التالي:

حدث تعطل في الخادم. وهذا أمر طبيعي. بالنسبة للإجراء [retour]، يتوقع وحدة التحكم أن تجد في الجلسة كائنًا [NameValueCollection] يمثل قيم النموذج الذي يجب عليها عرضه. لكنها لا تجده. تتيح آلية وحدة التحكم تقديم حل أنيق لهذه المشكلة. لكل طلب، يمكن لوحدة التحكم [main.aspx] التحقق من أن الإجراء المطلوب هو بالفعل أحد الإجراءات الموجودة في الصفحة التي تم إرسالها مسبقًا إلى المستخدم. يمكن استخدام الآلية التالية:
- يقوم وحدة التحكم، قبل إرسال ردها إلى العميل، بتخزين معلومات تحدد هوية هذه الصفحة في جلسة عمل العميل
- عندما يتلقى طلبًا جديدًا من العميل، يتحقق من أن الإجراء المطلوب ينتمي بالفعل إلى آخر صفحة تم إرسالها إلى هذا العميل
- يمكن إدخال المعلومات التي تربط بين الصفحات والإجراءات المسموح بها في هذه الصفحات في ملف التكوين [web.config] الخاص بالتطبيق.
- تُظهر التجربة أن وحدات التحكم في التطبيقات لها قاعدة مشتركة واسعة وأنه من الممكن إنشاء وحدة تحكم عامة، حيث يتم تخصيصها لتطبيق معين عبر ملف التكوين. وهذا هو النهج الذي تتبعه، على سبيل المثال، الأداة [Struts] في مجال برمجة الويب بلغة Java.



