Skip to content

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، فسنجد السطر

    8382        0.0683        291.09

يكون الضريبة I عندئذٍ مساوية لـ 0.0683*R - 291.09*nbParts. إذا كان QF بحيث لا يتم التحقق من العلاقة QF<=champ1 أبدًا، فسيتم استخدام معاملات السطر الأخير. هنا:

    0                0.4809    9505.54

مما يعطي الضريبة I=0.4809*R - 9505.54*nbParts.

6.1.2. هيكل MVC للتطبيق

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

Image

ستقوم الصفحة [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

تحتوي الفئة على السمات المحمية التالية:

limites
جدول حدود شرائح الضرائب
coeffr
جدول المعاملات المطبقة على الدخل الخاضع للضريبة
coeffn
جدول المعاملات المطبقة على عدد الحصص
checked
قيمة منطقية تشير إلى ما إذا كانت البيانات (الحدود، المعاملات، المعاملات) قد تم التحقق منها
valide
قيمة منطقية تشير إلى ما إذا كانت البيانات (الحدود، 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>vbc /r:impot.dll testimpots1.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
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] لأول مرة:

Image

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

Image

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

Image

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

Image

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

Image

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

Image

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>

الحقول الديناميكية في هذه الصفحة هي التالية:

rdouichecked
"checked" إذا كان يجب تحديد المربع [oui]، و"" في حالة عدم تحديد المربع
rdnonchecked
نفس الشيء بالنسبة للمربع [non]
txtEnfants
القيمة التي يجب إدخالها في حقل الإدخال [txtEnfants]
txtSalaire
القيمة التي يجب إدخالها في حقل الإدخال [txtSalaire]
txtImpot
القيمة التي يجب إدخالها في حقل الإدخال [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>

تحتوي الصفحة على ثلاثة حقول ديناميكية:

erreursHTML
رمز HTML من قائمة الأخطاء
href
عنوان URL لرابط
lien
نص الرابط

يتم حساب هذه الحقول بواسطة جزء التحكم في الصفحة في [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

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

Context.Items("erreurs"
كائن ArrayList الذي يحتوي على قائمة رسائل الأخطاء المطلوب عرضها
Context.Items("href"
عنوان URL لرابط
Context.Items("lien"
نص الرابط

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

6.1.9. وحدات التحكم [global.asax, main.aspx]

لنتذكر مخطط MVC لتطبيقنا:

Image

العميل منطق التطبيق

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

<%@ Application src="Global.asax.vb" Inherits="Global" %>

[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]

<%@ page src="main.aspx.vb" inherits="main" AutoEventWireup="false"%>

[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>.

Image

في هذا المجلد، يتم إنشاء مجلد فرعي [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]:

Image

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

Image

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

Image

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

Image

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

Image

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

Image

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

Image

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 كتابة مثل هذا التطبيق. وتصبح كما يلي:

Image

لا يتغير الكتلة [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]. سيكون محتوى هذا الجدول كما يلي:

Image

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

توجد العديد من قواعد البيانات لمنصات ويندوز. للوصول إليها، تمر التطبيقات عبر برامج تسمى برامج التشغيل (drivers).

Image

في المخطط أعلاه، يقدم برنامج التشغيل واجهتين:

  • واجهة 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 مزودة بثلاثة أنواع من فئات الوصول إلى البيانات:

  1. فئات SQL و Server.NET، للوصول إلى قواعد بيانات Microsoft SQL Server
  2. فئات Ole Db.NET، للوصول إلى قواعد بيانات SGBD التي توفر برنامج تشغيل OLE و DB
  3. فئات odbc.net، للوصول إلى قواعد بيانات SGBD التي توفر برنامج تشغيل ODBC

تتوفر لفئة SGBD MySQL منذ فترة طويلة برنامج تشغيل ODBC. وهذا هو البرنامج الذي نستخدمه الآن. في نظام Windows، نختار الخيار [Menu Démarrer/Panneau de configuration/Outils d'administration/Sources ODBC 32 bits]. قد يختلف هذا المسار قليلاً حسب إصدار Windows. نحصل على التطبيق التالي الذي سيسمح لنا بإنشاء مصدرنا ODBC:

Image

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

Image

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

Image

يجب تقديم عدد من المعلومات:

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

بعد ذلك، نختبر صحة معلمات الاتصال الخاصة بنا باستخدام الزر [Test Data Source]:

Image

بعد ذلك، نكون متأكدين من مصدر البيانات ODBC. يمكننا الآن استخدامه. نكرر [OK] عدة مرات حسب الحاجة للخروج من المساعد ODBC.

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

Image

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

Image

المعلومات المطلوبة هي التالية:

Nom de la source de données
الاسم الذي سيشير إلى مصدر البيانات ODBC. سيتمكن أي تطبيق ويندوز من الوصول إلى المصدر عبر هذا الاسم
Description
نص تعسفي يصف مصدر البيانات
Base de données
الاسم الكامل لملف ACCESS المراد استخدامه

6.2.3. فئة جديدة للوصول إلى البيانات

لنعد إلى هيكل MVC لتطبيقنا:

Image

في المخطط أعلاه، تتولى الفئة [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"))
  • بعد ذلك، نقوم بتحرير الموارد المرتبطة بالاتصال:
                 ' تحرير الموارد
                myReader.Close()
                impotsConn.Close()
  • يتم نقل محتوى الكائنات الثلاثة [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

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

dos>vbc /r:impot.dll 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. ملخص التغييرات

التطبيق جاهز للاختبار. دعونا نسرد التعديلات التي أُدخلت على الإصدار السابق:

  1. تم إنشاء فئة جديدة للوصول إلى البيانات
  2. تم تعديل وحدة التحكم [global.asax.vb] في موضعين: إنشاء الكائن [impot] وتسجيل الرسالة المرتبطة بالاستثناء المحتمل في التطبيق
  3. تم تعديل وحدة التحكم [main.aspx.vb] في مكان واحد لعرض رسالة الاستثناء السابقة
  4. تمت إضافة ملف [web.config]

تمت أعمال التعديل بشكل أساسي في 1، c.a.d. خارج التطبيق الويب. وقد أصبح ذلك ممكنًا بفضل بنية التطبيق MVC التي تفصل وحدة التحكم عن فئات الأعمال. وهنا يكمن الفائدة الكبرى لهذه البنية. يمكن إثبات أنه باستخدام ملف تكوين مناسب، كان من الممكن تجنب أي تعديل على وحدة التحكم في التطبيق. من الممكن إدخال اسم فئة الوصول إلى البيانات المراد إنشاء مثيل لها ديناميكيًا في الملف، بالإضافة إلى المعلمات المختلفة اللازمة لعملية الإنشاء هذه. باستخدام هذه المعلومات، يمكن لـ [global.asax] إنشاء مثيل لكائن الوصول إلى البيانات. وبالتالي، فإن تغيير مصدر البيانات يعود إلى:

  • إنشاء فئة الوصول إلى هذا المصدر إذا لم تكن موجودة بعد
  • تعديل الملف [web.config] للسماح بإنشاء مثيل ديناميكي لهذه الفئة في [global.asax]

6.2.8. اختبار تطبيق الويب

يتم وضع جميع الملفات السابقة في مجلد <application-path>.

Image

في هذا المجلد، يتم إنشاء مجلد فرعي [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]. ونحصل على الرد التالي:

Image

6.3. المثال 3

6.3.1. المشكلة

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

6.3.2. مصدر البيانات OLEDB

ستوجد البيانات في جدول يسمى [IMPOTS] في قاعدة بيانات ACCESS. سيكون محتوى هذا الجدول كما يلي:

Image

6.3.3. فئة الوصول إلى البيانات

لنعد إلى البنية MVC لتطبيقنا:

Image

  • في المخطط أعلاه، تتولى فئة [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:

Image

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

Image

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

Image

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

Image

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

Image

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

Image

يتم تحديد سلسلة الاتصال 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
dos>vbc /r:impot.dll testimpots.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. ملخص التغييرات

التطبيق جاهز للاختبار. فيما يلي قائمة بالتعديلات التي أُدخلت على الإصدار السابق:

  1. تم إنشاء فئة جديدة للوصول إلى البيانات
  2. تم تعديل وحدة التحكم [global.asax.vb] في مكان واحد: إنشاء الكائن [impot]
  3. تمت إضافة ملف [web.config]

6.3.8. اختبار تطبيق الويب

يتم وضع جميع الملفات السابقة في مجلد <application-path>.

Image

في هذا المجلد، يتم إنشاء مجلد فرعي [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. المشكلة

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

Image

6.4.2. هيكل MVC للتطبيق

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

Image

تظهر شاشة جديدة [simulations.aspx] التي قدمنا للتو لقطة شاشة لها. ستكون فئة الوصول إلى البيانات هي الفئة [impotsODBC] من المثال 2.

6.4.3. طرق عرض تطبيق الويب

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

Image

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

Image

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


<%@ 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] بصريًا على النحو التالي:

Image

وتتوافق مع كود العرض التالي:


<%@ 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>

يحتوي هذا الرمز على ثلاثة حقول ديناميكية:

simulationsHTML
رمز HTML لقائمة محاكاة في شكل أسطر جدول HTML
href
رابط URL
lien
نص الرابط

يتم إنشاؤها بواسطة وحدة التحكم [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

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

Context.Items("simulations")
كائن ArrayList الذي يحتوي على قائمة المحاكاة المطلوب عرضها. كل عنصر عبارة عن مصفوفة من 4 سلاسل أحرف تمثل معلومات (متزوج، أطفال، راتب، ضريبة) المحاكاة.
Context.Items("href")
رابط URL
Context.Items("lien")
نص الرابط

6.4.4. عناصر التحكم [global.asax, main.aspx]

لنتذكر مخطط MVC لتطبيقنا:

Image

يتعين على وحدة التحكم [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. ملخص التغييرات

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

  1. تم إنشاء عرض جديد
  2. تم تعديل وحدة التحكم [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] مباشرةً دون ملء النموذج مسبقاً؟ لنجرب ذلك. نحصل على الرد التالي:

Image

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

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