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

کاربر فرم را پر میکند:

و دکمه [Calculer] را کلیک میکند تا پاسخ زیر را دریافت کند:

ممکن است هنگام وارد کردن دادهها اشتباه کنند:

کلیک روی دکمه [Calculer] سپس پاسخ دیگری را تولید میکند، [erreurs.aspx]:

آنها میتوانند از لینک [Retour au formulaire] بالا برای بازگشت به نمای [formulaire.aspx] که قبلاً قبل از خطا تأیید کرده بودند، استفاده کنند:

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

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

فرم را پر میکنیم:

سپس با استفاده از دکمه [Calculer] محاسبه مالیات را آغاز میکنیم. پاسخ زیر را دریافت میکنیم:

سپس دادههای نادرستی را وارد میکنیم:

کلیک روی دکمه [Calculer] پاسخ زیر را تولید میکند:

کلیک روی لینک [Retour au formulaire] ما را به فرم بازمیگرداند، همانطور که هنگام ارسال بود:

در نهایت، کلیک روی دکمه [Effacer] صفحه را ریست میکند:

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

بلوک [Classes métier, Classes d'accès aux données] بدون تغییر باقی میماند. این به این دلیل است که این بخش از برنامه با مشتری ارتباطی ندارد. بلوک [Contrôleur] تنها اندکی تغییر میکند اما باید پارامتر جدیدی را در درخواست در نظر بگیرد: پارامتر [client] که نوع مشتری را که با آن سروکار دارد، مشخص میکند. بلوک [vues] باید برای هر نوع مشتری، نماهایی تولید کند. شاید ارزش داشته باشد که از مرحله طراحی اپلیکیشن، وجود پارامتر [client] در درخواست را در نظر بگیریم، حتی اگر هدف کوتاهمدت یا میانمدت تنها به مرورگرهای وب محدود باشد. اگر بعداً از برنامه خواسته شود تا از نوع جدیدی از کلاینت پشتیبانی کند، تنها لازم است نماهای متناسب با آن نوع نوشته شوند.
6.2. مثال ۲
6.2.1. مشکل
در اینجا، هدف ما حل همان مشکل قبلی است، اما با اصلاح منبع داده برای شیء [impot] که توسط وباپلیکیشن ایجاد شده است. در نسخه قبلی، منبع دادهای که استفاده میشد، مقادیر آرایهای را بهصورت کد سخت در کد درج میکرد. این بار، منبع داده جدید آنها را از یک منبع داده ODBC که با پایگاه داده MySQL مرتبط است، بازیابی خواهد کرد.
6.2.2. منبع داده ODBC
دادهها در جدولی به نام [IMPOTS] در پایگاهداده MySQL که نام آن [dbimpots] است، ذخیره خواهند شد. محتویات این جدول به شرح زیر خواهد بود:

مالک پایگاه داده کاربر [admimpots] با رمز عبور [mdpimpots] است. ما یک منبع داده به نام ODBC را به این پایگاه داده مرتبط میکنیم. پیش از انجام این کار، ابتدا روشهای مختلف دسترسی به یک پایگاه داده با استفاده از پلتفرم NET را بررسی کنیم.
بانکهای اطلاعاتی متعددی برای پلتفرمهای ویندوز در دسترس هستند. برای دسترسی به آنها، برنامهها از برنامههایی به نام درایور استفاده میکنند.

در نمودار بالا، درایور دو رابط دارد:
- رابط I1 که به برنامه ارائه میشود
- رابط I2 به پایگاه داده
برای اطمینان از اینکه یک برنامه نوشته شده برای پایگاه داده B1 هنگام مهاجرت به پایگاه داده متفاوت B2 نیازی به بازنویسی نداشته باشد، کارهای استانداردسازی روی رابط I1 انجام شده است. اگر از پایگاههای دادهای که از درایورهای «استانداردسازیشده» استفاده میکنند بهره گرفته شود، پایگاه داده B1 به همراه درایور P1 ارائه خواهد شد، پایگاه داده B2 با درایور P2، و رابط کاربری I1 برای هر دوی این درایورها یکسان خواهد بود. در نتیجه، نیازی به بازنویسی برنامه نخواهد بود. به عنوان مثال، امکان مهاجرت یک پایگاه داده ACCESS به یک پایگاه داده MySQL بدون تغییر در برنامه وجود خواهد داشت.
دو نوع درایور استاندارد وجود دارد:
- رانندههای ODBC (اتصال باز DataBase)
- رانندههای OLE و DB (پیوند و جاسازی اشیاء DataBase)
رانندههای ODBC دسترسی به پایگاههای داده را فراهم میکنند. منابع داده برای رانندههای OLE و DB متنوعتر هستند: پایگاههای داده، سیستمهای ایمیل، دایرکتوریها و غیره. هیچ محدودیتی وجود ندارد. هر منبع دادهای میتواند در صورت تصمیم یک فروشنده نرمافزاری، موضوع یک درایور Ole DB باشد. مزایای آن، البته، قابل توجه است: شما دسترسی یکنواختی به طیف گستردهای از دادهها دارید.
پلتفرم .NET 1.1 با سه نوع کلاس دسترسی به داده عرضه میشود:
- کلاسهای SQL و Server.NET، برای دسترسی به پایگاههای داده Microsoft SQL Server
- کلاسهای Ole Db.NET، برای دسترسی به پایگاههای داده با استفاده از SGBD که درایوری OLE DB را فراهم میکنند
- کلاسهای odbc.net، برای دسترسی به پایگاههای داده SGBD، که یک درایور ODBC را فراهم میکنند
SGBD و MySQL مدتهاست که یک درایور به نام ODBC دارند. این همان درایوری است که اکنون از آن استفاده میکنیم. در ویندوز، گزینه [Menu Démarrer/Panneau de configuration/Outils d'administration/Sources ODBC 32 bits] را انتخاب میکنیم. بسته به نسخه ویندوز، این مسیر ممکن است کمی متفاوت باشد. این به ما برنامه زیر را میدهد که به ما امکان میدهد منبع ODBC خود را ایجاد کنیم:

ما قصد داریم یک منبع داده سیستمی به نام c.a.d ایجاد کنیم. این منبعی از دادهها است که هر کاربری از سیستم قادر به دسترسی به آن خواهد بود (utiliser.Aussi)؛ در بالا، زبانه [Source de données système] را انتخاب میکنیم. صفحه نمایشدادهشده دارای دکمهای با برچسب [Ajouter] است که از آن برای ایجاد یک منبع داده جدید، ODBC، استفاده میکنیم:

ویزارد از شما میخواهد درایور ODBC را برای استفاده انتخاب کنید. ویندوز با تعدادی درایور ODBC از پیش نصبشده عرضه میشود. راننده ODBC برای MySQL در این مجموعه گنجانده نشده است. بنابراین باید ابتدا آن را نصب کنید. میتوانید آن را بهصورت آنلاین با وارد کردن عبارتهای جستجو «MySQL ODBC» یا «MyODBC» در یک موتور جستجو پیدا کنید. در اینجا، ما درایور [MySQL ODBC 3.51] را نصب کردهایم. آن را انتخاب کرده و به [Terminer] میرویم:

تعدادی جزئیات باید ارائه شود:
نامی که منبع داده ODBC را شناسایی میکند. هر برنامه ویندوزی قادر خواهد بود از طریق این نام به منبع دسترسی پیدا کند | |
یک متن دلخواه که منبع داده را توصیف میکند | |
نام ماشینی که میزبان SGBD و MySQL است. در این مورد، این ماشین محلی است. میتواند یک ماشین راه دور باشد. این امکان را برای یک برنامه ویندوز فراهم میکند تا بدون هیچگونه کدنویسی ویژه به یک پایگاه داده راه دور دسترسی پیدا کند. این یک مزیت عمده منبع داده ODBC است. | |
یک SGBD یا MySQL میتواند چندین پایگاه داده را مدیریت کند. در اینجا، مشخص میکنیم که کدام یک را میخواهیم مدیریت کنیم: dbimpots | |
یک نام کاربری در SGBD MySQL ثبت شده است. دسترسی به منبع داده تحت این نام انجام خواهد شد. در این مورد: admimpots | |
رمز عبور این کاربر. در اینجا: mdpimpots | |
پورت کاری برای SGBD MySQL. بهطور پیشفرض، این پورت ۳۳۰۶ است. ما آن را تغییر ندادهایم |
پس از انجام این کار، با استفاده از دکمه [Test Data Source] اعتبار تنظیمات اتصال خود را آزمایش میکنیم:

پس از انجام این کار، میتوانیم از منبع دادهی خود ODBC مطمئن شویم. اکنون میتوانیم از آن استفاده کنیم. ما [OK] را به تعداد دفعات لازم کلیک میکنیم تا از جادوگر ODBC خارج شویم.
اگر خواننده SGBD mySQL را در اختیار ندارد، میتواند آن را بهصورت رایگان از آدرس URL [http://www.mysql.com] دانلود کند. در زیر روند ایجاد یک منبع داده ODBC با استفاده از Access را تشریح میکنیم. مراحل اولیه با آنچه قبلاً توضیح داده شد یکسان است. یک منبع داده سیستمی جدید اضافه کنید:

رانندهٔ انتخابشده [Microsoft Access Driver] خواهد بود. برای ادامه به تعریف منبع ODBC، [Terminer] را انتخاب کنید:

اطلاعاتی که باید ارائه شود به شرح زیر است:
نامی که منبع داده ODBC را شناسایی میکند. هر برنامه ویندوزی قادر خواهد بود از طریق این نام به منبع دسترسی پیدا کند | |
یک متن دلخواه که منبع داده را توصیف میکند | |
نام کامل فایل ACCESS که باید استفاده شود |
6.2.3. یک کلاس جدید دسترسی به داده
بیایید به ساختار MVC در برنامهٔ خود بازگردیم:

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

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

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

در داخل این پوشه، یک زیرپوشه به نام [bin] ایجاد میشود که اسمبلی [impot.dll]—تولید شده از کامپایل فایلهای کلاس کسبوکار ([impots.vb, impotsData.vb, impotsArray.vb, impotsOLEDB.vb])—در آن قرار میگیرد. دستور کامپایل مورد نیاز در زیر نشان داده شده است:
dos>vbc /r:system.dll /r:system.data.dll /t:library /out:impot.dll impots.vb impotsArray.vb impotsData.vb impotsOLEDB.vb
فایل [impot.dll] تولید شده توسط این دستور باید در <application-path>\bin قرار داده شود تا وباپلیکیشن بتواند به آن دسترسی داشته باشد. سرور Cassini با پارامترهای (<application-path>,/impots3) راهاندازی میشود. تستها نتایج مشابهی با نسخه قبلی دارند.
6.4. مثال ۴
6.4.1. مشکل
اکنون پیشنهاد میکنیم که اپلیکیشن خود را به یک اپلیکیشن شبیهسازی محاسبه مالیات تبدیل کنیم. کاربر قادر خواهد بود محاسبات مالیاتی متوالی را انجام دهد که در یک نمای جدید مشابه شکل زیر نمایش داده میشوند:

6.4.2. ساختار برنامه MVC
ساختار برنامه MVC به شکل زیر درمیآید:

یک نمای جدید، [simulations.aspx]، ظاهر میشود؛ ما همیناکنون یک اسکرینشات از آن ارائه دادهایم. کلاس دسترسی به دادهها، کلاس [impotsODBC] از مثال ۲ خواهد بود.
6.4.3. نماهای برنامه وب
نما [erreurs.aspx] بدون تغییر باقی میماند. نما [formulaire.aspx] کمی تغییر میکند. مبلغ مالیات دیگر در این نما نمایش داده نمیشود؛ اکنون در نما [simulations.aspx] نمایش داده میشود. بنابراین، هنگام راهاندازی، صفحهای که به کاربر نمایش داده میشود به شرح زیر است:

علاوه بر این، نمای [formulaire] شامل یک اسکریپت جاوااسکریپت است که اعتبار دادههای وارد شده را قبل از ارسال به سرور بررسی میکند، همانطور که در مثال زیر نشان داده شده است:

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

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

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

ما با خرابی سرور مواجه شدهایم. این امر قابل انتظار است. برای اقدام [retour]، کنترلر انتظار دارد یک شیء [NameValueCollection] را در جلسه بیابد که نمایانگر مقادیر فرم مورد نیاز برای نمایش باشد. اما نمیتواند آنها را پیدا کند. مکانیزم کنترلر راهحل شیک و موثری برای این مشکل ارائه میدهد. برای هر درخواست، کنترلر [main.aspx] میتواند بررسی کند که آیا اقدام درخواستی واقعاً یکی از اقداماتی است که قبلاً به کاربر ارسال شده است. میتوان از مکانیزم زیر استفاده کرد:
- پیش از ارسال پاسخ خود به کلاینت، کنترلر اطلاعاتی را که آن صفحه را شناسایی میکند در جلسه کلاینت ذخیره میکند
- هنگامی که یک درخواست جدید از سمت کلاینت دریافت میکند، بررسی میکند که آیا اقدام درخواستی واقعاً به آخرین صفحهای که برای آن کلاینت ارسال شده است، تعلق دارد یا خیر
- اطلاعات مربوط به پیوند صفحات و اقدامات مجاز در آن صفحات را میتوان در فایل پیکربندی [web.config] برنامه وارد کرد.
- تجربه نشان میدهد که کنترلکنندههای برنامهها پایه مشترکی گسترده دارند و میتوان یک کنترلکننده عمومی ساخت که سپس از طریق یک فایل پیکربندی برای یک برنامه مشخص تخصصی میشود. این رویکردی است که برای مثال توسط ابزار [Struts] در زمینه برنامهنویسی وب جاوا اتخاذ شده است.



