6. Przykłady
W niniejszym rozdziale zamierzamy zilustrować omówione wcześniej zagadnienia za pomocą serii przykładów.
6.1. Przykład 1
6.1.1. Problem
Ta aplikacja ma umożliwić użytkownikowi obliczenie wysokości podatku. Rozważamy uproszczony przypadek podatnika, który ma do zadeklarowania wyłącznie swoje wynagrodzenie (dane z 2004 r. dotyczące dochodów z 2003 r.):
- liczba udziałów pracownika obliczana jest jako nbParts = nbEnfants/2 + 1, jeśli jest on osobą stanu wolnego, nbEnfants/2 + 2, jeśli jest żonaty, gdzie nbEnfants to liczba jego dzieci.
- jeśli ma co najmniej troje dzieci, otrzymuje dodatkową połowę udziału
- oblicza się jego dochód podlegający opodatkowaniu R = 0,72 * S, gdzie S to jego roczne wynagrodzenie
- oblicza się jego współczynnik rodzinny QF = R / nbParts
- oblicza się jego podatek I. Rozważmy następującą tabelę:
4262 | 0 | 0 |
8382 | 0,0683 | 291,09 |
14753 | 0,1914 | 1322,92 |
23 888 | 0,2826 | 2668,39 |
38 868 | 0,3738 | 4846,98 |
47932 | 0,4262 | 6883,66 |
0 | 0,4809 | 9505,54 |
Każdy wiersz zawiera 3 pola. Aby obliczyć podatek I, należy znaleźć pierwszy wiersz, w którym QF <= pole1. Na przykład, jeśli QF = 5000, znajdziemy wiersz
Podatek I wynosi wówczas 0,0683*R – 291,09*nbParts. Jeśli QF jest takie, że warunek QF<=pole1 nigdy nie jest spełniony, wówczas stosuje się współczynniki z ostatniego wiersza. W tym przypadku:
co daje podatek I = 0,4809*R – 9505,54*nbParts.
6.1.2. Struktura MVC w aplikacji
Struktura MVC aplikacji będzie wyglądać następująco:

Rolę kontrolera pełni strona [main.aspx]. Możliwe są trzy akcje:
- init: odpowiada pierwszemu żądaniu klienta. Kontroler wyświetli widok [formulaire.aspx]
- calcul: odpowiada żądaniu obliczenia podatku. Jeśli dane z formularza wprowadzania danych są poprawne, podatek jest obliczany za pomocą klasy biznesowej [impots]. Kontroler zwraca klientowi widok [formulaire.aspx] w postaci, w jakiej został zatwierdzony, wraz z obliczonym podatkiem. Jeśli dane z formularza wprowadzania danych są nieprawidłowe, kontroler zwróci widok [erreurs.aspx] wraz z listą błędów i linkiem umożliwiającym powrót do formularza.
- powrót: odpowiada powrotowi do formularza po wystąpieniu błędu. Kontroler wyświetla widok [formulaire.aspx] w stanie, w jakim został zatwierdzony przed wystąpieniem błędu.
Kontroler [main.aspx] nie zajmuje się obliczaniem podatków. Odpowiada jedynie za zarządzanie komunikacją klient-serwer oraz za wykonanie czynności żądanych przez klienta. W przypadku akcji [calcul] będzie on korzystał z klasy biznesowej [impot].
6.1.3. Klasa biznesowa
Klasa podatkowa zostanie zdefiniowana w następujący sposób:
' importowanych przestrzeni nazw
Imports System
' klasa
Namespace st.istia.univangers.fr
Public Class impot
Private limites(), coeffR(), coeffN() As Decimal
' konstruktor
Public Sub New(ByRef source As impotsData)
' dane niezbędne do obliczenia podatku
' pochodzą z zewnętrznego źródła [source]
' są pobierane – może wystąpić wyjątek
Dim data() As Object = source.getData
limites = CType(data(0), Decimal())
coeffR = CType(data(1), Decimal())
coeffN = CType(data(2), Decimal())
End Sub
' obliczenie podatku
Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Long) As Long
' obliczenie liczby udziałów
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
' obliczenie dochodu podlegającego opodatkowaniu i współczynnika rodzinnego
Dim revenu As Decimal = 0.72D * salaire
Dim QF As Decimal = revenu / nbParts
' obliczenie podatku
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
Obiekt podatku tworzy się poprzez przekazanie do jego konstruktora źródła danych typu [impotsData]. Klasa ta posiada metodę publiczną [getData], która pozwala uzyskać trzy tabele danych niezbędne do obliczenia podatku, które zostały przedstawione wcześniej. Metoda ta może obsłużyć wyjątek, jeśli dane nie mogły zostać pobrane lub jeśli okażą się nieprawidłowe. Po utworzeniu obiektu [impot] można wielokrotnie wywoływać jego metodę calculer, która oblicza podatek podatnika na podstawie jego stanu cywilnego (żonaty lub nie), liczby dzieci oraz rocznego wynagrodzenia.
6.1.4. Klasa dostępu do danych
Klasa [impotsData] umożliwia dostęp do danych. Jest to klasa abstrakcyjna. Dla każdego nowego potencjalnego źródła danych (tabele, pliki tekstowe, bazy danych, konsola itp.) należy utworzyć klasę pochodną. Jej definicja jest następująca:
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
' metoda dostępu do danych
Public MustOverride Function getData() As Object()
' metoda weryfikacji danych
Protected Function checkData() As Integer
' weryfikacja pobranych danych
' muszą być dostępne dane
valide = Not limites Is Nothing AndAlso Not coeffr Is Nothing AndAlso Not coeffn Is Nothing
If Not valide Then Return 1
' muszą istnieć 3 tabele tej samej wielkości
If valide Then valide = limites.Length = coeffr.Length AndAlso limites.Length = coeffn.Length
If Not valide Then Return 2
' tabele muszą być niepuste
valide = limites.Length <> 0
If Not valide Then Return 3
' każda tablica musi zawierać elementy >=0 w porządku rosnącym
valide = check(limites, limites.Length - 1) AndAlso check(coeffr, coeffr.Length) AndAlso check(coeffn, coeffn.Length)
If Not valide Then Return 4
' wszystko jest w porządku
Return 0
End Function
' sprawdza poprawność zawartości tablicy
Protected Function check(ByRef tableau() As Decimal, ByVal n As Integer) As Boolean
' tablica musi mieć pierwsze n elementów >=0 i uporządkowanych w ściśle rosnącej kolejności
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
' w porządku
Return True
End Function
End Class
End Namespace
Klasa posiada następujące atrybuty chronione:
tabela progów podatkowych | |
tabela współczynników stosowanych do dochodu podlegającego opodatkowaniu | |
tabela współczynników stosowanych do liczby udziałów | |
wartość logiczna wskazująca, czy dane (limity, współczynnik1, współczynnik2) zostały zweryfikowane | |
wartość logiczna wskazująca, czy dane (limity, współczynnikr, współczynnikn) są prawidłowe |
Klasa nie posiada konstruktora. Posiada metodę abstrakcyjną [getData], którą klasy pochodne będą musiały zaimplementować. Zadaniem tej metody jest:
- przypisanie wartości do trzech tablic: limity, współczynniki_r, współczynniki_n
- wygenerowanie wyjątku, jeśli dane nie mogły zostać pobrane lub okazały się nieprawidłowe.
Klasa udostępnia metody chronione [checkData] i [check], które sprawdzają poprawność atrybutów (limites, coeffr, coeffn). Dzięki temu klasy pochodne nie muszą ich implementować. Wystarczy, że będą z nich korzystać.
Pierwszą klasą pochodną, z której skorzystamy, będzie następująca:
Imports System.Collections
Imports System
Namespace st.istia.univangers.fr
Public Class impotsArray
Inherits impotsData
' konstruktor bez argumentów
Public Sub New()
' inicjalizacja tablic za pomocą stałych
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
' konstruktor z trzema tablicami jako argumentami wejściowymi
Public Sub New(ByRef limites() As Decimal, ByRef coeffr() As Decimal, ByRef coeffn() As Decimal)
' zapisujemy dane
Me.limites = limites
Me.coeffr = coeffr
Me.coeffn = coeffn
checked = False
End Sub
Public Overrides Function getData() As Object()
' ewentualnie sprawdzamy dane
Dim erreur As Integer
If Not checked Then erreur = checkData() : checked = True
' jeśli dane są nieprawidłowe, to zgłaszamy wyjątek
If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
' w przeciwnym razie zwracane są trzy tablice
Return New Object() {limites, coeffr, coeffn}
End Function
End Class
End Namespace
Ta klasa o nazwie [impotsArray] posiada dwa konstruktory:
- konstruktor bez argumentów, który inicjuje atrybuty (limity, coeffr, coeffn) klasy bazowej za pomocą „na stałe” zakodowanych tablic
- konstruktor, który inicjuje atrybuty (limity, coeffr, coeffn) klasy bazowej za pomocą tablic przekazanych mu jako parametry
Metoda [getData], która umożliwi klasom zewnętrznym uzyskanie tablic (limites, coeffr, coeffn), ogranicza się do sprawdzenia poprawności tych trzech tablic za pomocą metody [checkData] klasy bazowej. W przypadku nieprawidłowych danych zgłasza wyjątek.
6.1.5. Testowanie klas biznesowych i klas dostępu do danych
Ważne jest, aby w aplikacji internetowej uwzględniać wyłącznie klasy biznesowe i klasy dostępu do danych, których poprawność została potwierdzona. Dzięki temu faza debugowania aplikacji internetowej będzie mogła skupić się na części dotyczącej kontrolera i widoków. Program testowy mógłby wyglądać następująco:
' opcje
Option Strict On
Option Explicit On
' przestrzenie nazw
Imports System
Imports Microsoft.VisualBasic
Namespace st.istia.univangers.fr
Module test
Sub Main()
' interaktywny program do obliczania podatku
' użytkownik wprowadza trzy dane za pomocą klawiatury: żonaty nbEnfants wynagrodzenie
' następnie program wyświetla kwotę podatku do zapłaty
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"
' utworzenie obiektu „podatek”
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
' pętla nieskończona
Dim marié As String
Dim nbEnfants As Integer
Dim salaire As Long
While True
' wyświetlane jest zapytanie o parametry obliczenia podatku
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()
' co należy zrobić?
If paramètres Is Nothing OrElse paramètres = "" Then
Exit While
End If
' sprawdzanie liczby argumentów w wprowadzonej linii
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
' sprawdzanie poprawności parametrów
If Not erreur Then
' żonaty
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
' wynagrodzenie
Try
salaire = Integer.Parse(args(2))
If salaire < 0 Then
Throw New Exception
End If
Catch
erreur = True
End Try
End If
' jeśli parametry są poprawne – obliczamy podatek
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
Aplikacja prosi użytkownika o wpisanie z klawiatury trzech informacji potrzebnych do obliczenia podatku:
- stan cywilny: o – żonaty/zamężna, n – niezamężna/nieżonaty
- liczba dzieci
- jego roczne wynagrodzenie
Obliczenie podatku odbywa się za pomocą obiektu typu [impot], tworzonego w momencie uruchomienia aplikacji:
' utworzenie obiektu podatku
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
Jako źródło danych wykorzystuje się obiekt typu [impotsArray]. Wykorzystywany jest konstruktor tej klasy bez argumentów, który dostarcza trzy tablice (limites, coeffr, coeffn) z wartościami „stałymi”. Utworzenie obiektu typu [impot] może teoretycznie spowodować wyjątek, ponieważ w trakcie tworzenia obiekt ten będzie żądał danych (limity, coeffr, coeffn) od źródła danych przekazanego mu jako parametr, a proces pozyskiwania tych danych może wywołać wyjątek. Okazuje się jednak, że w tym przypadku metoda pozyskiwania danych (wartości stałe) nie może spowodować wyjątku. Pozostawiliśmy jednak obsługę wyjątku, aby zwrócić uwagę czytelnika na możliwość nieprawidłowego utworzenia obiektu [impot].
Oto przykład wykonania powyższego programu:
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
Kompilujemy wszystkie klasy [impot, impotsData, impotsArray] do zestawu [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
Kompilujemy program testowy:
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
Możemy przeprowadzić testy:
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. Widoki aplikacji internetowej
Aplikacja będzie miała dwa widoki: [formulaire.aspx] i [erreurs.aspx]. Zilustrujmy działanie aplikacji za pomocą zrzutów ekranu. Widok [formulaire.aspx] jest wyświetlany przy pierwszym wywołaniu adresu URL [main.aspx]:

Użytkownik wypełnia formularz:

i korzysta z przycisku [Calculer], aby uzyskać następującą odpowiedź:

Może popełnić błąd przy wprowadzaniu danych:

Kliknięcie przycisku [Calculer] powoduje wówczas wyświetlenie innej odpowiedzi [erreurs.aspx]:

Może skorzystać z powyższego linku [Retour au formulaire], aby powrócić do widoku [formulaire.aspx] w stanie, w jakim został zatwierdzony przed wystąpieniem błędu:

6.1.7. Widok [formulaire.aspx]
Strona [formulaire.aspx] będzie wyglądać następująco:
<%@ 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>
Pola dynamiczne na tej stronie to:
„checked”, jeśli pole wyboru [oui] ma być zaznaczone, w przeciwnym razie „” | |
to samo dotyczy pola wyboru [non] | |
wartość do umieszczenia w polu wprowadzania danych [txtEnfants] | |
wartość do wpisania w polu wprowadzania danych [txtSalaire] | |
wartość do umieszczenia w polu wprowadzania danych [txtImpot] |
Strona zawiera dwa formularze, z których każdy posiada przycisk [submit]. Przycisk [Calculer] jest przyciskiem [submit] z następującego formularza:
<form method="post" action="main.aspx?action=calcul">
...
<P>
<INPUT type="submit" value="Calculer">
</P>
</form>
Widać, że parametry formularza zostaną przesłane do kontrolera za pomocą [action=calcul]. Przycisk [Effacer] to przycisk [submit] z następującego formularza:
<form method="post" action="main.aspx?action=effacer">
<INPUT type="submit" value="Effacer">
</form>
Widać, że parametry formularza zostaną przesłane do kontrolera za pomocą [action=effacer]. W tym przypadku formularz nie ma żadnych parametrów. Liczy się tylko akcja.
Pola [formulaire.aspx] są obliczane przez [formulaire.aspx.vb]:
Imports System.Collections.Specialized
Public Class formulaire
Inherits System.Web.UI.Page
' pola na stronie
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
' pobieranie poprzedniego zapytania z kontekstu
Dim form As NameValueCollection = Context.Items("formulaire")
' przygotowuje się stronę do wyświetlenia
' przyciski opcji
rdouichecked = ""
rdnonchecked = "checked"
If form("rdMarie").ToString = "oui" Then
rdouichecked = "checked"
rdnonchecked = ""
End If
' reszta
txtEnfants = CType(form("txtEnfants"), String)
txtSalaire = CType(form("txtSalaire"), String)
txtImpot = CType(Context.Items("txtImpot"), String)
End Sub
End Class
Obliczenia pól [main.aspx] opierają się na dwóch informacjach umieszczonych przez kontroler w kontekście strony:
- Context.Items („formularz”): słownik typu NameValueCollection zawierający wartości pól HTML [rdmarie,txtEnfants,txtSalaire]
- Context.Items („txtImpot”): wartość podatku
6.1.8. Widok [erreurs.aspx]
Widok [erreurs.aspx] wyświetla ewentualne błędy, które mogą wystąpić podczas działania aplikacji. Jego kod prezentacji jest następujący:
<%@ 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>
Strona zawiera trzy pola dynamiczne:
kod HTML z listy błędów | |
adres URL linku | |
tekst linku |
Pola te są obliczane przez część sterującą strony w [erreurs.aspx.vb]:
Imports System.Collections
Imports Microsoft.VisualBasic
Public Class erreurs
Inherits System.Web.UI.Page
' parametr strony
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
' pobieramy elementy z kontekstu
Dim erreurs As ArrayList = CType(context.Items("erreurs"), ArrayList)
href = context.Items("href").ToString
lien = context.Items("lien").ToString
' generowanie kodu HTML z listy
Dim i As Integer
For i = 0 To erreurs.Count - 1
erreursHTML += "<li> " + erreurs(i).ToString + "</li>" + ControlChars.CrLf
Next
End Sub
End Class
Kontroler strony pobiera informacje umieszczone przez kontroler aplikacji w kontekście strony:
obiekt ArrayList zawierający listę komunikatów o błędach do wyświetlenia | |
adres URL linku | |
tekst linku |
Teraz, gdy wiemy już, co widzi użytkownik aplikacji, możemy przejść do pisania jej kontrolera.
6.1.9. Kontrolery [global.asax, main.aspx]
Przypomnijmy schemat MVC naszej aplikacji:

Klient Logika aplikacji
Kontroler [main.aspx] musi obsłużyć trzy akcje:
- init: odpowiada pierwszemu żądaniu klienta. Kontroler wyświetla widok [formulaire.aspx]
- calcul: odpowiada żądaniu obliczenia podatku. Jeśli dane z formularza wprowadzania danych są poprawne, podatek jest obliczany za pomocą klasy biznesowej [impots]. Kontroler zwraca klientowi widok [formulaire.aspx] w postaci, w jakiej został zatwierdzony, wraz z obliczonym podatkiem. Jeśli dane w formularzu wprowadzania danych są nieprawidłowe, kontroler zwraca widok [erreurs.aspx] wraz z listą błędów i linkiem umożliwiającym powrót do formularza.
- powrót: odpowiada powrotowi do formularza po wystąpieniu błędu. Kontroler wyświetla widok [formulaire.aspx] w stanie, w jakim został zatwierdzony przed wystąpieniem błędu.
Wiadomo ponadto, że każde żądanie kierowane do aplikacji przechodzi przez kontroler [global.asax], o ile istnieje. Mamy zatem na wejściu do aplikacji łańcuch dwóch kontrolerów:
- [global.asax], który zgodnie z architekturą ASP.NET odbiera wszystkie żądania kierowane do aplikacji
- [main.aspx], który zgodnie z decyzją programisty również odbiera wszystkie żądania kierowane do aplikacji
Konieczność zastosowania [main.aspx] wynika z faktu, że będziemy musieli zarządzać sesją. Widzieliśmy już, że [global.asax] nie nadaje się w tym przypadku na kontroler. W tym przypadku moglibyśmy całkowicie zrezygnować z [global.asax]. Wykorzystamy go jednak do wykonania kodu podczas uruchamiania aplikacji. Powyższy schemat MVC pokazuje, że będziemy musieli utworzyć obiekt [impot] w celu obliczenia podatku. Nie ma sensu tworzyć go wielokrotnie – wystarczy raz. Utworzymy go zatem podczas uruchamiania aplikacji w ramach zdarzenia [Application_Start] obsługiwanego przez kontroler [global.asax]. Jego kod wygląda następująco:
[global.asax]
[global.asax.vb]
Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr
Public Class Global
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' tworzy się obiekt importu
Dim objImpot As impot
Try
objImpot = New impot(New impotsArray)
' umieszczamy obiekt w aplikacji
Application("objImpot") = objImpot
' brak błędu
Application("erreur") = False
Catch ex As Exception
'wystąpił błąd, odnotowuje się go w aplikacji
Application("erreur") = True
End Try
End Sub
End Class
Po utworzeniu obiekt typu [impot] jest umieszczany w aplikacji. To właśnie stamtąd będą go pobierać różne zapytania od różnych klientów. Ponieważ tworzenie obiektu typu [impot] może się nie powieść, obsługujemy ewentualny wyjątek i umieszczamy w aplikacji klucz [erreur], aby zasygnalizować, czy podczas tworzenia obiektu [impot] wystąpił błąd, czy nie.
Kod kontrolera [main.aspx, main.aspx.vb] będzie wyglądał następująco:
[main.aspx]
[main.aspx.vb]
Imports System
Imports System.Collections.Specialized
Imports System.Collections
Imports st.istia.univangers.fr
Public Class main
Inherits System.Web.UI.Page
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' przede wszystkim sprawdzamy, czy aplikacja zainicjowała się poprawnie
If CType(Application("erreur"), Boolean) Then
' przekierowujemy na stronę błędów
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
' pobieramy akcję do wykonania
Dim action As String
If Request.QueryString("action") Is Nothing Then
action = "init"
Else
action = Request.QueryString("action").ToString.ToLower
End If
' wykonujemy działanie
Select Case action
Case "init"
' inicjowanie aplikacji
initAppli()
Case "calcul"
' obliczanie podatku
calculImpot()
Case "retour"
' powrót do formularza
retourFormulaire()
Case "effacer"
' inicjowanie aplikacji
initAppli()
Case Else
' nieznana akcja = inicjalizacja
initAppli()
End Select
End Sub
Private Sub initAppli()
' wyświetlanie wstępnie wypełnionego formularza
Context.Items("formulaire") = initForm()
Context.Items("txtImpot") = ""
Server.Transfer("formulaire.aspx", True)
End Sub
Private Function initForm() As NameValueCollection
' formularz jest inicjowany
Dim form As New NameValueCollection
form.Set("rdMarie", "non")
form.Set("txtEnfants", "")
form.Set("txtSalaire", "")
Return form
End Function
Private Sub calculImpot()
' sprawdzana jest poprawność wprowadzonych danych
Dim erreurs As ArrayList = checkData()
' jeśli występują błędy, wyświetla się komunikat
If erreurs.Count <> 0 Then
' zapisuje się wprowadzone dane
Session.Item("formulaire") = Request.Form
' przygotowuje się stronę błędów
context.Items("href") = "main.aspx?action=retour"
context.Items("lien") = "Retour au formulaire"
context.Items("erreurs") = erreurs
Server.Transfer("erreurs.aspx")
End If
' nie ma błędów – oblicza się podatek
Dim impot As Long = CType(Application("objImpot"), impot).calculer( _
Request.Form("rdMarie") = "oui", _
CType(Request.Form("txtEnfants"), Integer), _
CType(Request.Form("txtSalaire"), Long))
' wyświetla się strona z wynikami
context.Items("txtImpot") = impot.ToString + " euro(s)"
context.Items("formulaire") = Request.Form
Server.Transfer("formulaire.aspx", True)
End Sub
Private Sub retourFormulaire()
' wyświetla się formularz z wartościami pobranymi z sesji
Context.Items("formulaire") = Session.Item("formulaire")
Context.Items("txtImpot") = ""
Server.Transfer("formulaire.aspx", True)
End Sub
Private Function checkData() As ArrayList
' na początku brak błędów
Dim erreurs As New ArrayList
Dim erreur As Boolean = False
' przycisk opcji „żonaty”
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
' liczba dzieci
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
' wynagrodzenie
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
' wyświetla się lista błędów
Return erreurs
End Function
End Class
Kontroler rozpoczyna od sprawdzenia, czy aplikacja została poprawnie zainicjowana:
' przede wszystkim sprawdzamy, czy aplikacja zainicjowała się poprawnie
If CType(Application("erreur"), Boolean) Then
' przekierowujemy na stronę błędów
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
Jeśli kontroler stwierdzi, że aplikacja nie mogła się poprawnie zainicjować (nie udało się utworzyć obiektu [impot] niezbędnego do obliczeń), wyświetla stronę błędów z odpowiednimi parametrami. W tym przypadku nie ma potrzeby umieszczania linku powrotnego do formularza, ponieważ cała aplikacja jest niedostępna. Ogólny komunikat o błędzie jest umieszczany w obiekcie [Context.Items("erreurs")] typu [ArrayList].
Jeśli kontroler stwierdzi, że aplikacja działa, analizuje następnie akcję, o której wykonanie został poproszony za pośrednictwem parametru [action]. Z tym sposobem działania mieliśmy już do czynienia wielokrotnie. Obsługa każdego rodzaju akcji jest powierzona funkcji.
6.1.9.1. Akcje init, effacer
Te dwie akcje powinny spowodować wyświetlenie pustego formularza wprowadzania danych. Przypominamy, że formularz ten (patrz widoki) ma dwa parametry:
- Context.Items („formularz”): słownik typu [NameValueCollection] zawierający wartości pól HTML [rdmarie,txtEnfants,txtSalaire]
- Context.Items("txtImpot”): wartość podatku
Funkcja [initAppli] inicjuje te dwa parametry w taki sposób, aby wyświetlić pusty formularz.
6.1.9.2. Akcja obliczeniowa
Ta akcja musi obliczyć podatek do zapłaty na podstawie danych wprowadzonych w formularzu i zwrócić go wstępnie wypełnionego wprowadzonymi wartościami oraz dodatkowo z obliczoną kwotą podatku. Funkcja [calculImpot], odpowiedzialna za to zadanie, rozpoczyna od sprawdzenia, czy dane w formularzu są poprawne:
- pole [rdMarie] musi istnieć i mieć wartość [oui] lub [non]
- pole [txtEnfants] musi istnieć i zawierać liczbę całkowitą >=0
- pole [txtSalaire] musi być obecne i zawierać liczbę całkowitą >=0
Jeśli wprowadzone dane okażą się nieprawidłowe, kontroler wyświetla widok [erreurs.aspx], po uprzednim umieszczeniu w kontekście wartości oczekiwanych przez ten widok:
- komunikaty o błędach są umieszczane w obiekcie [ArrayList], który następnie jest umieszczany w kontekście [Context.Items("erreurs")]
- adres URL linku powrotnego oraz tekst tego linku są również umieszczane w kontekście.
Przed przekazaniem kontroli do strony [erreurs.aspx], która wyśle odpowiedź do klienta, wartości wprowadzone w formularzu (Request.Form) są umieszczane w sesji, powiązane z kluczem „formularz”. Umożliwi to ich odzyskanie w późniejszym żądaniu.
Można się tutaj zastanowić, czy warto sprawdzać, czy pola [rdMarie, txtEnfants, txtSalaire] znajdują się w żądaniu wysłanym przez klienta. Jest to zbędne, jeśli mamy pewność, że naszym klientem jest przeglądarka, która otrzymała widok [formulaire.aspx] zawierający te pola. Nigdy jednak nie można mieć co do tego pewności. Nieco później pokażemy przykład, w którym klientem jest aplikacja [curl], z którą już mieliśmy do czynienia. Wyślemy zapytanie do aplikacji bez przesyłania pól, których ona oczekuje, i zobaczymy, jak zareaguje. Jest to zasada wielokrotnie już podkreślana, o której przypominamy w tym miejscu: aplikacja nigdy nie powinna zakładać, jaki typ klienta wysyła do niej zapytanie. Ze względów bezpieczeństwa musi zakładać, że może zostać zapytana przez zaprogramowaną aplikację, która może wysłać jej nieoczekiwane ciągi parametrów. Musi zachowywać się poprawnie w każdym przypadku.
W naszym przypadku sprawdziliśmy, czy pola [rdMarie, txtEnfants, txtSalaire] występują w zapytaniu, ale nie sprawdziliśmy, czy może ono zawierać inne pola. W tej aplikacji zostałyby one zignorowane. Niemniej jednak, również ze względów bezpieczeństwa, warto byłoby zapisać tego typu żądania w pliku logów i zgłosić alert administratorowi aplikacji, aby ten wiedział, że aplikacja otrzymuje „dziwne” żądania. Analizując je w pliku logów, mógłby wykryć ewentualny atak na aplikację i podjąć niezbędne działania w celu jej ochrony.
Jeśli oczekiwane dane są poprawne, kontroler uruchamia obliczenie podatku przy użyciu obiektu [impot] przechowywanego w aplikacji. Następnie zapisuje w kontekście dwie informacje oczekiwane przez widok [formulaire.aspx]:
- Context.Items („formularz”): słownik typu [NameValueCollection] zawierający wartości pól HTML, [rdmarie,txtEnfants,txtSalaire], w tym przypadku [Request.Form)] oraz c.a.d. wartości wprowadzone wcześniej w formularzu
- Context.Items("txtImpot”): wartość podatku, która została właśnie obliczona
Uważny czytelnik mógł zadać sobie pytanie po przeczytaniu powyższego: skoro obiekt [impot] utworzony podczas uruchomienia aplikacji jest współdzielony przez wszystkie zapytania, czy nie mogą wystąpić konflikty dostępu prowadzące do uszkodzenia danych obiektu [impot]? Aby odpowiedzieć na to pytanie, musimy powrócić do kodu klasy [impot]. Zapytania wywołują metodę [impot].calculerImpot w celu uzyskania kwoty podatku do zapłaty. Musimy zatem przyjrzeć się właśnie temu kodowi:
Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Long) As Long
' obliczanie liczby udziałów
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
' obliczanie dochodu podlegającego opodatkowaniu i wskaźnika rodzinnego
Dim revenu As Decimal = 0.72D * salaire
Dim QF As Decimal = revenu / nbParts
' obliczanie podatku
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
Załóżmy, że jakiś wątek wykonuje poprzednią metodę i zostaje przerwany. Wówczas metodę tę wykonuje inny wątek. Jakie są zagrożenia? Aby to sprawdzić, dodaliśmy następujący kod:
Dim impot As Long = CLng(revenu * coeffR(i) - nbParts * coeffN(i))
' czekamy 10 sekund
Thread.Sleep(10000)
Return impot
Wątek 1, po obliczeniu wartości [impot1] zmiennej lokalnej [impot], zostaje przerwany. Następnie uruchamia się wątek 2 i oblicza nową wartość [impot2] dla tej samej zmiennej [impot], po czym zostaje przerwany. Wątek 1 odzyskuje kontrolę. Co znajduje w zmiennej lokalnej [impot]? Ponieważ zmienna ta jest zmienną lokalną metody, jest przechowywana w strukturze pamięci zwanej stosem. Stos ten stanowi część kontekstu wątku, który jest zapisywany w momencie jego przerwania. Kiedy uruchamia się wątek 2, jego kontekst jest ustanawiany wraz z nowym stosem, a tym samym nową zmienną lokalną [impot]. Gdy z kolei wątek 2 zostanie przerwany, jego kontekst również zostanie zapisany. Po ponownym uruchomieniu wątku 1 jego kontekst zostaje przywrócony, w tym jego stos. Odzyskuje on wówczas swoją zmienną lokalną [impot], a nie tę z wątku 2. Mamy zatem do czynienia z sytuacją, w której nie występują konflikty dostępu między żądaniami. Testy przeprowadzone z 10-sekundową przerwą, o której mowa powyżej, potwierdziły, że równoczesne żądania rzeczywiście dawały oczekiwany wynik.
6.1.9.3. Akcja powrotu
Ta akcja odpowiada kliknięciu linku [Retour vers le formulaire] w widoku [erreurs.aspx] w celu powrotu do widoku [formulaire.aspx], wstępnie wypełnionego wartościami wprowadzonymi wcześniej i zapisanymi w sesji. Funkcja [retourFormulaire] pobiera te informacje. Dwa parametry wymagane przez widok [formulaire.aspx] są inicjowane:
- Context.Items („formularz”) z wartościami wprowadzonymi wcześniej i zapisanymi w sesji
- Context.Items("txtImpot”) z pustym ciągiem znaków
6.1.10. Testowanie aplikacji internetowej
Wszystkie powyższe pliki znajdują się w folderze <application-path>.

W tym folderze tworzony jest podfolder [bin], w którym umieszczany jest pakiet [impot.dll] powstały w wyniku kompilacji plików klas biznesowych: [impots.vb, impotsData.vb, impotsArray.vb]. Poniżej przypominamy polecenie kompilacji:
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
Powyższy plik [impot.dll] należy umieścić w katalogu <application-path>\bin, aby aplikacja internetowa miała do niego dostęp. Serwer Cassini uruchamia się z parametrami (<application-path>,/impots1). W przeglądarce wpisujemy adres URL [http://localhost/impots1/main.aspx]:

Wypełniamy formularz:

Następnie uruchamiamy obliczenie podatku za pomocą przycisku [Calculer]. Otrzymujemy następującą odpowiedź:

Następnie wprowadzamy błędne dane:

Naciśnięcie przycisku [Calculer] powoduje wyświetlenie następującej odpowiedzi:

Kliknięcie linku [Retour au formulaire] powoduje powrót do formularza w stanie, w jakim znajdował się w momencie zatwierdzenia:

Wreszcie, kliknięcie przycisku [Effacer] powoduje zresetowanie strony:

6.1.11. Korzystanie z klienta [curl]
Ważne jest, aby testować aplikacje internetowe przy użyciu innych klientów niż przeglądarki. Jeśli wyślemy do przeglądarki formularz z parametrami, które mają zostać wysłane po jego zatwierdzeniu, przeglądarka odeśle wartości tych parametrów do serwera. Inny klient może tego nie zrobić, a wtedy serwer otrzyma żądanie, w którym brakuje parametrów. Musi on wiedzieć, jak postąpić w takiej sytuacji. Innym przykładem są weryfikacje danych wprowadzanych przez użytkownika, przeprowadzane po stronie klienta. Jeśli formularz zawiera dane wymagające weryfikacji, weryfikacja ta może zostać przeprowadzona po stronie klienta za pomocą skryptów zawartych w dokumencie zawierającym formularz. Przeglądarka wyśle formularz tylko wtedy, gdy wszystkie dane zweryfikowane po stronie klienta są prawidłowe. Można by zatem ulec pokusie, aby po stronie serwera założyć, że otrzymamy już zweryfikowane dane i nie przeprowadzać tej weryfikacji po raz drugi. Byłoby to błędem. W rzeczywistości klient inny niż przeglądarka mógłby przesłać do serwera nieprawidłowe dane, co groziłoby nieoczekiwanym zachowaniem aplikacji internetowej. Zilustrujemy te kwestie na przykładzie klienta [curl].
Najpierw wysyłamy żądanie pod adresem 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>
Serwer przesłał nam kod formularza HTML. W nagłówkach HTTP znajduje się plik cookie sesji. Wykorzystamy go w kolejnych żądaniach w celu utrzymania sesji. Wywołajmy akcję [calcul] bez podawania parametrów:
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>
Widzimy, że aplikacja internetowa zwróciła widok [erreurs] wraz z trzema komunikatami o błędach dotyczącymi trzech brakujących parametrów. Wyślijmy teraz błędne parametry:
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>
Wszystkie trzy błędy zostały poprawnie wykryte. Teraz wyślijmy prawidłowe parametry:
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>
Udało nam się prawidłowo obliczyć należny podatek: 4300 euro. Z tego przykładu wynika, że nie należy dać się zwieść temu, że piszemy aplikację internetową przeznaczoną dla klientów korzystających z przeglądarek. Aplikacja internetowa jest usługą TCP/IP, a ten protokół sieciowy nie pozwala określić charakteru aplikacji klienckiej danej usługi. Nie można więc stwierdzić, czy klient aplikacji internetowej jest przeglądarką, czy nie. Stosujemy zatem dwie zasady:
- po otrzymaniu żądania od klienta nie przyjmujemy żadnych założeń dotyczących klienta i sprawdzamy, czy parametry oczekiwane w żądaniu są obecne i prawidłowe
- tworzymy odpowiedź przeznaczoną dla przeglądarek, a więc zazwyczaj dokumenty HTML
Aplikacja internetowa może być zaprojektowana tak, aby obsługiwać jednocześnie różnych klientów, na przykład przeglądarki i telefony komórkowe. W takim przypadku w każdym żądaniu można dołączyć nowy parametr wskazujący typ klienta. W ten sposób przeglądarka zażąda obliczenia podatku poprzez żądanie wysłane pod adresem http://machine/impots/main.aspx?client=przeglądarka&action=obliczenie, podczas gdy telefon komórkowy wyśle żądanie pod adresem http://machine/impots/main.aspx?client=mobile&action=calcul. Struktura MVC ułatwia napisanie takiej aplikacji. Wygląda ona następująco:

Blok [Classes métier, Classes d'accès aux données] pozostaje bez zmian. Jest to bowiem część, która nie ma znaczenia dla klienta. Blok [Contrôleur] ulega niewielkiej zmianie, ale musi uwzględniać nowy parametr w zapytaniu – parametr [client] wskazujący, z jakim typem klienta ma do czynienia. Blok [vues] musi generować widoki dla każdego typu klienta. Warto byłoby uwzględnić obecność parametru [client] w zapytaniu już na etapie projektowania aplikacji, nawet jeśli w perspektywie krótko- lub średnioterminowej celem są wyłącznie przeglądarki. Jeśli aplikacja będzie musiała w przyszłości obsługiwać nowy typ klienta, wystarczy napisać tylko widoki dostosowane do tego typu.
6.2. Przykład 2
6.2.1. Problem
W tym miejscu zamierzamy zająć się tym samym problemem, co poprzednio, ale zmieniając źródło danych obiektu [impot] utworzonego przez aplikację internetową. W poprzedniej wersji wykorzystywano źródło danych dostarczające wartości z tablic „na stałe” zapisanych w kodzie. Tym razem nowe źródło danych będzie pobierać je ze źródła danych ODBC powiązanego z bazą MySQL.
6.2.2. Źródło danych ODBC
Dane będą znajdować się w tabeli o nazwie [IMPOTS] w bazie danych o nazwie MySQL, zwanej [dbimpots]. Zawartość tej tabeli będzie następująca:

Właścicielem bazy danych jest użytkownik o nazwie [admimpots] z hasłem [mdpimpots]. Do tej bazy danych przypisujemy źródło danych o nazwie ODBC. Zanim to zrobimy, przypomnijmy najpierw różne sposoby uzyskiwania dostępu do bazy danych za pomocą platformy .NET.
Istnieje wiele baz danych przeznaczonych dla platform Windows. Aby uzyskać do nich dostęp, aplikacje korzystają z programów zwanych sterownikami (drivers).

Na powyższym schemacie sterownik posiada dwa interfejsy:
- interfejs I1 udostępniany aplikacji
- interfejs I2 skierowany do bazy danych
Aby uniknąć konieczności przepisywania aplikacji napisanej dla bazy danych B1 w przypadku migracji do innej bazy danych B2, podjęto działania normalizacyjne dotyczące interfejsu I1. W przypadku korzystania z baz danych wykorzystujących „znormalizowane” sterowniki baza B1 zostanie dostarczona wraz ze sterownikiem P1, baza B2 wraz ze sterownikiem P2, a interfejs I1 dla obu tych sterowników będzie identyczny. Dzięki temu nie będzie konieczności przepisywania aplikacji. W ten sposób będzie można na przykład przenieść bazę danych ACCESS do bazy danych MySQL bez konieczności wprowadzania zmian w aplikacji.
Istnieją dwa rodzaje standardowych sterowników:
- sterowniki ODBC (Open DataBase Connectivity)
- sterowniki OLE i DB (Object Linking and Embedding DataBase)
Sterowniki ODBC umożliwiają dostęp do baz danych. Źródła danych dla sterowników OLE i DB są bardziej zróżnicowane: bazy danych, systemy pocztowe, katalogi... Nie ma żadnych ograniczeń. Każde źródło danych może stać się przedmiotem sterownika Ole DB, jeśli zdecyduje się na to wydawca. Korzyści są oczywiście ogromne: uzyskujemy jednolity dostęp do szerokiej gamy danych.
Platforma .NET 1.1 zawiera trzy rodzaje klas dostępu do danych:
- klasy SQL i Server.NET, służące do uzyskiwania dostępu do baz danych Microsoft SQL Server
- klasy Ole Db.NET, umożliwiające dostęp do baz SGBD wyposażonych w sterownik OLE i DB
- klasy odbc.net, umożliwiające dostęp do baz SGBD, oferujące sterownik ODBC
Klasa SGBD MySQL od dawna posiada sterownik ODBC. To właśnie tego sterownika używamy obecnie. W systemie Windows wybieramy opcję [Menu Démarrer/Panneau de configuration/Outils d'administration/Sources ODBC 32 bits]. W zależności od wersji systemu Windows ścieżka ta może się nieznacznie różnić. Otrzymujemy następującą aplikację, która pozwoli nam utworzyć nasze źródło ODBC:

Utworzymy źródło danych systemowe o nazwie c.a.d. Będzie to źródło danych, z którego będzie mógł korzystać każdy użytkownik komputera. W powyższym oknie wybieramy zakładkę [Source de données système]. Na wyświetlonej stronie znajduje się przycisk [Ajouter], którego użyjemy do utworzenia nowego źródła danych ODBC:

Kreator prosi o wybranie sterownika ODBC, który ma być używany. System Windows zawiera szereg wstępnie zainstalowanych sterowników ODBC. Sterownik ODBC dla MySQL nie znajduje się w tym pakiecie. Należy go zatem wcześniej zainstalować. Można go znaleźć w Internecie, wpisując w wyszukiwarce frazę „MySQL ODBC” lub „MyODBC”. W tym przypadku zainstalowaliśmy sterownik [MySQL ODBC 3.51]. Wybieramy go i przechodzimy do [Terminer]:

Należy podać pewne informacje:
nazwa, która będzie określać źródło danych ODBC. Każda aplikacja systemu Windows będzie mogła uzyskać dostęp do źródła za pomocą tej nazwy | |
dowolny tekst opisujący źródło danych | |
nazwa komputera, na którym znajduje się SGBD MySQL. W tym przypadku jest to komputer lokalny. Może to być również komputer zdalny. Umożliwiłoby to aplikacji systemu Windows dostęp do zdalnej bazy danych bez konieczności stosowania specjalnego kodowania. Jest to jedna z głównych zalet źródła ODBC. | |
SGBD MySQL może obsługiwać wiele baz danych. Tutaj określamy, którą z nich chcemy obsługiwać: dbimpots | |
nazwa użytkownika zarejestrowanego w ramach SGBD MySQL. Dostęp do źródła danych będzie odbywał się pod tą nazwą. W tym przypadku: admimpots | |
hasło tego użytkownika. Tutaj: mdpimpots | |
port roboczy SGBD MySQL. Domyślnie jest to port 3306. Nie zmieniliśmy go |
Po wykonaniu tych czynności sprawdzamy poprawność naszych parametrów połączenia za pomocą przycisku [Test Data Source]:

Po wykonaniu tej czynności mamy pewność co do naszego źródła danych ODBC. Teraz możemy z niego korzystać. Wykonujemy operację [OK] tyle razy, ile jest to konieczne, aby wyjść z kreatora ODBC.
Jeśli czytelnik nie posiada SGBD mySQL, może go bezpłatnie pobrać pod adresem URL [http://www.mysql.com]. Poniżej przedstawiamy procedurę tworzenia źródła danych ODBC w programie Access. Pierwsze kroki są identyczne jak te opisane wcześniej. Dodajemy nowe źródło danych systemowych:

Wybranym sterownikiem będzie [Microsoft Access Driver]. Wybieramy [Terminer], aby przejść do definicji źródła ODBC:

Należy podać następujące informacje:
nazwa, która będzie określać źródło danych ODBC. Każda aplikacja systemu Windows będzie mogła uzyskać dostęp do źródła za pomocą tej nazwy | |
dowolny tekst opisujący źródło danych | |
pełna nazwa pliku ACCESS, który ma być przetwarzany |
6.2.3. Nowa klasa dostępu do danych
Wróćmy do struktury MVC naszej aplikacji:

Na powyższym schemacie klasa [impotsData] odpowiada za pobieranie danych. W tym przypadku będzie musiała pobrać je z bazy MySQL [dbimpots]. Z poprzedniej wersji tej aplikacji wiemy już, że [impotsData] jest klasą abstrakcyjną, którą należy derywować za każdym razem, gdy chcemy dostosować ją do nowego źródła danych. Przypomnijmy strukturę tej klasy abstrakcyjnej:
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
' metoda dostępu do danych
Public MustOverride Function getData() As Object()
' metoda weryfikacji danych
Protected Function checkData() As Integer
' weryfikuje pozyskane dane
...
End Function
' sprawdza poprawność zawartości tablicy
Protected Function check(ByRef tableau() As Decimal, ByVal n As Integer) As Boolean
...
End Function
End Class
End Namespace
Klasa dziedzicząca po [impotsData] musi zaimplementować dwie metody:
- konstruktor, jeśli konstruktor bez argumentów klasy [impotsData] nie jest dla niej odpowiedni
- metodę [getData], która zwraca trzy tablice (limites, coeffr, coeffn)
Tworzymy klasę [impotsODBC], która będzie pobierać dane (limity, coeffr, coeffn) ze źródła ODBC, któremu nadamy nazwę:
Imports System.Data.Odbc
Imports System.Data
Imports System.Collections
Imports System
Namespace st.istia.univangers.fr
Public Class impotsODBC
Inherits impotsData
' zmienne instancji
Protected DSNimpots As String
' konstruktor
Public Sub New(ByVal DSNimpots As String)
' odnotowujemy trzy informacje
Me.DSNimpots = DSNimpots
End Sub
Public Overrides Function getdata() As Object()
' inicjuje trzy tablice: limites, coeffr, coeffn na podstawie
' zawartości tabeli [impots] z bazy ODBC DSNimpots
' limity, coeffr, coeffn to trzy kolumny tej tabeli
' może wygenerować różne wyjątki
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
' zapytanie SELECT
Dim selectCommand As String = "select limites,coeffr,coeffn from impots"
' tabele służące do pobierania danych
Dim aLimites As New ArrayList
Dim aCoeffR As New ArrayList
Dim aCoeffN As New ArrayList
Try
' próbuje się uzyskać dostęp do bazy danych
impotsConn = New OdbcConnection(connectString)
impotsConn.Open()
' tworzy się obiekt polecenia
sqlCommand = New OdbcCommand(selectCommand, impotsConn)
' wykonuje się zapytanie
Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader()
' Analiza pobranej tabeli
While myReader.Read()
' dane z bieżącego wiersza są umieszczane w tablicach
aLimites.Add(myReader("limites"))
aCoeffR.Add(myReader("coeffr"))
aCoeffN.Add(myReader("coeffn"))
End While
' zwolnienie zasobów
myReader.Close()
impotsConn.Close()
Catch e As Exception
Throw New Exception("Erreur d'accès à la base de données (" + e.Message + ")")
End Try
' tabele dynamiczne są umieszczane w tabelach statycznych
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
' sprawdzanie pobranych danych
Dim erreur As Integer = checkData()
' jeśli dane są nieprawidłowe, to zgłaszany jest wyjątek
If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
' w przeciwnym razie zwracane są trzy tabele
Return New Object() {limites, coeffr, coeffn}
End Function
End Class
End Namespace
Przyjrzyjmy się producentowi:
' konstruktor
Public Sub New(ByVal DSNimpots As String)
' zapisuje się trzy informacje
Me.DSNimpots = DSNimpots
End Sub
Jako parametr otrzymuje nazwę źródła ODBC, w którym znajdują się dane do pobrania. Konstruktor ogranicza się do zapamiętania tej nazwy. Metoda [getData] odpowiada za odczytanie danych z tabeli [impots] i umieszczenie ich w trzech tablicach (limites, coeffr, coeffn). Omówmy jej kod:
- parametry połączenia ze źródłem danych ODBC są zdefiniowane, ale połączenie nie zostało otwarte
' ciąg połączenia z bazą danych
Dim connectString As String = "DSN=" + DSNimpots + ";"
' tworzymy obiekt połączenia z bazą danych – to połączenie nie jest otwarte
Dim impotsConn As OdbcConnection = New OdbcConnection(connectString)
- definiuje się trzy obiekty [ArrayList] w celu pobrania danych z tabeli [impots]:
' tabele służące do pobierania danych
Dim aLimites As New ArrayList
Dim aCoeffR As New ArrayList
Dim aCoeffN As New ArrayList
- Cały kod dostępu do bazy danych jest otoczony blokiem try/catch w celu obsługi ewentualnych błędów dostępu. Otwieramy połączenie z bazą danych:
' próbuje się uzyskać dostęp do bazy danych
impotsConn = New OdbcConnection(connectString)
impotsConn.Open()
- na otwartym połączeniu wykonujemy polecenie [select]. Otrzymujemy obiekt [OdbcDataReader], który pozwoli nam przeglądać wiersze tabeli wynikowej zapytania SELECT:
' tworzy się obiekt polecenia
Dim sqlCommand As OdbcCommand = New OdbcCommand(selectCommand, impotsConn)
' wykonuje się zapytanie
Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader()
- Przeglądamy tabelę wyników wiersz po wierszu. W tym celu wykorzystujemy metodę [Read] obiektu [OdbcDataReader] uzyskanego wcześniej. Metoda ta wykonuje dwie czynności:
- przechodzi o jeden wiersz dalej w tabeli. Na początku znajdujemy się przed pierwszym wierszem
- zwraca wartość logiczną [true], jeśli udało się przejść do następnego wiersza, a [false] w przeciwnym razie; ten ostatni przypadek oznacza, że wszystkie wiersze zostały już przetworzone.
Kolumny bieżącego wiersza obiektu [OdbcDataReader] są uzyskiwane za pomocą funkcji OdbcDataReader. Otrzymujemy obiekt reprezentujący wartość kolumny. Przeglądamy całą tabelę, aby umieścić jej zawartość w trzech obiektach [ArrayList]:
' Analiza pobranej tabeli
While myReader.Read()
' dane z bieżącego wiersza są umieszczane w tablicach
aLimites.Add(myReader("limites"))
aCoeffR.Add(myReader("coeffr"))
aCoeffN.Add(myReader("coeffn"))
- Po wykonaniu tej czynności zwalniamy zasoby związane z połączeniem:
- Zawartość trzech obiektów [ArrayList] jest przenoszona do trzech standardowych tablic:
' tabele dynamiczne są umieszczane w tabelach statycznych
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
- po przeniesieniu danych z tabeli [impots] do trzech tablic pozostaje jedynie sprawdzić ich zawartość za pomocą metody [checkData] klasy bazowej [impotsData]:
' sprawdzanie pobranych danych
Dim erreur As Integer = checkData()
' jeśli dane są nieprawidłowe, to zgłaszany jest wyjątek
If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
' w przeciwnym razie zwracane są trzy tabele
Return New Object() {limites, coeffr, coeffn}
6.2.4. Testy klasy dostępu do danych
Program testowy mógłby wyglądać następująco:
Option Explicit On
Option Strict On
' przestrzenie nazw
Imports System
Imports Microsoft.VisualBasic
Namespace st.istia.univangers.fr
' strona testowa
Module testimpots
Sub Main(ByVal arguments() As String)
' interaktywny program do obliczania podatku
' użytkownik wprowadza trzy dane za pomocą klawiatury: żonaty nbEnfants wynagrodzenie
' następnie program wyświetla podatek do zapłaty
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"
' sprawdzenie parametrów programu
If arguments.Length <> 1 Then
' komunikat o błędzie
Console.Error.WriteLine(syntaxe1)
' koniec
Environment.Exit(1)
End If
' pobieranie argumentów
Dim DSNimpots As String = arguments(0)
' tworzenie obiektu podatku
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
' pętla nieskończona
While True
' na początku brak błędów
Dim erreur As Boolean = False
' żądane są parametry obliczenia podatku
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()
' co należy zrobić?
If paramètres Is Nothing Or paramètres = "" Then
Exit While
End If
' sprawdzanie liczby argumentów w wprowadzonej linii
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
' sprawdzanie poprawności parametrów
' żonaty
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
' wynagrodzenie
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
' parametry są poprawne – obliczamy podatek
Console.Out.WriteLine(("impôt=" & objImpot.calculer(marié = "o", nbEnfants, salaire).ToString + " euro(s)"))
End If
End While
End Sub
End Module
End Namespace
Aplikacja jest uruchamiana z jednym parametrem:
- DSNimpots: nazwa źródła danych ODBC, które ma zostać wykorzystane
Obliczenie podatku odbywa się za pomocą obiektu typu [impot], utworzonego w momencie uruchomienia aplikacji:
' tworzenie obiektu podatku
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
Po uruchomieniu aplikacja wielokrotnie prosi użytkownika o wpisanie z klawiatury trzech informacji niezbędnych do obliczenia podatku:
- stan cywilny: o – żonaty/zamężna, n – niezamężna/nieżonaty
- liczba dzieci
- jego roczne wynagrodzenie
Kompilacja wszystkich klas:
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
Następnie kompilowany jest program testowy:
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
Program testowy jest najpierw uruchamiany z wykorzystaniem źródła danych 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)
Zmieniamy źródło na ODBC, aby użyć źródła 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. Widoki aplikacji internetowej
Są to te same widoki, co w poprzedniej aplikacji: formulaire.aspx i erreurs.aspx
6.2.6. Kontrolery aplikacji [global.asax, main.aspx]
Należy zmodyfikować jedynie kontroler [global.asax]. Jest on bowiem odpowiedzialny za utworzenie obiektu [impot] podczas uruchamiania aplikacji. Jedynym parametrem konstruktora tego obiektu jest obiekt typu [impotsData], którego zadaniem jest pobieranie danych. Parametr ten zmienia się zatem dla każdego nowego typu źródła danych. Kontroler [global.asax.vb] przyjmuje następujący kształt:
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)
' tworzy się obiekt podatku
Dim objImpot As impot
Try
objImpot = New impot(New impotsODBC(ConfigurationSettings.AppSettings("DSNimpots")))
' obiekt jest umieszczany w aplikacji
Application("objImpot") = objImpot
' brak błędu
Application("erreur") = False
Catch ex As Exception
'wystąpił błąd, odnotowuje się go w aplikacji
Application("erreur") = True
Application("message") = ex.Message
End Try
End Sub
End Class
Źródłem danych obiektu [impot] jest teraz obiekt [impotODBC]. Ten ostatni ma jako parametr nazwę DSN źródła danych ODBC, które ma być wykorzystywane. Zamiast wpisywania tej nazwy na stałe w kodzie, umieszczamy ją w pliku konfiguracyjnym aplikacji o nazwie [web.config]:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="DSNimpots" value="odbc-mysql-dbimpots" />
</appSettings>
</configuration>
Wiadomo, że wartość klucza C w sekcji <appSettings> pliku [web.config] jest uzyskiwana w kodzie aplikacji za pomocą [ConfigurationSettings.AppSettings(C)].
Aby ustalić przyczynę wyjątku, komunikat o nim jest rejestrowany w aplikacji, tak aby był dostępny na potrzeby zapytań. Kontroler [main.aspx.vb] uwzględni ten komunikat na swojej liście błędów:
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' przede wszystkim sprawdzamy, czy aplikacja zainicjowała się poprawnie
If CType(Application("erreur"), Boolean) Then
' przekierowujemy na stronę błędów
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
' pobieramy akcję do wykonania
...
6.2.7. Podsumowanie zmian
Aplikacja jest gotowa do testów. Wymieńmy zmiany wprowadzone w stosunku do poprzedniej wersji:
- utworzono nową klasę dostępu do danych
- kontroler [global.asax.vb] został zmodyfikowany w dwóch miejscach: podczas tworzenia obiektu [impot] oraz podczas rejestrowania w aplikacji komunikatu związanego z ewentualnym wyjątkiem
- kontroler [main.aspx.vb] został zmodyfikowany w jednym miejscu w celu wyświetlenia poprzedniego komunikatu o wyjątku
- dodano plik [web.config]
Prace modyfikacyjne przeprowadzono głównie w pliku c.a.d, poza aplikacją internetową. Było to możliwe dzięki architekturze aplikacji MVC, która oddziela kontroler od klas biznesowych. Na tym właśnie polega zaleta tej architektury. Można by wykazać, że przy użyciu odpowiedniego pliku konfiguracyjnego [web.config] można było uniknąć jakichkolwiek zmian w kontrolerze aplikacji. W pliku [web.config] można podać nazwę klasy dostępu do danych, która ma zostać dynamicznie zainicjowana, a także różne parametry niezbędne do tego zainicjowania. Dzięki tym informacjom plik [global.asax] może utworzyć instancję obiektu dostępu do danych. Zmiana źródła danych sprowadza się wówczas do:
- utworzenia klasy dostępu do tego źródła, jeśli jeszcze nie istnieje
- zmodyfikowanie pliku [web.config], aby umożliwić dynamiczne utworzenie instancji tej klasy w pliku [global.asax]
6.2.8. Testowanie aplikacji internetowej
Wszystkie powyższe pliki umieszcza się w folderze <application-path>.

W tym folderze tworzony jest podfolder [bin], w którym umieszczany jest zestaw [impot.dll] powstały w wyniku kompilacji plików klas biznesowych: [impots.vb, impotsData.vb, impotsArray.vb, impotsODBC.vb]. Poniżej przypominamy polecenie kompilacji niezbędne do tego celu:
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
Powyższy plik [impot.dll] należy umieścić w katalogu <application-path>\bin, aby aplikacja internetowa miała do niego dostęp. Serwer Cassini uruchamia się z parametrami (<application-path>,/impots2). Testy dają takie same wyniki jak w poprzedniej wersji, a obecność bazy danych jest dla użytkownika niezauważalna. Aby jednak zilustrować tę obecność, sprawiamy, że źródło ODBC staje się niedostępne poprzez zatrzymanie plików SGBD i MySQL, a następnie wysyłamy żądanie pod adresem URL [http://localhost/impots2/main.aspx]. Otrzymujemy następującą odpowiedź:

6.3. Przykład 3
6.3.1. Problem
W tym przykładzie zamierzamy rozwiązać ten sam problem, ponownie zmieniając źródło danych obiektu [impot] utworzonego przez aplikację internetową. Tym razem nowym źródłem danych będzie baza danych ACCESS, do której uzyskamy dostęp za pośrednictwem sterownika OLEDB. Naszym celem jest pokazanie innego sposobu uzyskania dostępu do bazy danych.
6.3.2. Źródło danych OLEDB
Dane będą znajdować się w tabeli o nazwie [IMPOTS] w bazie ACCESS. Zawartość tej tabeli będzie następująca:

6.3.3. Klasa dostępu do danych
Wróćmy do struktury MVC naszej aplikacji:

- Na powyższym schemacie klasa [impotsData] jest odpowiedzialna za pobieranie danych. Tym razem będzie musiała to zrobić ze źródła OLEDB.
Tworzymy klasę [impotsOLEDB], która będzie pobierać dane (limites, coeffr, coeffn) ze źródła ODBC, któremu nadamy nazwę:
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
' zmienne instancji
Protected chaineConnexion As String
' konstruktor
Public Sub New(ByVal chaineConnexion As String)
' zapisuje się trzy informacje
Me.chaineConnexion = chaineConnexion
End Sub
Public Overrides Function getData() As Object()
' inicjuje trzy tablice: „limites”, „coeffr” i „coeffn” na podstawie
' zawartości tabeli [impots] z bazy OLEDB [chaineConnexion]
' limity, coeffr, coeffn to trzy kolumny tej tabeli
' może wywołać różne wyjątki
' tworzy się obiekt DataAdapter w celu odczytania danych ze źródła OLEDB
Dim adaptateur As New OleDbDataAdapter("select limites,coeffr,coeffn from impots", chaineConnexion)
' tworzy się obraz w pamięci wyniku zapytania 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
' pobierana jest zawartość tabeli impots
Dim lignesImpots As DataRowCollection = contenu.Rows
' określa się rozmiar tablic odbiorczych
Me.limites = New Decimal(lignesImpots.Count - 1) {}
Me.coeffr = New Decimal(lignesImpots.Count - 1) {}
Me.coeffn = New Decimal(lignesImpots.Count - 1) {}
' przenosimy zawartość tabeli impots do tablic
Dim i As Integer
Dim ligne As DataRow
Try
For i = 0 To lignesImpots.Count - 1
' wiersz i tabeli
ligne = lignesImpots.Item(i)
' pobierana jest zawartość wiersza
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
' sprawdzamy pobrane dane
Dim erreur As Integer = checkData()
' jeśli dane są nieprawidłowe, to zgłaszamy wyjątek
If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
' w przeciwnym razie zwracane są trzy tablice
Return New Object() {limites, coeffr, coeffn}
End Function
End Class
End Namespace
Przyjrzyjmy się producentowi:
' konstruktor
Public Sub New(ByVal chaineConnexion As String)
' zapisuje się trzy informacje
Me.chaineConnexion = chaineConnexion
End Sub
Jako parametr otrzymuje ciąg połączenia ze źródłem OLEDB, w którym znajdują się dane do pobrania. Sterownik ogranicza się do jego zapamiętania. Ciąg połączenia zawiera wszystkie parametry niezbędne sterownikowi OLEDB do nawiązania połączenia ze źródłem OLEDB. Zazwyczaj jest on dość złożony. Aby poznać ciąg połączenia bazy danych ACCESS, można skorzystać z narzędzia [WebMatrix]. Uruchamiamy to narzędzie. Wyświetla się okno umożliwiające połączenie się ze źródłem danych:
![]() ![]() | ![]() ![]() |
Dzięki ikonie oznaczonej strzałką powyżej można nawiązać połączenie z dwoma typami baz danych Microsoft: SQL Server i ACCESS. Wybierzmy ACCESS:

Użyliśmy przycisku [...], aby wskazać bazę ACCESS. Potwierdzamy działanie kreatora. W zakładce [Data] ikony symbolizują połączenie:

Teraz utwórzmy nowy plik .aspx za pomocą [Files/New File]:

Otrzymujemy pusty arkusz, na którym możemy zaprojektować nasz interfejs internetowy:

Przeciągnijmy z zakładki [Data] tabelę [impots] na powyższy arkusz. Otrzymujemy następujący wynik:

Kliknijmy prawym przyciskiem myszy na obiekt [AccessDataSourceControl] poniżej, aby uzyskać dostęp do jego właściwości:

Ciąg połączenia OLEDB z bazą ACCESS jest określony przez powyższą właściwość [ConnectionString]:
Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=D:\data\serge\devel\aspnet\poly\chap5\impots\3\impots.mdb
Widać, że ciąg ten składa się z części stałej oraz części zmiennej, którą jest po prostu nazwa pliku ACCESS. Wykorzystamy ten fakt do wygenerowania ciągu połączenia z naszym źródłem danych OLEDB.
Wróćmy teraz do naszej klasy [impotsOLEDB]. Metoda [getData] odpowiada za odczytanie danych z tabeli [impots] i umieszczenie ich w trzech tablicach (limites, coeffr, coeffn). Omówmy jej kod:
- definiujemy obiekt [DataAdapter], który pozwoli nam przenieść do pamięci wynik zapytania select o nazwie SQL. W tym celu definiujemy zapytanie [select], które ma zostać wykonane, i powiązujemy je z obiektem [DataAdapter]. Konstruktor tego obiektu wymaga również podania ciągu połączenia, którego będzie używał do połączenia się ze źródłem OLEDB
' tworzymy obiekt DataAdapter w celu odczytania danych ze źródła OLEDB
Dim adaptateur As New OleDbDataAdapter("select limites,coeffr,coeffn from impots", chaineConnexion)
- wykonujemy polecenie [select] za pomocą metody [Fill] obiektu [DataAdapter]. Wynik operacji [select] jest wprowadzany do obiektu [DataTable] utworzonego na tę okazję. Obiekt [DataTable] stanowi obraz w pamięci tabeli bazy danych c.a.d – zbioru wierszy i kolumn. Obsługujemy wyjątek, który może wystąpić, jeśli na przykład ciąg połączenia jest nieprawidłowy.
' tworzymy obraz w pamięci wyniku zapytania 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
- W obiekcie [contenu] znajduje się tabela [impots], pobrana przez obiekt [select]. Obiekt [DataTable] jest tabelą, a więc zbiorem wierszy. Dostęp do nich można uzyskać poprzez właściwość [rows] obiektu [datatable]:
' pobieramy zawartość tabeli impots
Dim lignesImpots As DataRowCollection = contenu.Rows
- każdy element kolekcji [lignesImpots] jest obiektem typu [DataRow] reprezentującym wiersz tabeli. Kolumny tej tabeli są dostępne za pośrednictwem obiektu [DataRow] poprzez jego właściwość [Item]. [DataRow].[Item(i)] to kolumna nr i w wierszu [DataRow]. Przeglądając zbiór wierszy (zbiór DataRows z lignesImpots) oraz zbiory kolumn każdego wiersza, można uzyskać całą tabelę:
' określa się rozmiar tablic odbiorczych
Me.limites = New Decimal(lignesImpots.Count - 1) {}
Me.coeffr = New Decimal(lignesImpots.Count - 1) {}
Me.coeffn = New Decimal(lignesImpots.Count - 1) {}
' przenosimy zawartość tabeli impots do tablic
Dim i As Integer
Dim ligne As DataRow
Try
For i = 0 To lignesImpots.Count - 1
' wiersz i tabeli
ligne = lignesImpots.Item(i)
' pobierana jest zawartość wiersza
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
- po przeniesieniu danych z tabeli [impots] do trzech tablic pozostaje jedynie sprawdzić ich zawartość za pomocą metody [checkData] klasy bazowej [impotsData]:
' sprawdzamy pobrane dane
Dim erreur As Integer = checkData()
' jeśli dane są nieprawidłowe, to zgłaszamy wyjątek
If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
' w przeciwnym razie zwracane są trzy tablice
Return New Object() {limites, coeffr, coeffn}
6.3.4. Testy klasy dostępu do danych
Program testowy mógłby wyglądać następująco:
Option Explicit On
Option Strict On
' przestrzenie nazw
Imports System
Imports Microsoft.VisualBasic
Namespace st.istia.univangers.fr
' strona testowa
Module testimpots
Sub Main(ByVal arguments() As String)
' interaktywny program do obliczania podatku
' użytkownik wprowadza trzy dane za pomocą klawiatury: żonaty nbEnfants wynagrodzenie
' następnie program wyświetla podatek do zapłaty
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"
' sprawdzenie parametrów programu
If arguments.Length <> 1 Then
' komunikat o błędzie
Console.Error.WriteLine(syntaxe1)
' koniec
Environment.Exit(1)
End If
' pobierane są argumenty
Dim chemin As String = arguments(0)
' przygotowywanie ciągu połączenia
Dim chaineConnexion As String = "Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=" + chemin
' tworzenie obiektu podatku
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
' pętla nieskończona
While True
' na początku brak błędów
Dim erreur As Boolean = False
' prosimy o podanie parametrów obliczania podatku
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()
' czy należy coś zrobić?
If paramètres Is Nothing Or paramètres = "" Then
Exit While
End If
' sprawdzanie liczby argumentów w wprowadzonej linii
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
' sprawdzanie poprawności parametrów
' żonaty
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
' wynagrodzenie
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
' parametry są poprawne – obliczamy podatek
Console.Out.WriteLine(("impôt=" & objImpot.calculer(marié = "o", nbEnfants, salaire).ToString + " euro(s)"))
End If
End While
End Sub
End Module
End Namespace
Aplikacja jest uruchamiana z jednym parametrem:
- bdACCESS: nazwa pliku ACCESS, który ma zostać przetworzony
Obliczenie podatku odbywa się za pomocą obiektu typu [impot], utworzonego w momencie uruchomienia aplikacji:
' pobieramy argumenty
Dim chemin As String = arguments(0)
' przygotowuje się ciąg połączenia
Dim chaineConnexion As String = "Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=" + chemin
' tworzenie obiektu podatku
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
Łańcuch połączenia ze źródłem OLEDB został utworzony na podstawie informacji uzyskanych za pomocą [WebMatrix].
Po uruchomieniu aplikacja wielokrotnie prosi użytkownika o wpisanie z klawiatury trzech informacji niezbędnych do obliczenia podatku:
- stan cywilny: o – żonaty/zamężna, n – niezamężna/nieżonaty
- liczba dzieci
- jego roczne wynagrodzenie
Kompilowany jest cały zestaw klas:
dos>vbc /r:system.dll /r:system.data.dll /t:library /out:impot.dll impots.vb impotsArray.vb impotsData.vb impotsOLEDB.vb
Plik [impots.mdb] jest umieszczany w folderze aplikacji testowej, a aplikacja jest uruchamiana w następujący sposób:
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)
Aplikację można uruchomić przy użyciu nieprawidłowego pliku 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. Widoki aplikacji internetowej
Są to te same, co w poprzedniej aplikacji: formulaire.aspx i erreurs.aspx
6.3.6. Kontrolery aplikacji [global.asax, main.aspx]
Należy zmodyfikować jedynie kontroler [global.asax]. Odpowiada on bowiem za utworzenie obiektu [impot] podczas uruchamiania aplikacji. Jedynym parametrem konstruktora tego obiektu jest obiekt typu [impotsData], odpowiedzialny za pobieranie danych. Parametr ten ulega zatem zmianie, ponieważ zmieniamy źródło danych. Kontroler [global.asax.vb] przyjmuje następujący kształt:
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)
' tworzy się obiekt podatku
Dim objImpot As impot
Try
objImpot = New impot(New impotsOLEDB(ConfigurationSettings.AppSettings("chaineConnexion")))
' umieszczamy obiekt w aplikacji
Application("objImpot") = objImpot
' brak błędu
Application("erreur") = False
Catch ex As Exception
'wystąpił błąd, odnotowuje się go w aplikacji
Application("erreur") = True
Application("message") = ex.Message
End Try
End Sub
End Class
Źródłem danych obiektu [impot] jest teraz obiekt [impotOLEDB]. Ten ostatni ma jako parametr ciąg połączenia źródła danych OLEDB, które ma być wykorzystywane. Znajduje się on w pliku konfiguracyjnym aplikacji [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>
Kontroler [main.aspx] pozostaje bez zmian.
6.3.7. Podsumowanie zmian
Aplikacja jest gotowa do testów. Wymieńmy zmiany wprowadzone w stosunku do poprzedniej wersji:
- utworzono nową klasę dostępu do danych
- kontroler [global.asax.vb] został zmodyfikowany w jednym miejscu: utworzono obiekt [impot]
- dodano plik [web.config]
6.3.8. Testowanie aplikacji internetowej
Wszystkie powyższe pliki umieszczono w folderze <application-path>.

W tym folderze tworzony jest podfolder [bin], w którym umieszczany jest zestaw [impot.dll] powstały w wyniku kompilacji plików klas biznesowych: [impots.vb, impotsData.vb, impotsArray.vb, impotsOLEDB.vb]. Poniżej przypominamy polecenie kompilacji:
dos>vbc /r:system.dll /r:system.data.dll /t:library /out:impot.dll impots.vb impotsArray.vb impotsData.vb impotsOLEDB.vb
Plik [impot.dll] wygenerowany przez to polecenie należy umieścić w katalogu <application-path>\bin, aby aplikacja internetowa miała do niego dostęp. Serwer Cassini uruchamia się z parametrami (<application-path>,/impots3). Testy dają takie same wyniki jak w poprzedniej wersji.
6.4. Przykład 4
6.4.1. Problem
Teraz zamierzamy przekształcić naszą aplikację w aplikację do symulacji obliczeń podatkowych. Użytkownik będzie mógł wykonywać kolejne obliczenia podatkowe, a ich wyniki będą mu prezentowane w nowym widoku wyglądającym mniej więcej tak:

6.4.2. Struktura aplikacji MVC
Struktura aplikacji MVC przyjmuje następujący wygląd:

Pojawia się nowy widok [simulations.aspx], którego zrzut ekranu właśnie przedstawiliśmy. Klasą dostępu do danych będzie klasa [impotsODBC] z przykładu 2.
6.4.3. Widoki aplikacji internetowej
Widok [erreurs.aspx] pozostaje bez zmian. Widok [formulaire.aspx] ulega niewielkiej zmianie. Kwota podatku nie pojawia się już bowiem w tym widoku. Znajduje się ona teraz w widoku [simulations.aspx]. W związku z tym po uruchomieniu aplikacji użytkownikowi wyświetla się następująca strona:

Ponadto widok [formulaire] zawiera skrypt JavaScript, który sprawdza poprawność wprowadzonych danych przed wysłaniem ich na serwer, jak pokazuje poniższy przykład:

Kod prezentacji wygląda następująco:
<%@ page src="formulaire.aspx.vb" inherits="formulaire" AutoEventWireup="false"%>
<html>
<head>
<title>Impôt</title>
<script language="javascript">
function calculer(){
// sprawdzanie parametrów przed wysłaniem ich na serwer
with(document.frmImpots){
//liczba elementów podrzędnych
champs=/^\s*(\d+)\s*$/.exec(txtEnfants.value);
if(champs==null){
// model nie został zweryfikowany
alert("Le nombre d'enfants n'a pas été donné ou est incorrect");
txtEnfants.focus();
return;
}//if
//wynagrodzenie
champs=/^\s*(\d+)\s*$/.exec(txtSalaire.value);
if(champs==null){
// szablon nie został zweryfikowany
alert("Le salaire n'a pas été donné ou est incorrect");
txtSalaire.focus();
return;
}//jeśli
// w porządku – wysyłamy formularz na serwer
submit();
}//with
}//obliczyć
</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>
Pola dynamiczne na stronie są takie same jak w poprzednich wersjach. Zniknęło dynamiczne pole kwoty podatku. Przycisk [Calculer] nie jest już przyciskiem typu [submit]. Jest to przycisk typu [button], a po jego kliknięciu uruchamiana jest funkcja JavaScript [calculer()]:
<INPUT type="button" value="Calculer" onclick="calculer()">
Formularzowi nadano nazwę [frmImpots], aby można było odwołać się do niego w skrypcie [calculer]:
<form name="frmImpots" method="post" action="main.aspx?action=calcul">
Funkcja JavaScript [calculer] wykorzystuje wyrażenia regularne do sprawdzania poprawności pól formularzy [document.frmImpots.txtEnfants] i [document.frmImpots.txtSalaire]. Jeśli wprowadzone wartości są poprawne, są one wysyłane na serwer przez funkcję [document.frmImpots.submit()].
Strona prezentacyjna pobiera swoje pola dynamiczne z kontrolera [formulaire.aspx.vb] w następujący sposób:
Imports System.Collections.Specialized
Public Class formulaire
Inherits System.Web.UI.Page
' pola strony
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
' pobieramy poprzednie żądanie z kontekstu
Dim form As NameValueCollection = Context.Items("formulaire")
' przygotowujemy stronę do wyświetlenia
' przyciski opcji
rdouichecked = ""
rdnonchecked = "checked"
If form("rdMarie").ToString = "oui" Then
rdouichecked = "checked"
rdnonchecked = ""
End If
' reszta
txtEnfants = CType(form("txtEnfants"), String)
txtSalaire = CType(form("txtSalaire"), String)
End Sub
End Class
Kontroler [formulaire.aspx.vb] jest identyczny z poprzednimi wersjami, z tą różnicą, że nie musi już pobierać pola [txtImpot] z kontekstu, ponieważ pole to zniknęło ze strony.
Widok [simulations.aspx] wygląda następująco:

i odpowiada następującemu kodowi prezentacji:
<%@ 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>
Ten kod zawiera trzy pola dynamiczne:
kod HTML z listy symulacji w postaci wierszy tabeli HTML | |
adres URL linku | |
tekst linku |
Są one generowane przez moduł sterujący [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
'pobieramy symulacje z kontekstu
Dim simulations As ArrayList = CType(context.Items("simulations"), ArrayList)
' każda symulacja to tablica zawierająca 4 elementy typu 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
' pobieramy pozostałe elementy z kontekstu
href = context.Items("href").ToString
lien = context.Items("lien").ToString
End Sub
End Class
Kontroler strony pobiera informacje umieszczone przez kontroler aplikacji w kontekście strony:
obiekt ArrayList zawierający listę symulacji do wyświetlenia. Każdy element jest tablicą składającą się z 4 ciągów znaków przedstawiających informacje (stan cywilny, dzieci, wynagrodzenie, podatek) dotyczące danej symulacji. | |
adres URL linku | |
tekst linku |
6.4.4. Kontrolery [global.asax, main.aspx]
Przypomnijmy schemat MVC naszej aplikacji:

Kontroler [main.aspx] musi obsłużyć trzy akcje:
- init: odpowiada pierwszemu żądaniu klienta. Kontroler wyświetla widok [formulaire.aspx]
- calcul: odpowiada żądaniu obliczenia podatku. Jeśli dane z formularza wprowadzania danych są poprawne, podatek jest obliczany za pomocą klasy biznesowej [impotsODBC]. Kontroler zwraca klientowi widok [simulations.aspx] wraz z wynikiem bieżącej symulacji oraz wszystkich poprzednich. Jeśli dane z formularza wprowadzania danych są nieprawidłowe, kontroler zwraca widok [erreurs.aspx] wraz z listą błędów i linkiem umożliwiającym powrót do formularza.
- powrót: oznacza powrót do formularza po wystąpieniu błędu. Kontroler wyświetla widok [formulaire.aspx] w stanie, w jakim został zatwierdzony przed wystąpieniem błędu.
W tej nowej wersji zmieniła się jedynie akcja [calcul]. Jeśli dane są prawidłowe, powinna ona bowiem prowadzić do widoku [simulations.aspx], podczas gdy wcześniej prowadziła do widoku [formulaire.aspx]. Kontroler [main.aspx.vb] przyjmuje następujący kształt:
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
' przede wszystkim sprawdzamy, czy aplikacja zainicjowała się poprawnie
...
' wykonujemy akcję
Select Case action
Case "init"
' inicjowanie aplikacji
initAppli()
Case "calcul"
' obliczanie podatku
calculImpot()
Case "retour"
' powrót do formularza
retourFormulaire()
Case "effacer"
' inicjowanie aplikacji
initAppli()
Case Else
' nieznana akcja = inicjalizacja
initAppli()
End Select
End Sub
...
Private Sub calculImpot()
' zapisywanie wprowadzonych danych
Session.Item("formulaire") = Request.Form
' sprawdzanie poprawności wprowadzonych danych
Dim erreurs As ArrayList = checkData()
' w przypadku błędów wyświetla się komunikat
If erreurs.Count <> 0 Then
' przygotowuje się stronę błędów
context.Items("href") = "main.aspx?action=retour"
context.Items("lien") = "Retour au formulaire"
context.Items("erreurs") = erreurs
Server.Transfer("erreurs.aspx")
End If
' nie ma błędów – oblicza się podatek
Dim impot As Long = CType(Application("objImpot"), impot).calculer( _
Request.Form("rdMarie") = "oui", _
CType(Request.Form("txtEnfants"), Integer), _
CType(Request.Form("txtSalaire"), Long))
' dodaje się wynik do istniejących symulacji
Dim simulations As ArrayList
If Not Session.Item("simulations") Is Nothing Then
simulations = CType(Session.Item("simulations"), ArrayList)
Else
simulations = New ArrayList
End If
' dodano bieżącą symulację
Dim simulation() As String = New String() {Request.Form("rdMarie").ToString, _
Request.Form("txtEnfants").ToString, Request.Form("txtSalaire").ToString, _
impot.ToString}
simulations.Add(simulation)
' umieszczamy symulacje w sesji i kontekście
context.Items("simulations") = simulations
Session.Item("simulations") = simulations
' wyświetla stronę wyników
context.Items("href") = "main.aspx?action=retour"
context.Items("lien") = "Retour au formulaire"
Server.Transfer("simulations.aspx", True)
End Sub
...
End Class
Powyżej zachowaliśmy jedynie te elementy, które są niezbędne do zrozumienia zmian występujących wyłącznie w funkcji [calculImpots]:
- po pierwsze funkcja ta zapisuje formularz [Request.Form] w sesji, aby móc odtworzyć formularz w stanie, w jakim został zatwierdzony. Należy to zrobić w każdym przypadku, ponieważ niezależnie od tego, czy operacja zakończy się odpowiedzią [erreurs.aspx], czy odpowiedzią [simulations.aspx], powrót do formularza odbywa się poprzez link [Retour au formulaire]. Aby poprawnie przywrócić formularz, należy wcześniej zapisać jego wartości w sesji.
- Jeśli wprowadzone dane okażą się poprawne, funkcja umieszcza bieżącą symulację (stan cywilny, dzieci, wynagrodzenie, podatek) na liście symulacji. Znajduje się ona w sesji powiązanej z kluczem „simulations”.
- Lista symulacji jest ponownie umieszczana w sesji do wykorzystania w przyszłości. Jest ona również umieszczana w bieżącym kontekście, ponieważ właśnie tam oczekuje na nią widok [simulations.aspx]
- Widok [simulations.aspx] jest wyświetlany po umieszczeniu w kontekście pozostałych informacji, których oczekuje
6.4.5. Podsumowanie zmian
Aplikacja jest gotowa do testowania. Wymieńmy zmiany wprowadzone w stosunku do poprzednich wersji:
- utworzono nowy widok
- kontroler [main.aspx.vb] został zmodyfikowany w jednym miejscu: obsługa akcji [calcul]
6.4.6. Testowanie aplikacji internetowej
Zachęcamy czytelnika do przeprowadzenia testów. Przypomnijmy procedurę. Wszystkie pliki aplikacji znajdują się w folderze <application-path>. W tym folderze utworzono podfolder [bin], w którym umieszczono zestaw [impot.dll] powstały w wyniku kompilacji plików klas biznesowych: [impots.vb, impotsData.vb, impotsArray.vb, impotsODBC.vb. Plik [impot.dll] wygenerowany przez to polecenie należy umieścić w katalogu <application-path>\bin, aby aplikacja internetowa miała do niego dostęp. Serwer Cassini uruchamia się z parametrami (<application-path>,/impots4).
6.5. Conclusion
Powyższe przykłady pokazały na konkretnym przypadku mechanizmy powszechnie stosowane w tworzeniu aplikacji internetowych. Systematycznie korzystaliśmy z architektury MVC ze względu na jej wartość edukacyjną. Te same przykłady można by było zrealizować inaczej, a być może prościej, bez tej architektury. Jednak zapewnia ona znaczne korzyści, gdy aplikacja staje się nieco bardziej złożona i zawiera wiele stron.
Moglibyśmy kontynuować nasze przykłady na różne sposoby. Oto kilka z nich:
- użytkownik mógłby chcieć zachować swoje symulacje na przestrzeni czasu. Przeprowadzałby symulacje w dniu J i mógłby je odzyskać na przykład w dniu J+3. Możliwym rozwiązaniem tego problemu jest wykorzystanie plików cookie. Wiemy, że token sesji między serwerem a klientem jest przesyłany za pomocą tego mechanizmu. Można by go również wykorzystać do przesyłania symulacji między klientem a serwerem.
- W tym samym czasie, gdy serwer wysyła stronę z wynikami symulacji, wysyła w swoich nagłówkach HTTP plik cookie zawierający ciąg znaków reprezentujący symulacje. Ponieważ znajdują się one w obiekcie [ArrayList], konieczne jest przekształcenie tego obiektu do postaci [String]. Serwer nadałby plikowi cookie okres ważności, na przykład 30 dni.
- Przeglądarka klienta przechowuje otrzymane pliki cookie w pliku i odsyła je za każdym razem, gdy wysyła żądanie do serwera, który je wysłał, o ile są one nadal ważne (okres ważności nie upłynął). W ramach symulacji serwer otrzyma ciąg znaków [String], który będzie musiał przekształcić w obiekt [ArrayList].
Pliki cookie są zarządzane przez obiekt [Response.Cookies] podczas wysyłania ich do klienta oraz przez obiekt [Request.Cookies] podczas ich odbioru na serwerze.
- Powyższy mechanizm może stać się dość obciążający, jeśli liczba symulacji jest znaczna. Ponadto często zdarza się, że użytkownik okresowo czyści swoje pliki cookie, usuwając je wszystkie, nawet jeśli jednocześnie zezwala przeglądarce na ich używanie. W związku z tym prędzej czy później utracimy plik cookie związany z symulacjami. Można więc rozważyć przechowywanie tych danych na serwerze zamiast na komputerze klienta, na przykład w bazie danych. Aby powiązać symulacje z konkretnym użytkownikiem, aplikacja mogłaby rozpoczynać się od etapu identyfikacji, wymagającego podania loginu i hasła, które same są przechowywane w bazie danych lub innym rodzaju magazynie danych.
- Możemy również chcieć zabezpieczyć działanie naszej aplikacji. Obecnie opiera się ona na dwóch założeniach:
- użytkownik zawsze przechodzi przez kontroler [main.aspx]
- i w tym przypadku zawsze korzysta z akcji dostępnych na stronie, która została mu przesłana
Co się stanie na przykład, jeśli użytkownik bezpośrednio wywoła adres URL [http://localhost/impots4/formulaire.aspx]? Sytuacja ta jest mało prawdopodobna, ponieważ użytkownik nie wie o istnieniu tego adresu URL. Należy jednak ją przewidzieć. Można ją obsłużyć za pomocą kontrolera aplikacji [global.asax], który przetwarza wszystkie żądania kierowane do aplikacji. W ten sposób może on sprawdzić, czy żądany zasób to rzeczywiście [main.aspx].
Bardziej prawdopodobnym przypadkiem jest sytuacja, w której użytkownik nie korzysta z akcji dostępnych na stronie przesłanej mu przez serwer. Na przykład, co się stanie, jeśli użytkownik bezpośrednio wywoła adres URL [http://localhost/impots4/main.aspx?action=retour] bez uprzedniego wypełnienia formularza? Spróbujmy. Otrzymujemy następującą odpowiedź:

Mamy awarię serwera. To normalne. W przypadku akcji [retour] kontroler oczekuje, że w sesji znajdzie obiekt [NameValueCollection] reprezentujący wartości formularza, które ma wyświetlić. Nie znajduje ich. Mechanizm kontrolera pozwala na eleganckie rozwiązanie tego problemu. Dla każdego żądania kontroler [main.aspx] może sprawdzić, czy żądana akcja jest rzeczywiście jedną z akcji strony wcześniej wysłanej do użytkownika. Można zastosować następujący mechanizm:
- kontroler, przed wysłaniem odpowiedzi do klienta, zapisuje w jego sesji informację identyfikującą tę stronę
- gdy otrzymuje nowe żądanie od klienta, sprawdza, czy żądana akcja rzeczywiście należy do ostatniej strony wysłanej do tego klienta
- informacje łączące strony z akcjami dozwolonymi na tych stronach można wprowadzić do pliku konfiguracyjnego aplikacji o nazwie [web.config].
- doświadczenie pokazuje, że kontrolery aplikacji mają szeroką wspólną podstawę i że możliwe jest stworzenie kontrolera generycznego, którego dostosowanie do konkretnej aplikacji odbywa się za pomocą pliku konfiguracyjnego. Taką ścieżkę obrało na przykład narzędzie [Struts] w dziedzinie programowania internetowego w języku Java.



