6. Приклади
У цьому розділі ми пропонуємо проілюструвати розглянуте вище за допомогою низки прикладів.
6.1. Приклад 1
6.1.1. Задача
Цей додаток має дати змогу користувачеві розрахувати свій податок. Розглянемо спрощений випадок, коли платник податків має задекларувати лише свою заробітну плату (дані за 2004 рік щодо доходів за 2003 рік):
- кількість часткок працівника обчислюється за формулою nbParts=nbEnfants/2 +1, якщо він не одружений, nbEnfants/2+2, якщо він одружений, де nbEnfants — кількість його дітей.
- якщо у нього щонайменше троє дітей, він отримує на пів частки більше
- розраховується його оподатковуваний дохід R = 0,72 * S, де S — його річна заробітна плата
- розраховується його сімейний коефіцієнт QF = R / nbParts
- розраховуємо його податок I. Розглянемо таку таблицю:
4262 | 0 | 0 |
8382 | 0,0683 | 291,09 |
14753 | 0,1914 | 1322,92 |
23888 | 0,2826 | 2668,39 |
38868 | 0,3738 | 4846,98 |
47932 | 0,4262 | 6883,66 |
0 | 0,4809 | 9505,54 |
Кожен рядок має 3 поля. Щоб обчислити податок I, шукаємо перший рядок, де QF <= поле1. Наприклад, якщо QF = 5000, то знайдемо рядок
Тоді податок I дорівнює 0,0683*R - 291,09*nbParts. Якщо QF є таким, що співвідношення QF<=поле1 ніколи не виконується, то використовуються коефіцієнти останнього рядка. У даному випадку:
що дає податок I = 0,4809*R - 9505,54*nbParts.
6.1.2. Структура MVC додатка
Структура MVC додатка матиме такий вигляд:

Роль контролера виконуватиме сторінка [main.aspx]. Можливі три дії:
- init: відповідає першому запиту клієнта. Контролер відобразить представлення [formulaire.aspx]
- calcul: відповідає запиту на розрахунок податку. Якщо дані форми введення правильні, податок розраховується за допомогою бізнес-класу [impots]. Контролер повертає клієнту представлення [formulaire.aspx] у тому вигляді, в якому воно було перевірено, а також з розрахованим податком. Якщо дані у формі введення неправильні, контролер поверне представлення [erreurs.aspx] зі списком помилок та посиланням для повернення до форми.
- повернення: відповідає поверненню до форми після помилки. Контролер відображає вигляд [formulaire.aspx] у тому вигляді, в якому він був перевірений на правильність до виникнення помилки.
Контролер [main.aspx] не бере участі в розрахунку податків. Він просто відповідає за управління діалогом «клієнт-сервер» та виконання дій, запитуваних клієнтом. Для дії [calcul] він використовуватиме бізнес-клас [impot].
6.1.3. Бізнес-клас
Клас «impot» буде визначено наступним чином:
' імпортованих просторів імен
Imports System
' клас
Namespace st.istia.univangers.fr
Public Class impot
Private limites(), coeffR(), coeffN() As Decimal
' конструктор
Public Sub New(ByRef source As impotsData)
' дані, необхідні для розрахунку податку
' походять із зовнішнього джерела [source]
' їх отримують — можливий виняток
Dim data() As Object = source.getData
limites = CType(data(0), Decimal())
coeffR = CType(data(1), Decimal())
coeffN = CType(data(2), Decimal())
End Sub
' розрахунок податку
Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Long) As Long
' розрахунок кількості часток
Dim nbParts As Decimal
If marié Then
nbParts = CDec(nbEnfants) / 2 + 2
Else
nbParts = CDec(nbEnfants) / 2 + 1
End If
If nbEnfants >= 3 Then
nbParts += 0.5D
End If
' розрахунок оподатковуваного доходу та сімейного коефіцієнта
Dim revenu As Decimal = 0.72D * salaire
Dim QF As Decimal = revenu / nbParts
' розрахунок податку
limites((limites.Length - 1)) = QF + 1
Dim i As Integer = 0
While QF > limites(i)
i += 1
End While
Return CLng(revenu * coeffR(i) - nbParts * coeffN(i))
End Function
End Class
End Namespace
Об'єкт податку створюється шляхом передачі його конструктору джерела даних типу [impotsData]. Цей клас має публічний метод [getData], який дозволяє отримати три масиви даних, необхідні для розрахунку податку, що були наведені вище. Цей метод може обробляти виняток, якщо дані не вдалося отримати або якщо вони виявляються некоректними. Після створення об’єкта [impot] можна неодноразово викликати його метод **calculer**, який обчислює податок платника податків на основі його сімейного стану (одружений чи ні), кількості дітей та річної заробітної плати.
6.1.4. Клас доступу до даних
Клас [impotsData] — це клас, що забезпечує доступ до даних. Це абстрактний клас. Для кожного нового можливого джерела даних (таблиці, текстові файли, бази даних, консоль тощо) потрібно створити похідний клас. Його визначення таке:
Imports System.Collections
Namespace st.istia.univangers.fr
Public MustInherit Class impotsData
Protected limites() As Decimal
Protected coeffr() As Decimal
Protected coeffn() As Decimal
Protected checked As Boolean
Protected valide As Boolean
' метод доступу до даних
Public MustOverride Function getData() As Object()
' метод перевірки даних
Protected Function checkData() As Integer
' перевірка отриманих даних
' мають бути дані
valide = Not limites Is Nothing AndAlso Not coeffr Is Nothing AndAlso Not coeffn Is Nothing
If Not valide Then Return 1
' має бути 3 масиви однакового розміру
If valide Then valide = limites.Length = coeffr.Length AndAlso limites.Length = coeffn.Length
If Not valide Then Return 2
' масиви мають бути непорожніми
valide = limites.Length <> 0
If Not valide Then Return 3
' кожен масив повинен містити елементи >=0, упорядковані за зростанням
valide = check(limites, limites.Length - 1) AndAlso check(coeffr, coeffr.Length) AndAlso check(coeffn, coeffn.Length)
If Not valide Then Return 4
' все правильно
Return 0
End Function
' перевіряє правильність вмісту масиву
Protected Function check(ByRef tableau() As Decimal, ByVal n As Integer) As Boolean
' масив повинен мати перші n елементів >=0, розташованих у строго зростаючому порядку
If tableau(0) < 0 Then Return False
For i As Integer = 1 To n - 1
If tableau(i) <= tableau(i - 1) Then Return False
Next
' все правильно
Return True
End Function
End Class
End Namespace
Клас має такі захищені атрибути:
таблиця меж податкових груп | |
таблиця коефіцієнтів, що застосовуються до оподатковуваного доходу | |
таблиця коефіцієнтів, що застосовуються до кількості паїв | |
логічне значення, що вказує, чи були перевірені дані (ліміти, коефіцієнт1, коефіцієнт2) | |
логічне значення, що вказує, чи дані (межі, коефіцієнт, коефіцієнт) є дійсними |
Клас не має конструктора. Він має абстрактний метод [getData], який повинні реалізувати похідні класи. Цей метод призначений для:
- присвоїти значення трьом масивам: межі, коефіцієнти_r, коефіцієнти_n
- генерувати виняток, якщо дані не вдалося отримати або якщо вони виявилися недійсними.
Клас надає захищені методи [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], має два конструктори:
- конструктор без аргументів, який ініціалізує атрибути (limites, coeffr, coeffn) базового класу за допомогою «жорстко» закодованих масивів
- конструктор, який ініціалізує атрибути (limites, coeffr, coeffn) базового класу за допомогою масивів, що передаються йому як параметри
Метод [getData], який дозволить зовнішнім класам отримувати масиви (limites, coeffr, coeffn), обмежується перевіркою правильності трьох масивів за допомогою методу [checkData] базового класу. Він генерує виняток, якщо дані є недійсними.
6.1.5. Тестування бізнес-класів та класів доступу до даних
Важливо включати у веб-додаток лише ті бізнес-класи та класи доступу до даних, правильність яких підтверджено. Таким чином, на етапі налагодження веб-додатка можна буде зосередитися на контролерах та представленнях. Програма тестування може виглядати так:
' опції
Option Strict On
Option Explicit On
' простори імен
Imports System
Imports Microsoft.VisualBasic
Namespace st.istia.univangers.fr
Module test
Sub Main()
' інтерактивна програма для розрахунку податку
' користувач вводить три значення з клавіатури: одружений nbEnfants зарплата
' після чого програма відображає суму податку до сплати
Const syntaxe As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"
' створення об’єкта «податок»
Dim objImpôt As impot = Nothing
Try
objImpôt = New impot(New impotsArray)
Catch ex As Exception
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
Environment.Exit(1)
End Try
' нескінченний цикл
Dim marié As String
Dim nbEnfants As Integer
Dim salaire As Long
While True
' запитуються параметри розрахунку податку
Console.Out.Write("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :")
Dim paramètres As String = Console.In.ReadLine().Trim()
' що робити?
If paramètres Is Nothing OrElse paramètres = "" Then
Exit While
End If
' перевірка кількості аргументів у введеному рядку
Dim erreur As Boolean = False
Dim args As String() = paramètres.Split(Nothing)
Dim nbParamètres As Integer = args.Length
If nbParamètres <> 3 Then
Console.Error.WriteLine(syntaxe)
erreur = True
End If
' перевірка правильності параметрів
If Not erreur Then
' одружений
marié = args(0).ToLower()
If marié <> "o" And marié <> "n" Then
erreur = True
End If
' nbEnfants
Try
nbEnfants = Integer.Parse(args(1))
If nbEnfants < 0 Then
Throw New Exception
End If
Catch
erreur = True
End Try
' зарплата
Try
salaire = Integer.Parse(args(2))
If salaire < 0 Then
Throw New Exception
End If
Catch
erreur = True
End Try
End If
' якщо параметри правильні — розраховується податок
If Not erreur Then
Console.Out.WriteLine(("impôt=" & objImpôt.calculer(marié = "o", nbEnfants, salaire) & " euro(s)"))
Else
Console.Error.WriteLine(syntaxe)
End If
End While
End Sub
End Module
End Namespace
Додаток просить користувача ввести з клавіатури три дані, необхідні для розрахунку податку:
- сімейний стан: «o» — одружений, «n» — неодружений
- кількість дітей
- річний дохід
Розрахунок податку здійснюється за допомогою об’єкта типу [impot], який створюється одразу після запуску програми:
' створення об’єкта податку
Dim objImpôt As impot = Nothing
Try
objImpôt = New impot(New impotsArray)
Catch ex As Exception
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
Environment.Exit(1)
End Try
Як джерело даних використовується об’єкт типу [impotsArray]. Використовується конструктор цього класу без аргументів, який надає три масиви (limites, coeffr, coeffn) із «жорстко заданими» значеннями. Створення об’єкта [impot] теоретично може спричинити виняток, оскільки для свого створення об’єкт запитуватиме дані (limites, coeffr, coeffn) у свого джерела даних, яке було передано йому як параметр, і це отримання даних може викликати виняток. Виявляється, що в даному випадку метод отримання даних (зафіксовані значення) не може спричинити винятку. Проте ми залишили обробку винятку, щоб привернути увагу читача до можливості того, що об’єкт [impot] може бути сформований неправильно.
Ось приклад виконання попередньої програми:
dos>dir
05/04/2004 13:28 1 337 impots.vb
21/04/2004 08:23 1 311 impotsArray.vb
21/04/2004 08:26 1 634 impotsData.vb
21/04/2004 08:42 2 490 testimpots1.vb
Ми компілюємо набір класів [impot, impotsData, impotsArray] у збірку [impot.dll]:
dos>vbc /t:library /out:impot.dll impotsData.vb impotsArray.vb impots.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4
dos>dir
05/04/2004 13:28 1 337 impots.vb
21/04/2004 08:23 1 311 impotsArray.vb
21/04/2004 08:26 1 634 impotsData.vb
21/04/2004 08:42 2 490 testimpots1.vb
21/04/2004 09:21 5 632 impot.dll
Компілюємо тестову програму:
dos>dir
05/04/2004 13:28 1 337 impots.vb
21/04/2004 08:23 1 311 impotsArray.vb
21/04/2004 08:26 1 634 impotsData.vb
21/04/2004 08:42 2 490 testimpots1.vb
21/04/2004 09:21 5 632 impot.dll
21/04/2004 09:23 4 608 testimpots1.exe
Ми можемо провести тестування:
dos>testimpots1
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 60000
impôt=4300 euro(s)
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :n 2 60000
impôt=6872 euro(s)
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :
6.1.6. Види веб-додатку
Додаток матиме два види: [formulaire.aspx] та [erreurs.aspx]. Проілюструємо роботу додатка за допомогою знімків екрана. Вигляд [formulaire.aspx] відображається, коли URL-адреса [main.aspx] запитується вперше:

Користувач заповнює форму:

і натискає кнопку [Calculer], щоб отримати таку відповідь:

Він може помилитися при введенні даних:

Натискання кнопки [Calculer] призводить до отримання іншої відповіді [erreurs.aspx]:

Він може скористатися наведеним вище посиланням [Retour au formulaire], щоб повернутися до екрана [formulaire.aspx] у тому вигляді, в якому він його підтвердив до виникнення помилки:

6.1.7. Вигляд [formulaire.aspx]
Сторінка [formulaire.aspx] матиме такий вигляд:
<%@ page src="formulaire.aspx.vb" inherits="formulaire" AutoEventWireup="false"%>
<html>
<head>
<title>Impôt</title>
</head>
<body>
<P>Calcul de votre impôt</P>
<HR>
<form method="post" action="main.aspx?action=calcul">
<TABLE border="0">
<TR>
<TD>Etes-vous marié(e)</TD>
<TD>
<INPUT type="radio" value="oui" name="rdMarie" <%=rdouichecked%>>Oui
<INPUT type="radio" value="non" name="rdMarie" <%=rdnonchecked%>>Non
</TD>
</TR>
<TR>
<TD>Nombre d'enfants</TD>
<TD><INPUT type="text" size="3" maxLength="3" name="txtEnfants" value="<%=txtEnfants%>"></TD>
</TR>
<TR>
<TD>Salaire annuel (euro)</TD>
<TD><INPUT type="text" maxLength="12" size="12" name="txtSalaire" value="<%=txtSalaire%>"></TD>
</TR>
<TR>
<TD>Impôt à payer :
</TD>
<TD><%=txtImpot%></TD>
</TR>
</TABLE>
<hr>
<P>
<INPUT type="submit" value="Calculer">
</P>
</form>
<form method="post" action="main.aspx?action=effacer">
<INPUT type="submit" value="Effacer">
</form>
</body>
</html>
Динамічні поля цієї сторінки такі:
«checked», якщо поле [oui] має бути позначене, «» — у протилежному випадку | |
те саме для поля [non] | |
значення, яке слід ввести у поле введення [txtEnfants] | |
значення, яке слід ввести у поле введення [txtSalaire] | |
значення, яке слід ввести у поле введення [txtImpot] |
На сторінці є дві форми, кожна з яких має кнопку [submit]. Кнопка [Calculer] — це кнопка [submit] з наступної форми:
<form method="post" action="main.aspx?action=calcul">
...
<P>
<INPUT type="submit" value="Calculer">
</P>
</form>
Як бачимо, параметри форми будуть передані до контролера за допомогою [action=calcul]. Кнопка [Effacer] — це кнопка [submit] з наступної форми:
<form method="post" action="main.aspx?action=effacer">
<INPUT type="submit" value="Effacer">
</form>
Бачимо, що параметри форми будуть відправлені до контролера разом із [action=effacer]. У цьому випадку форма не має жодних параметрів. Важливою є лише дія.
Поля [formulaire.aspx] обчислюються за допомогою [formulaire.aspx.vb]:
Imports System.Collections.Specialized
Public Class formulaire
Inherits System.Web.UI.Page
' поля сторінки
Protected rdouichecked As String
Protected rdnonchecked As String
Protected txtEnfants As String
Protected txtSalaire As String
Protected txtImpot As String
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' отримуємо попередній запит із контексту
Dim form As NameValueCollection = Context.Items("formulaire")
' підготовка сторінки до відображення
' перемикачі
rdouichecked = ""
rdnonchecked = "checked"
If form("rdMarie").ToString = "oui" Then
rdouichecked = "checked"
rdnonchecked = ""
End If
' решта
txtEnfants = CType(form("txtEnfants"), String)
txtSalaire = CType(form("txtSalaire"), String)
txtImpot = CType(Context.Items("txtImpot"), String)
End Sub
End Class
Обчислення полів [main.aspx] здійснюється на основі двох даних, розміщених контролером у контексті сторінки:
- Context.Items («форма»): словник типу NameValueCollection, що містить значення полів HTML [rdmarie,txtEnfants,txtSalaire]
- Context.Items("txtImpot"): значення податку
6.1.8. Вигляд [erreurs.aspx]
Вигляд [erreurs.aspx] відображає можливі помилки, що можуть виникнути під час роботи програми. Його код представлення такий:
<%@ page src="erreurs.aspx.vb" inherits="erreurs" AutoEventWireup="false"%>
<HTML>
<HEAD>
<title>Impôt</title>
</HEAD>
<body>
<P>Les erreurs suivantes se sont produites :</P>
<HR>
<ul>
<%=erreursHTML%>
</ul>
<a href="<%=href%>">
<%=lien%>
</a>
</body>
</HTML>
Сторінка містить три динамічні поля:
код HTML зі списку помилок | |
URL-адреса посилання | |
текст посилання |
Ці поля обчислюються контролером сторінки в [erreurs.aspx.vb]:
Imports System.Collections
Imports Microsoft.VisualBasic
Public Class erreurs
Inherits System.Web.UI.Page
' параметри сторінки
Protected erreursHTML As String = ""
Protected href As String
Protected lien As String
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' отримуємо елементи контексту
Dim erreurs As ArrayList = CType(context.Items("erreurs"), ArrayList)
href = context.Items("href").ToString
lien = context.Items("lien").ToString
' генерується код HTML зі списку
Dim i As Integer
For i = 0 To erreurs.Count - 1
erreursHTML += "<li> " + erreurs(i).ToString + "</li>" + ControlChars.CrLf
Next
End Sub
End Class
Контролер сторінки отримує інформацію, розміщену контролером додатка в контексті сторінки:
об’єкт ArrayList, що містить список повідомлень про помилки, які потрібно відобразити | |
URL-адреса посилання | |
текст посилання |
Тепер, коли ми знаємо, що бачить користувач додатка, можемо перейти до написання контролера для нього.
6.1.9. Контролери [global.asax, main.aspx]
Нагадаємо схему MVC нашого додатка:

Клієнт Логіка додатка
Контролер [main.aspx] повинен обробити три дії:
- init: відповідає першому запиту клієнта. Контролер відображає представлення [formulaire.aspx]
- calcul: відповідає запиту на розрахунок податку. Якщо дані у формі введення правильні, податок розраховується за допомогою бізнес-класу [impots]. Контролер повертає клієнту представлення [formulaire.aspx] у тому вигляді, в якому воно було перевірено, а також з розрахованим податком. Якщо дані у формі введення неправильні, контролер повертає представлення [erreurs.aspx] зі списком помилок та посиланням для повернення до форми.
- повернення: відповідає поверненню до форми після помилки. Контролер відображає вигляд [formulaire.aspx] у тому вигляді, в якому він був перевірений до виникнення помилки.
Крім того, відомо, що будь-який запит до додатка проходить через контролер [global.asax], якщо він існує. Отже, на вході до додатка ми маємо ланцюжок із двох контролерів:
- [global.asax], який, згідно з архітектурою, через ASP.NET отримує всі запити до додатка
- [main.aspx], який, згідно з рішенням розробника, також отримує всі запити до додатка
Необхідність у [main.aspx] зумовлена тим, що нам доведеться керувати сесією. Ми бачили, що [global.asax] у цьому випадку не підходить як контролер. Тут можна було б повністю обійтися без [global.asax]. Проте ми будемо використовувати його для виконання коду під час запуску додатка. Наведена вище схема MVC показує, що нам знадобиться створити об’єкт [impot] для розрахунку податку. Немає сенсу створювати його кілька разів — одного разу достатньо. Отже, ми створимо його під час запуску додатка під час події [Application_Start], яку обробляє контролер [global.asax]. Його код такий:
[global.asax]
[global.asax.vb]
Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr
Public Class Global
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' створюємо об’єкт «impot»
Dim objImpot As impot
Try
objImpot = New impot(New impotsArray)
' об'єкт додається до додатка
Application("objImpot") = objImpot
' помилок немає
Application("erreur") = False
Catch ex As Exception
'сталася помилка, її фіксують у додатку
Application("erreur") = True
End Try
End Sub
End Class
Після створення об’єкт типу [impot] розміщується в додатку. Саме звідти різні запити від різних клієнтів будуть його отримувати. Оскільки створення об’єкта [impot] може завершитися невдачею, ми обробляємо можливе виключення та розміщуємо у додатку ключ [erreur], щоб повідомити, чи сталася помилка під час створення об’єкта [impot].
Код контролера [main.aspx, main.aspx.vb] буде таким:
[main.aspx]
[main.aspx.vb]
Imports System
Imports System.Collections.Specialized
Imports System.Collections
Imports st.istia.univangers.fr
Public Class main
Inherits System.Web.UI.Page
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' насамперед перевіряємо, чи вдалося додатку правильно ініціалізуватися
If CType(Application("erreur"), Boolean) Then
' перенаправляємо на сторінку помилок
Dim erreurs As New ArrayList
erreurs.Add("Application momentanément indisponible...")
context.Items("erreurs") = erreurs
context.Items("lien") = ""
context.Items("href") = ""
Server.Transfer("erreurs.aspx")
End If
' визначаємо необхідну дію
Dim action As String
If Request.QueryString("action") Is Nothing Then
action = "init"
Else
action = Request.QueryString("action").ToString.ToLower
End If
' виконуємо дію
Select Case action
Case "init"
' ініціалізація додатка
initAppli()
Case "calcul"
' розрахунок податку
calculImpot()
Case "retour"
' повернення до форми
retourFormulaire()
Case "effacer"
' ініціалізація додатка
initAppli()
Case Else
' невідома дія = ініціалізація
initAppli()
End Select
End Sub
Private Sub initAppli()
' відображення попередньо заповненої форми
Context.Items("formulaire") = initForm()
Context.Items("txtImpot") = ""
Server.Transfer("formulaire.aspx", True)
End Sub
Private Function initForm() As NameValueCollection
' ініціалізація форми
Dim form As New NameValueCollection
form.Set("rdMarie", "non")
form.Set("txtEnfants", "")
form.Set("txtSalaire", "")
Return form
End Function
Private Sub calculImpot()
' перевіряється правильність введених даних
Dim erreurs As ArrayList = checkData()
' якщо є помилки, про це повідомляється
If erreurs.Count <> 0 Then
' збереження введених даних
Session.Item("formulaire") = Request.Form
' готується сторінка з повідомленням про помилки
context.Items("href") = "main.aspx?action=retour"
context.Items("lien") = "Retour au formulaire"
context.Items("erreurs") = erreurs
Server.Transfer("erreurs.aspx")
End If
' помилок немає — обчислюється податок
Dim impot As Long = CType(Application("objImpot"), impot).calculer( _
Request.Form("rdMarie") = "oui", _
CType(Request.Form("txtEnfants"), Integer), _
CType(Request.Form("txtSalaire"), Long))
' відображається сторінка з результатами
context.Items("txtImpot") = impot.ToString + " euro(s)"
context.Items("formulaire") = Request.Form
Server.Transfer("formulaire.aspx", True)
End Sub
Private Sub retourFormulaire()
' відображається форма зі значеннями, взятими з сесії
Context.Items("formulaire") = Session.Item("formulaire")
Context.Items("txtImpot") = ""
Server.Transfer("formulaire.aspx", True)
End Sub
Private Function checkData() As ArrayList
' спочатку помилок немає
Dim erreurs As New ArrayList
Dim erreur As Boolean = False
' перемикач «одружений»
Try
Dim rdMarie As String = Request.Form("rdMarie").ToString
If rdMarie <> "oui" And rdMarie <> "non" Then
Throw New Exception
End If
Catch
erreurs.Add("Vous n'avez pas indiqué votre statut marital")
End Try
' кількість дітей
Try
Dim txtEnfants As String = Request.Form("txtEnfants").ToString
Dim nbEnfants As Integer = CType(txtEnfants, Integer)
If nbEnfants < 0 Then Throw New Exception
Catch
erreurs.Add("Le nombre d'enfants est incorrect")
End Try
' зарплата
Try
Dim txtSalaire As String = Request.Form("txtSalaire").ToString
Dim salaire As Integer = CType(txtSalaire, Long)
If salaire < 0 Then Throw New Exception
Catch
erreurs.Add("Le salaire annuel est incorrect")
End Try
' виводимо список помилок
Return erreurs
End Function
End Class
Контролер спочатку перевіряє, чи правильно ініціалізувався додаток:
' насамперед перевіряємо, чи вдалося програмі правильно запуститися
If CType(Application("erreur"), Boolean) Then
' перенаправляємо на сторінку помилок
Dim erreurs As New ArrayList
erreurs.Add("Application momentanément indisponible...")
context.Items("erreurs") = erreurs
context.Items("lien") = ""
context.Items("href") = ""
Server.Transfer("erreurs.aspx")
End If
Якщо контролер виявляє, що додаток не зміг правильно ініціалізуватися (не вдалося створити об’єкт [impot], необхідний для розрахунку), він відображає сторінку помилок із відповідними параметрами. У цьому випадку немає необхідності розміщувати посилання для повернення до форми, оскільки весь додаток виявляється недоступним. Загальне повідомлення про помилку розміщується в об’єкті [Context.Items("erreurs")] типу [ArrayList].
Якщо контролер виявляє, що додаток працює, він аналізує дію, яку йому доручено виконати, за допомогою параметра [action]. Ми вже неодноразово стикалися з таким режимом роботи. Обробка кожного типу дії делегується функції.
6.1.9.1. Дії init, effacer
Ці дві дії повинні відображати порожню форму введення даних. Нагадаємо, що вона (див. «види») має два параметри:
- Context.Items("форма"): словник типу [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), поміщаються в сесію, пов’язані з ключем «форма». Це дозволить отримати їх під час наступного запиту.
Тут можна задатися питанням, чи доцільно перевіряти наявність полів [rdMarie, txtEnfants, txtSalaire] у запиті, надісланому клієнтом. Це непотрібно, якщо ми впевнені, що наш клієнт — це браузер, який отримав сторінку [formulaire.aspx], що містить ці поля. Однак у цьому ніколи не можна бути впевненим. Трохи пізніше ми наведемо приклад, де клієнтом є вже згаданий додаток [curl]. Ми надішлемо запит до додатка, не надсилаючи полів, яких він очікує, і подивимося, як він відреагує. Це правило вже неодноразово озвучувалося, і ми нагадуємо про нього тут: додаток ніколи не повинен робити припущень щодо типу клієнта, який надсилає йому запит. З міркувань безпеки він повинен враховувати, що запит може надходити від програмованого додатка, який може надсилати йому несподівані рядки параметрів. Він повинен коректно поводитися в будь-якому випадку.
У нашому випадку ми перевірили, що поля [rdMarie, txtEnfants, txtSalaire] були присутні в запиті, але не перевірили, чи міг він містити й інші. У цьому додатку вони ігнорувалися б. Проте, знову ж таки з міркувань безпеки, було б доцільно записати такий запит у файл журналу та надіслати сповіщення адміністратору додатка, щоб той знав, що додаток отримує «дивні» запити. Аналізуючи їх у файлі журналу, адміністратор міг би виявити можливу атаку на додаток і вжити необхідних заходів для його захисту.
Якщо очікувані дані правильні, контролер запускає розрахунок податку з об’єктом [impot], що зберігається в додатку. Потім він зберігає в контексті дві інформації, які очікує представлення [formulaire.aspx]:
- Context.Items («форма»): словник типу [NameValueCollection], що містить значення полів HTML, [rdmarie,txtEnfants,txtSalaire], а також [Request.Form)] та c.a.d. значення, введені раніше у форму
- Context.Items("txtImpot") — щойно отримане значення податку
Уважний читач, можливо, задав собі питання під час читання вищевикладеного: оскільки об’єкт [impot], створений під час запуску програми, є спільним для всіх запитів, чи не можуть виникнути конфлікти доступу, що призведуть до пошкодження даних об’єкта [impot]. Щоб відповісти на це питання, нам слід повернутися до коду класу [impot]. Запити використовують метод [impot].calculerImpot для отримання суми податку до сплати. Отже, саме цей код нам і слід розглянути:
Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Long) As Long
' розрахунок кількості часток
Dim nbParts As Decimal
If marié Then
nbParts = CDec(nbEnfants) / 2 + 2
Else
nbParts = CDec(nbEnfants) / 2 + 1
End If
If nbEnfants >= 3 Then
nbParts += 0.5D
End If
' розрахунок оподатковуваного доходу та сімейного коефіцієнта
Dim revenu As Decimal = 0.72D * salaire
Dim QF As Decimal = revenu / nbParts
' розрахунок податку
limites((limites.Length - 1)) = QF + 1
Dim i As Integer = 0
While QF > limites(i)
i += 1
End While
Dim impot As Long = CLng(revenu * coeffR(i) - nbParts * coeffN(i))
Return impot
End Function
Припустимо, що потік виконує попередній метод і його переривають. Тоді цей метод виконує інший потік. Які ризики при цьому існують? Щоб це з’ясувати, ми додали такий код:
Dim impot As Long = CLng(revenu * coeffR(i) - nbParts * coeffN(i))
' очікування 10 секунд
Thread.Sleep(10000)
Return impot
Потік 1, обчисливши значення [impot1] локальної змінної [impot], переривається. Потім виконується потік 2, який обчислює нове значення [impot2] для тієї самої змінної [impot], перш ніж його перервати. Потік 1 знову отримує контроль. Що він знаходить у локальній змінній [impot]? Оскільки ця змінна є локальною для методу, вона зберігається у структурі пам’яті, яка називається стеком. Цей стек є частиною контексту потоку, який зберігається під час його переривання. Коли запускається потік 2, його контекст створюється з новим стеком і, отже, новою локальною змінною [impot]. Коли почергово буде перервано роботу потоку 2, його контекст також буде збережено. Коли потік 1 запускається знову, його контекст відновлюється, у тому числі й стек. Тоді він отримує свою локальну змінну [impot], а не змінну потоку 2. Отже, ми маємо ситуацію, в якій між запитами немає конфліктів доступу. Тести, проведені з вищезазначеною 10-секундною паузою, підтвердили, що одночасні запити дійсно дають очікуваний результат.
6.1.9.3. Дія повернення
Ця дія відповідає активації посилання [Retour vers le formulaire] у вікні [erreurs.aspx] для повернення до вікна [formulaire.aspx], попередньо заповненого значеннями, введеними раніше та збереженими у сесії. Функція [retourFormulaire] отримує цю інформацію. Два параметри, які очікує вигляд [formulaire.aspx], ініціалізуються:
- Context.Items("форма") — значеннями, введеними раніше та збереженими у сесії
- 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 міститься сесійний файл cookie. Ми будемо використовувати його в наступних запитах для підтримки сесії. Надішлімо запит на дію [calcul] без вказання параметрів:
dos>curl --cookie ASP.NET_SessionId=ivthkl45tjdjrzznevqsf255 --include --url http://localhost/impots1/main.aspx?action=calcul
HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Thu, 01 Apr 2004 15:22:42 GMT
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 380
Connection: Close
<HTML>
<HEAD>
<title>Impôt</title>
</HEAD>
<body>
<P>Les erreurs suivantes se sont produites :</P>
<HR>
<ul>
<li> Vous n'avez pas indiqué votre statut marital</li>
<li> Le nombre d'enfants est incorrect</li>
<li> Le salaire annuel est incorrect</li>
</ul>
<a href="main.aspx?action=retour">
Retour au formulaire
</a>
</body>
</HTML>
Ми бачимо, що веб-додаток повернув вигляд [erreurs] із трьома повідомленнями про помилки щодо трьох відсутніх параметрів. Тепер надішлімо неправильні параметри:
dos>curl --cookie ASP.NET_SessionId=ivthkl45tjdjrzznevqsf255 --include --data rdMarie=xx --data txtEnfants=xx --data txtSalaire=xx --url http://localhost/impots1/main.aspx?action=calcul
HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Thu, 01 Apr 2004 15:25:50 GMT
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 380
Connection: Close
<HTML>
<HEAD>
<title>Impôt</title>
</HEAD>
<body>
<P>Les erreurs suivantes se sont produites :</P>
<HR>
<ul>
<li> Vous n'avez pas indiqué votre statut marital</li>
<li> Le nombre d'enfants est incorrect</li>
<li> Le salaire annuel est incorrect</li>
</ul>
<a href="main.aspx?action=retour">
Retour au formulaire
</a>
</body>
</HTML>
Ці три помилки були правильно виявлені. Тепер надішлімо правильні параметри:
dos>curl --cookie ASP.NET_SessionId=ivthkl45tjdjrzznevqsf255 --include --data rdMarie=oui --data txtEnfants=2 --data txtSalaire=60000 --url http://localhost/impots1/main.aspx?action=calcul
HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Thu, 01 Apr 2004 15:28:24 GMT
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 1000
Connection: Close
<html>
<head>
<title>Impôt</title>
</head>
<body>
<P>Calcul de votre impôt</P>
<HR width="100%" SIZE="1">
<form method="post" action="main.aspx?action=calcul">
<TABLE border="0">
<TR>
<TD>Etes-vous marié(e)</TD>
<TD>
<INPUT type="radio" value="oui" name="rdMarie" checked>Oui <INPUT type="radio" value="non" name="rdMarie" >Non</TD>
</TR>
<TR>
<TD>Nombre d'enfants</TD>
<TD><INPUT type="text" size="3" maxLength="3" name="txtEnfants" value="2"></TD>
</TR>
<TR>
<TD>Salaire annuel (euro)</TD>
<TD><INPUT type="text" maxLength="12" size="12" name="txtSalaire" value="60000"></TD>
</TR>
<TR>
<TD>Impôt à payer :
</TD>
<TD>4300 euro(s)</TD>
</TR>
</TABLE>
<hr>
<P>
<INPUT type="submit" value="Calculer">
</P>
</form>
<form method="post" action="main.aspx?action=effacer">
<INPUT type="submit" value="Effacer">
</form>
</body>
</html>
Ми правильно розрахували суму податку до сплати: 4300 євро. З цього прикладу ми зробимо висновок, що не слід обманювати себе тим, що ми пишемо веб-додаток, призначений для клієнтів, які є браузерами. Веб-додаток — це служба TCP/IP, і цей мережевий протокол не дозволяє визначити характер клієнтського додатка, що використовує цю службу. Отже, ми не можемо знати, чи є клієнт веб-додатка браузером, чи ні. Тому дотримуємося двох правил:
- при отриманні запиту від клієнта не робимо жодних припущень щодо клієнта та перевіряємо, чи очікувані параметри у запиті дійсно присутні та є дійсними
- складаємо відповідь, призначену для браузерів, тобто, як правило, документи у форматі HTML
Веб-додаток можна побудувати так, щоб він одночасно обслуговував різних клієнтів, наприклад, браузери та мобільні телефони. У такому разі в кожен запит можна включити новий параметр, що вказує тип клієнта. Так, браузер запитає про розрахунок податку за допомогою запиту за адресою http://machine/impots/main.aspx?client=navigateur&action=calcul, тоді як мобільний телефон надсилатиме запит за адресою http://machine/impots/main.aspx?client=mobile&action=calcul. Структура MVC спрощує написання такого додатка. Вона виглядає наступним чином:

Блок [Classes métier, Classes d'accès aux données] не змінюється. Адже це частина, яка не має значення для клієнта. Блок [Contrôleur] змінюється незначно, але повинен враховувати новий параметр у запиті — параметр [client], що вказує, з яким типом клієнта він має справу. Блок [vues] повинен генерувати види для кожного типу клієнта. Було б доцільно вже на етапі проектування додатка врахувати наявність параметра [client] у запиті, навіть якщо в коротко- або середньостроковій перспективі ми орієнтуємося виключно на веб-браузери. Якщо в майбутньому додатку доведеться підтримувати новий тип клієнта, потрібно буде написати лише відповідні для нього види.
6.2. Приклад 2
6.2.1. Проблема
Тут ми пропонуємо розглянути ту саму проблему, що й раніше, але зі зміною джерела даних об’єкта [impot], створеного веб-додатком. У попередній версії використовувалося джерело даних, яке надавало значення масивів, «жорстко» записаних у коді. Цього разу нове джерело даних буде отримувати їх із джерела даних ODBC, пов’язаного з базою даних MySQL.
6.2.2. Джерело даних ODBC
Дані будуть міститися в таблиці з назвою [IMPOTS] у базі даних MySQL, яка називається [dbimpots]. Вміст цієї таблиці буде таким:

Власником бази даних є користувач [admimpots] з паролем [mdpimpots]. Ми пов’язуємо джерело даних ODBC із цією базою даних. Перш ніж це зробити, нагадаємо про різні способи доступу до бази даних за допомогою платформи .NET.
Існує безліч баз даних для платформ Windows. Для доступу до них програми використовують спеціальні програми, які називаються драйверами (drivers).

На схемі вище драйвер має два інтерфейси:
- інтерфейс I1, що надається додатку
- інтерфейс I2, що з’єднує програму з базою даних
Щоб уникнути необхідності переписання програми, написаної для бази даних B1, у разі переходу на іншу базу даних B2, було проведено стандартизацію інтерфейсу I1. Якщо використовуються бази даних із «нормалізованими» драйверами, база B1 буде постачатися з драйвером P1, база B2 — з драйвером P2, і інтерфейс I1 для цих двох драйверів буде однаковим. Отже, переписати додаток не доведеться. Таким чином, наприклад, можна буде перенести базу даних ACCESS у базу даних MySQL без внесення змін у програму.
Існує два типи стандартизованих драйверів:
- драйвери ODBC (Open DataBase Connectivity)
- драйвери OLE та DB (Object Linking and Embedding DataBase)
Драйвери ODBC забезпечують доступ до баз даних. Джерела даних для драйверів OLE та DB є більш різноманітними: бази даних, поштові системи, довідники тощо. Обмежень немає. Будь-яке джерело даних може стати об’єктом драйвера Ole DB, якщо розробник вирішить це зробити. Переваги, звісно, значні: ми отримуємо уніфікований доступ до великої різноманітності даних.
Платформа .NET 1.1 постачається з трьома типами класів доступу до даних:
- класи SQL та Server.NET для доступу до баз даних Microsoft SQL Server
- класи Ole Db.NET для доступу до баз даних SGBD, що підтримують драйвери OLE та DB
- класи odbc.net, для доступу до баз даних SGBD, що надають драйвер ODBC
SGBD MySQL вже давно має драйвер ODBC. Саме його ми зараз використовуємо. У Windows ми вибираємо опцію [Menu Démarrer/Panneau de configuration/Outils d'administration/Sources ODBC 32 bits]. Залежно від версії Windows цей шлях може дещо відрізнятися. Ми отримуємо таку програму, яка дозволить нам створити наш файл-джерело ODBC:

Ми створимо системне джерело даних c.a.d — джерело даних, до якого матиме доступ будь-який користувач комп’ютера utiliser.Aussi. Для цього вибираємо вкладку [Source de données système]. На цій сторінці є кнопка [Ajouter], яку ми використовуємо для створення нового джерела даних ODBC:

Майстер запропонує вибрати драйвер ODBC, який потрібно використовувати. У Windows вже встановлено кілька драйверів ODBC. Драйвер ODBC від MySQL не входить до цього набору. Тому його спочатку потрібно встановити. Його можна знайти в Інтернеті, ввівши у пошукову систему ключову фразу «MySQL ODBC» або «MyODBC». Тут ми встановили драйвер [MySQL ODBC 3.51]. Вибираємо його та виконуємо [Terminer]:

Необхідно вказати певну інформацію:
ім’я, яке позначатиме джерело даних ODBC. Будь-яка програма Windows зможе отримати доступ до джерела за цим ім’ям | |
довільний текст, що описує джерело даних | |
ім'я комп'ютера, на якому розміщено SGBD MySQL. У даному випадку це локальний комп’ютер. Це може бути й віддалений комп’ютер. Це дозволить програмі Windows отримати доступ до віддаленої бази даних без будь-якого спеціального кодування. У цьому полягає велика цінність джерела ODBC. | |
SGBD та MySQL можуть керувати кількома базами даних. Тут вказується, якою саме базою даних потрібно керувати: dbimpots | |
ім'я користувача, зареєстрованого в SGBD MySQL. Саме під цим ім'ям буде здійснюватися доступ до джерела даних. У даному випадку: admimpots | |
пароль цього користувача. У даному випадку: mdpimpots | |
робочий порт SGBD MySQL. За замовчуванням це порт 3306. Ми його не змінювали |
Після цього перевіряємо правильність наших параметрів підключення за допомогою кнопки [Test Data Source]:

Після цього ми впевнені в нашому джерелі даних ODBC. Тепер ми можемо ним користуватися. Ми виконуємо [OK] стільки разів, скільки потрібно, щоб вийти з майстра ODBC.
Якщо у читача немає SGBD mySQL, він може безкоштовно завантажити його за посиланням [http://www.mysql.com]. Нижче наведено покрокову інструкцію щодо створення джерела даних ODBC за допомогою Access. Перші кроки збігаються з описаними вище. Додаємо нове джерело системних даних:

Вибраним драйвером буде [Microsoft Access Driver]. Виконуємо [Terminer], щоб перейти до визначення джерела ODBC:

Необхідно вказати таку інформацію:
ім’я, яке позначатиме джерело даних ODBC. Будь-яка програма 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
' метод доступу до даних
Public MustOverride Function getData() As Object()
' метод перевірки даних
Protected Function checkData() As Integer
' перевіряє отримані дані
...
End Function
' перевіряє правильність вмісту масиву
Protected Function check(ByRef tableau() As Decimal, ByVal n As Integer) As Boolean
...
End Function
End Class
End Namespace
Клас, що успадковує [impotsData], повинен реалізувати два методи:
- конструктор, якщо конструктор без аргументів класу [impotsData] йому не підходить
- метод [getData], який повертає три масиви (limites, coeffr, coeffn)
Ми створюємо клас [impotsODBC], який буде отримувати дані (limites, coeffr, coeffn) із джерела ODBC, якому ми надамо ім’я:
Imports System.Data.Odbc
Imports System.Data
Imports System.Collections
Imports System
Namespace st.istia.univangers.fr
Public Class impotsODBC
Inherits impotsData
' змінні екземпляру
Protected DSNimpots As String
' конструктор
Public Sub New(ByVal DSNimpots As String)
' записуємо три відомості
Me.DSNimpots = DSNimpots
End Sub
Public Overrides Function getdata() As Object()
' ініціалізує три масиви limits, coeffr, coeffn на основі
' вмісту таблиці [impots] у базі даних ODBC DSNimpots
' «limites», «coeffr» та «coeffn» — це три стовпці цієї таблиці
' може генерувати різні винятки
Dim connectString As String = "DSN=" + DSNimpots + ";" ' chaîne de connexion à la base
Dim impotsConn As OdbcConnection = Nothing ' la connexion
Dim sqlCommand As OdbcCommand = Nothing ' la commande SQL
' запит SELECT
Dim selectCommand As String = "select limites,coeffr,coeffn from impots"
' таблиці для отримання даних
Dim aLimites As New ArrayList
Dim aCoeffR As New ArrayList
Dim aCoeffN As New ArrayList
Try
' здійснюється спроба доступу до бази даних
impotsConn = New OdbcConnection(connectString)
impotsConn.Open()
' створюється об’єкт команди
sqlCommand = New OdbcCommand(selectCommand, impotsConn)
' виконується запит
Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader()
' Обробка отриманої таблиці
While myReader.Read()
' дані поточного рядка заносяться в масиви
aLimites.Add(myReader("limites"))
aCoeffR.Add(myReader("coeffr"))
aCoeffN.Add(myReader("coeffn"))
End While
' звільнення ресурсів
myReader.Close()
impotsConn.Close()
Catch e As Exception
Throw New Exception("Erreur d'accès à la base de données (" + e.Message + ")")
End Try
' динамічні масиви записуються у статичні масиви
Me.limites = New Decimal(aLimites.Count - 1) {}
Me.coeffr = New Decimal(aLimites.Count - 1) {}
Me.coeffn = New Decimal(aLimites.Count - 1) {}
Dim i As Integer
For i = 0 To aLimites.Count - 1
limites(i) = Decimal.Parse(aLimites(i).ToString())
coeffR(i) = Decimal.Parse(aCoeffR(i).ToString())
coeffN(i) = Decimal.Parse(aCoeffN(i).ToString())
Next i
' перевірка отриманих даних
Dim erreur As Integer = checkData()
' якщо дані недійсні, то генерується виняток
If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
' інакше повертаються три масиви
Return New Object() {limites, coeffr, coeffn}
End Function
End Class
End Namespace
Звернемо увагу на виробника:
' конструктор
Public Sub New(ByVal DSNimpots As String)
' записуємо три значення
Me.DSNimpots = DSNimpots
End Sub
Він отримує як параметр ім'я джерела ODBC, у якому містяться дані, що підлягають зчитуванню. Конструктор лише запам'ятовує це ім'я. Метод [getData] відповідає за зчитування даних із таблиці [impots] та їхнє розміщення у трьох масивах (limites, coeffr, coeffn). Прокоментуємо його код:
- параметри підключення до джерела даних ODBC визначені, але саме джерело не відкрито
' рядок підключення до бази даних
Dim connectString As String = "DSN=" + DSNimpots + ";"
' створюємо об’єкт підключення до бази даних — це підключення не відкрито
Dim impotsConn As OdbcConnection = New OdbcConnection(connectString)
- визначаються три об’єкти [ArrayList] для отримання даних із таблиці [impots]:
' таблиці для отримання даних
Dim aLimites As New ArrayList
Dim aCoeffR As New ArrayList
Dim aCoeffN As New ArrayList
- Весь код доступу до бази даних обгорнуто блоком try/catch для обробки можливих помилок доступу. Відкриваємо з’єднання з базою даних:
' здійснюється спроба доступу до бази даних
impotsConn = New OdbcConnection(connectString)
impotsConn.Open()
- виконуємо команду [select] на відкритому з’єднанні. Отримуємо об’єкт [OdbcDataReader], який дозволить нам переглянути рядки таблиці, отриманої в результаті запиту SELECT:
' створюється об’єкт команди
Dim sqlCommand As OdbcCommand = New OdbcCommand(selectCommand, impotsConn)
' виконується запит
Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader()
- Ми переглядаємо таблицю результатів рядок за рядком. Для цього використовуємо метод [Read] об’єкта [OdbcDataReader], отриманого раніше. Цей метод виконує дві дії:
- переміщається на один рядок вперед у таблиці. Спочатку позиція знаходиться перед першим рядком
- вона повертає логічне значення [true], якщо перехід відбувся, або [false] в іншому випадку; останній випадок означає, що всі рядки вже оброблено.
Столбці поточного рядка об’єкта [OdbcDataReader] отримуються за допомогою OdbcDataReader. Отримуємо об’єкт, що представляє значення стовпця. Ми пробігаємо всю таблицю, щоб помістити її вміст у три об’єкти [ArrayList]:
' Обробка отриманої таблиці
While myReader.Read()
' дані поточного рядка заносяться в масиви
aLimites.Add(myReader("limites"))
aCoeffR.Add(myReader("coeffr"))
aCoeffN.Add(myReader("coeffn"))
- Після цього ми звільняємо ресурси, пов’язані з підключенням:
- Вміст трьох об’єктів [ArrayList] переноситься у три звичайні масиви:
' динамічні масиви записуються у статичні масиви
limites = New Decimal(aLimites.Count - 1) {}
coeffr = New Decimal(aLimites.Count - 1) {}
coeffn = New Decimal(aLimites.Count - 1) {}
Dim i As Integer
For i = 0 To aLimites.Count - 1
limites(i) = CType(aLimites(i), Decimal)
coeffR(i) = CType(aCoeffR(i), Decimal)
coeffN(i) = CType(aCoeffN(i), Decimal)
Next i
- після того, як дані з таблиці [impots] потрапили до трьох масивів, залишається лише перевірити їхній вміст за допомогою методу [checkData] базового класу [impotsData]:
' перевірка отриманих даних
Dim erreur As Integer = checkData()
' якщо дані недійсні, то генерується виняток
If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
' інакше повертаються три масиви
Return New Object() {limites, coeffr, coeffn}
6.2.4. Тести класу доступу до даних
Програма тестування може виглядати так:
Option Explicit On
Option Strict On
' простори імен
Imports System
Imports Microsoft.VisualBasic
Namespace st.istia.univangers.fr
' тестова сторінка
Module testimpots
Sub Main(ByVal arguments() As String)
' інтерактивна програма розрахунку податку
' користувач вводить три значення з клавіатури: одружений nbEnfants зарплата
' після чого програма відображає суму податку до сплати
Const syntaxe1 As String = "pg DSNimpots"
Const syntaxe2 As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"
' перевірка параметрів програми
If arguments.Length <> 1 Then
' повідомлення про помилку
Console.Error.WriteLine(syntaxe1)
' кінець
Environment.Exit(1)
End If
' отримуємо аргументи
Dim DSNimpots As String = arguments(0)
' створення об’єкта податку
Dim objImpot As impot = Nothing
Try
objImpot = New impot(New impotsODBC(DSNimpots))
Catch ex As Exception
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
Environment.Exit(2)
End Try
' нескінченний цикл
While True
' спочатку помилок немає
Dim erreur As Boolean = False
' запитуються параметри розрахунку податку
Console.Out.Write("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :")
Dim paramètres As String = Console.In.ReadLine().Trim()
' що робити?
If paramètres Is Nothing Or paramètres = "" Then
Exit While
End If
' перевірка кількості аргументів у введеному рядку
Dim args As String() = paramètres.Split(Nothing)
Dim nbParamètres As Integer = args.Length
If nbParamètres <> 3 Then
Console.Error.WriteLine(syntaxe2)
erreur = True
End If
Dim marié As String
Dim nbEnfants As Integer
Dim salaire As Integer
If Not erreur Then
' перевірка правильності параметрів
' одружений
marié = args(0).ToLower()
If marié <> "o" And marié <> "n" Then
Console.Error.WriteLine((syntaxe2 + ControlChars.Lf + "Argument marié incorrect : tapez o ou n"))
erreur = True
End If
' nbEnfants
nbEnfants = 0
Try
nbEnfants = Integer.Parse(args(1))
If nbEnfants < 0 Then
Throw New Exception
End If
Catch
Console.Error.WriteLine(syntaxe2 + "\nArgument nbEnfants incorrect : tapez un entier positif ou nul")
erreur = True
End Try
' зарплата
salaire = 0
Try
salaire = Integer.Parse(args(2))
If salaire < 0 Then
Throw New Exception
End If
Catch
Console.Error.WriteLine(syntaxe2 + "\nArgument salaire incorrect : tapez un entier positif ou nul")
erreur = True
End Try
End If
If Not erreur Then
' параметри правильні — розраховується податок
Console.Out.WriteLine(("impôt=" & objImpot.calculer(marié = "o", nbEnfants, salaire).ToString + " euro(s)"))
End If
End While
End Sub
End Module
End Namespace
Додаток запускається з одним параметром:
- DSNimpots: ім’я джерела даних ODBC, яке слід використовувати
Розрахунок податку здійснюється за допомогою об’єкта типу [impot], який створюється одразу після запуску програми:
' створення об’єкта податку
Dim objImpôt As impot = Nothing
Try
objImpot = New impot(New impotsODBC(DSNimpots))
Catch ex As Exception
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
Environment.Exit(1)
End Try
Після запуску програма неодноразово просить користувача ввести з клавіатури три дані, необхідні для розрахунку податку:
- сімейний стан: «o» — одружений, «n» — неодружений
- кількість дітей
- річний дохід
Усі класи компілюються:
dos>vbc /r:system.dll /r:system.data.dll /t:library /out:impot.dll impots.vb impotsArray.vb impotsData.vb impotsODBC.vb
dos>dir
01/04/2004 19:34 7 168 impot.dll
01/04/2004 19:31 1 360 impots.vb
21/04/2004 08:23 1 311 impotsArray.vb
21/04/2004 08:26 1 634 impotsData.vb
01/04/2004 19:34 2 735 impotsODBC.vb
01/04/2004 19:32 3 210 testimpots.vb
Тестова програма, у свою чергу, компілюється:
dir>dir
01/04/2004 19:34 7 168 impot.dll
01/04/2004 19:31 1 360 impots.vb
21/04/2004 08:23 1 311 impotsArray.vb
21/04/2004 08:26 1 634 impotsData.vb
01/04/2004 19:34 2 735 impotsODBC.vb
01/04/2004 19:34 6 144 testimpots.exe
01/04/2004 19:32 3 210 testimpots.vb
Програма тестування спочатку виконується з джерелом даних ODBC MySQL:
dos>testimpots odbc-mysql-dbimpots
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 60000
impôt=4300 euro(s)
Змінюємо джерело на ODBC, щоб використовувати джерело Access:
dos>testimpots odbc-access-dbimpots
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 60000
impôt=4300 F
6.2.5. Види веб-додатку
Це ті самі, що й у попередньому додатку: formulaire.aspx та erreurs.aspx
6.2.6. Контролери додатка [global.asax, main.aspx]
Змінити потрібно лише контролер [global.asax]. Він відповідає за створення об’єкта [impot] під час запуску додатка. Єдиним параметром конструктора цього об’єкта є об’єкт типу [impotsData], який відповідає за отримання даних. Отже, цей параметр змінюється для кожного нового типу джерел даних. Контролер [global.asax.vb] набуває такого вигляду:
Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr
Imports System.Configuration
Public Class Global
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' створюється об’єкт податку
Dim objImpot As impot
Try
objImpot = New impot(New impotsODBC(ConfigurationSettings.AppSettings("DSNimpots")))
' об'єкт додано до програми
Application("objImpot") = objImpot
' помилок немає
Application("erreur") = False
Catch ex As Exception
'сталася помилка, її фіксують у додатку
Application("erreur") = True
Application("message") = ex.Message
End Try
End Sub
End Class
Джерелом даних об'єкта [impot] тепер є об'єкт [impotODBC]. Параметром останнього є ім'я DSN джерела даних ODBC, яке потрібно використовувати. Замість того, щоб вказувати це ім’я безпосередньо в коді, його вказують у конфігураційному файлі [web.config] додатка:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="DSNimpots" value="odbc-mysql-dbimpots" />
</appSettings>
</configuration>
Відомо, що значення ключа C у розділі <appSettings> файлу [web.config] отримується в коді програми за допомогою [ConfigurationSettings.AppSettings(C)].
Щоб з’ясувати причину виникнення винятку, його повідомлення записується в програмі, щоб воно залишалося доступним для запитів. Контролер [main.aspx.vb] включить це повідомлення до свого списку помилок:
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' насамперед перевіряємо, чи вдалося додатку правильно ініціалізуватися
If CType(Application("erreur"), Boolean) Then
' перенаправляємо на сторінку помилок
Dim erreurs As New ArrayList
erreurs.Add("Application momentanément indisponible...(" + Application("message").ToString + ")")
context.Items("erreurs") = erreurs
context.Items("lien") = ""
context.Items("href") = ""
Server.Transfer("erreurs.aspx")
End If
' визначаємо необхідну дію
...
6.2.7. Підсумок змін
Додаток готовий до тестування. Перелічимо зміни, внесені до попередньої версії:
- створено новий клас для доступу до даних
- контролер [global.asax.vb] було змінено у двох місцях: створення об’єкта [impot] та запис у програмі повідомлення, пов’язаного з можливим винятком
- контролер [main.aspx.vb] було змінено в одному місці для відображення попереднього повідомлення про виняток
- було додано файл [web.config]
Робота з внесення змін в основному проводилася в 1, c.a.d. поза межами веб-додатку. Це стало можливим завдяки архітектурі MVC додатка, яка відокремлює контролер від бізнес-класів. У цьому і полягає вся цінність цієї архітектури. Можна було б продемонструвати, що за допомогою відповідного конфігураційного файлу [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 було недоступним, зупинивши SGBD та MySQL, і запитуємо URL-адресу [http://localhost/impots2/main.aspx]. Ми отримуємо таку відповідь:

6.3. Приклад 3
6.3.1. Проблема
Тут ми пропонуємо розглянути ту саму проблему, знову змінивши джерело даних об’єкта [impot], створеного веб-додатком. Цього разу новим джерелом даних буде база даних ACCESS, доступ до якої здійснюватиметься через драйвер OLEDB. Наша мета — продемонструвати інший спосіб доступу до бази даних.
6.3.2. Джерело даних OLEDB
Дані міститимуться в таблиці з назвою [IMPOTS] у базі даних ACCESS. Вміст цієї таблиці буде таким:

6.3.3. Клас доступу до даних
Повернемося до структури MVC нашого додатка:

- На схемі вище клас [impotsData] відповідає за отримання даних. Цього разу він повинен зробити це з джерела OLEDB.
Ми створюємо клас [impotsOLEDB], який буде отримувати дані (limites, coeffr, coeffn) із джерела ODBC, якому ми надамо таку назву:
Imports System.Data
Imports System.Collections
Imports System
Imports System.Xml
Imports System.Data.OleDb
Namespace st.istia.univangers.fr
Public Class impotsOLEDB
Inherits impotsData
' змінні екземпляра
Protected chaineConnexion As String
' конструктор
Public Sub New(ByVal chaineConnexion As String)
' записуються три значення
Me.chaineConnexion = chaineConnexion
End Sub
Public Overrides Function getData() As Object()
' ініціалізує три масиви: limits, coeffr, coeffn на основі
' вмісту таблиці [impots] у базі даних OLEDB [chaineConnexion]
' «limites», «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]. Запускаємо цей інструмент. Він відкриває вікно, що дозволяє підключитися до джерела даних:
![]() ![]() | ![]() ![]() |
За допомогою піктограми, позначеної стрілкою вище, можна створити з’єднання з двома типами баз даних Microsoft: SQL Server та ACCESS. Оберемо ACCESS:

Ми використали кнопку [...], щоб вказати базу даних ACCESS. Підтверджуємо дії майстра. На вкладці [Data] підключення позначаються піктограмами:

Тепер створимо новий файл .aspx за допомогою [Files/New File]:

Ми отримуємо порожній аркуш, на якому можна намалювати наш веб-інтерфейс:

Перетягнімо з вкладки [Data] таблицю [impots] на аркуш вище. Отримуємо такий результат:

Клацнімо правою кнопкою миші на об’єкті [AccessDataSourceControl] нижче, щоб отримати доступ до його властивостей:

Рядок з’єднання OLEDB із базою ACCESS задається властивістю [ConnectionString], наведеною вище:
Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=D:\data\serge\devel\aspnet\poly\chap5\impots\3\impots.mdb
Бачимо, що цей рядок складається з фіксованої частини та змінної частини, яка є просто іменем файлу ACCESS. Ми використаємо цей факт для генерації рядка підключення до нашого джерела даних OLEDB.
Тепер повернемося до нашого класу [impotsOLEDB]. Метод [getData] відповідає за зчитування даних із таблиці [impots] та їхнє розміщення у трьох масивах (limites, coeffr, coeffn). Прокоментуємо його код:
- ми визначаємо об’єкт [DataAdapter], який дозволить нам перенести в пам’ять результат запиту SQL select. Для цього ми визначаємо запит [select], який потрібно виконати, і пов’язуємо його з об’єктом [DataAdapter]. Конструктор цього об’єкта також вимагає рядок підключення, який він повинен використовувати для підключення до джерела OLEDB
' створюється об’єкт DataAdapter для зчитування даних із джерела OLEDB
Dim adaptateur As New OleDbDataAdapter("select limites,coeffr,coeffn from impots", chaineConnexion)
- команда [select] виконується за допомогою методу [Fill] об’єкта [DataAdapter]. Результат роботи [select] вставляється в об’єкт [DataTable], створений спеціально для цього. Об’єкт [DataTable] є зображенням у пам’яті таблиці бази даних c.a.d — набору рядків і стовпців. Ми обробляємо виняток, який може виникнути, наприклад, у разі неправильного рядка підключення.
' створюємо образ результату запиту в пам'яті
Dim contenu As New DataTable("impots")
Try
adaptateur.Fill(contenu)
Catch e As Exception
Throw New Exception("Erreur d'accès à la base de données (" + e.Message + ")")
End Try
- у [contenu] ми маємо таблицю [impots], яка була приведена об’єктом [select]. Об’єкт [DataTable] є таблицею, а отже — набором рядків. Доступ до них здійснюється через властивість [rows] об’єкта [datatable]:
' отримуємо вміст таблиці impots
Dim lignesImpots As DataRowCollection = contenu.Rows
- кожен елемент колекції [lignesImpots] є об’єктом типу [DataRow], що представляє рядок таблиці. Цей рядок має стовпці, доступ до яких здійснюється через об’єкт [DataRow] за допомогою його властивості [Item]. [DataRow].[Item(i)] — це стовпець № i рядка [DataRow]. Переглядаючи колекцію рядків (колекцію DataRows від lignesImpots) та колекції стовпців кожного рядка, можна отримати всю таблицю:
' визначаємо розміри масивів для прийому даних
Me.limites = New Decimal(lignesImpots.Count - 1) {}
Me.coeffr = New Decimal(lignesImpots.Count - 1) {}
Me.coeffn = New Decimal(lignesImpots.Count - 1) {}
' переносимо вміст таблиці impots у масиви
Dim i As Integer
Dim ligne As DataRow
Try
For i = 0 To lignesImpots.Count - 1
' i-й рядок таблиці
ligne = lignesImpots.Item(i)
' витягується вміст рядка
limites(i) = CType(ligne.Item(0), Decimal)
coeffr(i) = CType(ligne.Item(1), Decimal)
coeffn(i) = CType(ligne.Item(2), Decimal)
Next
Catch
Throw New Exception("Les données des tranches d'impôts n'ont pas le bon type")
End Try
- після того, як дані з таблиці [impots] потрапили до трьох масивів, залишається лише перевірити їхній вміст за допомогою методу [checkData] базового класу [impotsData]:
' перевіряються отримані дані
Dim erreur As Integer = checkData()
' якщо дані недійсні, то генерується виняток
If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
' інакше повертаються три масиви
Return New Object() {limites, coeffr, coeffn}
6.3.4. Тести класу доступу до даних
Програма тестування може виглядати так:
Option Explicit On
Option Strict On
' простори імен
Imports System
Imports Microsoft.VisualBasic
Namespace st.istia.univangers.fr
' тестова сторінка
Module testimpots
Sub Main(ByVal arguments() As String)
' інтерактивна програма для розрахунку податку
' користувач вводить три значення з клавіатури: одружений nbEnfants зарплата
' після чого програма відображає суму податку до сплати
Const syntaxe1 As String = "pg bdACCESS"
Const syntaxe2 As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"
' перевірка параметрів програми
If arguments.Length <> 1 Then
' повідомлення про помилку
Console.Error.WriteLine(syntaxe1)
' кінець
Environment.Exit(1)
End If
' отримуємо аргументи
Dim chemin As String = arguments(0)
' підготовка рядка з'єднання
Dim chaineConnexion As String = "Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=" + chemin
' створення об’єкта податку
Dim objImpot As impot = Nothing
Try
objImpot = New impot(New impotsOLEDB(chaineConnexion))
Catch ex As Exception
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
Environment.Exit(2)
End Try
' нескінченний цикл
While True
' спочатку помилок немає
Dim erreur As Boolean = False
' потрібні параметри для розрахунку податку
Console.Out.Write("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :")
Dim paramètres As String = Console.In.ReadLine().Trim()
' що робити?
If paramètres Is Nothing Or paramètres = "" Then
Exit While
End If
' перевірка кількості аргументів у введеному рядку
Dim args As String() = paramètres.Split(Nothing)
Dim nbParamètres As Integer = args.Length
If nbParamètres <> 3 Then
Console.Error.WriteLine(syntaxe2)
erreur = True
End If
Dim marié As String
Dim nbEnfants As Integer
Dim salaire As Integer
If Not erreur Then
' перевірка правильності параметрів
' одружений
marié = args(0).ToLower()
If marié <> "o" And marié <> "n" Then
Console.Error.WriteLine((syntaxe2 + ControlChars.Lf + "Argument marié incorrect : tapez o ou n"))
erreur = True
End If
' nbEnfants
nbEnfants = 0
Try
nbEnfants = Integer.Parse(args(1))
If nbEnfants < 0 Then
Throw New Exception
End If
Catch
Console.Error.WriteLine(syntaxe2 + "\nArgument nbEnfants incorrect : tapez un entier positif ou nul")
erreur = True
End Try
' зарплата
salaire = 0
Try
salaire = Integer.Parse(args(2))
If salaire < 0 Then
Throw New Exception
End If
Catch
Console.Error.WriteLine(syntaxe2 + "\nArgument salaire incorrect : tapez un entier positif ou nul")
erreur = True
End Try
End If
If Not erreur Then
' параметри правильні — розраховується податок
Console.Out.WriteLine(("impôt=" & objImpot.calculer(marié = "o", nbEnfants, salaire).ToString + " euro(s)"))
End If
End While
End Sub
End Module
End Namespace
Додаток запускається з одним параметром:
- bdACCESS: ім’я файлу ACCESS, який потрібно обробити
Розрахунок податку здійснюється за допомогою об’єкта типу [impot], який створюється одразу після запуску програми:
' отримуємо аргументи
Dim chemin As String = arguments(0)
' готуємо рядок з'єднання
Dim chaineConnexion As String = "Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=" + chemin
' створення об’єкта податку
Dim objImpot As impot = Nothing
Try
objImpot = New impot(New impotsOLEDB(chaineConnexion))
Catch ex As Exception
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
Environment.Exit(2)
End Try
Ланцюжок підключення до джерела OLEDB було побудовано на основі інформації, отриманої за допомогою [WebMatrix].
Після запуску програма неодноразово просить користувача ввести з клавіатури три дані, необхідні для розрахунку податку:
- сімейний стан: o — одружений, n — неодружений
- кількість дітей
- річний дохід
Усі класи компілюються:
dos>vbc /r:system.dll /r:system.data.dll /t:library /out:impot.dll impots.vb impotsArray.vb impotsData.vb impotsOLEDB.vb
Файл [impots.mdb] розміщується в папці тестового додатка, який запускається наступним чином:
dos>testimpots impots.mdb
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 60000
impôt=4300 euro(s)
Можна запустити програму з некоректним файлом ACCESS:
dos>testimpots xx
L'erreur suivante s'est produite : Erreur d'accès à la base de données (Ficher 'D:\data\serge\devel\aspnet\poly\chap5\impots\3\xx' introuvable.)
6.3.5. Види веб-додатку
Це ті самі, що й у попередньому додатку: formulaire.aspx та erreurs.aspx
6.3.6. Контролери додатка [global.asax, main.aspx]
Змінити потрібно лише контролер [global.asax]. Він відповідає за створення об’єкта [impot] під час запуску додатка. Єдиним параметром конструктора цього об’єкта є об’єкт типу [impotsData], який відповідає за отримання даних. Отже, цей параметр змінюється, оскільки змінюється джерело даних. Контролер [global.asax.vb] набуває такого вигляду:
Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr
Imports System.Configuration
Public Class Global
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' створення об’єкта податку
Dim objImpot As impot
Try
objImpot = New impot(New impotsOLEDB(ConfigurationSettings.AppSettings("chaineConnexion")))
' додаємо об’єкт до програми
Application("objImpot") = objImpot
' помилок немає
Application("erreur") = False
Catch ex As Exception
'сталася помилка, її фіксують у додатку
Application("erreur") = True
Application("message") = ex.Message
End Try
End Sub
End Class
Джерелом даних об'єкта [impot] тепер є об'єкт [impotOLEDB]. Параметром останнього є рядок підключення до джерела даних OLEDB, яке потрібно використовувати. Він розміщений у конфігураційному файлі [web.config] додатка:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="chaineConnexion"
value="Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=D:\data\serge\devel\aspnet\poly\chap5\impots2\impots.mdb" />
</appSettings>
</configuration>
Контролер [main.aspx] залишається без змін.
6.3.7. Підсумок змін
Додаток готовий до тестування. Перелічимо зміни, внесені до попередньої версії:
- створено новий клас для доступу до даних
- контролер [global.asax.vb] було змінено в одному місці: створення об’єкта [impot]
- додано файл [web.config]
6.3.8. Тестування веб-додатку
Усі вищезазначені файли розміщуються в папці <application-path>.

У цій папці створюється підпапка [bin], у яку поміщається збірка [impot.dll], отримана в результаті компіляції файлів бізнес-класів: [impots.vb, impotsData.vb, impotsArray.vb, impotsOLEDB.vb]. Нижче наведено необхідну команду компіляції:
dos>vbc /r:system.dll /r:system.data.dll /t:library /out:impot.dll impots.vb impotsArray.vb impotsData.vb impotsOLEDB.vb
Файл [impot.dll], створений цією командою, потрібно розмістити в <application-path>\bin, щоб веб-додаток мав до нього доступ. Сервер Cassini запускається з параметрами (<application-path>,/impots3). Тестування дає ті самі результати, що й у попередній версії.
6.4. Приклад 4
6.4.1. Проблема
Тепер ми пропонуємо перетворити наш додаток на додаток для моделювання розрахунків податків. Користувач зможе виконувати послідовні розрахунки податків, і їх результати будуть відображатися на новій сторінці, схожій на таку:

6.4.2. Структура MVC додатка
Структура MVC додатка виглядає наступним чином:

З’являється новий вигляд [simulations.aspx], знімок екрана якого ми щойно навели. Класом доступу до даних буде клас [impotsODBC] із прикладу 2.
6.4.3. Види веб-додатку
Вигляд [erreurs.aspx] не змінюється. Вигляд [formulaire.aspx] дещо змінюється. Адже сума податку тепер більше не відображається на цьому вигляді. Тепер вона знаходиться на вигляд [simulations.aspx]. Таким чином, при запуску користувачеві відображається така сторінка:

Крім того, вигляд [formulaire] містить скрипт JavaScript, який перевіряє правильність введених даних перед їх надсиланням на сервер, як показано в наступному прикладі:

Код відображення виглядає так:
<%@ page src="formulaire.aspx.vb" inherits="formulaire" AutoEventWireup="false"%>
<html>
<head>
<title>Impôt</title>
<script language="javascript">
function calculer(){
// перевірка параметрів перед їх надсиланням на сервер
with(document.frmImpots){
//кількість дочірніх елементів
champs=/^\s*(\d+)\s*$/.exec(txtEnfants.value);
if(champs==null){
// модель не перевірена
alert("Le nombre d'enfants n'a pas été donné ou est incorrect");
txtEnfants.focus();
return;
}//if
//заробітна плата
champs=/^\s*(\d+)\s*$/.exec(txtSalaire.value);
if(champs==null){
// модель не перевірена
alert("Le salaire n'a pas été donné ou est incorrect");
txtSalaire.focus();
return;
}//якщо
// все гаразд — форму надсилаємо на сервер
submit();
}//з
}//обчислити
</script>
</head>
<body>
<P>Calcul de votre impôt</P>
<HR width="100%" SIZE="1">
<form name="frmImpots" method="post" action="main.aspx?action=calcul">
<TABLE border="0">
<TR>
<TD>Etes-vous marié(e)</TD>
<TD>
<INPUT type="radio" value="oui" name="rdMarie" <%=rdouichecked%>>Oui
<INPUT type="radio" value="non" name="rdMarie" <%=rdnonchecked%>>Non</TD>
</TR>
<TR>
<TD>Nombre d'enfants</TD>
<TD><INPUT type="text" size="3" maxLength="3" name="txtEnfants" value="<%=txtEnfants%>"></TD>
</TR>
<TR>
<TD>Salaire annuel (euro)</TD>
<TD><INPUT type="text" maxLength="12" size="12" name="txtSalaire" value="<%=txtSalaire%>"></TD>
</TR>
</TABLE>
<hr>
<P>
<INPUT type="button" value="Calculer" onclick="calculer()">
</P>
</form>
<form method="post" action="main.aspx?action=effacer">
<INPUT type="submit" value="Effacer">
</form>
</body>
</html>
Динамічні поля на сторінці відповідають попереднім версіям. Динамічне поле суми податку зникло. Кнопка [Calculer] більше не є кнопкою типу [submit]. Вона має тип [button], і при натисканні на неї виконується функція JavaScript [calculer()]:
<INPUT type="button" value="Calculer" onclick="calculer()">
Формі було присвоєно ім’я [frmImpots], щоб можна було посилатися на неї у скрипті [calculer]:
<form name="frmImpots" method="post" action="main.aspx?action=calcul">
Функція JavaScript [calculer] використовує регулярні вирази для перевірки правильності заповнення полів форм [document.frmImpots.txtEnfants] та [document.frmImpots.txtSalaire]. Якщо введені значення є правильними, вони надсилаються на сервер за допомогою [document.frmImpots.submit()].
Сторінка відображення отримує свої динамічні поля від контролера [formulaire.aspx.vb] наступним чином:
Imports System.Collections.Specialized
Public Class formulaire
Inherits System.Web.UI.Page
' поля сторінки
Protected rdouichecked As String
Protected rdnonchecked As String
Protected txtEnfants As String
Protected txtSalaire As String
Protected txtImpot As String
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' отримуємо попередній запит із контексту
Dim form As NameValueCollection = Context.Items("formulaire")
' готуємо сторінку до відображення
' перемикачі
rdouichecked = ""
rdnonchecked = "checked"
If form("rdMarie").ToString = "oui" Then
rdouichecked = "checked"
rdnonchecked = ""
End If
' решта
txtEnfants = CType(form("txtEnfants"), String)
txtSalaire = CType(form("txtSalaire"), String)
End Sub
End Class
Контролер [formulaire.aspx.vb] ідентичний попереднім версіям, за винятком того, що йому більше не потрібно отримувати поле [txtImpot] з контексту, оскільки це поле зникло зі сторінки.
Візуально представлення [simulations.aspx] виглядає наступним чином:

і відповідає такому коду відображення:
<%@ page src="simulations.aspx.vb" inherits="simulations" autoeventwireup="false" %>
<HTML>
<HEAD>
<title>simulations</title>
</HEAD>
<body>
<P>Résultats des simulations</P>
<HR width="100%" SIZE="1">
<table>
<tr>
<th>
Marié</th>
<th>
Enfants</th>
<th>
Salaire annuel (euro)</th>
<th>
Impôt à payer (euro)</th>
</tr>
<%=simulationsHTML%>
</table>
<p></p>
<a href="<%=href%>">
<%=lien%>
</a>
</body>
</HTML>
Цей код містить три динамічні поля:
код HTML зі списку моделювань у вигляді рядків таблиці HTML | |
URL-адреса посилання | |
текст посилання |
Вони генеруються контролером [simulations.aspx.vb]:
Imports System.Collections
Imports Microsoft.VisualBasic
Public Class simulations
Inherits System.Web.UI.Page
Protected simulationsHTML As String = ""
Protected href As String
Protected lien As String
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
'отримуємо симуляції з контексту
Dim simulations As ArrayList = CType(context.Items("simulations"), ArrayList)
' кожна симуляція — це масив із 4 елементів типу string
Dim simulation() As String
Dim i, j As Integer
For i = 0 To simulations.Count - 1
simulation = CType(simulations(i), String())
simulationsHTML += "<tr>"
For j = 0 To simulation.Length - 1
simulationsHTML += "<td>" + simulation(j) + "</td>"
Next
simulationsHTML += "</tr>" + ControlChars.CrLf
Next
' отримуємо інші елементи контексту
href = context.Items("href").ToString
lien = context.Items("lien").ToString
End Sub
End Class
Контролер сторінки отримує інформацію, розміщену контролером додатка в контексті сторінки:
об’єкт ArrayList, що містить список симуляцій, які потрібно відобразити. Кожен елемент — це масив із 4 рядків, що містять інформацію (статус шлюбу, діти, зарплата, податок) про симуляцію. | |
URL-адреса посилання | |
текст посилання |
6.4.4. Контролери [global.asax, main.aspx]
Нагадаємо схему MVC нашого додатка:

Контролер [main.aspx] має обробити три дії:
- init: відповідає першому запиту клієнта. Контролер відображає вигляд [formulaire.aspx]
- calcul: відповідає запиту на розрахунок податку. Якщо дані у формі введення правильні, податок розраховується за допомогою бізнес-класу [impotsODBC]. Контролер повертає клієнту представлення [simulations.aspx] із результатом поточного моделювання та всіх попередніх. Якщо дані у формі введення неправильні, контролер повертає представлення [erreurs.aspx] із переліком помилок та посиланням для повернення до форми.
- повернення: відповідає поверненню до форми після помилки. Контролер відображає вигляд [formulaire.aspx] у тому вигляді, в якому він був перевірений на правильність перед виникненням помилки.
У цій новій версії змінилася лише дія [calcul]. Адже якщо дані є правильними, вона повинна привести до перегляду [simulations.aspx], тоді як раніше вона приводила до перегляду [formulaire.aspx]. Контролер [main.aspx.vb] виглядає наступним чином:
Imports System
...
Public Class main
Inherits System.Web.UI.Page
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' насамперед перевіряємо, чи вдалося програмі правильно ініціалізуватися
...
' виконуємо дію
Select Case action
Case "init"
' ініціалізація програми
initAppli()
Case "calcul"
' розрахунок податку
calculImpot()
Case "retour"
' повернення до форми
retourFormulaire()
Case "effacer"
' ініціалізація додатка
initAppli()
Case Else
' невідома дія = ініціалізація
initAppli()
End Select
End Sub
...
Private Sub calculImpot()
' збереження введених даних
Session.Item("formulaire") = Request.Form
' перевіряється правильність введених даних
Dim erreurs As ArrayList = checkData()
' якщо є помилки, про це повідомляється
If erreurs.Count <> 0 Then
' готується сторінка помилок
context.Items("href") = "main.aspx?action=retour"
context.Items("lien") = "Retour au formulaire"
context.Items("erreurs") = erreurs
Server.Transfer("erreurs.aspx")
End If
' помилок немає — обчислюється податок
Dim impot As Long = CType(Application("objImpot"), impot).calculer( _
Request.Form("rdMarie") = "oui", _
CType(Request.Form("txtEnfants"), Integer), _
CType(Request.Form("txtSalaire"), Long))
' результат додається до існуючих моделювань
Dim simulations As ArrayList
If Not Session.Item("simulations") Is Nothing Then
simulations = CType(Session.Item("simulations"), ArrayList)
Else
simulations = New ArrayList
End If
' додавання поточного моделювання
Dim simulation() As String = New String() {Request.Form("rdMarie").ToString, _
Request.Form("txtEnfants").ToString, Request.Form("txtSalaire").ToString, _
impot.ToString}
simulations.Add(simulation)
' моделювання додано до сесії та контексту
context.Items("simulations") = simulations
Session.Item("simulations") = simulations
' відображаємо сторінку результатів
context.Items("href") = "main.aspx?action=retour"
context.Items("lien") = "Retour au formulaire"
Server.Transfer("simulations.aspx", True)
End Sub
...
End Class
Вище ми навели лише те, що було необхідно для розуміння змін, які містяться виключно у функції [calculImpots]:
- по-перше, функція зберігає форму [Request.Form] у сесії, щоб мати змогу відтворити форму в тому стані, в якому вона була затверджена. Це потрібно робити в будь-якому випадку, оскільки незалежно від того, чи операція завершується відповіддю [erreurs.aspx] чи [simulations.aspx], повернення до форми здійснюється за посиланням [Retour au formulaire]. Щоб правильно відновити форму, необхідно попередньо зберегти її значення в сесії.
- Якщо введені дані виявляються правильними, функція додає поточну симуляцію (статус одруженого, наявність дітей, заробітна плата, податок) до списку симуляцій. Цей список знаходиться в сесії, пов’язаній із ключем «simulations».
- Список симуляцій зберігається у сесії для подальшого використання. Він також розміщується в поточному контексті, оскільки саме там його очікує подання [simulations.aspx]
- Погляд [simulations.aspx] відображається після того, як у контекст буде додано іншу інформацію, на яку він очікує
6.4.5. Підсумок змін
Додаток готовий до тестування. Перелічимо зміни, внесені порівняно з попередніми версіями:
- створено новий вигляд
- контролер [main.aspx.vb] було змінено в одному місці: обробка дії [calcul]
6.4.6. Тестування веб-додатку
Читачеві пропонується провести тестування. Нагадаємо порядок дій. Усі файли додатка розміщені в папці <application-path>. У цій папці створено підпапку [bin], у якій розміщено збірку [impot.dll], отриману в результаті компіляції файлів бізнес-класів: [impots.vb, impotsData.vb, impotsArray.vb, impotsODBC.vb. Файл [impot.dll], створений цією командою, потрібно розмістити в <application-path>\bin, щоб веб-додаток мав до нього доступ. Сервер Cassini запускається з параметрами (<application-path>,/impots4).
6.5. Conclusion
Попередні приклади продемонстрували на конкретному випадку механізми, що часто використовуються у веб-розробці. Ми систематично використовували архітектуру MVC з педагогічною метою. Ці самі приклади можна було б опрацювати інакше, і, можливо, простіше, без цієї архітектури. Однак вона надає значні переваги, щойно додаток стає дещо складнішим і містить багато сторінок.
Ми могли б продовжити наші приклади різними способами. Ось декілька з них:
- користувач може захотіти зберегти свої симуляції з плином часу. Він міг би виконати симуляції в день J і, наприклад, переглянути їх у день J+3. Одним із можливих рішень цієї проблеми є використання файлів cookie. Ми знаємо, що сесійний токен між сервером і клієнтом передається саме за допомогою цього механізму. Його також можна використовувати для передачі симуляцій між клієнтом і сервером.
- одночасно з тим, як сервер надсилає сторінку з результатами симуляцій, він надсилає у своїх заголовках HTTP файл cookie, що містить рядок символів, який представляє симуляції. Оскільки ці дані містяться в об’єкті [ArrayList], необхідно виконати перетворення цього об’єкта в [String]. Сервер призначає файлу cookie термін дії, наприклад, 30 днів.
- Клієнтський браузер зберігає отримані файли cookie у файлі та надсилає їх щоразу, коли надсилає запит до сервера, який їх надіслав, якщо вони все ще дійсні (термін дії не минув). Для моделювання сервер отримає рядок символів [String], який він повинен перетворити на об’єкт [ArrayList].
Файли cookie обробляються об’єктом [Response.Cookies] під час їх надсилання клієнту та об’єктом [Request.Cookies] під час їх отримання на сервері.
- Попередній механізм може стати досить обтяжливим, якщо кількість симуляцій значна. Крім того, користувачі часто періодично очищують свої файли cookie, видаляючи їх усі, навіть якщо водночас дозволяють браузеру їх використовувати. Отже, рано чи пізно файл cookie симуляцій буде втрачено. Тоді може виникнути потреба зберігати їх на сервері, а не на клієнті, наприклад, у базі даних. Щоб пов’язати симуляції з конкретним користувачем, додаток міг би розпочинатися з етапу ідентифікації, що вимагає введення логіна та пароля, які самі по собі зберігаються в базі даних або будь-якому іншому сховищі даних.
- Ми також можемо захотіти забезпечити безпеку роботи нашого додатка. Наразі він базується на двох припущеннях:
- користувач завжди проходить через контролер [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] у сфері веб-програмування на Java.



