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، فسيكون الصف الذي تم العثور عليه هو
تكون الضريبة I عندئذٍ مساوية لـ 0.0683*R - 291.09*nbParts. إذا كان QF بحيث لا يتم استيفاء الشرط QF<=field1 أبدًا، يتم استخدام المعاملات من الصف الأخير. هنا:
مما يعطي الضريبة I = 0.4809*R - 9505.54*nbParts.
6.1.2. هيكل MVC للتطبيق
ستكون بنية MVC للتطبيق كما يلي:

ستتولى صفحة [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) قد تم التحقق منها | |
قيمة منطقية تشير إلى ما إذا كانت البيانات (الحدود، 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>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] لأول مرة:

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

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

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

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

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

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>
الحقول الديناميكية في هذه الصفحة هي كما يلي:
"checked" إذا كان يجب تحديد مربع الاختيار [yes]، و"" في الحالات الأخرى | |
نفس الشيء بالنسبة لمربع الاختيار [لا] | |
القيمة التي سيتم وضعها في حقل الإدخال [txtChildren] | |
القيمة التي سيتم إدخالها في حقل الإدخال [txtSalary] | |
القيمة التي سيتم إدخالها في حقل الإدخال [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>
تحتوي الصفحة على ثلاثة حقول ديناميكية:
كود HTML لقائمة الأخطاء | |
عنوان URL للرابط | |
نص الرابط |
يتم حساب هذه الحقول بواسطة وحدة التحكم الخاصة بالصفحة في [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
يسترد وحدة التحكم في الصفحة المعلومات التي وضعتها وحدة التحكم في التطبيق في سياق الصفحة:
كائن ArrayList يحتوي على قائمة رسائل الخطأ المراد عرضها | |
عنوان URL للرابط | |
نص الرابط |
الآن بعد أن عرفنا ما يراه مستخدم التطبيق، يمكننا الانتقال إلى كتابة وحدة التحكم الخاصة بالتطبيق.
6.1.9. وحدات التحكم [global.asax، main.aspx]
دعونا نراجع بنية MVC لتطبيقنا:

منطق التطبيقالعميل
يجب أن يتعامل وحدة التحكم [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]
[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]
[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>.

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

نملأ النموذج:

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

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

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

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

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

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 تطوير مثل هذا التطبيق. وتتخذ الشكل التالي:

يظل كتلة [فئات الأعمال، فئات الوصول إلى البيانات] دون تغيير. وذلك لأنها مكونة لا تعتمد على نوع العميل. تتغير كتلة [وحدة التحكم] بشكل طفيف فقط، ولكن يجب أن تأخذ في الاعتبار معلمة جديدة في الطلب: المعلمة [client]، التي تشير إلى نوع العميل الذي يتم التعامل معه. يجب أن يقوم كتلة [العروض] بإنشاء عروض لكل نوع من أنواع العملاء. قد يكون من المفيد مراعاة وجود المعلمة [العميل] في الاستعلام منذ مرحلة تصميم التطبيق، حتى لو كان الهدف قصير أو متوسط المدى يقتصر على المتصفحات. إذا احتاج التطبيق لاحقًا إلى دعم نوع جديد من العملاء، فلن يتعين سوى كتابة العروض المصممة خصيصًا لهذا العميل.
6.2. المثال 2
6.2.1. المشكلة
هنا، نقترح معالجة نفس المشكلة السابقة ولكن عن طريق تعديل مصدر البيانات للكائن [tax] الذي أنشأه تطبيق الويب. في الإصدار السابق، كان مصدر البيانات يوفر قيمًا من مصفوفات مبرمجة بشكل ثابت في الكود. هذه المرة، سيسترد مصدر البيانات الجديد هذه القيم من مصدر بيانات ODBC مرتبط بقاعدة بيانات MySQL.
6.2.2. مصدر بيانات ODBC
ستكون البيانات موجودة في جدول باسم [IMPOTS] في قاعدة بيانات MySQL باسم [dbimpots]. سيكون محتوى هذا الجدول كما يلي:

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

في الرسم البياني أعلاه، يحتوي برنامج التشغيل على واجهتين:
- واجهة I1 المعروضة للتطبيق
- واجهة I2 لقاعدة البيانات
لمنع الحاجة إلى إعادة كتابة تطبيق مكتوب لقاعدة البيانات B1 في حالة الترحيل إلى قاعدة بيانات مختلفة B2، تم بذل جهود لتوحيد واجهة I1. إذا تم استخدام قواعد بيانات تستخدم برامج تشغيل "موحدة"، فسيتم تزويد قاعدة البيانات B1 ببرنامج التشغيل P1، وقاعدة البيانات B2 ببرنامج التشغيل P2، وستكون واجهة I1 لهذين البرنامجين متطابقة. وبالتالي، لن تكون هناك حاجة إلى إعادة كتابة التطبيق. على سبيل المثال، يمكنك ترحيل قاعدة بيانات Access إلى قاعدة بيانات MySQL دون تغيير التطبيق.
هناك نوعان من برامج التشغيل المعيارية:
- برامج تشغيل ODBC (اتصال قاعدة البيانات المفتوحة)
- برامج تشغيل OLE DB (ربط الكائنات وتضمين قاعدة البيانات)
توفر برامج تشغيل ODBC الوصول إلى قواعد البيانات. مصادر البيانات لبرامج تشغيل OLE DB أكثر تنوعًا: قواعد البيانات، وأنظمة البريد الإلكتروني، والدلائل، وما إلى ذلك. لا توجد حدود. يمكن أن يكون أي مصدر بيانات موضوعًا لبرنامج تشغيل OLE DB إذا قرر المورد ذلك. الفائدة واضحة وهامة: لديك وصول موحد إلى مجموعة متنوعة من البيانات.
تأتي منصة .NET 1.1 مع ثلاثة أنواع من فئات الوصول إلى البيانات:
- فئات SQL Server.NET، للوصول إلى قواعد بيانات Microsoft SQL Server
- فئات Ole Db.NET، للوصول إلى قواعد البيانات من أنظمة إدارة قواعد البيانات (DBMS) التي توفر برنامج تشغيل OLE DB
- فئات ODBC.NET، للوصول إلى قواعد البيانات من أنظمة إدارة قواعد البيانات (DBMS) التي توفر برنامج تشغيل ODBC
يحتوي نظام إدارة قواعد البيانات MySQL منذ فترة طويلة على برنامج تشغيل ODBC. وهذا هو البرنامج الذي سنستخدمه الآن. في Windows، نختار [قائمة ابدأ/لوحة التحكم/أدوات إدارية/مصادر ODBC 32 بت]. قد يختلف هذا المسار قليلاً حسب إصدار Windows. يؤدي هذا إلى فتح التطبيق التالي، الذي سيسمح لنا بإنشاء مصدر ODBC الخاص بنا:

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

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

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

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

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

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

في الرسم البياني أعلاه، تتولى فئة [impotsData] مسؤولية استرداد البيانات. في هذه الحالة، ستسترد البيانات من قاعدة بيانات MySQL [dbimpots]. كما تعلمنا في الإصدار السابق من هذا التطبيق، فإن [impotsData] هي فئة مجردة يجب اشتقاقها كلما أردنا تكييفها مع مصدر بيانات جديد. دعونا نراجع بنية هذه الفئة المجردة:
Imports System.Collections
Namespace st.istia.univangers.fr
Public MustInherit Class impotsData
Protected limites() As Decimal
Protected coeffr() As Decimal
Protected coeffn() As Decimal
Protected checked As Boolean
Protected valide As Boolean
' 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"))
- بمجرد الانتهاء من ذلك، نقوم بتحرير الموارد المرتبطة بالاتصال:
- يتم نقل محتويات الكائنات الثلاثة [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
ثم يتم ترجمة برنامج الاختبار:
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. ملخص التغييرات
التطبيق جاهز للاختبار. دعونا نستعرض التغييرات التي أُجريت على الإصدار السابق:
- تم إنشاء فئة جديدة للوصول إلى البيانات
- تم تعديل وحدة التحكم [global.asax.vb] في مكانين: إنشاء كائن [import] وتسجيل الرسالة المتعلقة بأي استثناء في التطبيق
- تم تعديل وحدة التحكم [main.aspx.vb] في مكان واحد لعرض رسالة الاستثناء السابقة
- تمت إضافة ملف [web.config]
تم إجراء أعمال التعديل بشكل أساسي في 1، أي خارج تطبيق الويب. وقد أصبح ذلك ممكنًا بفضل بنية MVC للتطبيق، التي تفصل وحدة التحكم عن فئات الأعمال. وهذا هو جوهر هذه البنية. ويمكن إثبات أنه باستخدام ملف [web.config] مناسب، كان من الممكن تجنب أي تعديل على وحدة التحكم في التطبيق. من الممكن تحديد اسم فئة الوصول إلى البيانات التي سيتم إنشاء مثيل لها ديناميكيًا في [web.config] —مثل —بالإضافة إلى المعلمات المختلفة المطلوبة لإنشاء هذا المثيل. باستخدام هذه المعلومات، يمكن لـ [global.asax] إنشاء مثيل لكائن الوصول إلى البيانات. عندئذٍ يكون تغيير مصدر البيانات هو:
- إنشاء فئة الوصول إلى البيانات لهذا المصدر إذا لم تكن موجودة بعد
- تعديل ملف [web.config] للسماح بالإنشاء الديناميكي لمثيل لهذه الفئة في [global.asax]
6.2.8. اختبار تطبيق الويب
يتم وضع جميع الملفات المذكورة أعلاه في مجلد باسم <application-path>.

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

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

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

- في الرسم البياني أعلاه، تتولى فئة [impotsData] مسؤولية استرداد البيانات. هذه المرة، سيتعين عليها القيام بذلك من مصدر OLEDB.
نقوم بإنشاء فئة [impotsOLEDB]، التي ستسترد البيانات (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:

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

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

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

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

انقر بزر الماوس الأيمن على كائن [AccessDataSourceControl] أدناه للوصول إلى خصائصه:

يتم توفير سلسلة اتصال OLEDB بقاعدة بيانات ACCESS من خلال خاصية [ConnectionString] أعلاه:
Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=D:\data\serge\devel\aspnet\poly\chap5\impots\3\impots.mdb
يمكننا أن نرى أن هذه السلسلة تتكون من جزء ثابت وجزء متغير، وهو ببساطة اسم ملف ACCESS. سنستخدم هذه الحقيقة لإنشاء سلسلة الاتصال بمصدر بيانات OLEDB الخاص بنا.
لنعد الآن إلى فئة [impotsOLEDB] الخاصة بنا. تتولى طريقة [getData] مسؤولية قراءة البيانات من الجدول [impots] ووضعها في ثلاثة مصفوفات (limites، coeffr، coeffn). دعونا نعلق على كودها:
- نقوم بتعريف كائن [DataAdapter]، الذي سيسمح لنا بنقل نتيجة استعلام SQL SELECT إلى الذاكرة. للقيام بذلك، نقوم بتعريف استعلام [select] المراد تنفيذه وربطه بكائن [DataAdapter]. يتطلب منشئ [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
يتم وضع ملف [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. ملخص التغييرات
التطبيق جاهز للاختبار. دعونا نسرد التغييرات التي تم إجراؤها على الإصدار السابق:
- تم إنشاء فئة جديدة للوصول إلى البيانات
- تم تعديل وحدة التحكم [global.asax.vb] في مكان واحد: إنشاء كائن [import]
- تمت إضافة ملف [web.config]
6.3.8. اختبار تطبيق الويب
يتم وضع جميع الملفات المذكورة أعلاه في مجلد باسم <application-path>.

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

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

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

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

فيما يلي كود العرض:
<%@ 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] كما يلي:

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

يجب أن يتعامل وحدة التحكم [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. ملخص التغييرات
التطبيق جاهز للاختبار. دعونا نسرد التغييرات التي تم إجراؤها على الإصدارات السابقة:
- تم إنشاء عرض جديد
- تم تعديل وحدة التحكم [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] عند استلامها على الخادم.
- قد تصبح الآلية الموضحة أعلاه مستهلكة للموارد بشكل كبير في حالة وجود عدد كبير من عمليات المحاكاة. علاوة على ذلك، من الشائع أن يقوم المستخدم بمسح ملفات تعريف الارتباط بشكل دوري عن طريق حذفها جميعًا، حتى لو كان يسمح لمتصفحه باستخدامها. لذلك، ستفقد ملفات تعريف الارتباط الخاصة بالمحاكاة عاجلاً أم آجلاً. لذلك قد نرغب في تخزينها على الخادم بدلاً من العميل، في قاعدة بيانات على سبيل المثال. لربط عمليات المحاكاة بمستخدم معين، يمكن أن يبدأ التطبيق بمرحلة مصادقة تتطلب اسم مستخدم وكلمة مرور، يتم تخزينهما في قاعدة بيانات أو أي نوع آخر من مستودعات البيانات.
- قد نرغب أيضًا في تأمين تشغيل تطبيقنا. يعتمد التطبيق حاليًا على افتراضين:
- يمر المستخدم دائمًا عبر وحدة التحكم [main.aspx]
- وفي هذه الحالة، يستخدم دائمًا الإجراءات المتاحة على الصفحة التي تم إرسالها إليه
ماذا يحدث، على سبيل المثال، إذا طلب المستخدم مباشرةً عنوان URL [http://localhost/impots4/formulaire.aspx]؟ هذا السيناريو غير مرجح لأن المستخدم لا يعرف هذا العنوان. ومع ذلك، يجب أخذه في الاعتبار. يمكن معالجته بواسطة وحدة التحكم في التطبيق [global.asax]، التي تعالج جميع الطلبات الموجهة إلى التطبيق. وبالتالي يمكنها التحقق من أن المورد المطلوب هو بالفعل [main.aspx].
السيناريو الأكثر احتمالاً هو أن المستخدم لا يستخدم الإجراءات المتاحة على الصفحة التي أرسلها إليه الخادم. على سبيل المثال، ماذا يحدث إذا طلب المستخدم عنوان URL [http://localhost/impots4/main.aspx?action=retour] مباشرةً دون ملء النموذج أولاً؟ دعونا نجرب ذلك. نحصل على الاستجابة التالية:

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



