Skip to content

6. Приклади

У цьому розділі ми пропонуємо проілюструвати розглянуте вище за допомогою низки прикладів.

6.1. Приклад 1

6.1.1. Задача

Цей додаток має дати змогу користувачеві розрахувати свій податок. Розглянемо спрощений випадок, коли платник податків має задекларувати лише свою заробітну плату (дані за 2004 рік щодо доходів за 2003 рік):

  • кількість часткок працівника обчислюється за формулою nbParts=nbEnfants/2 +1, якщо він не одружений, nbEnfants/2+2, якщо він одружений, де nbEnfants — кількість його дітей.
  • якщо у нього щонайменше троє дітей, він отримує на пів частки більше
  • розраховується його оподатковуваний дохід R = 0,72 * S, де S — його річна заробітна плата
  • розраховується його сімейний коефіцієнт QF = R / nbParts
  • розраховуємо його податок I. Розглянемо таку таблицю:
4262
0
0
8382
0,0683
291,09
14753
0,1914
1322,92
23888
0,2826
2668,39
38868
0,3738
4846,98
47932
0,4262
6883,66
0
0,4809
9505,54

Кожен рядок має 3 поля. Щоб обчислити податок I, шукаємо перший рядок, де QF <= поле1. Наприклад, якщо QF = 5000, то знайдемо рядок

    8382        0.0683        291.09

Тоді податок I дорівнює 0,0683*R - 291,09*nbParts. Якщо QF є таким, що співвідношення QF<=поле1 ніколи не виконується, то використовуються коефіцієнти останнього рядка. У даному випадку:

    0                0.4809    9505.54

що дає податок I = 0,4809*R - 9505,54*nbParts.

6.1.2. Структура MVC додатка

Структура MVC додатка матиме такий вигляд:

Image

Роль контролера виконуватиме сторінка [main.aspx]. Можливі три дії:

  • init: відповідає першому запиту клієнта. Контролер відобразить представлення [formulaire.aspx]
  • calcul: відповідає запиту на розрахунок податку. Якщо дані форми введення правильні, податок розраховується за допомогою бізнес-класу [impots]. Контролер повертає клієнту представлення [formulaire.aspx] у тому вигляді, в якому воно було перевірено, а також з розрахованим податком. Якщо дані у формі введення неправильні, контролер поверне представлення [erreurs.aspx] зі списком помилок та посиланням для повернення до форми.
  • повернення: відповідає поверненню до форми після помилки. Контролер відображає вигляд [formulaire.aspx] у тому вигляді, в якому він був перевірений на правильність до виникнення помилки.

Контролер [main.aspx] не бере участі в розрахунку податків. Він просто відповідає за управління діалогом «клієнт-сервер» та виконання дій, запитуваних клієнтом. Для дії [calcul] він використовуватиме бізнес-клас [impot].

6.1.3. Бізнес-клас

Клас «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

Клас має такі захищені атрибути:

limites
таблиця меж податкових груп
coeffr
таблиця коефіцієнтів, що застосовуються до оподатковуваного доходу
coeffn
таблиця коефіцієнтів, що застосовуються до кількості паїв
checked
логічне значення, що вказує, чи були перевірені дані (ліміти, коефіцієнт1, коефіцієнт2)
valide
логічне значення, що вказує, чи дані (межі, коефіцієнт, коефіцієнт) є дійсними

Клас не має конструктора. Він має абстрактний метод [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>vbc /r:impot.dll testimpots1.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4
dos>dir
05/04/2004  13:28                1 337 impots.vb
21/04/2004  08:23                1 311 impotsArray.vb
21/04/2004  08:26                1 634 impotsData.vb
21/04/2004  08:42                2 490 testimpots1.vb
21/04/2004  09:21                5 632 impot.dll
21/04/2004  09:23                4 608 testimpots1.exe

Ми можемо провести тестування:

dos>testimpots1
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 60000
impôt=4300 euro(s)
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :n 2 60000
impôt=6872 euro(s)
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :

6.1.6. Види веб-додатку

Додаток матиме два види: [formulaire.aspx] та [erreurs.aspx]. Проілюструємо роботу додатка за допомогою знімків екрана. Вигляд [formulaire.aspx] відображається, коли URL-адреса [main.aspx] запитується вперше:

Image

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

Image

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

Image

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

Image

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

Image

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

Image

6.1.7. Вигляд [formulaire.aspx]

Сторінка [formulaire.aspx] матиме такий вигляд:


<%@ page src="formulaire.aspx.vb" inherits="formulaire" AutoEventWireup="false"%>
<html>
    <head>
        <title>Impôt</title>
    </head>
    <body>
        <P>Calcul de votre impôt</P>
        <HR>
        <form method="post" action="main.aspx?action=calcul">
            <TABLE border="0">
                <TR>
                    <TD>Etes-vous marié(e)</TD>
                    <TD>
                        <INPUT type="radio" value="oui" name="rdMarie" <%=rdouichecked%>>Oui 
                      <INPUT type="radio"  value="non" name="rdMarie" <%=rdnonchecked%>>Non
                     </TD>
                </TR>
                <TR>
                    <TD>Nombre d'enfants</TD>
                    <TD><INPUT type="text" size="3" maxLength="3" name="txtEnfants" value="<%=txtEnfants%>"></TD>
                </TR>
                <TR>
                    <TD>Salaire annuel (euro)</TD>
                    <TD><INPUT type="text" maxLength="12" size="12" name="txtSalaire" value="<%=txtSalaire%>"></TD>
                </TR>
                <TR>
                    <TD>Impôt à payer :
                    </TD>
                    <TD><%=txtImpot%></TD>
                </TR>
            </TABLE>
            <hr>
            <P>
                <INPUT type="submit" value="Calculer">
            </P>
        </form>
        <form method="post" action="main.aspx?action=effacer">
                <INPUT type="submit" value="Effacer">
        </form>
    </body>
</html>

Динамічні поля цієї сторінки такі:

rdouichecked
«checked», якщо поле [oui] має бути позначене, «» — у протилежному випадку
rdnonchecked
те саме для поля [non]
txtEnfants
значення, яке слід ввести у поле введення [txtEnfants]
txtSalaire
значення, яке слід ввести у поле введення [txtSalaire]
txtImpot
значення, яке слід ввести у поле введення [txtImpot]

На сторінці є дві форми, кожна з яких має кнопку [submit]. Кнопка [Calculer] — це кнопка [submit] з наступної форми:


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

Як бачимо, параметри форми будуть передані до контролера за допомогою [action=calcul]. Кнопка [Effacer] — це кнопка [submit] з наступної форми:


        <form method="post" action="main.aspx?action=effacer">
                <INPUT type="submit" value="Effacer">
        </form>

Бачимо, що параметри форми будуть відправлені до контролера разом із [action=effacer]. У цьому випадку форма не має жодних параметрів. Важливою є лише дія.

Поля [formulaire.aspx] обчислюються за допомогою [formulaire.aspx.vb]:


Imports System.Collections.Specialized

Public Class formulaire
    Inherits System.Web.UI.Page

    ' поля сторінки
    Protected rdouichecked As String
    Protected rdnonchecked As String
    Protected txtEnfants As String
    Protected txtSalaire As String
    Protected txtImpot As String

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' отримуємо попередній запит із контексту
        Dim form As NameValueCollection = Context.Items("formulaire")
        ' підготовка сторінки до відображення
        ' перемикачі
        rdouichecked = ""
        rdnonchecked = "checked"
        If form("rdMarie").ToString = "oui" Then
            rdouichecked = "checked"
            rdnonchecked = ""
        End If
        ' решта
        txtEnfants = CType(form("txtEnfants"), String)
        txtSalaire = CType(form("txtSalaire"), String)
        txtImpot = CType(Context.Items("txtImpot"), String)
    End Sub
End Class

Обчислення полів [main.aspx] здійснюється на основі двох даних, розміщених контролером у контексті сторінки:

  • Context.Items («форма»): словник типу NameValueCollection, що містить значення полів HTML [rdmarie,txtEnfants,txtSalaire]
  • Context.Items("txtImpot"): значення податку

6.1.8. Вигляд [erreurs.aspx]

Вигляд [erreurs.aspx] відображає можливі помилки, що можуть виникнути під час роботи програми. Його код представлення такий:


<%@ page src="erreurs.aspx.vb" inherits="erreurs" AutoEventWireup="false"%>
<HTML>
    <HEAD>
        <title>Impôt</title>
    </HEAD>
    <body>
        <P>Les erreurs suivantes se sont produites :</P>
        <HR>
        <ul>
            <%=erreursHTML%>
        </ul>
        <a href="<%=href%>">
            <%=lien%>
        </a>
    </body>
</HTML>

Сторінка містить три динамічні поля:

erreursHTML
код HTML зі списку помилок
href
URL-адреса посилання
lien
текст посилання

Ці поля обчислюються контролером сторінки в [erreurs.aspx.vb]:


Imports System.Collections
Imports Microsoft.VisualBasic

Public Class erreurs
    Inherits System.Web.UI.Page

    ' параметри сторінки
    Protected erreursHTML As String = ""
    Protected href As String
    Protected lien As String

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' отримуємо елементи контексту
        Dim erreurs As ArrayList = CType(context.Items("erreurs"), ArrayList)
        href = context.Items("href").ToString
        lien = context.Items("lien").ToString
        ' генерується код HTML зі списку
        Dim i As Integer
        For i = 0 To erreurs.Count - 1
            erreursHTML += "<li> " + erreurs(i).ToString + "</li>" + ControlChars.CrLf
        Next
    End Sub

End Class

Контролер сторінки отримує інформацію, розміщену контролером додатка в контексті сторінки:

Context.Items("erreurs"
об’єкт ArrayList, що містить список повідомлень про помилки, які потрібно відобразити
Context.Items("href"
URL-адреса посилання
Context.Items("lien"
текст посилання

Тепер, коли ми знаємо, що бачить користувач додатка, можемо перейти до написання контролера для нього.

6.1.9. Контролери [global.asax, main.aspx]

Нагадаємо схему MVC нашого додатка:

Image

Клієнт Логіка додатка

Контролер [main.aspx] повинен обробити три дії:

  • init: відповідає першому запиту клієнта. Контролер відображає представлення [formulaire.aspx]
  • calcul: відповідає запиту на розрахунок податку. Якщо дані у формі введення правильні, податок розраховується за допомогою бізнес-класу [impots]. Контролер повертає клієнту представлення [formulaire.aspx] у тому вигляді, в якому воно було перевірено, а також з розрахованим податком. Якщо дані у формі введення неправильні, контролер повертає представлення [erreurs.aspx] зі списком помилок та посиланням для повернення до форми.
  • повернення: відповідає поверненню до форми після помилки. Контролер відображає вигляд [formulaire.aspx] у тому вигляді, в якому він був перевірений до виникнення помилки.

Крім того, відомо, що будь-який запит до додатка проходить через контролер [global.asax], якщо він існує. Отже, на вході до додатка ми маємо ланцюжок із двох контролерів:

  • [global.asax], який, згідно з архітектурою, через ASP.NET отримує всі запити до додатка
  • [main.aspx], який, згідно з рішенням розробника, також отримує всі запити до додатка

Необхідність у [main.aspx] зумовлена тим, що нам доведеться керувати сесією. Ми бачили, що [global.asax] у цьому випадку не підходить як контролер. Тут можна було б повністю обійтися без [global.asax]. Проте ми будемо використовувати його для виконання коду під час запуску додатка. Наведена вище схема MVC показує, що нам знадобиться створити об’єкт [impot] для розрахунку податку. Немає сенсу створювати його кілька разів — одного разу достатньо. Отже, ми створимо його під час запуску додатка під час події [Application_Start], яку обробляє контролер [global.asax]. Його код такий:

[global.asax]

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

[global.asax.vb]


Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr

Public Class Global
    Inherits System.Web.HttpApplication

    Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' створюємо об’єкт «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]

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

[main.aspx.vb]


Imports System
Imports System.Collections.Specialized
Imports System.Collections
Imports st.istia.univangers.fr

Public Class main
    Inherits System.Web.UI.Page

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' насамперед перевіряємо, чи вдалося додатку правильно ініціалізуватися
        If CType(Application("erreur"), Boolean) Then
            ' перенаправляємо на сторінку помилок
            Dim erreurs As New ArrayList
            erreurs.Add("Application momentanément indisponible...")
            context.Items("erreurs") = erreurs
            context.Items("lien") = ""
            context.Items("href") = ""
            Server.Transfer("erreurs.aspx")
        End If
        ' визначаємо необхідну дію
        Dim action As String
        If Request.QueryString("action") Is Nothing Then
            action = "init"
        Else
            action = Request.QueryString("action").ToString.ToLower
        End If
        ' виконуємо дію
        Select Case action
            Case "init"
                ' ініціалізація додатка
                initAppli()
            Case "calcul"
                ' розрахунок податку
                calculImpot()
            Case "retour"
                ' повернення до форми
                retourFormulaire()
            Case "effacer"
                ' ініціалізація додатка
                initAppli()
            Case Else
                ' невідома дія = ініціалізація
                initAppli()
        End Select
    End Sub

    Private Sub initAppli()
        ' відображення попередньо заповненої форми
        Context.Items("formulaire") = initForm()
        Context.Items("txtImpot") = ""
        Server.Transfer("formulaire.aspx", True)
    End Sub

    Private Function initForm() As NameValueCollection
        ' ініціалізація форми
        Dim form As New NameValueCollection
        form.Set("rdMarie", "non")
        form.Set("txtEnfants", "")
        form.Set("txtSalaire", "")
        Return form
    End Function

    Private Sub calculImpot()
        ' перевіряється правильність введених даних
        Dim erreurs As ArrayList = checkData()
        ' якщо є помилки, про це повідомляється
        If erreurs.Count <> 0 Then
            ' збереження введених даних
            Session.Item("formulaire") = Request.Form
            ' готується сторінка з повідомленням про помилки
            context.Items("href") = "main.aspx?action=retour"
            context.Items("lien") = "Retour au formulaire"
            context.Items("erreurs") = erreurs
            Server.Transfer("erreurs.aspx")
        End If
        ' помилок немає — обчислюється податок
        Dim impot As Long = CType(Application("objImpot"), impot).calculer( _
        Request.Form("rdMarie") = "oui", _
        CType(Request.Form("txtEnfants"), Integer), _
        CType(Request.Form("txtSalaire"), Long))
        ' відображається сторінка з результатами
        context.Items("txtImpot") = impot.ToString + " euro(s)"
        context.Items("formulaire") = Request.Form
        Server.Transfer("formulaire.aspx", True)
    End Sub

    Private Sub retourFormulaire()
        ' відображається форма зі значеннями, взятими з сесії
        Context.Items("formulaire") = Session.Item("formulaire")
        Context.Items("txtImpot") = ""
        Server.Transfer("formulaire.aspx", True)
    End Sub

    Private Function checkData() As ArrayList
        ' спочатку помилок немає
        Dim erreurs As New ArrayList
        Dim erreur As Boolean = False
        ' перемикач «одружений»
        Try
            Dim rdMarie As String = Request.Form("rdMarie").ToString
            If rdMarie <> "oui" And rdMarie <> "non" Then
                Throw New Exception
            End If
        Catch
            erreurs.Add("Vous n'avez pas indiqué votre statut marital")
        End Try
        ' кількість дітей
        Try
            Dim txtEnfants As String = Request.Form("txtEnfants").ToString
            Dim nbEnfants As Integer = CType(txtEnfants, Integer)
            If nbEnfants < 0 Then Throw New Exception
        Catch
            erreurs.Add("Le nombre d'enfants est incorrect")
        End Try
        ' зарплата
        Try
            Dim txtSalaire As String = Request.Form("txtSalaire").ToString
            Dim salaire As Integer = CType(txtSalaire, Long)
            If salaire < 0 Then Throw New Exception
        Catch
            erreurs.Add("Le salaire annuel est incorrect")
        End Try
        ' виводимо список помилок
        Return erreurs
    End Function
End Class

Контролер спочатку перевіряє, чи правильно ініціалізувався додаток:


         ' насамперед перевіряємо, чи вдалося програмі правильно запуститися
        If CType(Application("erreur"), Boolean) Then
            ' перенаправляємо на сторінку помилок
            Dim erreurs As New ArrayList
            erreurs.Add("Application momentanément indisponible...")
            context.Items("erreurs") = erreurs
            context.Items("lien") = ""
            context.Items("href") = ""
            Server.Transfer("erreurs.aspx")
        End If

Якщо контролер виявляє, що додаток не зміг правильно ініціалізуватися (не вдалося створити об’єкт [impot], необхідний для розрахунку), він відображає сторінку помилок із відповідними параметрами. У цьому випадку немає необхідності розміщувати посилання для повернення до форми, оскільки весь додаток виявляється недоступним. Загальне повідомлення про помилку розміщується в об’єкті [Context.Items("erreurs")] типу [ArrayList].

Якщо контролер виявляє, що додаток працює, він аналізує дію, яку йому доручено виконати, за допомогою параметра [action]. Ми вже неодноразово стикалися з таким режимом роботи. Обробка кожного типу дії делегується функції.

6.1.9.1. Дії init, effacer

Ці дві дії повинні відображати порожню форму введення даних. Нагадаємо, що вона (див. «види») має два параметри:

  • Context.Items("форма"): словник типу [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>.

Image

У цій папці створюється підпапка [bin], в яку поміщається збірка [impot.dll], отримана в результаті компіляції файлів бізнес-класів: [impots.vb, impotsData.vb, impotsArray.vb]. Нижче наведено необхідну команду компіляції:

dos>vbc /t:library /out:impot.dll impotsData.vb impotsArray.vb impots.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4
dos>dir
05/04/2004  13:28                1 337 impots.vb
21/04/2004  08:23                1 311 impotsArray.vb
21/04/2004  08:26                1 634 impotsData.vb
21/04/2004  09:21                5 632 impot.dll

Вищезазначений файл [impot.dll] потрібно розмістити в папці <application-path>\bin, щоб веб-додаток мав до нього доступ. Сервер Cassini запускається з параметрами (<application-path>,/impots1). У браузері ми звертаємося за URL-адресою [http://localhost/impots1/main.aspx]:

Image

Заповнюємо форму:

Image

Потім запускаємо розрахунок податку за допомогою кнопки [Calculer]. Отримуємо таку відповідь:

Image

Потім вводимо неправильні дані:

Image

Натискання кнопки [Calculer] дає такий результат:

Image

Перехід за посиланням [Retour au formulaire] повертає нас до форми в тому стані, в якому вона перебувала на момент її підтвердження:

Image

Нарешті, натискання кнопки [Effacer] скидає сторінку:

Image

6.1.11. Використання клієнта [curl]

Важливо тестувати веб-додатки з використанням інших клієнтів, окрім браузерів. Якщо до браузера надсилати форму з параметрами, які мають бути відправлені після її підтвердження, браузер поверне значення цих параметрів на сервер. Інший клієнт може цього не зробити, і тоді сервер отримає запит, у якому бракуватиме параметрів. Він повинен знати, що робити в такому випадку. Іншим прикладом є перевірка введених даних, що виконується на стороні клієнта. Якщо форма містить дані, які потрібно перевірити, ця перевірка може здійснюватися на стороні клієнта за допомогою скриптів, включених у документ, що містить форму. Браузер відправить форму лише в тому випадку, якщо всі дані, перевірені на стороні клієнта, є правильними. Тоді на стороні сервера може виникнути спокуса вважати, що ми отримаємо вже перевірені дані, і не проводити цю перевірку вдруге. Це було б помилкою. Адже клієнт, відмінний від браузера, може надіслати серверу недійсні дані, і тоді веб-додаток може поводитися непередбачувано. Ми проілюструємо ці моменти на прикладі клієнта [curl].

Спочатку ми надсилаємо запит на URL-адресу [http://localhost/impots1/main.aspx]:

dos>curl --include --url http://localhost/impots1/main.aspx

HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Thu, 01 Apr 2004 15:18:10 GMT
Set-Cookie: ASP.NET_SessionId=ivthkl45tjdjrzznevqsf255; path=/
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 982
Connection: Close


<html>
    <head>
        <title>Impôt</title>
    </head>
    <body>
        <P>Calcul de votre impôt</P>
        <HR width="100%" SIZE="1">
        <form method="post" action="main.aspx?action=calcul">
            <TABLE border="0">
                <TR>
                    <TD>Etes-vous marié(e)</TD>
                    <TD>
                        <INPUT type="radio" value="oui" name="rdMarie" >Oui <INPUT type="radio"  value="non" name="rdMarie" checked>Non</TD>
                </TR>
                <TR>
                    <TD>Nombre d'enfants</TD>
                    <TD><INPUT type="text" size="3" maxLength="3" name="txtEnfants" value=""></TD>
                </TR>
                <TR>
                    <TD>Salaire annuel (euro)</TD>
                    <TD><INPUT type="text" maxLength="12" size="12" name="txtSalaire" value=""></TD>
                </TR>
                <TR>
                    <TD>Impôt à payer :
                    </TD>
                    <TD></TD>
                </TR>
            </TABLE>
            <hr>
            <P>
                <INPUT type="submit" value="Calculer">
            </P>
        </form>
        <form method="post" action="main.aspx?action=effacer">
                <INPUT type="submit" value="Effacer">
        </form>
    </body>
</html>

Сервер надіслав нам код форми HTML. У заголовках HTTP міститься сесійний файл 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 спрощує написання такого додатка. Вона виглядає наступним чином:

Image

Блок [Classes métier, Classes d'accès aux données] не змінюється. Адже це частина, яка не має значення для клієнта. Блок [Contrôleur] змінюється незначно, але повинен враховувати новий параметр у запиті — параметр [client], що вказує, з яким типом клієнта він має справу. Блок [vues] повинен генерувати види для кожного типу клієнта. Було б доцільно вже на етапі проектування додатка врахувати наявність параметра [client] у запиті, навіть якщо в коротко- або середньостроковій перспективі ми орієнтуємося виключно на веб-браузери. Якщо в майбутньому додатку доведеться підтримувати новий тип клієнта, потрібно буде написати лише відповідні для нього види.

6.2. Приклад 2

6.2.1. Проблема

Тут ми пропонуємо розглянути ту саму проблему, що й раніше, але зі зміною джерела даних об’єкта [impot], створеного веб-додатком. У попередній версії використовувалося джерело даних, яке надавало значення масивів, «жорстко» записаних у коді. Цього разу нове джерело даних буде отримувати їх із джерела даних ODBC, пов’язаного з базою даних MySQL.

6.2.2. Джерело даних ODBC

Дані будуть міститися в таблиці з назвою [IMPOTS] у базі даних MySQL, яка називається [dbimpots]. Вміст цієї таблиці буде таким:

Image

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

Існує безліч баз даних для платформ Windows. Для доступу до них програми використовують спеціальні програми, які називаються драйверами (drivers).

Image

На схемі вище драйвер має два інтерфейси:

  • інтерфейс 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 постачається з трьома типами класів доступу до даних:

  1. класи SQL та Server.NET для доступу до баз даних Microsoft SQL Server
  2. класи Ole Db.NET для доступу до баз даних SGBD, що підтримують драйвери OLE та DB
  3. класи odbc.net, для доступу до баз даних SGBD, що надають драйвер ODBC

SGBD MySQL вже давно має драйвер ODBC. Саме його ми зараз використовуємо. У Windows ми вибираємо опцію [Menu Démarrer/Panneau de configuration/Outils d'administration/Sources ODBC 32 bits]. Залежно від версії Windows цей шлях може дещо відрізнятися. Ми отримуємо таку програму, яка дозволить нам створити наш файл-джерело ODBC:

Image

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

Image

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

Image

Необхідно вказати певну інформацію:

Data Source Name
ім’я, яке позначатиме джерело даних ODBC. Будь-яка програма Windows зможе отримати доступ до джерела за цим ім’ям
Description
довільний текст, що описує джерело даних
Host Name
ім'я комп'ютера, на якому розміщено SGBD MySQL. У даному випадку це локальний комп’ютер. Це може бути й віддалений комп’ютер. Це дозволить програмі Windows отримати доступ до віддаленої бази даних без будь-якого спеціального кодування. У цьому полягає велика цінність джерела ODBC.
Database Name
SGBD та MySQL можуть керувати кількома базами даних. Тут вказується, якою саме базою даних потрібно керувати: dbimpots
User
ім'я користувача, зареєстрованого в SGBD MySQL. Саме під цим ім'ям буде здійснюватися доступ до джерела даних. У даному випадку: admimpots
Password
пароль цього користувача. У даному випадку: mdpimpots
Port
робочий порт SGBD MySQL. За замовчуванням це порт 3306. Ми його не змінювали

Після цього перевіряємо правильність наших параметрів підключення за допомогою кнопки [Test Data Source]:

Image

Після цього ми впевнені в нашому джерелі даних ODBC. Тепер ми можемо ним користуватися. Ми виконуємо [OK] стільки разів, скільки потрібно, щоб вийти з майстра ODBC.

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

Image

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

Image

Необхідно вказати таку інформацію:

Nom de la source de données
ім’я, яке позначатиме джерело даних ODBC. Будь-яка програма Windows зможе отримати доступ до джерела за цим ім’ям
Description
довільний текст, що описує джерело даних
Base de données
Повне ім’я файлу ACCESS, який потрібно обробити

6.2.3. Новий клас доступу до даних

Повернемося до структури MVC нашого додатка:

Image

На схемі вище клас [impotsData] відповідає за отримання даних. У цьому випадку він повинен отримати їх із бази MySQL [dbimpots]. З попередньої версії цього додатка ми знаємо, що [impotsData] — це абстрактний клас, який потрібно похідним класом щоразу, коли потрібно адаптувати його до нового джерела даних. Нагадаємо структуру цього абстрактного класу:


Imports System.Collections

Namespace st.istia.univangers.fr
    Public MustInherit Class impotsData
        Protected limites() As Decimal
        Protected coeffr() As Decimal
        Protected coeffn() As Decimal
        Protected checked As Boolean
        Protected valide As Boolean

        ' метод доступу до даних
        Public MustOverride Function getData() As Object()

        ' метод перевірки даних
        Protected Function checkData() As Integer
            ' перевіряє отримані дані
...
        End Function

        ' перевіряє правильність вмісту масиву
        Protected Function check(ByRef tableau() As Decimal, ByVal n As Integer) As Boolean
        ...
        End Function
    End Class
End Namespace

Клас, що успадковує [impotsData], повинен реалізувати два методи:

  • конструктор, якщо конструктор без аргументів класу [impotsData] йому не підходить
  • метод [getData], який повертає три масиви (limites, coeffr, coeffn)

Ми створюємо клас [impotsODBC], який буде отримувати дані (limites, coeffr, coeffn) із джерела ODBC, якому ми надамо ім’я:


Imports System.Data.Odbc
Imports System.Data
Imports System.Collections
Imports System

Namespace st.istia.univangers.fr
    Public Class impotsODBC
        Inherits impotsData

        ' змінні екземпляру
        Protected DSNimpots As String

        ' конструктор
        Public Sub New(ByVal DSNimpots As String)
            ' записуємо три відомості
            Me.DSNimpots = DSNimpots
        End Sub

        Public Overrides Function getdata() As Object()
            ' ініціалізує три масиви limits, coeffr, coeffn на основі
            ' вмісту таблиці [impots] у базі даних ODBC DSNimpots
            ' «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"))
  • Після цього ми звільняємо ресурси, пов’язані з підключенням:
                 ' звільнення ресурсів
                myReader.Close()
                impotsConn.Close()
  • Вміст трьох об’єктів [ArrayList] переноситься у три звичайні масиви:

             ' динамічні масиви записуються у статичні масиви
            limites = New Decimal(aLimites.Count - 1) {}
            coeffr = New Decimal(aLimites.Count - 1) {}
            coeffn = New Decimal(aLimites.Count - 1) {}
            Dim i As Integer
            For i = 0 To aLimites.Count - 1
                limites(i) = CType(aLimites(i), Decimal)
                coeffR(i) = CType(aCoeffR(i), Decimal)
                coeffN(i) = CType(aCoeffN(i), Decimal)
            Next i
  • після того, як дані з таблиці [impots] потрапили до трьох масивів, залишається лише перевірити їхній вміст за допомогою методу [checkData] базового класу [impotsData]:
             ' перевірка отриманих даних
            Dim erreur As Integer = checkData()
             ' якщо дані недійсні, то генерується виняток
            If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
             ' інакше повертаються три масиви
            Return New Object() {limites, coeffr, coeffn}

6.2.4. Тести класу доступу до даних

Програма тестування може виглядати так:

Option Explicit On 
Option Strict On

' простори імен
Imports System
Imports Microsoft.VisualBasic

Namespace st.istia.univangers.fr

     ' тестова сторінка
    Module testimpots
        Sub Main(ByVal arguments() As String)
             ' інтерактивна програма розрахунку податку
             ' користувач вводить три значення з клавіатури: одружений nbEnfants зарплата
             ' після чого програма відображає суму податку до сплати
            Const syntaxe1 As String = "pg DSNimpots"
            Const syntaxe2 As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"

             ' перевірка параметрів програми
            If arguments.Length <> 1 Then
                 ' повідомлення про помилку
                Console.Error.WriteLine(syntaxe1)
                 ' кінець
                Environment.Exit(1)
            End If
             ' отримуємо аргументи
            Dim DSNimpots As String = arguments(0)

             ' створення об’єкта податку
            Dim objImpot As impot = Nothing
            Try
                objImpot = New impot(New impotsODBC(DSNimpots))
            Catch ex As Exception
                Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
                Environment.Exit(2)
            End Try

             ' нескінченний цикл
            While True
                 ' спочатку помилок немає
                Dim erreur As Boolean = False

                 ' запитуються параметри розрахунку податку
                Console.Out.Write("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :")
                Dim paramètres As String = Console.In.ReadLine().Trim()

                 ' що робити?
                If paramètres Is Nothing Or paramètres = "" Then
                    Exit While
                End If

                 ' перевірка кількості аргументів у введеному рядку
                Dim args As String() = paramètres.Split(Nothing)
                Dim nbParamètres As Integer = args.Length
                If nbParamètres <> 3 Then
                    Console.Error.WriteLine(syntaxe2)
                    erreur = True
                End If
                Dim marié As String
                Dim nbEnfants As Integer
                Dim salaire As Integer
                If Not erreur Then
                     ' перевірка правильності параметрів
                     ' одружений
                    marié = args(0).ToLower()
                    If marié <> "o" And marié <> "n" Then
                        Console.Error.WriteLine((syntaxe2 + ControlChars.Lf + "Argument marié incorrect : tapez o ou n"))
                        erreur = True
                    End If
                     ' nbEnfants
                    nbEnfants = 0
                    Try
                        nbEnfants = Integer.Parse(args(1))
                        If nbEnfants < 0 Then
                            Throw New Exception
                        End If
                    Catch
                        Console.Error.WriteLine(syntaxe2 + "\nArgument nbEnfants incorrect : tapez un entier positif ou nul")
                        erreur = True
                    End Try
                     ' зарплата
                    salaire = 0
                    Try
                        salaire = Integer.Parse(args(2))
                        If salaire < 0 Then
                            Throw New Exception
                        End If
                    Catch
                        Console.Error.WriteLine(syntaxe2 + "\nArgument salaire incorrect : tapez un entier positif ou nul")
                        erreur = True
                    End Try
                End If
                If Not erreur Then
                     ' параметри правильні — розраховується податок
                    Console.Out.WriteLine(("impôt=" & objImpot.calculer(marié = "o", nbEnfants, salaire).ToString + " euro(s)"))
                End If
            End While
        End Sub
    End Module
End Namespace

Додаток запускається з одним параметром:

  • DSNimpots: ім’я джерела даних ODBC, яке слід використовувати

Розрахунок податку здійснюється за допомогою об’єкта типу [impot], який створюється одразу після запуску програми:


             ' створення об’єкта податку
            Dim objImpôt As impot = Nothing
            Try
                objImpot = New impot(New impotsODBC(DSNimpots))
            Catch ex As Exception
                Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
                Environment.Exit(1)
            End Try

Після запуску програма неодноразово просить користувача ввести з клавіатури три дані, необхідні для розрахунку податку:

  • сімейний стан: «o» — одружений, «n» — неодружений
  • кількість дітей
  • річний дохід

Усі класи компілюються:

dos>vbc /r:system.dll /r:system.data.dll /t:library /out:impot.dll impots.vb impotsArray.vb impotsData.vb impotsODBC.vb
dos>dir
01/04/2004  19:34             7 168 impot.dll
01/04/2004  19:31             1 360 impots.vb
21/04/2004  08:23             1 311 impotsArray.vb
21/04/2004  08:26             1 634 impotsData.vb
01/04/2004  19:34             2 735 impotsODBC.vb
01/04/2004  19:32             3 210 testimpots.vb

Тестова програма, у свою чергу, компілюється:

dos>vbc /r:impot.dll testimpots.vb
dir>dir
01/04/2004  19:34             7 168 impot.dll
01/04/2004  19:31             1 360 impots.vb
21/04/2004  08:23             1 311 impotsArray.vb
21/04/2004  08:26             1 634 impotsData.vb
01/04/2004  19:34             2 735 impotsODBC.vb
01/04/2004  19:34             6 144 testimpots.exe
01/04/2004  19:32             3 210 testimpots.vb

Програма тестування спочатку виконується з джерелом даних ODBC MySQL:

dos>testimpots odbc-mysql-dbimpots
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 60000
impôt=4300 euro(s)

Змінюємо джерело на ODBC, щоб використовувати джерело Access:

dos>testimpots odbc-access-dbimpots
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 60000
impôt=4300 F

6.2.5. Види веб-додатку

Це ті самі, що й у попередньому додатку: formulaire.aspx та erreurs.aspx

6.2.6. Контролери додатка [global.asax, main.aspx]

Змінити потрібно лише контролер [global.asax]. Він відповідає за створення об’єкта [impot] під час запуску додатка. Єдиним параметром конструктора цього об’єкта є об’єкт типу [impotsData], який відповідає за отримання даних. Отже, цей параметр змінюється для кожного нового типу джерел даних. Контролер [global.asax.vb] набуває такого вигляду:


Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr
Imports System.Configuration

Public Class Global
    Inherits System.Web.HttpApplication

    Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' створюється об’єкт податку
        Dim objImpot As impot
        Try
            objImpot = New impot(New impotsODBC(ConfigurationSettings.AppSettings("DSNimpots")))
            ' об'єкт додано до програми
            Application("objImpot") = objImpot
            ' помилок немає
            Application("erreur") = False
        Catch ex As Exception
            'сталася помилка, її фіксують у додатку
            Application("erreur") = True
            Application("message") = ex.Message
        End Try
    End Sub
End Class

Джерелом даних об'єкта [impot] тепер є об'єкт [impotODBC]. Параметром останнього є ім'я DSN джерела даних ODBC, яке потрібно використовувати. Замість того, щоб вказувати це ім’я безпосередньо в коді, його вказують у конфігураційному файлі [web.config] додатка:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <appSettings>
        <add key="DSNimpots" value="odbc-mysql-dbimpots" />
    </appSettings>
</configuration>

Відомо, що значення ключа C у розділі <appSettings> файлу [web.config] отримується в коді програми за допомогою [ConfigurationSettings.AppSettings(C)].

Щоб з’ясувати причину виникнення винятку, його повідомлення записується в програмі, щоб воно залишалося доступним для запитів. Контролер [main.aspx.vb] включить це повідомлення до свого списку помилок:


    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' насамперед перевіряємо, чи вдалося додатку правильно ініціалізуватися
        If CType(Application("erreur"), Boolean) Then
            ' перенаправляємо на сторінку помилок
            Dim erreurs As New ArrayList
            erreurs.Add("Application momentanément indisponible...(" + Application("message").ToString + ")")
            context.Items("erreurs") = erreurs
            context.Items("lien") = ""
            context.Items("href") = ""
            Server.Transfer("erreurs.aspx")
        End If
        ' визначаємо необхідну дію
...

6.2.7. Підсумок змін

Додаток готовий до тестування. Перелічимо зміни, внесені до попередньої версії:

  1. створено новий клас для доступу до даних
  2. контролер [global.asax.vb] було змінено у двох місцях: створення об’єкта [impot] та запис у програмі повідомлення, пов’язаного з можливим винятком
  3. контролер [main.aspx.vb] було змінено в одному місці для відображення попереднього повідомлення про виняток
  4. було додано файл [web.config]

Робота з внесення змін в основному проводилася в 1, c.a.d. поза межами веб-додатку. Це стало можливим завдяки архітектурі MVC додатка, яка відокремлює контролер від бізнес-класів. У цьому і полягає вся цінність цієї архітектури. Можна було б продемонструвати, що за допомогою відповідного конфігураційного файлу [web.config] можна було б уникнути будь-яких змін у контролері додатка. У [web.config] можна вказати ім’я класу доступу до даних, який потрібно динамічно інстанціювати, а також різні параметри, необхідні для цієї інстанціації. Маючи цю інформацію, [global.asax] може створити екземпляр об’єкта доступу до даних. Отже, зміна джерела даних зводиться до:

  • створення класу доступу до цього джерела, якщо він ще не існує
  • внести зміни до файлу [web.config], щоб забезпечити динамічне створення екземпляра цього класу у файлі [global.asax]

6.2.8. Тестування веб-додатку

Усі вищезазначені файли розміщуються в папці <application-path>.

Image

У цій папці створюється підпапка [bin], у яку поміщається збірка [impot.dll], отримана в результаті компіляції файлів бізнес-класів: [impots.vb, impotsData.vb, impotsArray.vb, impotsODBC.vb]. Нижче наведено необхідну команду компіляції:

dos>vbc /r:system.dll /r:system.data.dll /t:library /out:impot.dll impots.vb impotsArray.vb impotsData.vb impotsODBC.vb
dos>dir
01/04/2004  19:34             7 168 impot.dll
01/04/2004  19:31             1 360 impots.vb
21/04/2004  08:23             1 311 impotsArray.vb
21/04/2004  08:26             1 634 impotsData.vb
01/04/2004  19:34             2 735 impotsODBC.vb
01/04/2004  19:32             3 210 testimpots.vb

Вищезазначений файл [impot.dll] потрібно розмістити в папці <application-path>\bin, щоб веб-додаток мав до нього доступ. Сервер Cassini запускається з параметрами (<application-path>,/impots2). Тестування дає ті самі результати, що й у попередній версії, оскільки наявність бази даних є невидимою для користувача. Проте, щоб проілюструвати цю наявність, ми робимо так, щоб джерело ODBC було недоступним, зупинивши SGBD та MySQL, і запитуємо URL-адресу [http://localhost/impots2/main.aspx]. Ми отримуємо таку відповідь:

Image

6.3. Приклад 3

6.3.1. Проблема

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

6.3.2. Джерело даних OLEDB

Дані міститимуться в таблиці з назвою [IMPOTS] у базі даних ACCESS. Вміст цієї таблиці буде таким:

Image

6.3.3. Клас доступу до даних

Повернемося до структури MVC нашого додатка:

Image

  • На схемі вище клас [impotsData] відповідає за отримання даних. Цього разу він повинен зробити це з джерела OLEDB.

Ми створюємо клас [impotsOLEDB], який буде отримувати дані (limites, coeffr, coeffn) із джерела ODBC, якому ми надамо таку назву:


Imports System.Data
Imports System.Collections
Imports System
Imports System.Xml
Imports System.Data.OleDb

Namespace st.istia.univangers.fr
    Public Class impotsOLEDB
        Inherits impotsData

        ' змінні екземпляра
        Protected chaineConnexion As String

        ' конструктор
        Public Sub New(ByVal chaineConnexion As String)
            ' записуються три значення
            Me.chaineConnexion = chaineConnexion
        End Sub

        Public Overrides Function getData() As Object()
            ' ініціалізує три масиви: limits, coeffr, coeffn на основі
            ' вмісту таблиці [impots] у базі даних OLEDB [chaineConnexion]
            ' «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:

Image

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

Image

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

Image

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

Image

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

Image

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

Image

Рядок з’єднання OLEDB із базою ACCESS задається властивістю [ConnectionString], наведеною вище:

Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=D:\data\serge\devel\aspnet\poly\chap5\impots\3\impots.mdb

Бачимо, що цей рядок складається з фіксованої частини та змінної частини, яка є просто іменем файлу ACCESS. Ми використаємо цей факт для генерації рядка підключення до нашого джерела даних OLEDB.

Тепер повернемося до нашого класу [impotsOLEDB]. Метод [getData] відповідає за зчитування даних із таблиці [impots] та їхнє розміщення у трьох масивах (limites, coeffr, coeffn). Прокоментуємо його код:

  • ми визначаємо об’єкт [DataAdapter], який дозволить нам перенести в пам’ять результат запиту SQL select. Для цього ми визначаємо запит [select], який потрібно виконати, і пов’язуємо його з об’єктом [DataAdapter]. Конструктор цього об’єкта також вимагає рядок підключення, який він повинен використовувати для підключення до джерела OLEDB

             ' створюється об’єкт DataAdapter для зчитування даних із джерела OLEDB
            Dim adaptateur As New OleDbDataAdapter("select limites,coeffr,coeffn from impots", chaineConnexion)
  • команда [select] виконується за допомогою методу [Fill] об’єкта [DataAdapter]. Результат роботи [select] вставляється в об’єкт [DataTable], створений спеціально для цього. Об’єкт [DataTable] є зображенням у пам’яті таблиці бази даних c.a.d — набору рядків і стовпців. Ми обробляємо виняток, який може виникнути, наприклад, у разі неправильного рядка підключення.

             ' створюємо образ результату запиту в пам'яті
            Dim contenu As New DataTable("impots")
            Try
                adaptateur.Fill(contenu)
            Catch e As Exception
                Throw New Exception("Erreur d'accès à la base de données (" + e.Message + ")")
            End Try
  • у [contenu] ми маємо таблицю [impots], яка була приведена об’єктом [select]. Об’єкт [DataTable] є таблицею, а отже — набором рядків. Доступ до них здійснюється через властивість [rows] об’єкта [datatable]:

             ' отримуємо вміст таблиці impots
            Dim lignesImpots As DataRowCollection = contenu.Rows
  • кожен елемент колекції [lignesImpots] є об’єктом типу [DataRow], що представляє рядок таблиці. Цей рядок має стовпці, доступ до яких здійснюється через об’єкт [DataRow] за допомогою його властивості [Item]. [DataRow].[Item(i)] — це стовпець № i рядка [DataRow]. Переглядаючи колекцію рядків (колекцію DataRows від lignesImpots) та колекції стовпців кожного рядка, можна отримати всю таблицю:
             ' визначаємо розміри масивів для прийому даних
            Me.limites = New Decimal(lignesImpots.Count - 1) {}
            Me.coeffr = New Decimal(lignesImpots.Count - 1) {}
            Me.coeffn = New Decimal(lignesImpots.Count - 1) {}
             ' переносимо вміст таблиці impots у масиви
            Dim i As Integer
            Dim ligne As DataRow
            Try
                For i = 0 To lignesImpots.Count - 1
                     ' i-й рядок таблиці
                    ligne = lignesImpots.Item(i)
                     ' витягується вміст рядка
                    limites(i) = CType(ligne.Item(0), Decimal)
                    coeffr(i) = CType(ligne.Item(1), Decimal)
                    coeffn(i) = CType(ligne.Item(2), Decimal)
                Next
            Catch
                Throw New Exception("Les données des tranches d'impôts n'ont pas le bon type")
            End Try
  • після того, як дані з таблиці [impots] потрапили до трьох масивів, залишається лише перевірити їхній вміст за допомогою методу [checkData] базового класу [impotsData]:
            ' перевіряються отримані дані
            Dim erreur As Integer = checkData()
             ' якщо дані недійсні, то генерується виняток
            If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
             ' інакше повертаються три масиви
            Return New Object() {limites, coeffr, coeffn}

6.3.4. Тести класу доступу до даних

Програма тестування може виглядати так:

Option Explicit On 
Option Strict On

' простори імен
Imports System
Imports Microsoft.VisualBasic

Namespace st.istia.univangers.fr

     ' тестова сторінка
    Module testimpots
        Sub Main(ByVal arguments() As String)
             ' інтерактивна програма для розрахунку податку
             ' користувач вводить три значення з клавіатури: одружений nbEnfants зарплата
             ' після чого програма відображає суму податку до сплати
            Const syntaxe1 As String = "pg bdACCESS"
            Const syntaxe2 As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"

             ' перевірка параметрів програми
            If arguments.Length <> 1 Then
                 ' повідомлення про помилку
                Console.Error.WriteLine(syntaxe1)
                 ' кінець
                Environment.Exit(1)
            End If
             ' отримуємо аргументи
            Dim chemin As String = arguments(0)
             ' підготовка рядка з'єднання
            Dim chaineConnexion As String = "Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=" + chemin

             ' створення об’єкта податку
            Dim objImpot As impot = Nothing
            Try
                objImpot = New impot(New impotsOLEDB(chaineConnexion))
            Catch ex As Exception
                Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
                Environment.Exit(2)
            End Try

             ' нескінченний цикл
            While True
                 ' спочатку помилок немає
                Dim erreur As Boolean = False

                 ' потрібні параметри для розрахунку податку
                Console.Out.Write("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :")
                Dim paramètres As String = Console.In.ReadLine().Trim()

                 ' що робити?
                If paramètres Is Nothing Or paramètres = "" Then
                    Exit While
                End If

                 ' перевірка кількості аргументів у введеному рядку
                Dim args As String() = paramètres.Split(Nothing)
                Dim nbParamètres As Integer = args.Length
                If nbParamètres <> 3 Then
                    Console.Error.WriteLine(syntaxe2)
                    erreur = True
                End If
                Dim marié As String
                Dim nbEnfants As Integer
                Dim salaire As Integer
                If Not erreur Then
                     ' перевірка правильності параметрів
                     ' одружений
                    marié = args(0).ToLower()
                    If marié <> "o" And marié <> "n" Then
                        Console.Error.WriteLine((syntaxe2 + ControlChars.Lf + "Argument marié incorrect : tapez o ou n"))
                        erreur = True
                    End If
                     ' nbEnfants
                    nbEnfants = 0
                    Try
                        nbEnfants = Integer.Parse(args(1))
                        If nbEnfants < 0 Then
                            Throw New Exception
                        End If
                    Catch
                        Console.Error.WriteLine(syntaxe2 + "\nArgument nbEnfants incorrect : tapez un entier positif ou nul")
                        erreur = True
                    End Try
                     ' зарплата
                    salaire = 0
                    Try
                        salaire = Integer.Parse(args(2))
                        If salaire < 0 Then
                            Throw New Exception
                        End If
                    Catch
                        Console.Error.WriteLine(syntaxe2 + "\nArgument salaire incorrect : tapez un entier positif ou nul")
                        erreur = True
                    End Try
                End If
                If Not erreur Then
                     ' параметри правильні — розраховується податок
                    Console.Out.WriteLine(("impôt=" & objImpot.calculer(marié = "o", nbEnfants, salaire).ToString + " euro(s)"))
                End If
            End While
        End Sub
    End Module
End Namespace

Додаток запускається з одним параметром:

  • bdACCESS: ім’я файлу ACCESS, який потрібно обробити

Розрахунок податку здійснюється за допомогою об’єкта типу [impot], який створюється одразу після запуску програми:


             ' отримуємо аргументи
            Dim chemin As String = arguments(0)
            ' готуємо рядок з'єднання
            Dim chaineConnexion As String = "Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=" + chemin

             ' створення об’єкта податку
            Dim objImpot As impot = Nothing
            Try
                objImpot = New impot(New impotsOLEDB(chaineConnexion))
            Catch ex As Exception
                Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
                Environment.Exit(2)
            End Try

Ланцюжок підключення до джерела OLEDB було побудовано на основі інформації, отриманої за допомогою [WebMatrix].

Після запуску програма неодноразово просить користувача ввести з клавіатури три дані, необхідні для розрахунку податку:

  • сімейний стан: o — одружений, n — неодружений
  • кількість дітей
  • річний дохід

Усі класи компілюються:

dos>vbc /r:system.dll /r:system.data.dll /t:library /out:impot.dll impots.vb impotsArray.vb impotsData.vb impotsOLEDB.vb
dos>vbc /r:impot.dll testimpots.vb

Файл [impots.mdb] розміщується в папці тестового додатка, який запускається наступним чином:

dos>testimpots impots.mdb
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 60000
impôt=4300 euro(s)

Можна запустити програму з некоректним файлом ACCESS:

dos>testimpots xx
L'erreur suivante s'est produite : Erreur d'accès à la base de données (Ficher 'D:\data\serge\devel\aspnet\poly\chap5\impots\3\xx' introuvable.)

6.3.5. Види веб-додатку

Це ті самі, що й у попередньому додатку: formulaire.aspx та erreurs.aspx

6.3.6. Контролери додатка [global.asax, main.aspx]

Змінити потрібно лише контролер [global.asax]. Він відповідає за створення об’єкта [impot] під час запуску додатка. Єдиним параметром конструктора цього об’єкта є об’єкт типу [impotsData], який відповідає за отримання даних. Отже, цей параметр змінюється, оскільки змінюється джерело даних. Контролер [global.asax.vb] набуває такого вигляду:


Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr
Imports System.Configuration

Public Class Global
    Inherits System.Web.HttpApplication

    Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' створення об’єкта податку
        Dim objImpot As impot
        Try
            objImpot = New impot(New impotsOLEDB(ConfigurationSettings.AppSettings("chaineConnexion")))
            ' додаємо об’єкт до програми
            Application("objImpot") = objImpot
            ' помилок немає
            Application("erreur") = False
        Catch ex As Exception
            'сталася помилка, її фіксують у додатку
            Application("erreur") = True
            Application("message") = ex.Message
        End Try
    End Sub
End Class

Джерелом даних об'єкта [impot] тепер є об'єкт [impotOLEDB]. Параметром останнього є рядок підключення до джерела даних OLEDB, яке потрібно використовувати. Він розміщений у конфігураційному файлі [web.config] додатка:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <appSettings>
        <add key="chaineConnexion" 
        value="Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=D:\data\serge\devel\aspnet\poly\chap5\impots2\impots.mdb" />
    </appSettings>
</configuration>

Контролер [main.aspx] залишається без змін.

6.3.7. Підсумок змін

Додаток готовий до тестування. Перелічимо зміни, внесені до попередньої версії:

  1. створено новий клас для доступу до даних
  2. контролер [global.asax.vb] було змінено в одному місці: створення об’єкта [impot]
  3. додано файл [web.config]

6.3.8. Тестування веб-додатку

Усі вищезазначені файли розміщуються в папці <application-path>.

Image

У цій папці створюється підпапка [bin], у яку поміщається збірка [impot.dll], отримана в результаті компіляції файлів бізнес-класів: [impots.vb, impotsData.vb, impotsArray.vb, impotsOLEDB.vb]. Нижче наведено необхідну команду компіляції:

dos>vbc /r:system.dll /r:system.data.dll /t:library /out:impot.dll impots.vb impotsArray.vb impotsData.vb impotsOLEDB.vb

Файл [impot.dll], створений цією командою, потрібно розмістити в <application-path>\bin, щоб веб-додаток мав до нього доступ. Сервер Cassini запускається з параметрами (<application-path>,/impots3). Тестування дає ті самі результати, що й у попередній версії.

6.4. Приклад 4

6.4.1. Проблема

Тепер ми пропонуємо перетворити наш додаток на додаток для моделювання розрахунків податків. Користувач зможе виконувати послідовні розрахунки податків, і їх результати будуть відображатися на новій сторінці, схожій на таку:

Image

6.4.2. Структура MVC додатка

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

Image

З’являється новий вигляд [simulations.aspx], знімок екрана якого ми щойно навели. Класом доступу до даних буде клас [impotsODBC] із прикладу 2.

6.4.3. Види веб-додатку

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

Image

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

Image

Код відображення виглядає так:


<%@ page src="formulaire.aspx.vb" inherits="formulaire" AutoEventWireup="false"%>
<html>
    <head>
        <title>Impôt</title>
        <script language="javascript">
        function calculer(){
          // перевірка параметрів перед їх надсиланням на сервер
        with(document.frmImpots){
          //кількість дочірніх елементів
          champs=/^\s*(\d+)\s*$/.exec(txtEnfants.value);
          if(champs==null){
            // модель не перевірена
            alert("Le nombre d'enfants n'a pas été donné ou est incorrect");
            txtEnfants.focus();
            return;
          }//if
          //заробітна плата
          champs=/^\s*(\d+)\s*$/.exec(txtSalaire.value);
          if(champs==null){
            // модель не перевірена
            alert("Le salaire n'a pas été donné ou est incorrect");
            txtSalaire.focus();
            return;
          }//якщо
          // все гаразд — форму надсилаємо на сервер
          submit();
        }//з
      }//обчислити  
        </script>
    </head>
    <body>
        <P>Calcul de votre impôt</P>
        <HR width="100%" SIZE="1">
        <form name="frmImpots" method="post" action="main.aspx?action=calcul">
            <TABLE border="0">
                <TR>
                    <TD>Etes-vous marié(e)</TD>
                    <TD>
                        <INPUT type="radio" value="oui" name="rdMarie" <%=rdouichecked%>>Oui 
                      <INPUT type="radio"  value="non" name="rdMarie" <%=rdnonchecked%>>Non</TD>
                </TR>
                <TR>
                    <TD>Nombre d'enfants</TD>
                    <TD><INPUT type="text" size="3" maxLength="3" name="txtEnfants" value="<%=txtEnfants%>"></TD>
                </TR>
                <TR>
                    <TD>Salaire annuel (euro)</TD>
                    <TD><INPUT type="text" maxLength="12" size="12" name="txtSalaire" value="<%=txtSalaire%>"></TD>
                </TR>
            </TABLE>
            <hr>
            <P>
                <INPUT type="button" value="Calculer" onclick="calculer()">
            </P>
        </form>
        <form method="post" action="main.aspx?action=effacer">
            <INPUT type="submit" value="Effacer">
        </form>
    </body>
</html>

Динамічні поля на сторінці відповідають попереднім версіям. Динамічне поле суми податку зникло. Кнопка [Calculer] більше не є кнопкою типу [submit]. Вона має тип [button], і при натисканні на неї виконується функція 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] виглядає наступним чином:

Image

і відповідає такому коду відображення:


<%@ page src="simulations.aspx.vb" inherits="simulations" autoeventwireup="false" %>
<HTML>
    <HEAD>
        <title>simulations</title>
    </HEAD>
    <body>
        <P>Résultats des simulations</P>
        <HR width="100%" SIZE="1">
        <table>
            <tr>
                <th>
                    Marié</th>
                <th>
                    Enfants</th>
                <th>
                    Salaire annuel (euro)</th>
                <th>
                    Impôt à payer (euro)</th>
            </tr>
            <%=simulationsHTML%>
        </table>
        <p></p>
        <a href="<%=href%>">
            <%=lien%>
        </a>
    </body>
</HTML>

Цей код містить три динамічні поля:

simulationsHTML
код HTML зі списку моделювань у вигляді рядків таблиці HTML
href
URL-адреса посилання
lien
текст посилання

Вони генеруються контролером [simulations.aspx.vb]:


Imports System.Collections
Imports Microsoft.VisualBasic

Public Class simulations
    Inherits System.Web.UI.Page

    Protected simulationsHTML As String = ""
    Protected href As String
    Protected lien As String

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        'отримуємо симуляції з контексту
        Dim simulations As ArrayList = CType(context.Items("simulations"), ArrayList)
        ' кожна симуляція — це масив із 4 елементів типу string
        Dim simulation() As String
        Dim i, j As Integer
        For i = 0 To simulations.Count - 1
            simulation = CType(simulations(i), String())
            simulationsHTML += "<tr>"
            For j = 0 To simulation.Length - 1
                simulationsHTML += "<td>" + simulation(j) + "</td>"
            Next
            simulationsHTML += "</tr>" + ControlChars.CrLf
        Next
        ' отримуємо інші елементи контексту
        href = context.Items("href").ToString
        lien = context.Items("lien").ToString
    End Sub
End Class

Контролер сторінки отримує інформацію, розміщену контролером додатка в контексті сторінки:

Context.Items("simulations")
об’єкт ArrayList, що містить список симуляцій, які потрібно відобразити. Кожен елемент — це масив із 4 рядків, що містять інформацію (статус шлюбу, діти, зарплата, податок) про симуляцію.
Context.Items("href")
URL-адреса посилання
Context.Items("lien")
текст посилання

6.4.4. Контролери [global.asax, main.aspx]

Нагадаємо схему MVC нашого додатка:

Image

Контролер [main.aspx] має обробити три дії:

  • init: відповідає першому запиту клієнта. Контролер відображає вигляд [formulaire.aspx]
  • calcul: відповідає запиту на розрахунок податку. Якщо дані у формі введення правильні, податок розраховується за допомогою бізнес-класу [impotsODBC]. Контролер повертає клієнту представлення [simulations.aspx] із результатом поточного моделювання та всіх попередніх. Якщо дані у формі введення неправильні, контролер повертає представлення [erreurs.aspx] із переліком помилок та посиланням для повернення до форми.
  • повернення: відповідає поверненню до форми після помилки. Контролер відображає вигляд [formulaire.aspx] у тому вигляді, в якому він був перевірений на правильність перед виникненням помилки.

У цій новій версії змінилася лише дія [calcul]. Адже якщо дані є правильними, вона повинна привести до перегляду [simulations.aspx], тоді як раніше вона приводила до перегляду [formulaire.aspx]. Контролер [main.aspx.vb] виглядає наступним чином:


Imports System
...

Public Class main
    Inherits System.Web.UI.Page

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' насамперед перевіряємо, чи вдалося програмі правильно ініціалізуватися
...
        ' виконуємо дію
        Select Case action
            Case "init"
                ' ініціалізація програми
                initAppli()
            Case "calcul"
                ' розрахунок податку
                calculImpot()
            Case "retour"
                ' повернення до форми
                retourFormulaire()
            Case "effacer"
                ' ініціалізація додатка
                initAppli()
            Case Else
                ' невідома дія = ініціалізація
                initAppli()
        End Select
    End Sub

...
    Private Sub calculImpot()
        ' збереження введених даних
        Session.Item("formulaire") = Request.Form
        ' перевіряється правильність введених даних
        Dim erreurs As ArrayList = checkData()
        ' якщо є помилки, про це повідомляється
        If erreurs.Count <> 0 Then
            ' готується сторінка помилок
            context.Items("href") = "main.aspx?action=retour"
            context.Items("lien") = "Retour au formulaire"
            context.Items("erreurs") = erreurs
            Server.Transfer("erreurs.aspx")
        End If
        ' помилок немає — обчислюється податок
        Dim impot As Long = CType(Application("objImpot"), impot).calculer( _
        Request.Form("rdMarie") = "oui", _
        CType(Request.Form("txtEnfants"), Integer), _
        CType(Request.Form("txtSalaire"), Long))
        ' результат додається до існуючих моделювань
        Dim simulations As ArrayList
        If Not Session.Item("simulations") Is Nothing Then
            simulations = CType(Session.Item("simulations"), ArrayList)
        Else
            simulations = New ArrayList
        End If
        ' додавання поточного моделювання
        Dim simulation() As String = New String() {Request.Form("rdMarie").ToString, _
        Request.Form("txtEnfants").ToString, Request.Form("txtSalaire").ToString, _
        impot.ToString}
        simulations.Add(simulation)
        ' моделювання додано до сесії та контексту
        context.Items("simulations") = simulations
        Session.Item("simulations") = simulations
        ' відображаємо сторінку результатів
        context.Items("href") = "main.aspx?action=retour"
        context.Items("lien") = "Retour au formulaire"
        Server.Transfer("simulations.aspx", True)
    End Sub
...
End Class

Вище ми навели лише те, що було необхідно для розуміння змін, які містяться виключно у функції [calculImpots]:

  • по-перше, функція зберігає форму [Request.Form] у сесії, щоб мати змогу відтворити форму в тому стані, в якому вона була затверджена. Це потрібно робити в будь-якому випадку, оскільки незалежно від того, чи операція завершується відповіддю [erreurs.aspx] чи [simulations.aspx], повернення до форми здійснюється за посиланням [Retour au formulaire]. Щоб правильно відновити форму, необхідно попередньо зберегти її значення в сесії.
  • Якщо введені дані виявляються правильними, функція додає поточну симуляцію (статус одруженого, наявність дітей, заробітна плата, податок) до списку симуляцій. Цей список знаходиться в сесії, пов’язаній із ключем «simulations».
  • Список симуляцій зберігається у сесії для подальшого використання. Він також розміщується в поточному контексті, оскільки саме там його очікує подання [simulations.aspx]
  • Погляд [simulations.aspx] відображається після того, як у контекст буде додано іншу інформацію, на яку він очікує

6.4.5. Підсумок змін

Додаток готовий до тестування. Перелічимо зміни, внесені порівняно з попередніми версіями:

  1. створено новий вигляд
  2. контролер [main.aspx.vb] було змінено в одному місці: обробка дії [calcul]

6.4.6. Тестування веб-додатку

Читачеві пропонується провести тестування. Нагадаємо порядок дій. Усі файли додатка розміщені в папці <application-path>. У цій папці створено підпапку [bin], у якій розміщено збірку [impot.dll], отриману в результаті компіляції файлів бізнес-класів: [impots.vb, impotsData.vb, impotsArray.vb, impotsODBC.vb. Файл [impot.dll], створений цією командою, потрібно розмістити в <application-path>\bin, щоб веб-додаток мав до нього доступ. Сервер Cassini запускається з параметрами (<application-path>,/impots4).

6.5. Conclusion

Попередні приклади продемонстрували на конкретному випадку механізми, що часто використовуються у веб-розробці. Ми систематично використовували архітектуру MVC з педагогічною метою. Ці самі приклади можна було б опрацювати інакше, і, можливо, простіше, без цієї архітектури. Однак вона надає значні переваги, щойно додаток стає дещо складнішим і містить багато сторінок.

Ми могли б продовжити наші приклади різними способами. Ось декілька з них:

  • користувач може захотіти зберегти свої симуляції з плином часу. Він міг би виконати симуляції в день J і, наприклад, переглянути їх у день J+3. Одним із можливих рішень цієї проблеми є використання файлів 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], не заповнивши попередньо форму? Спробуймо. Ми отримаємо таку відповідь:

Image

Стався збій сервера. Це нормально. Для дії [retour] контролер очікує знайти в сесії об’єкт [NameValueCollection], який представляє значення форми, яку він повинен відобразити. Він їх не знаходить. Механізм контролера дозволяє елегантно вирішити цю проблему. Для кожного запиту контролер [main.aspx] може перевірити, чи запитувана дія дійсно є однією з дій сторінки, раніше надісланої користувачеві. Можна використати такий механізм:

  • контролер, перш ніж надіслати відповідь клієнту, зберігає в сесії клієнта інформацію, що ідентифікує цю сторінку
  • отримуючи новий запит від клієнта, він перевіряє, чи запитувана дія дійсно належить до останньої сторінки, надісланої цьому клієнту
  • інформацію, що пов’язує сторінки та дозволені на них дії, можна внести до конфігураційного файлу [web.config] додатка.
  • досвід показує, що контролери додатків мають широку спільну базу і що можна створити універсальний контролер, спеціалізація якого під конкретний додаток здійснюється за допомогою конфігураційного файлу. Цього підходу дотримується, наприклад, інструмент [Struts] у сфері веб-програмування на Java.