Skip to content

6. أمثلة

في هذا الفصل، سنوضح ما رأيناه سابقًا من خلال سلسلة من الأمثلة.

6.1. المثال 1

6.1.1. المشكلة

يجب أن يسمح هذا التطبيق للمستخدم بحساب ضرائبه. نحن ننظر في الحالة المبسطة لمكلف ضريبي ليس لديه سوى راتبه ليبلغ عنه (أرقام عام 2004 لدخل عام 2003):

  • نحسب عدد شرائح الضرائب للموظف على النحو التالي: nbParts = nbEnfants / 2 + 1 إذا كان غير متزوج، و nbEnfants / 2 + 2 إذا كان متزوجًا، حيث nbEnfants هو عدد الأطفال.
  • إذا كان لديه ثلاثة أطفال على الأقل، يحق له الحصول على نصف حصة إضافية
  • يُحسب دخله الخاضع للضريبة R على النحو التالي: R = 0.72 * S، حيث S هو راتبه السنوي
  • نحسب معامل الأسرة QF = R / nbParts
  • نحسب ضريبته I. انظر الجدول التالي:
4262
0
0
8382
0.0683
291.09
14,753
0.1914
1,322.92
23,888
0.2826
2,668.39
38,868
0.3738
4,846.98
47,932
0.4262
6,883.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<=field1 أبدًا، يتم استخدام المعاملات من الصف الأخير. هنا:

    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]. يعرض وحدة التحكم عرض [form.aspx] للعميل كما تم التحقق من صحته، مع الضريبة المحسوبة. إذا كانت البيانات في نموذج الإدخال غير صحيحة، ستعرض وحدة التحكم عرض [errors.aspx] مع قائمة بالأخطاء ورابط للعودة إلى النموذج.
  • return: يتوافق مع العودة إلى النموذج بعد حدوث خطأ. يعرض وحدة التحكم عرض [form.aspx] كما تم التحقق من صحته قبل حدوث الخطأ.

لا تعرف وحدة التحكم [main.aspx] أي شيء عن حسابات الضرائب. إنها مسؤولة ببساطة عن إدارة الحوار بين العميل والخادم وتنفيذ الإجراءات التي يطلبها العميل. بالنسبة لإجراء [calculate]، ستعتمد على فئة الأعمال [tax].

6.1.3. فئة الأعمال

سيتم تعريف فئة **impot** على النحو التالي:


' imported namespaces
Imports System
 
' class
Namespace st.istia.univangers.fr
    Public Class impot
        Private limites(), coeffR(), coeffN() As Decimal
 
        ' manufacturer
        Public Sub New(ByRef source As impotsData)
            ' data required for tax calculation
            ' come from an external source [source]
            ' we retrieve them - there may be an exception
            Dim data() As Object = source.getData
            limites = CType(data(0), Decimal())
            coeffR = CType(data(1), Decimal())
            coeffN = CType(data(2), Decimal())
        End Sub
 
        ' tAX CALCULATION
        Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Long) As Long
            ' calculating the number of shares
            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
            ' calculation of taxable income & family quota
            Dim revenu As Decimal = 0.72D * salaire
            Dim QF As Decimal = revenu / nbParts
            ' tAX CALCULATION
            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] تسترد مصفوفات البيانات الثلاثة اللازمة لحساب الضريبة، كما هو موضح سابقًا. يمكن لهذه الطريقة معالجة استثناء إذا تعذر استرداد البيانات أو إذا تبين أنها غير صحيحة. بمجرد إنشاء كائن [tax]، يمكن استدعاء طريقة calculate الخاصة به بشكل متكرر لحساب ضريبة دافع الضرائب بناءً على حالته الاجتماعية (متزوج أو أعزب) وعدد أطفاله وراتبه السنوي.

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
 
        ' data access method
        Public MustOverride Function getData() As Object()
 
        ' data verification method
        Protected Function checkData() As Integer
            ' verifies acquired data
            ' we need data
            valide = Not limites Is Nothing AndAlso Not coeffr Is Nothing AndAlso Not coeffn Is Nothing
            If Not valide Then Return 1
            ' we must have 3 arrays of the same size
            If valide Then valide = limites.Length = coeffr.Length AndAlso limites.Length = coeffn.Length
            If Not valide Then Return 2
            ' tables must be non-empty
            valide = limites.Length <> 0
            If Not valide Then Return 3
            ' each array must contain elements >=0 in ascending order
            valide = check(limites, limites.Length - 1) AndAlso check(coeffr, coeffr.Length) AndAlso check(coeffn, coeffn.Length)
            If Not valide Then Return 4
            ' all is good
            Return 0
        End Function
 
        ' checks the validity of an array's contents
        Protected Function check(ByRef tableau() As Decimal, ByVal n As Integer) As Boolean
            ' array must have its first n elements >=0 and in strictly ascending order
            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
            ' it's good
            Return True
        End Function
    End Class
End Namespace

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

حدود
مصفوفة حدود الشرائح الضريبية
coeffr
مصفوفة المعاملات المطبقة على الدخل الخاضع للضريبة
coeffn
جدول المعاملات المطبقة على عدد الأسهم
checked
قيمة منطقية تشير إلى ما إذا كانت البيانات (الحدود، coeffr، coeffn) قد تم التحقق منها
صالح
قيمة منطقية تشير إلى ما إذا كانت البيانات (الحدود، coeffr، coeffn) صالحة

لا تحتوي الفئة على منشئ. وتحتوي على طريقة مجردة [getData] يجب على الفئات المشتقة تنفيذها. الغرض من هذه الطريقة هو:

  • تعيين قيم للمصفوفات الثلاثة limits و coeffr و coeffn
  • إلقاء استثناء إذا تعذر الحصول على البيانات أو إذا تبين أنها غير صالحة.

توفر الفئة الطريقتين المحميتين [checkData] و [check] اللتين تتحققان من صحة السمات (limits، coeffr، coeffn). وهذا يعفي الفئات المشتقة من الحاجة إلى تنفيذها. سيكون عليها ببساطة استخدامها.

الفئة المشتقة الأولى التي سنستخدمها هي كما يلي:


Imports System.Collections
Imports System
 
Namespace st.istia.univangers.fr
    Public Class impotsArray
        Inherits impotsData
 
        ' constructor with no arguments
        Public Sub New()
            ' initializing tables with constants
            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
 
        ' builder with three input tables
        Public Sub New(ByRef limites() As Decimal, ByRef coeffr() As Decimal, ByRef coeffn() As Decimal)
            ' data storage
            Me.limites = limites
            Me.coeffr = coeffr
            Me.coeffn = coeffn
            checked = False
        End Sub
 
        Public Overrides Function getData() As Object()
            ' check data if necessary
            Dim erreur As Integer
            If Not checked Then erreur = checkData() : checked = True
            ' if invalid, then throw an exception
            If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
            ' otherwise we return the three tables
            Return New Object() {limites, coeffr, coeffn}
        End Function
    End Class
End Namespace

تحتوي هذه الفئة، المسماة [impotsArray]، على منشئين:

  • منشئ بدون حجج يقوم بتهيئة سمات (limits, coeffr, coeffn) للفئة الأساسية باستخدام مصفوفات مبرمجة بشكل ثابت
  • منشئ يقوم بتهيئة سمات (limits، coeffr، coeffn) للفئة الأساسية باستخدام مصفوفات يتم تمريرها إليه كمعلمات

تقوم طريقة [getData]، التي تسمح للفئات الخارجية باسترداد المصفوفات (limits، coeffr، coeffn)، ببساطة بالتحقق من صحة المصفوفات الثلاث باستخدام طريقة [checkData] للفئة الأساسية. وتقوم بإلقاء استثناء إذا كانت البيانات غير صالحة.

6.1.5. اختبار فئات الأعمال وفئات الوصول إلى البيانات

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


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System
Imports Microsoft.VisualBasic
 
Namespace st.istia.univangers.fr
    Module test
        Sub Main()
            ' interactive tax calculator
            ' the user enters three data points on the keyboard: married nbEnfants salary
            ' the program then displays the tax payable
            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"
 
            ' tax object creation
            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
            ' infinite loop
            Dim marié As String
            Dim nbEnfants As Integer
            Dim salaire As Long
            While True
                ' tax calculation parameters are requested
                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()
                ' anything to do?
                If paramètres Is Nothing OrElse paramètres = "" Then
                    Exit While
                End If
                ' check the number of arguments in the input line
                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
                ' checking the validity of parameters
                If Not erreur Then
                    ' married
                    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
                    ' salary
                    Try
                        salaire = Integer.Parse(args(2))
                        If salaire < 0 Then
                            Throw New Exception
                        End If
                    Catch
                        erreur = True
                    End Try
                End If
                ' if the parameters are correct - the tax is calculated
                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 لغير المتزوجين
  • عدد الأطفال
  • الراتب السنوي

يتم حساب الضريبة باستخدام كائن من النوع [tax] يتم إنشاؤه عند تشغيل التطبيق:


            ' tax object creation
            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. طرق عرض تطبيق الويب

سيكون للتطبيق عرضان: [form.aspx] و [errors.aspx]. دعونا نوضح كيفية عمل التطبيق باستخدام لقطات الشاشة. يتم عرض [form.aspx] عند طلب عنوان URL [main.aspx] لأول مرة:

Image

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

Image

ويستخدم زر [حساب] للحصول على النتيجة التالية:

Image

قد يدخلون بيانات غير صحيحة:

Image

ثم يؤدي النقر فوق الزر [Calculate] إلى إرجاع استجابة مختلفة [errors.aspx]:

Image

يمكنه استخدام الرابط [Back to Form] أعلاه للعودة إلى عرض [form.aspx] كما كان قبل الخطأ:

Image

6.1.7. عرض [form.aspx]

ستكون صفحة [form.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" إذا كان يجب تحديد مربع الاختيار [yes]، و"" في الحالات الأخرى
rdnonchecked
نفس الشيء بالنسبة لمربع الاختيار [لا]
txtChildren
القيمة التي سيتم وضعها في حقل الإدخال [txtChildren]
txtSalary
القيمة التي سيتم إدخالها في حقل الإدخال [txtSalary]
txtTax
القيمة التي سيتم إدخالها في حقل الإدخال [txtTax]

تحتوي الصفحة على نموذجين، كل منهما مزود بزر [submit]. زر [Calculate] هو زر [submit] للنموذج التالي:


        <form method="post" action="main.aspx?action=calcul">
...
            <P>
                <INPUT type="submit" value="Calculer">
            </P>
        </form>

يمكننا أن نرى أن معلمات النموذج سيتم إرسالها إلى وحدة التحكم باستخدام [action=calcul]. زر [Clear] هو زر [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
 
    ' page fields
    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
        ' we retrieve the previous request in the
        Dim form As NameValueCollection = Context.Items("formulaire")
        ' prepare the page to be displayed
        ' radio buttons
        rdouichecked = ""
        rdnonchecked = "checked"
        If form("rdMarie").ToString = "oui" Then
            rdouichecked = "checked"
            rdnonchecked = ""
        End If
        ' the rest
        txtEnfants = CType(form("txtEnfants"), String)
        txtSalaire = CType(form("txtSalaire"), String)
        txtImpot = CType(Context.Items("txtImpot"), String)
    End Sub
End Class

يتم حساب الحقول في [main.aspx] استنادًا إلى معلومتين يضعهما وحدة التحكم في سياق الصفحة:

  • Context.Items("form"): قاموس 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>

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

HTMLErrors
كود HTML لقائمة الأخطاء
href
عنوان URL للرابط
link
نص الرابط

يتم حساب هذه الحقول بواسطة وحدة التحكم الخاصة بالصفحة في [errors.aspx.vb]:


Imports System.Collections
Imports Microsoft.VisualBasic
 
Public Class erreurs
    Inherits System.Web.UI.Page
 
    ' page parameter
    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
        ' retrieve context elements
        Dim erreurs As ArrayList = CType(context.Items("erreurs"), ArrayList)
        href = context.Items("href").ToString
        lien = context.Items("lien").ToString
        ' we generate the HTML code from the list
        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("errors"
كائن ArrayList يحتوي على قائمة رسائل الخطأ المراد عرضها
Context.Items("href"
عنوان URL للرابط
Context.Items("link"
نص الرابط

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

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

دعونا نراجع بنية MVC لتطبيقنا:

Image

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

يجب أن يتعامل وحدة التحكم [main.aspx] مع ثلاثة إجراءات:

  • init: يتوافق مع الطلب الأول للعميل. تعرض وحدة التحكم طريقة العرض [formulaire.aspx]
  • calcul: يتوافق مع طلب حساب الضريبة. إذا كانت البيانات في نموذج الإدخال صحيحة، يتم حساب الضريبة باستخدام فئة الأعمال [taxes]. يعرض وحدة التحكم عرض [form.aspx] للعميل كما تم التحقق من صحته، مع الضريبة المحسوبة. إذا كانت البيانات في نموذج الإدخال غير صحيحة، يعرض وحدة التحكم عرض [errors.aspx] مع قائمة بالأخطاء ورابط للعودة إلى النموذج.
  • return: يتوافق مع العودة إلى النموذج بعد حدوث خطأ. يعرض وحدة التحكم عرض [form.aspx] كما تم التحقق من صحته قبل حدوث الخطأ.

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

  • [global.asax]، التي تستقبل، بسبب بنية ASP.NET، جميع الطلبات الموجهة إلى التطبيق
  • [main.aspx]، والتي، بناءً على اختيار المطور، تستقبل أيضًا جميع الطلبات الموجهة إلى التطبيق

تنبع الحاجة إلى [main.aspx] من حقيقة أننا سنحتاج إلى إدارة جلسة عمل. وقد رأينا أن [global.asax] غير مناسب كوحدة تحكم في هذه الحالة. يمكننا الاستغناء عن [global.asax] تمامًا هنا. ومع ذلك، سنستخدمه لتنفيذ التعليمات البرمجية عند بدء تشغيل التطبيق. يوضح مخطط MVC أعلاه أننا سنحتاج إلى إنشاء كائن [tax] لحساب الضريبة. لا داعي لإنشاء هذا الكائن عدة مرات؛ يكفي إنشاؤه مرة واحدة. لذلك سنقوم بإنشائه عند بدء تشغيل التطبيق أثناء حدث [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)
        ' create an impot object
        Dim objImpot As impot
        Try
            objImpot = New impot(New impotsArray)
            ' put the object in the application
            Application("objImpot") = objImpot
            ' no error
            Application("erreur") = False
        Catch ex As Exception
            'there has been an error, we note it in the application
            Application("erreur") = True
        End Try
    End Sub
End Class

بمجرد إنشائه، يتم تخزين الكائن [impot] في التطبيق. وهذا هو المكان الذي ستسترده منه الطلبات الواردة من العملاء المختلفين. ونظرًا لأن إنشاء الكائن [impot] قد يفشل، فإننا نتعامل مع أي استثناءات ونقوم بتعيين مفتاح [error] في التطبيق للإشارة إلى ما إذا كان قد حدث خطأ أم لا أثناء إنشاء الكائن [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
        ' first of all, we check whether the application has initialized correctly
        If CType(Application("erreur"), Boolean) Then
            ' redirects to error page
            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
        ' retrieve the action to be performed
        Dim action As String
        If Request.QueryString("action") Is Nothing Then
            action = "init"
        Else
            action = Request.QueryString("action").ToString.ToLower
        End If
        ' execute the action
        Select Case action
            Case "init"
                ' init application
                initAppli()
            Case "calcul"
                ' tax calculation
                calculImpot()
            Case "retour"
                ' back to form
                retourFormulaire()
            Case "effacer"
                ' init application
                initAppli()
            Case Else
                ' unknown action = init
                initAppli()
        End Select
    End Sub
 
    Private Sub initAppli()
        ' the pre-filled form is displayed
        Context.Items("formulaire") = initForm()
        Context.Items("txtImpot") = ""
        Server.Transfer("formulaire.aspx", True)
    End Sub
 
    Private Function initForm() As NameValueCollection
        ' initialize the form
        Dim form As New NameValueCollection
        form.Set("rdMarie", "non")
        form.Set("txtEnfants", "")
        form.Set("txtSalaire", "")
        Return form
    End Function

    Private Sub calculImpot()
        ' check the validity of the data entered
        Dim erreurs As ArrayList = checkData()
        ' if there are errors, we report them
        If erreurs.Count <> 0 Then
            ' save entries
            Session.Item("formulaire") = Request.Form
            ' prepare the error page
            context.Items("href") = "main.aspx?action=retour"
            context.Items("lien") = "Retour au formulaire"
            context.Items("erreurs") = erreurs
            Server.Transfer("erreurs.aspx")
        End If
        ' no errors here - the tax is calculated
        Dim impot As Long = CType(Application("objImpot"), impot).calculer( _
        Request.Form("rdMarie") = "oui", _
        CType(Request.Form("txtEnfants"), Integer), _
        CType(Request.Form("txtSalaire"), Long))
        ' the result page is displayed
        context.Items("txtImpot") = impot.ToString + " euro(s)"
        context.Items("formulaire") = Request.Form
        Server.Transfer("formulaire.aspx", True)
    End Sub
 
    Private Sub retourFormulaire()
        ' displays the form with values taken from the session
        Context.Items("formulaire") = Session.Item("formulaire")
        Context.Items("txtImpot") = ""
        Server.Transfer("formulaire.aspx", True)
    End Sub
 
    Private Function checkData() As ArrayList
        ' initially no errors
        Dim erreurs As New ArrayList
        Dim erreur As Boolean = False
        ' married radio button
        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
        ' no. of children
        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
        ' salary
        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 the list of errors
        Return erreurs
    End Function
End Class

يبدأ وحدة التحكم بالتحقق من أن التطبيق قد تم تهيئته بشكل صحيح:


        ' first of all, we check whether the application has initialized correctly
        If CType(Application("erreur"), Boolean) Then
            ' redirects to error page
            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

إذا اكتشف وحدة التحكم أن التطبيق فشل في التهيئة بشكل صحيح (تعذر إنشاء الكائن [import] المطلوب للحساب)، فإنه يعرض صفحة الخطأ مع المعلمات المناسبة. هنا، لا توجد حاجة لوضع رابط العودة في النموذج لأن التطبيق بأكمله غير متاح. يتم وضع رسالة خطأ عامة في [Context.Items("errors")] من النوع [ArrayList].

إذا قررت وحدة التحكم أن التطبيق يعمل، فإنها تقوم بعد ذلك بتحليل الإجراء المطلوب تنفيذها عبر المعلمة [action]. لقد صادفنا هذا النمط من التشغيل عدة مرات حتى الآن. يتم تفويض معالجة كل نوع من أنواع الإجراءات إلى دالة.

6.1.9.1. إجراءات init و delete

يجب أن يعرض هذان الإجراءان نموذج الإدخال الفارغ. تذكر أن هذا النموذج (انظر العروض) يحتوي على معلمتين:

  • Context.Items("form"): قاموس [NameValueCollection] يحتوي على قيم حقول HTML [rdmarie,txtEnfants,txtSalaire]
  • Context.Items("txtImpot"): قيمة الضريبة

تقوم الدالة [initAppli] بتهيئة هذين المعلمتين لعرض نموذج فارغ.

6.1.9.2. إجراء الحساب

يجب أن يحسب هذا الإجراء الضريبة المستحقة بناءً على البيانات المدخلة في النموذج ويعيد النموذج مملوءًا مسبقًا بالقيم المدخلة ومبلغ الضريبة المحسوب. تبدأ الدالة [calculImpot] المسؤولة عن هذه المهمة بالتحقق من صحة بيانات النموذج:

  • يجب أن يكون الحقل [rdMarie] موجودًا وأن تكون قيمته [yes] أو [no]
  • يجب أن يكون الحقل [txtEnfants] موجودًا وأن يكون عددًا صحيحًا >=0
  • يجب أن يكون الحقل [txtSalaire] موجودًا وأن يكون عددًا صحيحًا >=0

إذا كانت البيانات المدخلة غير صالحة، يعرض وحدة التحكم عرض [erreurs.aspx] بعد تعيين القيم المتوقعة له أولاً في السياق:

  • يتم وضع رسائل الخطأ في كائن [ArrayList]، والذي يتم إضافته بعد ذلك إلى السياق [Context.Items("errors")]
  • يتم أيضًا وضع عنوان URL لرابط العودة ونص هذا الرابط في السياق.

قبل تسليم التحكم إلى صفحة [erreurs.aspx]، التي سترسل الاستجابة إلى العميل، يتم وضع القيم التي تم إدخالها في النموذج (Request.Form) في الجلسة، مرتبطة بمفتاح "form". سيسمح ذلك لطلب لاحق باستردادها.

قد يتساءل المرء هنا عما إذا كان من المفيد التحقق من وجود الحقول [rdMarie، txtEnfants، txtSalaire] في الطلب المرسل من العميل. هذا غير ضروري إذا كنا متأكدين من أن عميلنا هو متصفح تلقى العرض [formulaire.aspx] الذي يحتوي على هذه الحقول. لا يمكننا أبدًا أن نكون متأكدين من ذلك. سنعرض مثالاً بعد قليل حيث يكون العميل هو تطبيق [curl] الذي سبق أن تعرفنا عليه. سنقوم باستعلام التطبيق دون إرسال الحقول التي يتوقعها ونرى كيف يتفاعل. هذه قاعدة تم ذكرها عدة مرات ونكررها هنا: يجب ألا يفترض التطبيق أبدًا أي شيء بشأن نوع العميل الذي يستعلم عنه. وللأمان، يجب أن يفترض أنه قد يتم استعلامه بواسطة تطبيق مبرمج قد يرسل له سلاسل معلمات غير متوقعة. ويجب أن يتصرف بشكل صحيح في جميع الحالات.

في حالتنا، تحققنا من وجود الحقول [rdMarie، txtEnfants، txtSalaire] في الطلب ولكننا لم نتحقق مما إذا كان يمكن أن يحتوي على حقول أخرى. في هذا التطبيق، سيتم تجاهلها. ومع ذلك، وكإجراء أمني مرة أخرى، سيكون من المفيد تسجيل هذا النوع من الطلبات في ملف سجل وإرسال تنبيه إلى مسؤول التطبيق حتى يكون على علم بأن التطبيق يتلقى طلبات "غريبة". من خلال تحليل هذه الطلبات في ملف السجل، يمكنهم اكتشاف هجوم محتمل على التطبيق ثم اتخاذ الإجراءات اللازمة لحمايته.

إذا كانت البيانات المتوقعة صحيحة، يبدأ وحدة التحكم في حساب الضريبة باستخدام كائن [tax] المخزن في التطبيق. ثم يقوم بتخزين معلومتين متوقعتين من قبل عرض [form.aspx] في السياق:

  • Context.Items("formulaire"): قاموس [NameValueCollection] يحتوي على قيم حقول HTML [rdmarie,txtEnfants,txtSalaire]، هنا [Request.Form)]، أي القيم التي تم إدخالها مسبقًا في النموذج
  • Context.Items("txtImpot"): قيمة الضريبة التي تم حسابها للتو

قد يتساءل القارئ اليقظ أثناء قراءة ما سبق: بما أن كائن [impot] الذي تم إنشاؤه عند بدء تشغيل التطبيق مشترك بين جميع الطلبات، فهل يمكن أن تحدث تعارضات في الوصول تؤدي إلى تلف بيانات كائن [impot]؟ للإجابة على هذا السؤال، نحتاج إلى العودة إلى كود فئة [impot]. تستدعي الطلبات طريقة [impot].calculateTax للحصول على الضريبة المستحقة. لذا، هذا هو الكود الذي نحتاج إلى فحصه:

        Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Long) As Long
            ' calculating the number of shares
            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
            ' calculation of taxable income & family quota
            Dim revenu As Decimal = 0.72D * salaire
            Dim QF As Decimal = revenu / nbParts
            ' tAX CALCULATION
            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))
            ' wait 10 seconds
            Thread.Sleep(10000)
            Return impot

يتم مقاطعة الخيط 1، بعد حساب قيمة [impot1] للمتغير المحلي [impot]. ثم يتم تنفيذ الخيط 2 وحساب قيمة جديدة [impot2] لنفس المتغير [impot] قبل مقاطعته. يستعيد الخيط 1 السيطرة. ماذا يجد في المتغير المحلي [impot]؟ نظرًا لأن هذا المتغير محلي بالنسبة لطريقة ما، يتم تخزينه في بنية ذاكرة تسمى المكدس. هذا المكدس هو جزء من سياق الخيط، والذي يتم حفظه عند تعليق الخيط. عند بدء تشغيل الخيط 2، يتم إعداد سياقه بمكدس جديد وبالتالي متغير محلي جديد [impot]. عندما يتم تعليق الخيط 2 بدوره، سيتم حفظ سياقه أيضًا. عند إعادة تشغيل الخيط 1، يتم استعادة سياقه، بما في ذلك المكدس الخاص به. ثم يسترد المتغير المحلي [impot] الخاص به وليس المتغير الخاص بالخيط 2. لذلك نحن في موقف لا يوجد فيه تعارض في الوصول بين الطلبات. أكدت الاختبارات التي أجريت مع التوقف المؤقت لمدة 10 ثوانٍ الموصوف أعلاه أن الطلبات المتزامنة أسفرت بالفعل عن النتيجة المتوقعة.

6.1.9.3. إجراء العودة

يتوافق هذا الإجراء مع النقر فوق الارتباط [العودة إلى النموذج] في عرض [errors.aspx] للعودة إلى عرض [form.aspx]، الذي تم ملؤه مسبقًا بالقيم التي تم إدخالها وحفظها سابقًا في الجلسة. تسترد الدالة [returnForm] هذه المعلومات. يتم تهيئة المعلمتين المتوقعين في عرض [form.aspx]:

  • Context.Items("form") بالقيم التي تم إدخالها وحفظها مسبقًا في الجلسة
  • 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

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

Image

ثم ندخل بيانات غير صحيحة:

Image

يؤدي النقر على زر [Calculate] إلى الحصول على الاستجابة التالية:

Image

يؤدي النقر على رابط [Back to Form] إلى إعادتنا إلى النموذج كما كان عند إرساله:

Image

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

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>

يمكننا أن نرى أن تطبيق الويب أعاد عرض [errors] مع ثلاث رسائل خطأ للمعلمات الثلاثة المفقودة. الآن دعونا نرسل بعض المعلمات غير الصحيحة:

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>

لقد نجحنا في استرداد الضريبة المستحقة: 4,300 يورو. الدرس المستفاد من هذا المثال هو أنه يجب ألا ننخدع بحقيقة أننا نكتب تطبيق ويب مخصص للعملاء الذين يستخدمون المتصفحات. تطبيق الويب هو خدمة 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

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

6.2. المثال 2

6.2.1. المشكلة

هنا، نقترح معالجة نفس المشكلة السابقة ولكن عن طريق تعديل مصدر البيانات للكائن [tax] الذي أنشأه تطبيق الويب. في الإصدار السابق، كان مصدر البيانات يوفر قيمًا من مصفوفات مبرمجة بشكل ثابت في الكود. هذه المرة، سيسترد مصدر البيانات الجديد هذه القيم من مصدر بيانات ODBC مرتبط بقاعدة بيانات MySQL.

6.2.2. مصدر بيانات ODBC

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

Image

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

هناك العديد من قواعد البيانات المتاحة لمنصات Windows. للوصول إليها، تستخدم التطبيقات برامج تسمى برامج التشغيل.

Image

في الرسم البياني أعلاه، يحتوي برنامج التشغيل على واجهتين:

  • واجهة I1 المعروضة للتطبيق
  • واجهة I2 لقاعدة البيانات

لمنع الحاجة إلى إعادة كتابة تطبيق مكتوب لقاعدة البيانات B1 في حالة الترحيل إلى قاعدة بيانات مختلفة B2، تم بذل جهود لتوحيد واجهة I1. إذا تم استخدام قواعد بيانات تستخدم برامج تشغيل "موحدة"، فسيتم تزويد قاعدة البيانات B1 ببرنامج التشغيل P1، وقاعدة البيانات B2 ببرنامج التشغيل P2، وستكون واجهة I1 لهذين البرنامجين متطابقة. وبالتالي، لن تكون هناك حاجة إلى إعادة كتابة التطبيق. على سبيل المثال، يمكنك ترحيل قاعدة بيانات Access إلى قاعدة بيانات MySQL دون تغيير التطبيق.

هناك نوعان من برامج التشغيل المعيارية:

  • برامج تشغيل ODBC (اتصال قاعدة البيانات المفتوحة)
  • برامج تشغيل OLE DB (ربط الكائنات وتضمين قاعدة البيانات)

توفر برامج تشغيل ODBC الوصول إلى قواعد البيانات. مصادر البيانات لبرامج تشغيل OLE DB أكثر تنوعًا: قواعد البيانات، وأنظمة البريد الإلكتروني، والدلائل، وما إلى ذلك. لا توجد حدود. يمكن أن يكون أي مصدر بيانات موضوعًا لبرنامج تشغيل OLE DB إذا قرر المورد ذلك. الفائدة واضحة وهامة: لديك وصول موحد إلى مجموعة متنوعة من البيانات.

تأتي منصة .NET 1.1 مع ثلاثة أنواع من فئات الوصول إلى البيانات:

  1. فئات SQL Server.NET، للوصول إلى قواعد بيانات Microsoft SQL Server
  2. فئات Ole Db.NET، للوصول إلى قواعد البيانات من أنظمة إدارة قواعد البيانات (DBMS) التي توفر برنامج تشغيل OLE DB
  3. فئات ODBC.NET، للوصول إلى قواعد البيانات من أنظمة إدارة قواعد البيانات (DBMS) التي توفر برنامج تشغيل ODBC

يحتوي نظام إدارة قواعد البيانات MySQL منذ فترة طويلة على برنامج تشغيل ODBC. وهذا هو البرنامج الذي سنستخدمه الآن. في Windows، نختار [قائمة ابدأ/لوحة التحكم/أدوات إدارية/مصادر ODBC 32 بت]. قد يختلف هذا المسار قليلاً حسب إصدار Windows. يؤدي هذا إلى فتح التطبيق التالي، الذي سيسمح لنا بإنشاء مصدر ODBC الخاص بنا:

Image

سنقوم بإنشاء مصدر بيانات النظام، أي مصدر بيانات يمكن لأي مستخدم للجهاز استخدامه. لذلك، نختار أعلاه علامة التبويب [مصدر بيانات النظام]. تحتوي الصفحة المعروضة على زر [إضافة]، والذي نستخدمه لإنشاء مصدر بيانات ODBC جديد:

Image

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

Image

ستحتاج إلى تقديم المعلومات التالية:

اسم مصدر البيانات
الاسم الذي سيحدد مصدر بيانات ODBC. سيتمكن أي تطبيق Windows من الوصول إلى المصدر باستخدام هذا الاسم
الوصف
أي نص يصف مصدر البيانات
اسم المضيف
اسم الجهاز الذي يستضيف نظام إدارة قواعد البيانات MySQL. هنا، هو الجهاز المحلي. يمكن أن يكون جهازًا بعيدًا. وهذا سيسمح لتطبيق Windows بالوصول إلى قاعدة بيانات بعيدة دون أي ترميز خاص. هذه ميزة رئيسية لمصدر ODBC.
اسم قاعدة البيانات
يمكن لنظام إدارة قواعد البيانات MySQL إدارة قواعد بيانات متعددة. هنا، نحدد القاعدة التي نريد إدارتها: dbimpots
المستخدم
اسم المستخدم المسجل في نظام إدارة قواعد البيانات MySQL. سيتم الوصول إلى مصدر البيانات باسم هذا المستخدم. هنا: admimpots
كلمة
كلمة مرور هذا المستخدم. هنا: mdpimpots
المنفذ
منفذ عمل نظام إدارة قواعد البيانات MySQL. بشكل افتراضي، هذا هو المنفذ 3306. لم نقم بتغييره

بمجرد الانتهاء من ذلك، نختبر صحة إعدادات الاتصال باستخدام الزر [اختبار مصدر البيانات]:

Image

وبمجرد الانتهاء من ذلك، نكون قد تأكدنا من صحة مصدر بيانات ODBC الخاص بنا. ويمكننا الآن استخدامه. نضغط على [موافق] عدة مرات حسب الحاجة للخروج من معالج ODBC.

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

Image

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

Image

المعلومات المطلوب تقديمها هي كما يلي:

اسم مصدر البيانات
الاسم الذي سيحدد مصدر بيانات ODBC. سيتمكن أي تطبيق Windows من الوصول إلى المصدر باستخدام هذا الاسم
الوصف
أي نص يصف مصدر البيانات
قاعدة البيانات
الاسم الكامل لملف 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
 
        ' data access method
        Public MustOverride Function getData() As Object()
 
        ' data verification method
        Protected Function checkData() As Integer
            ' verifies acquired data
...
        End Function
 
        ' checks the validity of an array's contents
        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]، التي ستسترد البيانات (limits، 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
 
        ' instance variables
        Protected DSNimpots As String
 
        ' manufacturer
        Public Sub New(ByVal DSNimpots As String)
            ' we note the three pieces of information
            Me.DSNimpots = DSNimpots
        End Sub
 
        Public Overrides Function getdata() As Object()
            ' initializes the three limit tables, coeffr, coeffn from
            ' the contents of the [impots] table in the ODBC DSNimpots database
            ' limites, coeffr, coeffn are the three columns of this table
            ' can launch various exceptions
 
            Dim connectString As String = "DSN=" + DSNimpots + ";"         ' base connection chain
            Dim impotsConn As OdbcConnection = Nothing         ' the connection
            Dim sqlCommand As OdbcCommand = Nothing         ' the SQL command
            ' the SELECT query
            Dim selectCommand As String = "select limites,coeffr,coeffn from impots"
            ' tables to retrieve data
            Dim aLimites As New ArrayList
            Dim aCoeffR As New ArrayList
            Dim aCoeffN As New ArrayList
            Try
                ' attempt to access the database
                impotsConn = New OdbcConnection(connectString)
                impotsConn.Open()
                ' create a command object
                sqlCommand = New OdbcCommand(selectCommand, impotsConn)
                ' execute the query
                Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader()
                ' Using the recovered table
                While myReader.Read()
                    ' the data of the current line are put in the tables
                    aLimites.Add(myReader("limites"))
                    aCoeffR.Add(myReader("coeffr"))
                    aCoeffN.Add(myReader("coeffn"))
                End While
                ' freeing up resources
                myReader.Close()
                impotsConn.Close()
            Catch e As Exception
                Throw New Exception("Erreur d'accès à la base de données (" + e.Message + ")")
            End Try
            ' dynamic tables are placed in static tables
            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
            ' verify acquired data
            Dim erreur As Integer = checkData()
            ' if invalid data, throws an exception
            If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
            ' otherwise we return the three tables
            Return New Object() {limites, coeffr, coeffn}
        End Function
    End Class
End Namespace

دعونا نلقي نظرة على المنشئ:


        ' manufacturer
        Public Sub New(ByVal DSNimpots As String)
            ' we note the three pieces of information
            Me.DSNimpots = DSNimpots
        End Sub

يأخذ كمعلمة اسم مصدر بيانات ODBC الذي يحتوي على البيانات المراد استردادها. يقوم المنشئ ببساطة بتخزين هذا الاسم. تتولى طريقة [getData] مسؤولية قراءة البيانات من الجدول [impots] ووضعها في ثلاثة مصفوفات (limites، coeffr، coeffn). دعونا نعلق على الكود:

  • تم تعريف المعلمات للاتصال بمصدر بيانات ODBC، ولكن الاتصال لم يتم فتحه
            ' base connection chain
            Dim connectString As String = "DSN=" + DSNimpots + ";"
            ' a database connection object is created - this connection is not open
            Dim impotsConn As OdbcConnection = New OdbcConnection(connectString)
  • تحديد ثلاثة كائنات [ArrayList] لاسترداد البيانات من الجدول [impots]:

            ' tables to retrieve data
            Dim aLimites As New ArrayList
            Dim aCoeffR As New ArrayList
            Dim aCoeffN As New ArrayList
  • يتم تضمين جميع أكواد الوصول إلى قاعدة البيانات في كتلة try/catch لمعالجة أي أخطاء في الوصول. نفتح الاتصال بقاعدة البيانات:

                ' on tente d'accéder à la base de données
                impotsConn = New OdbcConnection(connectString)
                impotsConn.Open()
  • نقوم بتنفيذ الأمر [select] على الاتصال المفتوح. نحصل على كائن [OdbcDataReader] الذي سيسمح لنا بالتكرار عبر صفوف الجدول الناتج عن الأمر select:

                ' on crée un objet command
                Dim sqlCommand As OdbcCommand = New OdbcCommand(selectCommand, impotsConn)
                ' on exécute la requête
                Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader()
  • نقوم بالتكرار عبر جدول النتائج، صفًا تلو الآخر. للقيام بذلك، نستخدم طريقة [Read] الخاصة بكائن [OdbcDataReader] الذي تم الحصول عليه مسبقًا. تقوم هذه الطريقة بأمرين:
    • تنتقل إلى الصف التالي في الجدول. في البداية، يتم وضع المؤشر قبل الصف الأول
    • تُرجع القيمة المنطقية [true] إذا تمكنت من التقدم، و[false] في حالة عدم تمكنها من ذلك، حيث تشير الحالة الأخيرة إلى أنه تمت معالجة جميع الصفوف.

يتم الحصول على أعمدة الصف الحالي لكائن [OdbcDataReader] عبر OdbcDataReader. وهذا يعيد كائنًا يمثل قيمة العمود. نقوم بالتكرار عبر الجدول بأكمله لملء محتوياته في كائنات [ArrayList] الثلاثة:


                ' Exploitation de la table récupérée
                While myReader.Read()
                    ' les données de la ligne courante sont mis dans les tableaux
                    aLimites.Add(myReader("limites"))
                    aCoeffR.Add(myReader("coeffr"))
                    aCoeffN.Add(myReader("coeffn"))
  • بمجرد الانتهاء من ذلك، نقوم بتحرير الموارد المرتبطة بالاتصال:
                ' libération des ressources
                myReader.Close()
                impotsConn.Close()
  • يتم نقل محتويات الكائنات الثلاثة [ArrayList] إلى ثلاثة مصفوفات قياسية:

            ' dynamic tables are placed in static tables
            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]:
            ' verify acquired data
            Dim erreur As Integer = checkData()
            ' if invalid data, throws an exception
            If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
            ' otherwise we return the three tables
            Return New Object() {limites, coeffr, coeffn}

6.2.4. اختبارات لفئة الوصول إلى البيانات

قد يبدو برنامج الاختبار كما يلي:

Option Explicit On 
Option Strict On

' namespaces
Imports System
Imports Microsoft.VisualBasic

Namespace st.istia.univangers.fr

    ' test pg
    Module testimpots
        Sub Main(ByVal arguments() As String)
            ' interactive tax calculator
            ' the user enters three data points on the keyboard: married nbEnfants salary
            ' the program then displays the tax payable
            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"

            ' checking program parameters
            If arguments.Length <> 1 Then
                ' error msg
                Console.Error.WriteLine(syntaxe1)
                ' end
                Environment.Exit(1)
            End If
            ' retrieve the arguments
            Dim DSNimpots As String = arguments(0)

            ' tax object creation
            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

            ' infinite loop
            While True
                ' initially no errors
                Dim erreur As Boolean = False

                ' tax calculation parameters are requested
                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()

                ' anything to do?
                If paramètres Is Nothing Or paramètres = "" Then
                    Exit While
                End If

                ' check the number of arguments in the input line
                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
                    ' checking the validity of parameters
                    ' married
                    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
                    ' salary
                    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
                    ' parameters are correct - tax is calculated
                    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 المراد استخدامه

يتم إجراء حساب الضريبة باستخدام كائن من النوع [tax] يتم إنشاؤه عند تشغيل التطبيق:


            ' tax object creation
            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

يتم تشغيل برنامج الاختبار أولاً باستخدام مصدر بيانات MySQL ODBC:

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)
        ' create an impot object
        Dim objImpot As impot
        Try
            objImpot = New impot(New impotsODBC(ConfigurationSettings.AppSettings("DSNimpots")))
            ' put the object in the application
            Application("objImpot") = objImpot
            ' no error
            Application("erreur") = False
        Catch ex As Exception
            'there has been an error, we note it in the application
            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
        ' first of all, we check whether the application has initialized correctly
        If CType(Application("erreur"), Boolean) Then
            ' redirects to error page
            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
        ' retrieve the action to be performed
...

6.2.7. ملخص التغييرات

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

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

تم إجراء أعمال التعديل بشكل أساسي في 1، أي خارج تطبيق الويب. وقد أصبح ذلك ممكنًا بفضل بنية MVC للتطبيق، التي تفصل وحدة التحكم عن فئات الأعمال. وهذا هو جوهر هذه البنية. ويمكن إثبات أنه باستخدام ملف [web.config] مناسب، كان من الممكن تجنب أي تعديل على وحدة التحكم في التطبيق. من الممكن تحديد اسم فئة الوصول إلى البيانات التي سيتم إنشاء مثيل لها ديناميكيًا في [web.config] —مثل —بالإضافة إلى المعلمات المختلفة المطلوبة لإنشاء هذا المثيل. باستخدام هذه المعلومات، يمكن لـ [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 عن طريق إيقاف نظام إدارة قواعد البيانات 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]، التي ستسترد البيانات (limits، 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
 
        ' instance variables
        Protected chaineConnexion As String
 
        ' manufacturer
        Public Sub New(ByVal chaineConnexion As String)
            ' we note the three pieces of information
            Me.chaineConnexion = chaineConnexion
        End Sub
 
        Public Overrides Function getData() As Object()
            ' initializes the three limit tables, coeffr, coeffn from
            ' the contents of the [impots] table in the OLEDB [chaineConnexion] database
            ' limits, coeffr, coeffn are the three columns of this table
            ' can launch various exceptions
 
            ' create a DataAdapter object to read data from source OLEDB
            Dim adaptateur As New OleDbDataAdapter("select limites,coeffr,coeffn from impots", chaineConnexion)
            ' create a memory image of the select result
            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
            ' retrieve the contents of the impots table
            Dim lignesImpots As DataRowCollection = contenu.Rows
            ' dimensioning of reception panels
            Me.limites = New Decimal(lignesImpots.Count - 1) {}
            Me.coeffr = New Decimal(lignesImpots.Count - 1) {}
            Me.coeffn = New Decimal(lignesImpots.Count - 1) {}
            ' transfer the contents of the impots table to the tables
            Dim i As Integer
            Dim ligne As DataRow
            Try
                For i = 0 To lignesImpots.Count - 1
                    ' table line i
                    ligne = lignesImpots.Item(i)
                    ' retrieve the contents of the line
                    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
            ' verify acquired data
            Dim erreur As Integer = checkData()
            ' if invalid data, throws an exception
            If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
            ' otherwise we return the three tables
            Return New Object() {limites, coeffr, coeffn}
        End Function
    End Class
End Namespace

دعونا نلقي نظرة على المنشئ:


        ' manufacturer
        Public Sub New(ByVal chaineConnexion As String)
            ' we note the three pieces of information
            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]. يتطلب منشئ [DataAdapter] أيضًا سلسلة الاتصال التي سيستخدمها للاتصال بمصدر OLEDB

            ' on crée un objet DataAdapter pour lire les données de la source OLEDB
            Dim adaptateur As New OleDbDataAdapter("select limites,coeffr,coeffn from impots", chaineConnexion)
  • نقوم بتنفيذ الأمر [select] باستخدام طريقة [Fill] الخاصة بكائن [DataAdapter]. يتم تحميل نتيجة [select] في كائن [DataTable] تم إنشاؤه لهذا الغرض. كائن [DataTable] هو تمثيل في الذاكرة لجدول قاعدة البيانات، أي مجموعة من الصفوف والأعمدة. نتعامل مع استثناء قد يحدث إذا كان، على سبيل المثال، سلسلة الاتصال غير صحيحة.

            ' on crée une image en mémoire du résultat du select
            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
  • في [content]، لدينا جدول [taxes] الذي تم إرجاعه بواسطة عبارة [select]. كائن [DataTable] هو جدول، أي مجموعة من الصفوف. يمكن الوصول إليها عبر خاصية [rows] في [DataTable]:

            ' retrieve the contents of the impots table
            Dim lignesImpots As DataRowCollection = contenu.Rows
  • كل عنصر في مجموعة [taxRows] هو كائن [DataRow] يمثل صفًا في الجدول. يحتوي هذا الصف على أعمدة يمكن الوصول إليها عبر كائن [DataRow] من خلال خاصية [Item] الخاصة به. [DataRow].[Item(i)] هو العمود رقم i في [DataRow]. من خلال التكرار عبر مجموعة الصفوف (مجموعة DataRows في taxRows) ومجموعة الأعمدة لكل صف، يمكننا استرداد الجدول بأكمله:
            ' dimensioning of reception panels
            Me.limites = New Decimal(lignesImpots.Count - 1) {}
            Me.coeffr = New Decimal(lignesImpots.Count - 1) {}
            Me.coeffn = New Decimal(lignesImpots.Count - 1) {}
            ' transfer the contents of the impots table to the tables
            Dim i As Integer
            Dim ligne As DataRow
            Try
                For i = 0 To lignesImpots.Count - 1
                    ' table line i
                    ligne = lignesImpots.Item(i)
                    ' retrieve the contents of the line
                    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
  • بمجرد تحميل البيانات من جدول [taxes] إلى المصفوفات الثلاثة، كل ما يتبقى هو التحقق من محتوياتها باستخدام طريقة [checkData] الخاصة بالفئة الأساسية [taxData]:
            ' verify acquired data
            Dim erreur As Integer = checkData()
            ' if invalid data, throws an exception
            If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
            ' otherwise we return the three tables
            Return New Object() {limites, coeffr, coeffn}

6.3.4. اختبارات لفئة الوصول إلى البيانات

قد يبدو برنامج الاختبار كما يلي:

Option Explicit On 
Option Strict On

' namespaces
Imports System
Imports Microsoft.VisualBasic

Namespace st.istia.univangers.fr

    ' test pg
    Module testimpots
        Sub Main(ByVal arguments() As String)
            ' interactive tax calculator
            ' the user enters three data points on the keyboard: married nbEnfants salary
            ' the program then displays the tax payable
            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"

            ' checking program parameters
            If arguments.Length <> 1 Then
                ' error msg
                Console.Error.WriteLine(syntaxe1)
                ' end
                Environment.Exit(1)
            End If
            ' retrieve the arguments
            Dim chemin As String = arguments(0)
            ' prepare the connection chain
            Dim chaineConnexion As String = "Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=" + chemin

            ' tax object creation
            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

            ' infinite loop
            While True
                ' initially no errors
                Dim erreur As Boolean = False

                ' tax calculation parameters are requested
                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()

                ' anything to do?
                If paramètres Is Nothing Or paramètres = "" Then
                    Exit While
                End If

                ' check the number of arguments in the input line
                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
                    ' checking the validity of parameters
                    ' married
                    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
                    ' salary
                    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
                    ' parameters are correct - tax is calculated
                    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 المراد استخدامه

يتم حساب الضريبة باستخدام كائن من النوع [tax] يتم إنشاؤه عند تشغيل التطبيق:


            ' retrieve the arguments
            Dim chemin As String = arguments(0)
            ' prepare the connection chain
            Dim chaineConnexion As String = "Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=" + chemin
 
            ' tax object creation
            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)
        ' create an impot object
        Dim objImpot As impot
        Try
            objImpot = New impot(New impotsOLEDB(ConfigurationSettings.AppSettings("chaineConnexion")))
            ' put the object in the application
            Application("objImpot") = objImpot
            ' no error
            Application("erreur") = False
        Catch ex As Exception
            'there has been an error, we note it in the application
            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] في مكان واحد: إنشاء كائن [import]
  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

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

Image

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


<%@ page src="formulaire.aspx.vb" inherits="formulaire" AutoEventWireup="false"%>
<html>
    <head>
        <title>Impôt</title>
        <script language="javascript">
        function calculer(){
          // vérification des paramètres avant de les envoyer au serveur
        with(document.frmImpots){
          //nbre d'enfants
          champs=/^\s*(\d+)\s*$/.exec(txtEnfants.value);
          if(champs==null){
            // le modéle n'est pas vérifié
            alert("Le nombre d'enfants n'a pas été donné ou est incorrect");
            txtEnfants.focus();
            return;
          }//if
          //salaire
          champs=/^\s*(\d+)\s*$/.exec(txtSalaire.value);
          if(champs==null){
            // le modéle n'est pas vérifié
            alert("Le salaire n'a pas été donné ou est incorrect");
            txtSalaire.focus();
            return;
          }//if
          // c'est bon - on envoie le formulaire au serveur
          submit();
        }//with
      }//calculer  
        </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>

الحقول الديناميكية في الصفحة هي نفسها الموجودة في الإصدارات السابقة. تمت إزالة الحقل الديناميكي الخاص بمبلغ الضريبة. لم يعد الزر [Calculate] زر [submit]. إنه [button]، وعند النقر عليه، يتم تنفيذ دالة JavaScript [calculate()]:


                <INPUT type="button" value="Calculer" onclick="calculer()">

لقد أطلقنا على النموذج اسم [frmImpots] حتى نتمكن من الإشارة إليه في البرنامج النصي [calculer]:


        <form name="frmImpots" method="post" action="main.aspx?action=calcul">

تستخدم دالة JavaScript [calculate] التعبيرات العادية للتحقق من صحة الحقول في النموذجين [document.frmImpots.txtEnfants] و [document.frmImpots.txtSalaire]. إذا كانت القيم المدخلة صحيحة، يتم إرسالها إلى الخادم عبر [document.frmImpots.submit()].

تحصل صفحة العرض على حقولها الديناميكية من وحدة التحكم التالية [formulaire.aspx.vb]:


Imports System.Collections.Specialized
 
Public Class formulaire
    Inherits System.Web.UI.Page
 
    ' page fields
    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
        ' we retrieve the previous request in the
        Dim form As NameValueCollection = Context.Items("formulaire")
        ' prepare the page to be displayed
        ' radio buttons
        rdouichecked = ""
        rdnonchecked = "checked"
        If form("rdMarie").ToString = "oui" Then
            rdouichecked = "checked"
            rdnonchecked = ""
        End If
        ' the rest
        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 للرابط
link
نص الرابط

يتم إنشاؤها بواسطة وحدة التحكم [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
        'simulations are retrieved from the context
        Dim simulations As ArrayList = CType(context.Items("simulations"), ArrayList)
        ' each simulation is an array of 4 string elements
        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
        ' recover the other elements of the context
        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("link")
نص الرابط

6.4.4. وحدات التحكم [global.asax، main.aspx]

دعونا نستعرض بنية MVC لتطبيقنا:

Image

يجب أن يتعامل وحدة التحكم [main.aspx] مع ثلاثة إجراءات:

  • init: تتوافق مع الطلب الأول للعميل. تعرض وحدة التحكم طريقة العرض [form.aspx]
  • calcul: يتوافق مع طلب حساب الضريبة. إذا كانت البيانات في نموذج الإدخال صحيحة، يتم حساب الضريبة باستخدام فئة الأعمال [impotsODBC]. يعرض وحدة التحكم العرض [simulations.aspx] للعميل مع نتيجة المحاكاة الحالية بالإضافة إلى جميع المحاكاة السابقة. إذا كانت البيانات في نموذج الإدخال غير صحيحة، يعرض وحدة التحكم العرض [erreurs.aspx] مع قائمة الأخطاء ورابط للعودة إلى النموذج.
  • return: يتوافق مع العودة إلى النموذج بعد حدوث خطأ. يعرض وحدة التحكم عرض [form.aspx] كما تم التحقق من صحته قبل حدوث الخطأ.

في هذا الإصدار الجديد، لم يتغير سوى الإجراء [calcul]. في الواقع، إذا كانت البيانات صالحة، فيجب أن تؤدي إلى عرض [simulations.aspx]، في حين أنها كانت تؤدي سابقًا إلى عرض [form.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
        ' first of all, we check whether the application has initialized correctly
...
        ' execute the action
        Select Case action
            Case "init"
                ' init application
                initAppli()
            Case "calcul"
                ' tax calculation
                calculImpot()
            Case "retour"
                ' back to form
                retourFormulaire()
            Case "effacer"
                ' init application
                initAppli()
            Case Else
                ' unknown action = init
                initAppli()
        End Select
    End Sub
 
...
    Private Sub calculImpot()
        ' save entries
        Session.Item("formulaire") = Request.Form
        ' check the validity of the data entered
        Dim erreurs As ArrayList = checkData()
        ' if there are errors, we report them
        If erreurs.Count <> 0 Then
            ' prepare the error page
            context.Items("href") = "main.aspx?action=retour"
            context.Items("lien") = "Retour au formulaire"
            context.Items("erreurs") = erreurs
            Server.Transfer("erreurs.aspx")
        End If
        ' no errors here - the tax is calculated
        Dim impot As Long = CType(Application("objImpot"), impot).calculer( _
        Request.Form("rdMarie") = "oui", _
        CType(Request.Form("txtEnfants"), Integer), _
        CType(Request.Form("txtSalaire"), Long))
        ' the result is added to existing simulations
        Dim simulations As ArrayList
        If Not Session.Item("simulations") Is Nothing Then
            simulations = CType(Session.Item("simulations"), ArrayList)
        Else
            simulations = New ArrayList
        End If
        ' add current simulation
        Dim simulation() As String = New String() {Request.Form("rdMarie").ToString, _
        Request.Form("txtEnfants").ToString, Request.Form("txtSalaire").ToString, _
        impot.ToString}
        simulations.Add(simulation)
        ' put simulations in session and context
        context.Items("simulations") = simulations
        Session.Item("simulations") = simulations
        ' the result page is displayed
        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]، فإننا نعود إلى النموذج عبر الرابط [العودة إلى النموذج]. لاستعادة النموذج بشكل صحيح، يجب أن تكون قيمه قد تم حفظها في الجلسة مسبقًا.
  • إذا كانت البيانات المدخلة صحيحة، تضيف الوظيفة المحاكاة الحالية (الحالة الاجتماعية، عدد الأبناء، الراتب، الضريبة) إلى قائمة المحاكاة. وتوجد هذه القائمة في الجلسة المرتبطة بمفتاح "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. الخلاصة

أظهرت الأمثلة السابقة، في سيناريو ملموس، الآليات المستخدمة بشكل شائع في تطوير الويب. لقد استخدمنا بنية MVC باستمرار لقيمتها التعليمية. كان بإمكاننا التعامل مع هذه الأمثلة نفسها بطريقة مختلفة وربما أبسط بدون هذه البنية. ومع ذلك، فإنها توفر مزايا كبيرة بمجرد أن يصبح التطبيق معقدًا إلى حد ما مع وجود صفحات متعددة.

يمكننا مواصلة أمثلةنا بطرق مختلفة. فيما يلي بعض منها:

  • قد يرغب المستخدم في حفظ محاكاته بمرور الوقت. يمكنه تشغيل المحاكاة في اليوم D واسترجاعها في اليوم D+3، على سبيل المثال. أحد الحلول الممكنة لهذه المشكلة هو استخدام ملفات تعريف الارتباط. نعلم أن رمز الجلسة بين الخادم والعميل يتم إرساله عبر هذه الآلية. يمكننا أيضًا استخدام هذه الآلية لنقل المحاكاة بين العميل والخادم.
    • في نفس الوقت الذي يرسل فيه الخادم الصفحة التي تحتوي على نتائج المحاكاة، يرسل ملف تعريف ارتباط في رؤوس HTTP الخاصة به يحتوي على سلسلة تمثل المحاكاة. نظرًا لوجودها في كائن [ArrayList]، يجب تحويل هذا الكائن إلى [String]. سيقوم الخادم بتعيين مدة صلاحية لملف تعريف الارتباط، على سبيل المثال 30 يومًا.
    • يخزن متصفح العميل ملفات تعريف الارتباط المستلمة في ملف ويقوم بإرسالها مرة أخرى في كل مرة يرسل فيها طلبًا إلى الخادم الذي أرسلها، بشرط أن تكون لا تزال صالحة (لم تتجاوز مدة صلاحيتها). سيتلقى الخادم سلسلة أحرف [String] للمحاكاة، والتي يجب عليه تحويلها إلى كائن [ArrayList].

تتم إدارة ملفات تعريف الارتباط بواسطة [Response.Cookies] عند إرسالها إلى العميل وبواسطة [Request.Cookies] عند استلامها على الخادم.

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

ماذا يحدث، على سبيل المثال، إذا طلب المستخدم مباشرةً عنوان URL [http://localhost/impots4/formulaire.aspx]؟ هذا السيناريو غير مرجح لأن المستخدم لا يعرف هذا العنوان. ومع ذلك، يجب أخذه في الاعتبار. يمكن معالجته بواسطة وحدة التحكم في التطبيق [global.asax]، التي تعالج جميع الطلبات الموجهة إلى التطبيق. وبالتالي يمكنها التحقق من أن المورد المطلوب هو بالفعل [main.aspx].

السيناريو الأكثر احتمالاً هو أن المستخدم لا يستخدم الإجراءات المتاحة على الصفحة التي أرسلها إليه الخادم. على سبيل المثال، ماذا يحدث إذا طلب المستخدم عنوان URL [http://localhost/impots4/main.aspx?action=retour] مباشرةً دون ملء النموذج أولاً؟ دعونا نجرب ذلك. نحصل على الاستجابة التالية:

Image

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

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