6. Beispiele
In diesem Kapitel werden wir das bisher Gesehene anhand einer Reihe von Beispielen veranschaulichen.
6.1. Beispiel 1
6.1.1. Das Problem
Diese Anwendung muss es einem Benutzer ermöglichen, seine Steuern zu berechnen. Wir betrachten den vereinfachten Fall eines Steuerpflichtigen, der nur sein Gehalt anzugeben hat (Zahlen von 2004 für das Einkommen von 2003):
- Wir berechnen die Anzahl der Steuerklassen für den Arbeitnehmer als nbParts = nbEnfants / 2 + 1, wenn er unverheiratet ist, und als nbEnfants / 2 + 2, wenn er verheiratet ist, wobei nbEnfants die Anzahl der Kinder ist.
- Hat er mindestens drei Kinder, hat er Anspruch auf einen zusätzlichen halben Anteil
- Sein zu versteuerndes Einkommen R berechnet sich als R = 0,72 * S, wobei S sein Jahresgehalt ist
- Wir berechnen seinen Familienkoeffizienten QF = R / nbParts
- Wir berechnen seine Steuer I. Betrachten Sie die folgende Tabelle:
4262 | 0 | 0 |
8382 | 0,0683 | 291,09 |
14.753 | 0,1914 | 1.322,92 |
23.888 | 0,2826 | 2.668,39 |
38.868 | 0,3738 | 4.846,98 |
47.932 | 0,4262 | 6.883,66 |
0 | 0,4809 | 9505,54 |
Jede Zeile hat 3 Felder. Um die Steuer I zu berechnen, suche die erste Zeile, in der QF <= Feld1 ist. Wenn beispielsweise QF = 5000 ist, lautet die gefundene Zeile
Steuer I ist dann gleich 0,0683*R – 291,09*nbParts. Wenn QF so ist, dass die Bedingung QF <= Feld1 nie erfüllt ist, werden die Koeffizienten aus der letzten Zeile verwendet. Hier:
was zu Steuer I = 0,4809*R - 9505,54*nbParts führt.
6.1.2. Die MVC-Struktur der Anwendung
Die MVC-Struktur der Anwendung sieht wie folgt aus:

Die Controller-Rolle wird von der Seite [main.aspx] übernommen. Es gibt drei mögliche Aktionen:
- init: entspricht der ersten Anfrage des Clients. Der Controller zeigt die Ansicht [formulaire.aspx] an
- calcul: entspricht der Anfrage zur Berechnung der Steuer. Sind die Daten im Eingabeformular korrekt, wird die Steuer mithilfe der Business-Klasse [impots] berechnet. Der Controller gibt die Ansicht [form.aspx] so an den Client zurück, wie sie validiert wurde, zusammen mit der berechneten Steuer. Sind die Daten im Eingabeformular fehlerhaft, gibt der Controller die Ansicht [errors.aspx] mit einer Liste der Fehler und einem Link zur Rückkehr zum Formular zurück.
- return: entspricht der Rückkehr zum Formular nach einem Fehler. Der Controller zeigt die Ansicht [form.aspx] so an, wie sie vor dem Fehler validiert wurde.
Der Controller [main.aspx] weiß nichts über Steuerberechnungen. Er ist lediglich für die Verwaltung des Client-Server-Dialogs und die Ausführung der vom Client angeforderten Aktionen zuständig. Für die Aktion [calculate] stützt er sich auf die Business-Klasse [tax].
6.1.3. Die Business-Klasse
Die Klasse **impot** wird wie folgt definiert:
' imported namespaces
Imports System
' class
Namespace st.istia.univangers.fr
Public Class impot
Private limites(), coeffR(), coeffN() As Decimal
' manufacturer
Public Sub New(ByRef source As impotsData)
' data required for tax calculation
' come from an external source [source]
' we retrieve them - there may be an exception
Dim data() As Object = source.getData
limites = CType(data(0), Decimal())
coeffR = CType(data(1), Decimal())
coeffN = CType(data(2), Decimal())
End Sub
' tAX CALCULATION
Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Long) As Long
' calculating the number of shares
Dim nbParts As Decimal
If marié Then
nbParts = CDec(nbEnfants) / 2 + 2
Else
nbParts = CDec(nbEnfants) / 2 + 1
End If
If nbEnfants >= 3 Then
nbParts += 0.5D
End If
' calculation of taxable income & family quota
Dim revenu As Decimal = 0.72D * salaire
Dim QF As Decimal = revenu / nbParts
' tAX CALCULATION
limites((limites.Length - 1)) = QF + 1
Dim i As Integer = 0
While QF > limites(i)
i += 1
End While
Return CLng(revenu * coeffR(i) - nbParts * coeffN(i))
End Function
End Class
End Namespace
Ein Steuerobjekt, indem sein Konstruktor mit einer Datenquelle vom Typ [impotsData] versorgt wird. Diese Klasse verfügt über eine öffentliche Methode [getData], die die drei zur Berechnung der Steuer erforderlichen Datenarrays abruft, wie zuvor beschrieben. Diese Methode kann eine Ausnahme behandeln, falls die Daten nicht abgerufen werden konnten oder sich als fehlerhaft erweisen. Sobald das [tax]-Objekt erstellt ist, kann seine calculate-Methode wiederholt aufgerufen werden, um die Steuer des Steuerzahlers basierend auf seinem Familienstand (verheiratet oder ledig), der Anzahl der Kinder und dem Jahresgehalt zu berechnen.
6.1.4. Die Datenzugriffsklasse
Die Klasse [impotsData] ist die Klasse, die den Zugriff auf die Daten ermöglicht. Es handelt sich um eine abstrakte Klasse. Für jede neue mögliche Datenquelle (Arrays, Flatfiles, Datenbanken, Konsole usw.) muss eine abgeleitete Klasse erstellt werden. Ihre Definition lautet wie folgt:
Imports System.Collections
Namespace st.istia.univangers.fr
Public MustInherit Class impotsData
Protected limites() As Decimal
Protected coeffr() As Decimal
Protected coeffn() As Decimal
Protected checked As Boolean
Protected valide As Boolean
' data access method
Public MustOverride Function getData() As Object()
' data verification method
Protected Function checkData() As Integer
' verifies acquired data
' we need data
valide = Not limites Is Nothing AndAlso Not coeffr Is Nothing AndAlso Not coeffn Is Nothing
If Not valide Then Return 1
' we must have 3 arrays of the same size
If valide Then valide = limites.Length = coeffr.Length AndAlso limites.Length = coeffn.Length
If Not valide Then Return 2
' tables must be non-empty
valide = limites.Length <> 0
If Not valide Then Return 3
' each array must contain elements >=0 in ascending order
valide = check(limites, limites.Length - 1) AndAlso check(coeffr, coeffr.Length) AndAlso check(coeffn, coeffn.Length)
If Not valide Then Return 4
' all is good
Return 0
End Function
' checks the validity of an array's contents
Protected Function check(ByRef tableau() As Decimal, ByVal n As Integer) As Boolean
' array must have its first n elements >=0 and in strictly ascending order
If tableau(0) < 0 Then Return False
For i As Integer = 1 To n - 1
If tableau(i) <= tableau(i - 1) Then Return False
Next
' it's good
Return True
End Function
End Class
End Namespace
Die Klasse verfügt über die folgenden geschützten Attribute:
Array der Steuerklassengrenzen | |
Array von Koeffizienten, die auf das zu versteuernde Einkommen angewendet werden | |
Tabelle der Koeffizienten, die auf die Anzahl der Anteile angewendet werden | |
Boolescher Wert, der angibt, ob die Daten (limits, coeffr, coeffn) überprüft wurden | |
Boolescher Wert, der angibt, ob die Daten (limits, coeffr, coeffn) gültig sind |
Die Klasse hat keinen Konstruktor. Sie verfügt über eine abstrakte Methode [getData], die abgeleitete Klassen implementieren müssen. Der Zweck dieser Methode ist es:
- den drei Arrays limits, coeffr, coeffn Werte zuweisen
- eine Ausnahme auszulösen, wenn die Daten nicht abgerufen werden konnten oder sich als ungültig herausstellten.
Die Klasse stellt die geschützten Methoden [checkData] und [check] bereit, die die Gültigkeit der Attribute (limits, coeffr, coeffn) überprüfen. Dadurch müssen abgeleitete Klassen diese nicht selbst implementieren. Sie müssen sie lediglich verwenden.
Die erste abgeleitete Klasse, die wir verwenden werden, lautet wie folgt:
Imports System.Collections
Imports System
Namespace st.istia.univangers.fr
Public Class impotsArray
Inherits impotsData
' constructor with no arguments
Public Sub New()
' initializing tables with constants
limites = New Decimal() {4262D, 8382D, 14753D, 23888D, 38868D, 47932D, 0D}
coeffr = New Decimal() {0D, 0.0683D, 0.1914D, 0.2826D, 0.3738D, 0.4262D, 0.4809D}
coeffn = New Decimal() {0D, 291.09D, 1322.92D, 2668.39D, 4846.98D, 6883.66D, 9505.54D}
checked = True
valide = True
End Sub
' builder with three input tables
Public Sub New(ByRef limites() As Decimal, ByRef coeffr() As Decimal, ByRef coeffn() As Decimal)
' data storage
Me.limites = limites
Me.coeffr = coeffr
Me.coeffn = coeffn
checked = False
End Sub
Public Overrides Function getData() As Object()
' check data if necessary
Dim erreur As Integer
If Not checked Then erreur = checkData() : checked = True
' if invalid, then throw an exception
If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
' otherwise we return the three tables
Return New Object() {limites, coeffr, coeffn}
End Function
End Class
End Namespace
Diese Klasse mit dem Namen [impotsArray] verfügt über zwei Konstruktoren:
- einen Konstruktor ohne Argumente, der die Attribute (limits, coeffr, coeffn) der Basisklasse mit fest codierten Arrays initialisiert
- einen Konstruktor, der die Attribute (limits, coeffr, coeffn) der Basisklasse mit Arrays initialisiert, die ihm als Parameter übergeben werden
Die Methode [getData], die es externen Klassen ermöglicht, die Arrays (limits, coeffr, coeffn) abzurufen, überprüft lediglich die Gültigkeit der drei Arrays mithilfe der Methode [checkData] der Basisklasse. Sie löst eine Ausnahme aus, wenn die Daten ungültig sind.
6.1.5. Testen von Geschäfts- und Datenzugriffsklassen
Es ist wichtig, in einer Webanwendung nur Geschäfts- und Datenzugriffsklassen einzubinden, deren Korrektheit überprüft wurde. Auf diese Weise kann sich die Debugging-Phase der Webanwendung auf die Controller- und View-Ebenen konzentrieren. Ein Testprogramm könnte wie folgt aussehen:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Imports Microsoft.VisualBasic
Namespace st.istia.univangers.fr
Module test
Sub Main()
' interactive tax calculator
' the user enters three data points on the keyboard: married nbEnfants salary
' the program then displays the tax payable
Const syntaxe As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"
' tax object creation
Dim objImpôt As impot = Nothing
Try
objImpôt = New impot(New impotsArray)
Catch ex As Exception
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
Environment.Exit(1)
End Try
' infinite loop
Dim marié As String
Dim nbEnfants As Integer
Dim salaire As Long
While True
' tax calculation parameters are requested
Console.Out.Write("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :")
Dim paramètres As String = Console.In.ReadLine().Trim()
' anything to do?
If paramètres Is Nothing OrElse paramètres = "" Then
Exit While
End If
' check the number of arguments in the input line
Dim erreur As Boolean = False
Dim args As String() = paramètres.Split(Nothing)
Dim nbParamètres As Integer = args.Length
If nbParamètres <> 3 Then
Console.Error.WriteLine(syntaxe)
erreur = True
End If
' checking the validity of parameters
If Not erreur Then
' married
marié = args(0).ToLower()
If marié <> "o" And marié <> "n" Then
erreur = True
End If
' nbEnfants
Try
nbEnfants = Integer.Parse(args(1))
If nbEnfants < 0 Then
Throw New Exception
End If
Catch
erreur = True
End Try
' salary
Try
salaire = Integer.Parse(args(2))
If salaire < 0 Then
Throw New Exception
End If
Catch
erreur = True
End Try
End If
' if the parameters are correct - the tax is calculated
If Not erreur Then
Console.Out.WriteLine(("impôt=" & objImpôt.calculer(marié = "o", nbEnfants, salaire) & " euro(s)"))
Else
Console.Error.WriteLine(syntaxe)
End If
End While
End Sub
End Module
End Namespace
Die Anwendung fordert den Benutzer auf, die drei für die Steuerberechnung erforderlichen Angaben einzugeben:
- Familienstand: o für verheiratet, n für unverheiratet
- Anzahl der Kinder
- jährliches Gehalt
Die Steuerberechnung erfolgt anhand eines Objekts vom Typ [tax], das beim Start der Anwendung erstellt wird:
' tax object creation
Dim objImpôt As impot = Nothing
Try
objImpôt = New impot(New impotsArray)
Catch ex As Exception
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
Environment.Exit(1)
End Try
Als Datenquelle verwenden wir ein Objekt vom Typ [impotsArray]. Es wird der Konstruktor ohne Argumente für diese Klasse verwendet, wobei die drei Arrays (limites, coeffr, coeffn) mit fest codierten Werten versehen werden. Das Erstellen eines [impot]-Objekts kann theoretisch eine Ausnahme auslösen, da das Objekt zur eigenen Erstellung die Daten (limites, coeffr, coeffn) von seiner Datenquelle anfordert, die ihm als Parameter übergeben wurde, und dieser Datenabruf eine Ausnahme auslösen kann. In diesem Fall kann die Methode zum Abrufen der Daten (fest codierte Werte) jedoch keine Ausnahme auslösen. Wir haben die Ausnahmebehandlung jedoch beibehalten, um den Leser auf die Möglichkeit aufmerksam zu machen, dass das [impot]-Objekt möglicherweise falsch erstellt wurde.
Hier ist ein Beispiel für die Ausführung des vorherigen Programms:
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
Wir kompilieren alle Klassen [impot, impotsData, impotsArray] zu einer einzigen Assembly [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
Wir kompilieren das Testprogramm:
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
Wir können die Tests ausführen:
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. Ansichten der Webanwendung
Die Anwendung verfügt über zwei Ansichten: [form.aspx] und [errors.aspx]. Lassen Sie uns anhand von Screenshots veranschaulichen, wie die Anwendung funktioniert. Die Ansicht [form.aspx] wird angezeigt, wenn die URL [main.aspx] zum ersten Mal aufgerufen wird:

Der Benutzer füllt das Formular aus:

und erhält über die Schaltfläche [Berechnen] das folgende Ergebnis:

Möglicherweise gibt er falsche Daten ein:

Ein Klick auf die Schaltfläche [Calculate] liefert dann eine andere Antwort [errors.aspx]:

Über den Link [Zurück zum Formular] oben kann er zur Ansicht [form.aspx] zurückkehren, wie sie vor dem Fehler war:

6.1.7. Die Ansicht [form.aspx]
Die Seite [form.aspx] sieht wie folgt aus:
<%@ 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>
Die dynamischen Felder auf dieser Seite sind wie folgt:
„checked“, wenn das Kontrollkästchen [yes] aktiviert sein soll, andernfalls „“ | |
Das Gleiche gilt für das Kontrollkästchen [nein] | |
Wert, der in das Eingabefeld [txtChildren] eingegeben werden soll | |
Wert, der in das Eingabefeld [txtSalary] eingegeben werden soll | |
Wert, der in das Eingabefeld [txtTax] eingegeben werden soll |
Die Seite enthält zwei Formulare, die jeweils über eine [submit]-Schaltfläche verfügen. Die Schaltfläche [Calculate] ist die [submit]-Schaltfläche für das folgende Formular:
<form method="post" action="main.aspx?action=calcul">
...
<P>
<INPUT type="submit" value="Calculer">
</P>
</form>
Wir sehen, dass die Formularparameter mit [action=calcul] an den Controller gesendet werden. Die Schaltfläche [Clear] ist die [submit]-Schaltfläche für das folgende Formular:
<form method="post" action="main.aspx?action=effacer">
<INPUT type="submit" value="Effacer">
</form>
Wir sehen, dass die Formularparameter mit [action=effacer] an den Controller gesendet werden. Hier hat das Formular keine Parameter. Nur die Aktion ist von Bedeutung.
Die Felder in [formulaire.aspx] werden von [formulaire.aspx.vb] berechnet:
Imports System.Collections.Specialized
Public Class formulaire
Inherits System.Web.UI.Page
' page fields
Protected rdouichecked As String
Protected rdnonchecked As String
Protected txtEnfants As String
Protected txtSalaire As String
Protected txtImpot As String
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' we retrieve the previous request in the
Dim form As NameValueCollection = Context.Items("formulaire")
' prepare the page to be displayed
' radio buttons
rdouichecked = ""
rdnonchecked = "checked"
If form("rdMarie").ToString = "oui" Then
rdouichecked = "checked"
rdnonchecked = ""
End If
' the rest
txtEnfants = CType(form("txtEnfants"), String)
txtSalaire = CType(form("txtSalaire"), String)
txtImpot = CType(Context.Items("txtImpot"), String)
End Sub
End Class
Die Felder in [main.aspx] werden auf der Grundlage von zwei Informationen berechnet, die vom Controller im Seitenkontext abgelegt werden:
- Context.Items("form"): ein NameValueCollection-Wörterbuch, das die Werte der HTML-Felder [rdmarie,txtEnfants,txtSalaire] enthält
- Context.Items("txtImpot"): Steuerwert
6.1.8. Die Ansicht [erreurs.aspx]
Die Ansicht [erreurs.aspx] zeigt alle Fehler an, die während der Laufzeit der Anwendung auftreten können. Der Darstellungscode lautet wie folgt:
<%@ 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>
Die Seite enthält drei dynamische Felder:
HTML-Code für eine Liste von Fehlern | |
URL eines Links | |
Linktext |
Diese Felder werden vom Controller der Seite in [errors.aspx.vb] berechnet:
Imports System.Collections
Imports Microsoft.VisualBasic
Public Class erreurs
Inherits System.Web.UI.Page
' page parameter
Protected erreursHTML As String = ""
Protected href As String
Protected lien As String
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' retrieve context elements
Dim erreurs As ArrayList = CType(context.Items("erreurs"), ArrayList)
href = context.Items("href").ToString
lien = context.Items("lien").ToString
' we generate the HTML code from the list
Dim i As Integer
For i = 0 To erreurs.Count - 1
erreursHTML += "<li> " + erreurs(i).ToString + "</li>" + ControlChars.CrLf
Next
End Sub
End Class
Der Seiten-Controller ruft Informationen ab, die vom Anwendungs-Controller im Seitenkontext abgelegt wurden:
ArrayList-Objekt, das die Liste der anzuzeigenden Fehlermeldungen enthält | |
URL eines Links | |
Linktext |
Da wir nun wissen, was der Anwendungsbenutzer sieht, können wir mit dem Schreiben des Controllers der Anwendung fortfahren.
6.1.9. Die Controller [global.asax, main.aspx]
Lassen Sie uns die MVC-Architektur unserer Anwendung noch einmal betrachten:

AnwendungslogikClient
Der Controller [main.aspx] muss drei Aktionen verarbeiten:
- init: entspricht der ersten Anfrage des Clients. Der Controller zeigt die Ansicht [formulaire.aspx] an
- calcul: entspricht der Anfrage zur Steuerberechnung. Sind die Daten im Eingabeformular korrekt, wird die Steuer mithilfe der Business-Klasse [taxes] berechnet. Der Controller gibt die Ansicht [form.aspx] in validiertem Zustand zusammen mit der berechneten Steuer an den Client zurück. Sind die Daten im Eingabeformular fehlerhaft, gibt der Controller die Ansicht [errors.aspx] mit einer Liste der Fehler und einem Link zur Rückkehr zum Formular zurück.
- return: entspricht der Rückkehr zum Formular nach einem Fehler. Der Controller zeigt die Ansicht [form.aspx] so an, wie sie vor dem Fehler validiert wurde.
Es ist außerdem bekannt, dass jede Anfrage an die Anwendung den [global.asax]-Controller durchläuft, sofern dieser vorhanden ist. Wir haben daher am Einstiegspunkt der Anwendung eine Kette aus zwei Controllern:
- [global.asax], der aufgrund der ASP.NET-Architektur alle Anfragen an die Anwendung empfängt
- [main.aspx], der nach Wahl des Entwicklers ebenfalls alle Anfragen an die Anwendung empfängt
Die Notwendigkeit von [main.aspx] ergibt sich aus der Tatsache, dass wir eine Sitzung verwalten müssen. Wir haben gesehen, dass [global.asax] in diesem Fall nicht als Controller geeignet ist. Wir könnten hier ganz auf [global.asax] verzichten. Wir werden es jedoch verwenden, um Code beim Start der Anwendung auszuführen. Das obige MVC-Diagramm zeigt, dass wir ein [tax]-Objekt erstellen müssen, um die Steuer zu berechnen. Es ist nicht notwendig, dieses Objekt mehrfach zu erstellen; einmal reicht aus. Wir werden es daher beim Start der Anwendung während des [Application_Start]-Ereignisses erstellen, das vom [global.asax]-Controller abgewickelt wird. Der Code hierfür lautet wie folgt:
[global.asax]
[global.asax.vb]
Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr
Public Class Global
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' create an impot object
Dim objImpot As impot
Try
objImpot = New impot(New impotsArray)
' put the object in the application
Application("objImpot") = objImpot
' no error
Application("erreur") = False
Catch ex As Exception
'there has been an error, we note it in the application
Application("erreur") = True
End Try
End Sub
End Class
Nach der Erstellung wird das [import]-Objekt in der Anwendung gespeichert. Von dort wird es bei Anfragen verschiedener Clients abgerufen. Da die Erstellung des [import]-Objekts fehlschlagen kann, behandeln wir alle Ausnahmen und setzen einen [error]-Schlüssel in der Anwendung, um anzugeben, ob bei der Erstellung des [import]-Objekts ein Fehler aufgetreten ist oder nicht.
Der Controller-Code [main.aspx, main.aspx.vb] sieht wie folgt aus:
[main.aspx]
[main.aspx.vb]
Imports System
Imports System.Collections.Specialized
Imports System.Collections
Imports st.istia.univangers.fr
Public Class main
Inherits System.Web.UI.Page
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' first of all, we check whether the application has initialized correctly
If CType(Application("erreur"), Boolean) Then
' redirects to error page
Dim erreurs As New ArrayList
erreurs.Add("Application momentanément indisponible...")
context.Items("erreurs") = erreurs
context.Items("lien") = ""
context.Items("href") = ""
Server.Transfer("erreurs.aspx")
End If
' retrieve the action to be performed
Dim action As String
If Request.QueryString("action") Is Nothing Then
action = "init"
Else
action = Request.QueryString("action").ToString.ToLower
End If
' execute the action
Select Case action
Case "init"
' init application
initAppli()
Case "calcul"
' tax calculation
calculImpot()
Case "retour"
' back to form
retourFormulaire()
Case "effacer"
' init application
initAppli()
Case Else
' unknown action = init
initAppli()
End Select
End Sub
Private Sub initAppli()
' the pre-filled form is displayed
Context.Items("formulaire") = initForm()
Context.Items("txtImpot") = ""
Server.Transfer("formulaire.aspx", True)
End Sub
Private Function initForm() As NameValueCollection
' initialize the form
Dim form As New NameValueCollection
form.Set("rdMarie", "non")
form.Set("txtEnfants", "")
form.Set("txtSalaire", "")
Return form
End Function
Private Sub calculImpot()
' check the validity of the data entered
Dim erreurs As ArrayList = checkData()
' if there are errors, we report them
If erreurs.Count <> 0 Then
' save entries
Session.Item("formulaire") = Request.Form
' prepare the error page
context.Items("href") = "main.aspx?action=retour"
context.Items("lien") = "Retour au formulaire"
context.Items("erreurs") = erreurs
Server.Transfer("erreurs.aspx")
End If
' no errors here - the tax is calculated
Dim impot As Long = CType(Application("objImpot"), impot).calculer( _
Request.Form("rdMarie") = "oui", _
CType(Request.Form("txtEnfants"), Integer), _
CType(Request.Form("txtSalaire"), Long))
' the result page is displayed
context.Items("txtImpot") = impot.ToString + " euro(s)"
context.Items("formulaire") = Request.Form
Server.Transfer("formulaire.aspx", True)
End Sub
Private Sub retourFormulaire()
' displays the form with values taken from the session
Context.Items("formulaire") = Session.Item("formulaire")
Context.Items("txtImpot") = ""
Server.Transfer("formulaire.aspx", True)
End Sub
Private Function checkData() As ArrayList
' initially no errors
Dim erreurs As New ArrayList
Dim erreur As Boolean = False
' married radio button
Try
Dim rdMarie As String = Request.Form("rdMarie").ToString
If rdMarie <> "oui" And rdMarie <> "non" Then
Throw New Exception
End If
Catch
erreurs.Add("Vous n'avez pas indiqué votre statut marital")
End Try
' no. of children
Try
Dim txtEnfants As String = Request.Form("txtEnfants").ToString
Dim nbEnfants As Integer = CType(txtEnfants, Integer)
If nbEnfants < 0 Then Throw New Exception
Catch
erreurs.Add("Le nombre d'enfants est incorrect")
End Try
' salary
Try
Dim txtSalaire As String = Request.Form("txtSalaire").ToString
Dim salaire As Integer = CType(txtSalaire, Long)
If salaire < 0 Then Throw New Exception
Catch
erreurs.Add("Le salaire annuel est incorrect")
End Try
' return the list of errors
Return erreurs
End Function
End Class
Der Controller überprüft zunächst, ob die Anwendung korrekt initialisiert wurde:
' first of all, we check whether the application has initialized correctly
If CType(Application("erreur"), Boolean) Then
' redirects to error page
Dim erreurs As New ArrayList
erreurs.Add("Application momentanément indisponible...")
context.Items("erreurs") = erreurs
context.Items("lien") = ""
context.Items("href") = ""
Server.Transfer("erreurs.aspx")
End If
Wenn der Controller feststellt, dass die Anwendung nicht korrekt initialisiert werden konnte (das für die Berechnung erforderliche [import]-Objekt konnte nicht erstellt werden), zeigt er die Fehlerseite mit den entsprechenden Parametern an. Hier ist es nicht erforderlich, den Rückkehrlink auf dem Formular zu platzieren, da die gesamte Anwendung nicht verfügbar ist. Eine allgemeine Fehlermeldung wird in [Context.Items("errors")] vom Typ [ArrayList] abgelegt.
Wenn der Controller feststellt, dass die Anwendung betriebsbereit ist, analysiert er die Aktion, die er über den [action]-Parameter ausführen soll. Diese Funktionsweise ist uns inzwischen schon oft begegnet. Die Verarbeitung jeder Art von Aktion wird an eine Funktion delegiert.
6.1.9.1. Die Aktionen „init“ und „delete“
Diese beiden Aktionen müssen das leere Eingabeformular anzeigen. Erinnern Sie sich daran, dass dieses Formular (siehe Ansichten) zwei Parameter hat:
- Context.Items("form"): ein [NameValueCollection]-Wörterbuch, das die Werte der HTML-Felder [rdmarie,txtEnfants,txtSalaire] enthält
- Context.Items("txtImpot"): Steuerwert
Die Funktion [initAppli] initialisiert diese beiden Parameter, um ein leeres Formular anzuzeigen.
6.1.9.2. Die Berechnungsaktion
Diese Aktion muss die fällige Steuer auf der Grundlage der im Formular eingegebenen Daten berechnen und das Formular mit den eingegebenen Werten und dem berechneten Steuerbetrag vorausgefüllt zurückgeben. Die für diese Aufgabe zuständige Funktion [calculImpot] überprüft zunächst, ob die Formulardaten korrekt sind:
- Das Feld [rdMarie] muss vorhanden sein und den Wert [yes] oder [no] haben
- das Feld [txtEnfants] muss vorhanden sein und eine ganze Zahl >=0 enthalten
- Das Feld [txtSalaire] muss vorhanden sein und eine ganze Zahl >=0 enthalten
Sind die eingegebenen Daten ungültig, zeigt der Controller die Ansicht [erreurs.aspx] an, nachdem er zuvor die erwarteten Werte dafür im Kontext festgelegt hat:
- Fehlermeldungen werden in ein [ArrayList]-Objekt gespeichert, das dann dem Kontext hinzugefügt wird [Context.Items("errors")]
- Die URL des Rücklink und der Text dieses Links werden ebenfalls in den Kontext gesetzt.
Bevor die Steuerung an die Seite [erreurs.aspx] übergeben wird, die die Antwort an den Client sendet, werden die im Formular (Request.Form) eingegebenen Werte in die Sitzung abgelegt und dem Schlüssel „form“ zugeordnet. Dadurch können sie bei einer nachfolgenden Anfrage abgerufen werden.
Man könnte sich hier fragen, ob es sinnvoll ist, zu überprüfen, ob die Felder [rdMarie, txtEnfants, txtSalaire] in der vom Client gesendeten Anfrage vorhanden sind. Dies ist unnötig, wenn wir sicher sind, dass unser Client ein Browser ist, der die Ansicht [formulaire.aspx] mit diesen Feldern erhalten hat. Wir können uns dessen jedoch nie sicher sein. Wir werden etwas später ein Beispiel zeigen, bei dem der Client die bereits bekannte [curl]-Anwendung ist. Wir werden die Anwendung abfragen, ohne die von ihr erwarteten Felder zu senden, und sehen, wie sie reagiert. Dies ist eine Regel, die bereits mehrfach erwähnt wurde und die wir hier wiederholen: Eine Anwendung darf niemals Annahmen über den Typ des Clients treffen, der sie abfragt. Um auf der sicheren Seite zu sein, muss sie davon ausgehen, dass sie von einer programmierten Anwendung abgefragt werden könnte, die ihr unerwartete Parameterzeichenfolgen sendet. Sie muss sich in allen Fällen korrekt verhalten.
In unserem Fall haben wir überprüft, ob die Felder [rdMarie, txtEnfants, txtSalaire] in der Anfrage vorhanden waren, aber nicht, ob sie weitere enthalten könnte. In dieser Anwendung würden diese ignoriert werden. Dennoch wäre es, wiederum als Sicherheitsmaßnahme, sinnvoll, diese Art von Anfragen in einer Protokolldatei zu erfassen und eine Warnung an den Anwendungsadministrator zu senden, damit dieser weiß, dass die Anwendung „seltsame“ Anfragen erhält. Durch die Analyse dieser Einträge in der Protokolldatei könnte er einen potenziellen Angriff auf die Anwendung erkennen und dann die notwendigen Maßnahmen zu deren Schutz ergreifen.
Wenn die erwarteten Daten korrekt sind, leitet der Controller die Steuerberechnung unter Verwendung des in der Anwendung gespeicherten [tax]-Objekts ein. Anschließend speichert er die beiden von der Ansicht [form.aspx] erwarteten Informationen im Kontext:
- Context.Items("formulaire"): ein [NameValueCollection]-Wörterbuch, das die Werte der HTML-Felder [rdmarie,txtEnfants,txtSalaire] enthält (hier [Request.Form]), d. h. die zuvor in das Formular eingegebenen Werte
- Context.Items("txtImpot"): der soeben berechnete Steuerwert
Der aufmerksame Leser hat sich beim Lesen des Vorstehenden vielleicht gefragt: Da das beim Start der Anwendung erstellte [impot]-Objekt von allen Anfragen gemeinsam genutzt wird, könnte es dann zu Zugriffskonflikten kommen, die zu einer Beschädigung der Daten des [impot]-Objekts führen? Um diese Frage zu beantworten, müssen wir zum Code der [impot]-Klasse zurückkehren. Die Anfragen rufen die Methode [impot].calculateTax auf, um die fällige Steuer zu ermitteln. Daher müssen wir folgenden Code untersuchen:
Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Long) As Long
' calculating the number of shares
Dim nbParts As Decimal
If marié Then
nbParts = CDec(nbEnfants) / 2 + 2
Else
nbParts = CDec(nbEnfants) / 2 + 1
End If
If nbEnfants >= 3 Then
nbParts += 0.5D
End If
' calculation of taxable income & family quota
Dim revenu As Decimal = 0.72D * salaire
Dim QF As Decimal = revenu / nbParts
' tAX CALCULATION
limites((limites.Length - 1)) = QF + 1
Dim i As Integer = 0
While QF > limites(i)
i += 1
End While
Dim impot As Long = CLng(revenu * coeffR(i) - nbParts * coeffN(i))
Return impot
End Function
Angenommen, ein Thread führt die vorherige Methode aus und wird unterbrochen. Ein anderer Thread führt dann die Methode aus. Welche Risiken bestehen? Um dies herauszufinden, haben wir den folgenden Code hinzugefügt:
Dim impot As Long = CLng(revenu * coeffR(i) - nbParts * coeffN(i))
' wait 10 seconds
Thread.Sleep(10000)
Return impot
Thread 1 wird nach der Berechnung des Werts [impot1] der lokalen Variablen [impot] unterbrochen. Anschließend wird Thread 2 ausgeführt und berechnet einen neuen Wert [impot2] für dieselbe Variable [impot], bevor er unterbrochen wird. Thread 1 erhält die Kontrolle zurück. Was findet er in der lokalen Variablen [impot]? Da diese Variable lokal zu einer Methode ist, wird sie in einer Speicherstruktur namens Stack gespeichert. Dieser Stack ist Teil des Thread-Kontexts, der gespeichert wird, wenn der Thread angehalten wird. Wenn Thread 2 startet, wird sein Kontext mit einem neuen Stack und somit einer neuen lokalen Variablen [impot] eingerichtet. Wenn Thread 2 seinerseits angehalten wird, wird auch sein Kontext gespeichert. Wenn Thread 1 neu gestartet wird, wird sein Kontext wiederhergestellt, einschließlich seines Stacks. Er ruft dann seine lokale Variable [impot] ab und nicht die von Thread 2. Wir befinden uns daher in einer Situation, in der es keine Zugriffskonflikte zwischen den Anfragen gibt. Die mit der oben beschriebenen 10-Sekunden-Pause durchgeführten Tests bestätigten, dass gleichzeitige Anfragen tatsächlich das erwartete Ergebnis lieferten.
6.1.9.3. Die Rückkehraktion
Diese Aktion entspricht dem Klicken auf den Link [Zurück zum Formular] in der Ansicht [errors.aspx], um zur Ansicht [form.aspx] zurückzukehren, die mit den zuvor eingegebenen und in der Sitzung gespeicherten Werten vorausgefüllt ist. Die Funktion [returnForm] ruft diese Informationen ab. Die beiden von der Ansicht [form.aspx] erwarteten Parameter werden initialisiert:
- Context.Items("form") mit den zuvor eingegebenen und in der Sitzung gespeicherten Werten
- Context.Items("txtImpot") mit der leeren Zeichenfolge
6.1.10. Testen der Webanwendung
Alle oben genannten Dateien werden in einem Ordner namens <application-path> abgelegt.

In diesem Ordner wird ein Unterordner [bin] erstellt, in dem die Assembly [impot.dll] – generiert aus der Kompilierung der Business-Class-Dateien: [impots.vb, impotsData.vb, impotsArray.vb] – abgelegt wird. Der erforderliche Kompilierungsbefehl lautet wie folgt:
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
Die oben genannte Datei [impot.dll] muss im Verzeichnis <Anwendungspfad>\bin abgelegt werden, damit die Webanwendung darauf zugreifen kann. Der Cassini-Server wird mit den Parametern (<Anwendungspfad>,/impots1) gestartet. Über einen Browser rufen wir die URL [http://localhost/impots1/main.aspx] auf:

Wir füllen das Formular aus:

Anschließend starten wir die Steuerberechnung über die Schaltfläche [Berechnen]. Wir erhalten folgende Antwort:

Anschließend geben wir falsche Daten ein:

Ein Klick auf die Schaltfläche [Berechnen] liefert die folgende Antwort:

Ein Klick auf den Link [Zurück zum Formular] bringt uns zurück zum Formular, wie es zum Zeitpunkt der Übermittlung aussah:

Schließlich wird durch Klicken auf die Schaltfläche [Löschen] die Seite zurückgesetzt:

6.1.11. Verwendung des [curl]-Clients
Es ist wichtig, Webanwendungen auch mit anderen Clients als Browsern zu testen. Wenn Sie einem Browser ein Formular mit Parametern senden, die beim Absenden übermittelt werden sollen, sendet der Browser die Werte dieser Parameter an den Server zurück. Ein anderer Client tut dies möglicherweise nicht, und der Server würde dann eine Anfrage mit fehlenden Parametern erhalten. Er muss wissen, wie er in diesem Fall vorgehen soll. Ein weiteres Beispiel ist die clientseitige Datenvalidierung. Wenn das Formular zu validierende Daten enthält, kann diese Validierung auf der Clientseite mithilfe von Skripten durchgeführt werden, die in dem Dokument enthalten sind, in dem sich das Formular befindet. Der Browser übermittelt das Formular nur, wenn alle clientseitig validierten Daten gültig sind. Man könnte dann auf der Serverseite versucht sein, davon auszugehen, dass wir validierte Daten erhalten, und diese Validierung nicht ein zweites Mal durchführen wollen. Das wäre ein Fehler. Tatsächlich könnte ein anderer Client als ein Browser ungültige Daten an den Server senden, und die Webanwendung könnte sich dann unerwartet verhalten. Wir werden diese Punkte anhand des [curl]-Clients veranschaulichen.
Zunächst rufen wir die URL [http://localhost/impots1/main.aspx] auf:
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>
Der Server hat uns den HTML-Code des Formulars gesendet. In den HTTP-Headern finden wir das Sitzungscookie. Wir werden es in nachfolgenden Anfragen verwenden, um die Sitzung aufrechtzuerhalten. Fordern wir die Aktion [calcul] ohne Angabe von Parametern an:
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>
Wir sehen, dass die Webanwendung die Ansicht [errors] mit drei Fehlermeldungen für die drei fehlenden Parameter zurückgegeben hat. Senden wir nun einige falsche Parameter:
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>
Die drei Fehler wurden korrekt erkannt. Senden wir nun gültige Parameter:
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>
Wir haben die fällige Steuer erfolgreich abgerufen: 4.300 Euro. Die Lehre aus diesem Beispiel ist, dass wir uns nicht davon täuschen lassen dürfen, dass wir eine Webanwendung schreiben, die für Clients bestimmt ist, bei denen es sich um Browser handelt. Eine Webanwendung ist ein TCP/IP-Dienst, und dieses Netzwerkprotokoll gibt keinen Aufschluss über die Art der Client-Anwendung eines Dienstes. Daher können wir nicht wissen, ob der Client einer Webanwendung ein Browser ist oder nicht. Wir befolgen daher zwei Regeln:
- Wenn wir eine Anfrage von einem Client erhalten, stellen wir keine Vermutungen über den Client an und überprüfen, ob die erwarteten Parameter in der Anfrage vorhanden und gültig sind
- Wir erstellen eine für Browser bestimmte Antwort, die in der Regel aus HTML-Dokumenten besteht
Eine Webanwendung kann so aufgebaut sein, dass sie verschiedene Clients gleichzeitig bedient, beispielsweise Browser und Mobiltelefone. Wir können dann in jede Anfrage einen neuen Parameter aufnehmen, der den Client-Typ angibt. So fordert ein Browser eine Steuerberechnung über eine Anfrage an die URL http://machine/impots/main.aspx?client=navigateur&action=calcul an, während ein Mobiltelefon eine Anfrage an die URL http://machine/impots/main.aspx?client=mobile&action=calcul sendet. Die MVC-Struktur erleichtert die Entwicklung einer solchen Anwendung. Sie hat folgende Form:

Der Block [Business Classes, Data Access Classes] bleibt unverändert. Dies liegt daran, dass es sich um eine clientunabhängige Komponente handelt. Der Block [Controller] ändert sich nur geringfügig, muss jedoch einen neuen Parameter in der Anfrage berücksichtigen: den Parameter [client], der den Typ des bearbeiteten Clients angibt. Der Block [Views] muss Ansichten für jeden Client-Typ generieren. Es könnte sich lohnen, das Vorhandensein des Parameters [client] in der Abfrage bereits in der Entwurfsphase der Anwendung zu berücksichtigen, auch wenn das kurz- oder mittelfristige Ziel auf Browser beschränkt ist. Wenn die Anwendung später einen neuen Client-Typ unterstützen muss, müssen nur noch auf diesen Client zugeschnittene Ansichten geschrieben werden.
6.2. Beispiel 2
6.2.1. Das Problem
Hier schlagen wir vor, dasselbe Problem wie zuvor anzugehen, jedoch durch eine Änderung der Datenquelle für das von der Webanwendung erstellte [tax]-Objekt. In der vorherigen Version lieferte die Datenquelle Werte aus Arrays, die fest im Code hinterlegt waren. Diesmal wird die neue Datenquelle diese Werte aus einer ODBC-Datenquelle abrufen, die mit einer MySQL-Datenbank verknüpft ist.
6.2.2. Die ODBC-Datenquelle
Die Daten befinden sich in einer Tabelle namens [IMPOTS] in einer MySQL-Datenbank namens [dbimpots]. Der Inhalt dieser Tabelle sieht wie folgt aus:

Der Datenbankbesitzer ist der Benutzer [admimpots] mit dem Passwort [mdpimpots]. Wir verknüpfen eine ODBC-Datenquelle mit dieser Datenbank. Bevor wir dies tun, wollen wir zunächst die verschiedenen Möglichkeiten des Zugriffs auf eine Datenbank über die .NET-Plattform betrachten.
Für Windows-Plattformen stehen zahlreiche Datenbanken zur Verfügung. Um auf diese zuzugreifen, verwenden Anwendungen Programme, die als Treiber bezeichnet werden.

In der obigen Abbildung verfügt der Treiber über zwei Schnittstellen:
- die Schnittstelle I1, die der Anwendung zur Verfügung steht
- die Schnittstelle I2 zur Datenbank
Um zu verhindern, dass eine für die Datenbank B1 geschriebene Anwendung bei einer Migration auf eine andere Datenbank B2 neu geschrieben werden muss, wurden Standardisierungsbemühungen für die Schnittstelle I1 unternommen. Wenn Datenbanken mit „standardisierten“ Treibern verwendet werden, wird die Datenbank B1 mit dem Treiber P1 und die Datenbank B2 mit dem Treiber P2 ausgestattet, wobei die I1-Schnittstelle dieser beiden Treiber identisch ist. Somit muss die Anwendung nicht neu geschrieben werden. So können Sie beispielsweise eine Access-Datenbank in eine MySQL-Datenbank migrieren, ohne die Anwendung zu ändern.
Es gibt zwei Arten von standardisierten Treibern:
- ODBC-Treiber (Open DataBase Connectivity)
- OLE DB-Treiber (Object Linking and Embedding DataBase)
ODBC-Treiber ermöglichen den Zugriff auf Datenbanken. Die Datenquellen für OLE DB-Treiber sind vielfältiger: Datenbanken, E-Mail-Systeme, Verzeichnisse usw. Es gibt keine Einschränkungen. Jede Datenquelle kann Gegenstand eines OLE DB-Treibers sein, wenn ein Anbieter dies beschließt. Der Vorteil liegt auf der Hand: Sie haben einheitlichen Zugriff auf eine Vielzahl von Daten.
Die .NET 1.1-Plattform enthält drei Arten von Datenzugriffsklassen:
- SQL Server.NET-Klassen für den Zugriff auf Microsoft SQL Server-Datenbanken
- die Ole Db.NET-Klassen für den Zugriff auf Datenbanken von DBMS, die einen OLE DB-Treiber anbieten
- ODBC.NET-Klassen für den Zugriff auf Datenbanken von DBMS, die einen ODBC-Treiber anbieten
Das MySQL-DBMS verfügt schon seit langem über einen ODBC-Treiber. Diesen werden wir nun verwenden. Unter Windows wählen wir [Startmenü/Systemsteuerung/Verwaltung/32-Bit-ODBC-Quellen]. Je nach Windows-Version kann dieser Pfad leicht variieren. Dadurch wird die folgende Anwendung geöffnet, mit der wir unsere ODBC-Quelle erstellen können:

Wir erstellen eine Systemdatenquelle, d. h. eine Datenquelle, die jeder Benutzer des Computers nutzen kann. Daher wählen wir oben die Registerkarte [Systemdatenquelle] aus. Die angezeigte Seite enthält eine Schaltfläche [Hinzufügen], mit der wir eine neue ODBC-Datenquelle erstellen:

Der Assistent fordert Sie auf, den zu verwendenden ODBC-Treiber auszuwählen. Windows wird mit einer Reihe vorinstallierter ODBC-Treiber ausgeliefert. Der MySQL-ODBC-Treiber ist in diesem Paket nicht enthalten. Sie müssen ihn daher zunächst installieren. Sie finden ihn online, indem Sie die Suchbegriffe „MySQL ODBC“ oder „MyODBC“ in eine Suchmaschine eingeben. Hier haben wir den Treiber [MySQL ODBC 3.51] installiert. Wir wählen ihn aus und klicken auf [Fertigstellen]:

Sie müssen die folgenden Informationen angeben:
Der Name, der die ODBC-Datenquelle identifiziert. Jede Windows-Anwendung kann über diesen Namen auf die Quelle zugreifen | |
Beliebiger Text zur Beschreibung der Datenquelle | |
Der Name des Rechners, auf dem das MySQL-DBMS gehostet wird. In diesem Fall ist es der lokale Rechner. Es könnte sich auch um einen Remote-Rechner handeln. Dies würde es einer Windows-Anwendung ermöglichen, ohne spezielle Programmierung auf eine Remote-Datenbank zuzugreifen. Dies ist ein wesentlicher Vorteil der ODBC-Quelle. | |
Ein MySQL-DBMS kann mehrere Datenbanken verwalten. Hier geben wir an, welche wir verwalten möchten: dbimpots | |
Der Name eines im MySQL-Datenbankmanagementsystem registrierten Benutzers. Der Zugriff auf die Datenquelle erfolgt unter dem Namen dieses Benutzers. Hier: admimpots | |
Das Passwort dieses Benutzers. Hier: mdpimpots | |
Arbeitsport des MySQL-DBMS. Standardmäßig ist dies Port 3306. Wir haben ihn nicht geändert |
Sobald dies erledigt ist, testen wir die Gültigkeit unserer Verbindungseinstellungen über die Schaltfläche [Datenquelle testen]:

Sobald dies erledigt ist, können wir uns auf unsere ODBC-Datenquelle verlassen. Wir können sie nun verwenden. Wir klicken so oft wie nötig auf [OK], um den ODBC-Assistenten zu verlassen.
Falls der Leser nicht über das MySQL-DBMS verfügt, kann er es kostenlos unter [http://www.mysql.com] herunterladen. Im Folgenden beschreiben wir die Schritte zum Erstellen einer ODBC-Quelle mit Access. Die ersten Schritte sind identisch mit den zuvor beschriebenen. Wir fügen eine neue Systemdatenquelle hinzu:

Der ausgewählte Treiber ist [Microsoft Access Driver]. Klicken Sie auf [Fertig stellen], um zur Definition der ODBC-Quelle zu gelangen:

Die folgenden Informationen müssen angegeben werden:
Der Name, der die ODBC-Datenquelle identifiziert. Jede Windows-Anwendung kann über diesen Namen auf die Quelle zugreifen | |
Beliebiger Text zur Beschreibung der Datenquelle | |
Der vollständige Name der zu verwendenden Access-Datei |
6.2.3. Eine neue Datenzugriffsklasse
Werfen wir noch einmal einen Blick auf die MVC-Struktur unserer Anwendung:

Im obigen Diagramm ist die Klasse [impotsData] für das Abrufen von Daten zuständig. In diesem Fall ruft sie Daten aus der MySQL-Datenbank [dbimpots] ab. Wie wir in der vorherigen Version dieser Anwendung gelernt haben, ist [impotsData] eine abstrakte Klasse, die immer dann abgeleitet werden muss, wenn wir sie an eine neue Datenquelle anpassen wollen. Sehen wir uns die Struktur dieser abstrakten Klasse noch einmal an:
Imports System.Collections
Namespace st.istia.univangers.fr
Public MustInherit Class impotsData
Protected limites() As Decimal
Protected coeffr() As Decimal
Protected coeffn() As Decimal
Protected checked As Boolean
Protected valide As Boolean
' data access method
Public MustOverride Function getData() As Object()
' data verification method
Protected Function checkData() As Integer
' verifies acquired data
...
End Function
' checks the validity of an array's contents
Protected Function check(ByRef tableau() As Decimal, ByVal n As Integer) As Boolean
...
End Function
End Class
End Namespace
Die Klasse, die von [impotsData] abgeleitet ist, muss zwei Methoden implementieren:
- einen Konstruktor, falls der Standardkonstruktor von [impotsData] nicht geeignet ist
- die Methode [getData], die die drei Arrays (limites, coeffr, coeffn) zurückgibt
Wir erstellen die Klasse [impotsODBC], die die Daten (limits, coeffr, coeffn) aus einer ODBC-Quelle abruft, deren Namen wir angeben:
Imports System.Data.Odbc
Imports System.Data
Imports System.Collections
Imports System
Namespace st.istia.univangers.fr
Public Class impotsODBC
Inherits impotsData
' instance variables
Protected DSNimpots As String
' manufacturer
Public Sub New(ByVal DSNimpots As String)
' we note the three pieces of information
Me.DSNimpots = DSNimpots
End Sub
Public Overrides Function getdata() As Object()
' initializes the three limit tables, coeffr, coeffn from
' the contents of the [impots] table in the ODBC DSNimpots database
' limites, coeffr, coeffn are the three columns of this table
' can launch various exceptions
Dim connectString As String = "DSN=" + DSNimpots + ";" ' base connection chain
Dim impotsConn As OdbcConnection = Nothing ' the connection
Dim sqlCommand As OdbcCommand = Nothing ' the SQL command
' the SELECT query
Dim selectCommand As String = "select limites,coeffr,coeffn from impots"
' tables to retrieve data
Dim aLimites As New ArrayList
Dim aCoeffR As New ArrayList
Dim aCoeffN As New ArrayList
Try
' attempt to access the database
impotsConn = New OdbcConnection(connectString)
impotsConn.Open()
' create a command object
sqlCommand = New OdbcCommand(selectCommand, impotsConn)
' execute the query
Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader()
' Using the recovered table
While myReader.Read()
' the data of the current line are put in the tables
aLimites.Add(myReader("limites"))
aCoeffR.Add(myReader("coeffr"))
aCoeffN.Add(myReader("coeffn"))
End While
' freeing up resources
myReader.Close()
impotsConn.Close()
Catch e As Exception
Throw New Exception("Erreur d'accès à la base de données (" + e.Message + ")")
End Try
' dynamic tables are placed in static tables
Me.limites = New Decimal(aLimites.Count - 1) {}
Me.coeffr = New Decimal(aLimites.Count - 1) {}
Me.coeffn = New Decimal(aLimites.Count - 1) {}
Dim i As Integer
For i = 0 To aLimites.Count - 1
limites(i) = Decimal.Parse(aLimites(i).ToString())
coeffR(i) = Decimal.Parse(aCoeffR(i).ToString())
coeffN(i) = Decimal.Parse(aCoeffN(i).ToString())
Next i
' verify acquired data
Dim erreur As Integer = checkData()
' if invalid data, throws an exception
If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
' otherwise we return the three tables
Return New Object() {limites, coeffr, coeffn}
End Function
End Class
End Namespace
Werfen wir einen Blick auf den Konstruktor:
' manufacturer
Public Sub New(ByVal DSNimpots As String)
' we note the three pieces of information
Me.DSNimpots = DSNimpots
End Sub
Er nimmt als Parameter den Namen der ODBC-Datenquelle entgegen, die die abzurufenden Daten enthält. Der Konstruktor speichert diesen Namen lediglich. Die Methode [getData] ist dafür zuständig, die Daten aus der Tabelle [impots] zu lesen und in drei Arrays (limites, coeffr, coeffn) zu speichern. Lassen Sie uns den Code kommentieren:
- Die Parameter für die Verbindung zur ODBC-Datenquelle sind definiert, aber die Verbindung ist nicht geöffnet
' base connection chain
Dim connectString As String = "DSN=" + DSNimpots + ";"
' a database connection object is created - this connection is not open
Dim impotsConn As OdbcConnection = New OdbcConnection(connectString)
- Definieren Sie drei [ArrayList]-Objekte, um Daten aus der Tabelle [impots] abzurufen:
' tables to retrieve data
Dim aLimites As New ArrayList
Dim aCoeffR As New ArrayList
Dim aCoeffN As New ArrayList
- Der gesamte Code für den Datenbankzugriff ist in einem try/catch-Block eingeschlossen, um eventuelle Zugriffsfehler zu behandeln. Wir öffnen die Verbindung zur Datenbank:
' on tente d'accéder à la base de données
impotsConn = New OdbcConnection(connectString)
impotsConn.Open()
- Wir führen den Befehl [select] auf der geöffneten Verbindung aus. Wir erhalten ein [OdbcDataReader]-Objekt, mit dem wir die Zeilen der durch den Befehl select erzeugten Tabelle durchlaufen können:
' on crée un objet command
Dim sqlCommand As OdbcCommand = New OdbcCommand(selectCommand, impotsConn)
' on exécute la requête
Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader()
- Wir durchlaufen die Ergebnistabelle Zeile für Zeile. Dazu verwenden wir die [Read]-Methode des zuvor erhaltenen [OdbcDataReader]-Objekts. Diese Methode bewirkt zwei Dinge:
- Sie springt eine Zeile in der Tabelle vor. Zu Beginn befindet sich der Cursor vor der ersten Zeile
- Sie gibt den booleschen Wert [true] zurück, wenn sie vorrücken konnte, andernfalls [false], wobei der letztere Fall anzeigt, dass alle Zeilen verarbeitet wurden.
Die Spalten der aktuellen Zeile des [OdbcDataReader]-Objekts werden über OdbcDataReader abgerufen. Dies gibt ein Objekt zurück, das den Wert der Spalte darstellt. Wir durchlaufen die gesamte Tabelle, um ihren Inhalt in die drei [ArrayList]-Objekte zu übertragen:
' Exploitation de la table récupérée
While myReader.Read()
' les données de la ligne courante sont mis dans les tableaux
aLimites.Add(myReader("limites"))
aCoeffR.Add(myReader("coeffr"))
aCoeffN.Add(myReader("coeffn"))
- Sobald dies erledigt ist, geben wir die mit der Verbindung verbundenen Ressourcen frei:
- Der Inhalt der drei [ArrayList]-Objekte wird in drei Standard-Arrays übertragen:
' dynamic tables are placed in static tables
limites = New Decimal(aLimites.Count - 1) {}
coeffr = New Decimal(aLimites.Count - 1) {}
coeffn = New Decimal(aLimites.Count - 1) {}
Dim i As Integer
For i = 0 To aLimites.Count - 1
limites(i) = CType(aLimites(i), Decimal)
coeffR(i) = CType(aCoeffR(i), Decimal)
coeffN(i) = CType(aCoeffN(i), Decimal)
Next i
- Sobald die Daten aus der Tabelle [impots] in die drei Arrays geladen wurden, muss nur noch deren Inhalt mithilfe der Methode [checkData] der Basisklasse [impotsData] überprüft werden:
' verify acquired data
Dim erreur As Integer = checkData()
' if invalid data, throws an exception
If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
' otherwise we return the three tables
Return New Object() {limites, coeffr, coeffn}
6.2.4. Tests für die Datenzugriffsklasse
Ein Testprogramm könnte wie folgt aussehen:
Option Explicit On
Option Strict On
' namespaces
Imports System
Imports Microsoft.VisualBasic
Namespace st.istia.univangers.fr
' test pg
Module testimpots
Sub Main(ByVal arguments() As String)
' interactive tax calculator
' the user enters three data points on the keyboard: married nbEnfants salary
' the program then displays the tax payable
Const syntaxe1 As String = "pg DSNimpots"
Const syntaxe2 As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"
' checking program parameters
If arguments.Length <> 1 Then
' error msg
Console.Error.WriteLine(syntaxe1)
' end
Environment.Exit(1)
End If
' retrieve the arguments
Dim DSNimpots As String = arguments(0)
' tax object creation
Dim objImpot As impot = Nothing
Try
objImpot = New impot(New impotsODBC(DSNimpots))
Catch ex As Exception
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
Environment.Exit(2)
End Try
' infinite loop
While True
' initially no errors
Dim erreur As Boolean = False
' tax calculation parameters are requested
Console.Out.Write("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :")
Dim paramètres As String = Console.In.ReadLine().Trim()
' anything to do?
If paramètres Is Nothing Or paramètres = "" Then
Exit While
End If
' check the number of arguments in the input line
Dim args As String() = paramètres.Split(Nothing)
Dim nbParamètres As Integer = args.Length
If nbParamètres <> 3 Then
Console.Error.WriteLine(syntaxe2)
erreur = True
End If
Dim marié As String
Dim nbEnfants As Integer
Dim salaire As Integer
If Not erreur Then
' checking the validity of parameters
' married
marié = args(0).ToLower()
If marié <> "o" And marié <> "n" Then
Console.Error.WriteLine((syntaxe2 + ControlChars.Lf + "Argument marié incorrect : tapez o ou n"))
erreur = True
End If
' nbEnfants
nbEnfants = 0
Try
nbEnfants = Integer.Parse(args(1))
If nbEnfants < 0 Then
Throw New Exception
End If
Catch
Console.Error.WriteLine(syntaxe2 + "\nArgument nbEnfants incorrect : tapez un entier positif ou nul")
erreur = True
End Try
' salary
salaire = 0
Try
salaire = Integer.Parse(args(2))
If salaire < 0 Then
Throw New Exception
End If
Catch
Console.Error.WriteLine(syntaxe2 + "\nArgument salaire incorrect : tapez un entier positif ou nul")
erreur = True
End Try
End If
If Not erreur Then
' parameters are correct - tax is calculated
Console.Out.WriteLine(("impôt=" & objImpot.calculer(marié = "o", nbEnfants, salaire).ToString + " euro(s)"))
End If
End While
End Sub
End Module
End Namespace
Die Anwendung wird mit einem Parameter gestartet:
- DSNimpots: Name der zu verwendenden ODBC-Datenquelle
Die Steuerberechnung erfolgt mithilfe eines Objekts vom Typ [tax], das beim Start der Anwendung erstellt wird:
' tax object creation
Dim objImpôt As impot = Nothing
Try
objImpot = New impot(New impotsODBC(DSNimpots))
Catch ex As Exception
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
Environment.Exit(1)
End Try
Nach der Initialisierung fordert die Anwendung den Benutzer wiederholt auf, die drei für die Steuerberechnung erforderlichen Angaben einzugeben:
- Familienstand: o für verheiratet, n für unverheiratet
- die Anzahl der Kinder
- Jahresgehalt
Alle Klassen werden kompiliert:
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
Das Testprogramm wird dann kompiliert:
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
Das Testprogramm wird zunächst mit der MySQL-ODBC-Datenquelle ausgeführt:
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)
Wir wechseln die ODBC-Quelle zu einer Access-Quelle:
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. Ansichten der Webanwendung
Diese sind dieselben wie in der vorherigen Anwendung: formulaire.aspx und erreurs.aspx
6.2.6. Anwendungscontroller [global.asax, main.aspx]
Nur der Controller [global.asax] muss geändert werden. Er ist dafür zuständig, das Objekt [impot] beim Start der Anwendung zu erstellen. Der Konstruktor dieses Objekts hat einen einzigen Parameter: das Objekt [impotsData], das für das Abrufen der Daten zuständig ist. Dieser Parameter ändert sich daher für jeden neuen Typ von Datenquelle. Der Controller [global.asax.vb] sieht nun wie folgt aus:
Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr
Imports System.Configuration
Public Class Global
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' create an impot object
Dim objImpot As impot
Try
objImpot = New impot(New impotsODBC(ConfigurationSettings.AppSettings("DSNimpots")))
' put the object in the application
Application("objImpot") = objImpot
' no error
Application("erreur") = False
Catch ex As Exception
'there has been an error, we note it in the application
Application("erreur") = True
Application("message") = ex.Message
End Try
End Sub
End Class
Die Datenquelle für das [import]-Objekt ist nun ein [importODBC]-Objekt. Dieses Objekt nimmt den DSN-Namen der zu verwendenden ODBC-Datenquelle als Parameter entgegen. Anstatt diesen Namen fest im Code zu hinterlegen, speichern wir ihn in der Konfigurationsdatei [web.config] der Anwendung:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="DSNimpots" value="odbc-mysql-dbimpots" />
</appSettings>
</configuration>
Wir wissen, dass der Wert eines Schlüssels C im Abschnitt <appSettings> der Datei [web.config] im Anwendungscode mit [ConfigurationSettings.AppSettings(C)] abgerufen wird.
Um die Ursache der Ausnahme zu ermitteln, protokollieren wir die Ausnahmemeldung in der Anwendung, damit sie für Abfragen verfügbar bleibt. Das Steuerelement [main.aspx.vb] nimmt diese Meldung in seine Fehlerliste auf:
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' first of all, we check whether the application has initialized correctly
If CType(Application("erreur"), Boolean) Then
' redirects to error page
Dim erreurs As New ArrayList
erreurs.Add("Application momentanément indisponible...(" + Application("message").ToString + ")")
context.Items("erreurs") = erreurs
context.Items("lien") = ""
context.Items("href") = ""
Server.Transfer("erreurs.aspx")
End If
' retrieve the action to be performed
...
6.2.7. Zusammenfassung der Änderungen
Die Anwendung ist bereit für den Test. Hier sind die Änderungen gegenüber der vorherigen Version:
- Es wurde eine neue Datenzugriffsklasse erstellt
- Der Controller [global.asax.vb] wurde an zwei Stellen geändert: die Konstruktion des [import]-Objekts und die Protokollierung der Meldung zu Ausnahmen in der Anwendung
- Der Controller [main.aspx.vb] wurde an einer Stelle geändert, um die vorherige Ausnahmemeldung anzuzeigen
- Eine [web.config]-Datei wurde hinzugefügt
Die Änderungsarbeiten wurden hauptsächlich in 1 durchgeführt, d. h. außerhalb der Webanwendung. Dies wurde durch die MVC-Architektur der Anwendung ermöglicht, die den Controller von den Geschäftsklassen trennt. Das ist der Sinn dieser Architektur. Es konnte gezeigt werden, dass mit einer entsprechenden [web.config]-Datei jede Änderung am Anwendungscontroller hätte vermieden werden können. In [web.config] ist es möglich, den Namen der dynamisch zu instanziierenden Datenzugriffsklasse – wie z. B. – sowie die verschiedenen für diese Instanziierung erforderlichen Parameter anzugeben. Mit diesen Informationen kann [global.asax] das Datenzugriffsobjekt instanziieren. Das Ändern der Datenquelle läuft dann darauf hinaus,
- Erstellen der Datenzugriffsklasse für diese Quelle, falls sie noch nicht existiert
- die Datei [web.config] so anzupassen, dass die dynamische Erstellung einer Instanz dieser Klasse in [global.asax] möglich ist
6.2.8. Testen der Webanwendung
Alle oben genannten Dateien werden in einem Ordner namens <application-path> abgelegt.

In diesem Ordner wird ein Unterordner [bin] erstellt, in dem die Assembly [impot.dll] – die durch die Kompilierung der Business-Class-Dateien [impots.vb, impotsData.vb, impotsArray.vb, impotsODBC.vb] generiert wurde – abgelegt wird. Der erforderliche Kompilierungsbefehl lautet wie folgt:
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
Die oben genannte Datei [impot.dll] muss im Verzeichnis <Anwendungspfad>\bin abgelegt werden, damit die Webanwendung darauf zugreifen kann. Der Cassini-Server wird mit den Parametern (<Anwendungspfad>,/impots2) gestartet. Die Tests liefern dieselben Ergebnisse wie in der vorherigen Version, da das Vorhandensein der Datenbank für den Benutzer transparent ist. Um dieses Vorhandensein zu demonstrieren, stellen wir jedoch sicher, dass die ODBC-Quelle nicht verfügbar ist, indem wir das MySQL-DBMS anhalten und die URL [http://localhost/impots2/main.aspx] aufrufen. Wir erhalten die folgende Antwort:

6.3. Beispiel 3
6.3.1. Das Problem
Hier schlagen wir vor, dasselbe Problem anzugehen, indem wir erneut die Datenquelle des von der Webanwendung erstellten [impot]-Objekts ändern. Diesmal wird die neue Datenquelle eine ACCESS-Datenbank sein, auf die über einen OLEDB-Treiber zugegriffen wird. Unser Ziel ist es, eine weitere Möglichkeit für den Zugriff auf eine Datenbank aufzuzeigen.
6.3.2. Die OLEDB-Datenquelle
Die Daten befinden sich in einer Tabelle namens [IMPOTS] in einer ACCESS-Datenbank. Der Inhalt dieser Tabelle sieht wie folgt aus:

6.3.3. Die Datenzugriffsklasse
Werfen wir noch einmal einen Blick auf die MVC-Struktur unserer Anwendung:

- In der obigen Abbildung ist die Klasse [impotsData] für das Abrufen der Daten zuständig. Diesmal muss sie dies aus einer OLEDB-Quelle tun.
Wir erstellen die Klasse [impotsOLEDB], die die Daten (limits, coeffr, coeffn) aus einer ODBC-Quelle abruft, die wir wie folgt benennen:
Imports System.Data
Imports System.Collections
Imports System
Imports System.Xml
Imports System.Data.OleDb
Namespace st.istia.univangers.fr
Public Class impotsOLEDB
Inherits impotsData
' instance variables
Protected chaineConnexion As String
' manufacturer
Public Sub New(ByVal chaineConnexion As String)
' we note the three pieces of information
Me.chaineConnexion = chaineConnexion
End Sub
Public Overrides Function getData() As Object()
' initializes the three limit tables, coeffr, coeffn from
' the contents of the [impots] table in the OLEDB [chaineConnexion] database
' limits, coeffr, coeffn are the three columns of this table
' can launch various exceptions
' create a DataAdapter object to read data from source OLEDB
Dim adaptateur As New OleDbDataAdapter("select limites,coeffr,coeffn from impots", chaineConnexion)
' create a memory image of the select result
Dim contenu As New DataTable("impots")
Try
adaptateur.Fill(contenu)
Catch e As Exception
Throw New Exception("Erreur d'accès à la base de données (" + e.Message + ")")
End Try
' retrieve the contents of the impots table
Dim lignesImpots As DataRowCollection = contenu.Rows
' dimensioning of reception panels
Me.limites = New Decimal(lignesImpots.Count - 1) {}
Me.coeffr = New Decimal(lignesImpots.Count - 1) {}
Me.coeffn = New Decimal(lignesImpots.Count - 1) {}
' transfer the contents of the impots table to the tables
Dim i As Integer
Dim ligne As DataRow
Try
For i = 0 To lignesImpots.Count - 1
' table line i
ligne = lignesImpots.Item(i)
' retrieve the contents of the line
limites(i) = CType(ligne.Item(0), Decimal)
coeffr(i) = CType(ligne.Item(1), Decimal)
coeffn(i) = CType(ligne.Item(2), Decimal)
Next
Catch
Throw New Exception("Les données des tranches d'impôts n'ont pas le bon type")
End Try
' verify acquired data
Dim erreur As Integer = checkData()
' if invalid data, throws an exception
If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
' otherwise we return the three tables
Return New Object() {limites, coeffr, coeffn}
End Function
End Class
End Namespace
Werfen wir einen Blick auf den Konstruktor:
' manufacturer
Public Sub New(ByVal chaineConnexion As String)
' we note the three pieces of information
Me.chaineConnexion = chaineConnexion
End Sub
Er erhält als Parameter die Verbindungszeichenfolge für die OLEDB-Quelle, die die abzurufenden Daten enthält. Der Konstruktor speichert diese lediglich. Eine Verbindungszeichenfolge enthält alle Parameter, die der OLEDB-Treiber benötigt, um eine Verbindung zur OLEDB-Quelle herzustellen. Sie ist in der Regel recht komplex. Um die Verbindungszeichenfolge für ACCESS-Datenbanken zu ermitteln, können Sie das Tool [WebMatrix] verwenden. Starten Sie dieses Tool. Es bietet ein Fenster zum Herstellen einer Verbindung zu einer Datenquelle:
![]() ![]() | ![]() ![]() |
Über das oben mit dem Pfeil markierte Symbol können Sie eine Verbindung zu zwei Arten von Microsoft-Datenbanken herstellen: SQL Server und ACCESS. Wählen wir ACCESS:

Wir haben die Schaltfläche [...] verwendet, um die ACCESS-Datenbank auszuwählen. Wir bestätigen den Assistenten. Auf der Registerkarte [Daten] stellen Symbole die Verbindung dar:

Erstellen wir nun über [Dateien/Neue Datei] eine neue .aspx-Datei:

Wir erhalten eine leere Seite, auf der wir unsere Weboberfläche gestalten können:

Ziehen wir die Tabelle [impots] aus der Registerkarte [Daten] auf das Blatt oben. Wir erhalten folgendes Ergebnis:

Klicken Sie mit der rechten Maustaste auf das [AccessDataSourceControl]-Objekt unten, um dessen Eigenschaften aufzurufen:

Die OLEDB-Verbindungszeichenfolge zur ACCESS-Datenbank wird durch die Eigenschaft [ConnectionString] oben bereitgestellt:
Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=D:\data\serge\devel\aspnet\poly\chap5\impots\3\impots.mdb
Wir sehen, dass diese Zeichenfolge aus einem festen Teil und einem variablen Teil besteht, bei dem es sich einfach um den Namen der ACCESS-Datei handelt. Wir werden diese Tatsache nutzen, um die Verbindungszeichenfolge zu unserer OLEDB-Datenquelle zu generieren.
Kehren wir nun zu unserer [impotsOLEDB]-Klasse zurück. Die [getData]-Methode ist dafür zuständig, Daten aus der [impots]-Tabelle zu lesen und in drei Arrays (limites, coeffr, coeffn) zu speichern. Sehen wir uns den Code an:
- Wir definieren das [DataAdapter]-Objekt, mit dem wir das Ergebnis einer SQL-SELECT-Abfrage in den Speicher übertragen können. Dazu definieren wir die auszuführende [select]-Abfrage und verknüpfen sie mit dem [DataAdapter]-Objekt. Der Konstruktor des [DataAdapter] benötigt außerdem die Verbindungszeichenfolge, über die er eine Verbindung zur OLEDB-Quelle herstellt
' on crée un objet DataAdapter pour lire les données de la source OLEDB
Dim adaptateur As New OleDbDataAdapter("select limites,coeffr,coeffn from impots", chaineConnexion)
- Wir führen den [select]-Befehl mithilfe der [Fill]-Methode des [DataAdapter]-Objekts aus. Das Ergebnis des [select]-Befehls wird in ein zu diesem Zweck erstelltes [DataTable]-Objekt geladen. Ein [DataTable]-Objekt ist die In-Memory-Darstellung einer Datenbanktabelle, d. h. eine Sammlung von Zeilen und Spalten. Wir behandeln eine Ausnahme, die auftreten kann, wenn beispielsweise die Verbindungszeichenfolge falsch ist.
' on crée une image en mémoire du résultat du select
Dim contenu As New DataTable("impots")
Try
adaptateur.Fill(contenu)
Catch e As Exception
Throw New Exception("Erreur d'accès à la base de données (" + e.Message + ")")
End Try
- In [content] haben wir die Tabelle [taxes], die von der [select]-Anweisung zurückgegeben wird. Ein [DataTable]-Objekt ist eine Tabelle, d. h. eine Reihe von Zeilen. Auf diese kann über die [rows]-Eigenschaft von [DataTable] zugegriffen werden:
' retrieve the contents of the impots table
Dim lignesImpots As DataRowCollection = contenu.Rows
- Jedes Element der [taxRows]-Sammlung ist ein [DataRow]-Objekt, das eine Zeile in der Tabelle darstellt. Diese Zeile verfügt über Spalten, auf die über das [DataRow]-Objekt mittels seiner [Item]-Eigenschaft zugegriffen werden kann. [DataRow].[Item(i)] ist die Spalte Nummer i des [DataRow]. Durch Durchlaufen der Sammlung von Zeilen (die DataRows-Sammlung von taxRows) und der Sammlung von Spalten für jede Zeile können wir die gesamte Tabelle abrufen:
' dimensioning of reception panels
Me.limites = New Decimal(lignesImpots.Count - 1) {}
Me.coeffr = New Decimal(lignesImpots.Count - 1) {}
Me.coeffn = New Decimal(lignesImpots.Count - 1) {}
' transfer the contents of the impots table to the tables
Dim i As Integer
Dim ligne As DataRow
Try
For i = 0 To lignesImpots.Count - 1
' table line i
ligne = lignesImpots.Item(i)
' retrieve the contents of the line
limites(i) = CType(ligne.Item(0), Decimal)
coeffr(i) = CType(ligne.Item(1), Decimal)
coeffn(i) = CType(ligne.Item(2), Decimal)
Next
Catch
Throw New Exception("Les données des tranches d'impôts n'ont pas le bon type")
End Try
- Sobald die Daten aus der Tabelle [taxes] in die drei Arrays geladen wurden, muss nur noch deren Inhalt mithilfe der Methode [checkData] der Basisklasse [taxData] überprüft werden:
' verify acquired data
Dim erreur As Integer = checkData()
' if invalid data, throws an exception
If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
' otherwise we return the three tables
Return New Object() {limites, coeffr, coeffn}
6.3.4. Tests für die Datenzugriffsklasse
Ein Testprogramm könnte wie folgt aussehen:
Option Explicit On
Option Strict On
' namespaces
Imports System
Imports Microsoft.VisualBasic
Namespace st.istia.univangers.fr
' test pg
Module testimpots
Sub Main(ByVal arguments() As String)
' interactive tax calculator
' the user enters three data points on the keyboard: married nbEnfants salary
' the program then displays the tax payable
Const syntaxe1 As String = "pg bdACCESS"
Const syntaxe2 As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"
' checking program parameters
If arguments.Length <> 1 Then
' error msg
Console.Error.WriteLine(syntaxe1)
' end
Environment.Exit(1)
End If
' retrieve the arguments
Dim chemin As String = arguments(0)
' prepare the connection chain
Dim chaineConnexion As String = "Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=" + chemin
' tax object creation
Dim objImpot As impot = Nothing
Try
objImpot = New impot(New impotsOLEDB(chaineConnexion))
Catch ex As Exception
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
Environment.Exit(2)
End Try
' infinite loop
While True
' initially no errors
Dim erreur As Boolean = False
' tax calculation parameters are requested
Console.Out.Write("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :")
Dim paramètres As String = Console.In.ReadLine().Trim()
' anything to do?
If paramètres Is Nothing Or paramètres = "" Then
Exit While
End If
' check the number of arguments in the input line
Dim args As String() = paramètres.Split(Nothing)
Dim nbParamètres As Integer = args.Length
If nbParamètres <> 3 Then
Console.Error.WriteLine(syntaxe2)
erreur = True
End If
Dim marié As String
Dim nbEnfants As Integer
Dim salaire As Integer
If Not erreur Then
' checking the validity of parameters
' married
marié = args(0).ToLower()
If marié <> "o" And marié <> "n" Then
Console.Error.WriteLine((syntaxe2 + ControlChars.Lf + "Argument marié incorrect : tapez o ou n"))
erreur = True
End If
' nbEnfants
nbEnfants = 0
Try
nbEnfants = Integer.Parse(args(1))
If nbEnfants < 0 Then
Throw New Exception
End If
Catch
Console.Error.WriteLine(syntaxe2 + "\nArgument nbEnfants incorrect : tapez un entier positif ou nul")
erreur = True
End Try
' salary
salaire = 0
Try
salaire = Integer.Parse(args(2))
If salaire < 0 Then
Throw New Exception
End If
Catch
Console.Error.WriteLine(syntaxe2 + "\nArgument salaire incorrect : tapez un entier positif ou nul")
erreur = True
End Try
End If
If Not erreur Then
' parameters are correct - tax is calculated
Console.Out.WriteLine(("impôt=" & objImpot.calculer(marié = "o", nbEnfants, salaire).ToString + " euro(s)"))
End If
End While
End Sub
End Module
End Namespace
Die Anwendung wird mit einem Parameter gestartet:
- bdACCESS: Name der zu verwendenden ACCESS-Datei
Die Steuerberechnung erfolgt mithilfe eines Objekts vom Typ [tax], das beim Start der Anwendung erstellt wird:
' retrieve the arguments
Dim chemin As String = arguments(0)
' prepare the connection chain
Dim chaineConnexion As String = "Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=" + chemin
' tax object creation
Dim objImpot As impot = Nothing
Try
objImpot = New impot(New impotsOLEDB(chaineConnexion))
Catch ex As Exception
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
Environment.Exit(2)
End Try
Die Verbindungszeichenfolge zur OLEDB-Quelle wurde unter Verwendung von Informationen erstellt, die mit [WebMatrix] abgerufen wurden.
Nach der Initialisierung fordert die Anwendung den Benutzer wiederholt auf, die drei für die Berechnung seiner Steuer erforderlichen Angaben einzugeben:
- Familienstand: o für verheiratet, n für unverheiratet
- Anzahl der Kinder
- Jahresgehalt
Alle Klassen werden kompiliert:
dos>vbc /r:system.dll /r:system.data.dll /t:library /out:impot.dll impots.vb impotsArray.vb impotsData.vb impotsOLEDB.vb
Die Datei [impots.mdb] wird im Ordner der Testanwendung abgelegt, und die Anwendung wird wie folgt gestartet:
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)
Sie können die Anwendung mit einer fehlerhaften ACCESS-Datei ausführen:
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. Die Ansichten der Webanwendung
Diese sind dieselben wie in der vorherigen Anwendung: formulaire.aspx und erreurs.aspx
6.3.6. Anwendungscontroller [global.asax, main.aspx]
Nur der Controller [global.asax] muss geändert werden. Er ist dafür zuständig, das Objekt [impot] beim Start der Anwendung zu erstellen. Der Konstruktor dieses Objekts hat einen einzigen Parameter: das Objekt [impotsData], das für das Abrufen der Daten zuständig ist. Dieser Parameter ändert sich daher, da wir die Datenquelle wechseln. Der Controller [global.asax.vb] sieht nun wie folgt aus:
Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr
Imports System.Configuration
Public Class Global
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' create an impot object
Dim objImpot As impot
Try
objImpot = New impot(New impotsOLEDB(ConfigurationSettings.AppSettings("chaineConnexion")))
' put the object in the application
Application("objImpot") = objImpot
' no error
Application("erreur") = False
Catch ex As Exception
'there has been an error, we note it in the application
Application("erreur") = True
Application("message") = ex.Message
End Try
End Sub
End Class
Die Datenquelle für das [import]-Objekt ist nun ein [importOLEDB]-Objekt. Dieses Objekt nimmt als Parameter die Verbindungszeichenfolge für die zu verwendende OLEDB-Datenquelle entgegen. Diese Zeichenfolge ist in der Konfigurationsdatei [web.config] der Anwendung gespeichert:
<?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>
Der Controller [main.aspx] bleibt unverändert.
6.3.7. Zusammenfassung der Änderungen
Die Anwendung ist bereit für den Test. Hier sind die Änderungen gegenüber der vorherigen Version:
- Es wurde eine neue Datenzugriffsklasse erstellt
- Der Controller [global.asax.vb] wurde an einer Stelle geändert: Erstellung des Objekts [import]
- Eine [web.config]-Datei wurde hinzugefügt
6.3.8. Testen der Webanwendung
Alle oben genannten Dateien befinden sich in einem Ordner namens <application-path>.

In diesem Ordner wird ein Unterordner [bin] erstellt, in dem die Assembly [impot.dll] – die durch die Kompilierung der Business-Class-Dateien [impots.vb, impotsData.vb, impotsArray.vb, impotsOLEDB.vb] generiert wurde – abgelegt wird. Der erforderliche Kompilierungsbefehl lautet wie folgt:
dos>vbc /r:system.dll /r:system.data.dll /t:library /out:impot.dll impots.vb impotsArray.vb impotsData.vb impotsOLEDB.vb
Die durch diesen Befehl erzeugte Datei [impot.dll] muss im Verzeichnis <application-path>\bin abgelegt werden, damit die Webanwendung darauf zugreifen kann. Der Cassini-Server wird mit den Parametern (<application-path>,/impots3) gestartet. Die Tests liefern die gleichen Ergebnisse wie in der vorherigen Version.
6.4. Beispiel 4
6.4.1. Das Problem
Wir schlagen nun vor, unsere Anwendung in eine Anwendung zur Simulation von Steuerberechnungen umzuwandeln. Ein Benutzer kann aufeinanderfolgende Steuerberechnungen durchführen, die ihm in einer neuen Ansicht wie dieser angezeigt werden:

6.4.2. Die MVC-Struktur der Anwendung
Die MVC-Struktur der Anwendung sieht nun wie folgt aus:

Es erscheint eine neue Ansicht [simulations.aspx], von der wir gerade einen Screenshot bereitgestellt haben. Die Datenzugriffsklasse ist die Klasse [impotsODBC] aus Beispiel 2.
6.4.3. Die Ansichten der Webanwendung
Die Ansicht [erreurs.aspx] bleibt unverändert. Die Ansicht [formulaire.aspx] ändert sich geringfügig. Tatsächlich erscheint der Steuerbetrag nicht mehr in dieser Ansicht. Er befindet sich nun in der Ansicht [simulations.aspx]. Somit wird dem Benutzer beim Start folgende Seite angezeigt:

Zusätzlich enthält die Ansicht [form] ein JavaScript-Skript, das die eingegebenen Daten validiert, bevor sie an den Server gesendet werden, wie im folgenden Beispiel gezeigt:

Der Präsentationscode lautet wie folgt:
<%@ page src="formulaire.aspx.vb" inherits="formulaire" AutoEventWireup="false"%>
<html>
<head>
<title>Impôt</title>
<script language="javascript">
function calculer(){
// vérification des paramètres avant de les envoyer au serveur
with(document.frmImpots){
//nbre d'enfants
champs=/^\s*(\d+)\s*$/.exec(txtEnfants.value);
if(champs==null){
// le modéle n'est pas vérifié
alert("Le nombre d'enfants n'a pas été donné ou est incorrect");
txtEnfants.focus();
return;
}//if
//salaire
champs=/^\s*(\d+)\s*$/.exec(txtSalaire.value);
if(champs==null){
// le modéle n'est pas vérifié
alert("Le salaire n'a pas été donné ou est incorrect");
txtSalaire.focus();
return;
}//if
// c'est bon - on envoie le formulaire au serveur
submit();
}//with
}//calculer
</script>
</head>
<body>
<P>Calcul de votre impôt</P>
<HR width="100%" SIZE="1">
<form name="frmImpots" method="post" action="main.aspx?action=calcul">
<TABLE border="0">
<TR>
<TD>Etes-vous marié(e)</TD>
<TD>
<INPUT type="radio" value="oui" name="rdMarie" <%=rdouichecked%>>Oui
<INPUT type="radio" value="non" name="rdMarie" <%=rdnonchecked%>>Non</TD>
</TR>
<TR>
<TD>Nombre d'enfants</TD>
<TD><INPUT type="text" size="3" maxLength="3" name="txtEnfants" value="<%=txtEnfants%>"></TD>
</TR>
<TR>
<TD>Salaire annuel (euro)</TD>
<TD><INPUT type="text" maxLength="12" size="12" name="txtSalaire" value="<%=txtSalaire%>"></TD>
</TR>
</TABLE>
<hr>
<P>
<INPUT type="button" value="Calculer" onclick="calculer()">
</P>
</form>
<form method="post" action="main.aspx?action=effacer">
<INPUT type="submit" value="Effacer">
</form>
</body>
</html>
Die dynamischen Felder auf der Seite sind dieselben wie in früheren Versionen. Das dynamische Feld für den Steuerbetrag wurde entfernt. Die Schaltfläche [Berechnen] ist keine [Submit]-Schaltfläche mehr. Es handelt sich um eine [Schaltfläche], und wenn sie angeklickt wird, wird die JavaScript-Funktion [calculate()] ausgeführt:
<INPUT type="button" value="Calculer" onclick="calculer()">
Wir haben dem Formular den Namen [frmImpots] gegeben, damit wir im Skript [calculer] darauf verweisen können:
<form name="frmImpots" method="post" action="main.aspx?action=calcul">
Die JavaScript-Funktion [calculate] verwendet reguläre Ausdrücke, um die Felder in den Formularen [document.frmImpots.txtEnfants] und [document.frmImpots.txtSalaire] zu validieren. Sind die eingegebenen Werte korrekt, werden sie über [document.frmImpots.submit()] an den Server gesendet.
Die Präsentationsseite bezieht ihre dynamischen Felder aus dem folgenden Controller [formulaire.aspx.vb]:
Imports System.Collections.Specialized
Public Class formulaire
Inherits System.Web.UI.Page
' page fields
Protected rdouichecked As String
Protected rdnonchecked As String
Protected txtEnfants As String
Protected txtSalaire As String
Protected txtImpot As String
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' we retrieve the previous request in the
Dim form As NameValueCollection = Context.Items("formulaire")
' prepare the page to be displayed
' radio buttons
rdouichecked = ""
rdnonchecked = "checked"
If form("rdMarie").ToString = "oui" Then
rdouichecked = "checked"
rdnonchecked = ""
End If
' the rest
txtEnfants = CType(form("txtEnfants"), String)
txtSalaire = CType(form("txtSalaire"), String)
End Sub
End Class
Der Controller [formulaire.aspx.vb] ist identisch mit früheren Versionen, außer dass er das Feld [txtImpot] nicht mehr aus dem Kontext abrufen muss, da dieses Feld von der Seite entfernt wurde.
Die Ansicht [simulations.aspx] sieht wie folgt aus:

und entspricht dem folgenden Präsentationscode:
<%@ 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>
Dieser Code enthält drei dynamische Felder:
HTML-Code für eine Liste von Simulationen in Form von HTML-Tabellenzeilen | |
URL eines Links | |
Linktext |
Sie werden vom Controller [simulations.aspx.vb] generiert:
Imports System.Collections
Imports Microsoft.VisualBasic
Public Class simulations
Inherits System.Web.UI.Page
Protected simulationsHTML As String = ""
Protected href As String
Protected lien As String
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
'simulations are retrieved from the context
Dim simulations As ArrayList = CType(context.Items("simulations"), ArrayList)
' each simulation is an array of 4 string elements
Dim simulation() As String
Dim i, j As Integer
For i = 0 To simulations.Count - 1
simulation = CType(simulations(i), String())
simulationsHTML += "<tr>"
For j = 0 To simulation.Length - 1
simulationsHTML += "<td>" + simulation(j) + "</td>"
Next
simulationsHTML += "</tr>" + ControlChars.CrLf
Next
' recover the other elements of the context
href = context.Items("href").ToString
lien = context.Items("lien").ToString
End Sub
End Class
Der Seiten-Controller ruft Informationen ab, die vom Anwendungs-Controller im Seitenkontext abgelegt wurden:
ArrayList-Objekt, das die Liste der anzuzeigenden Simulationen enthält. Jedes Element ist ein Array aus 4 Zeichenfolgen, die die Informationen der Simulation darstellen (verheiratet, Kinder, Gehalt, Steuern). | |
URL eines Links | |
Linktext |
6.4.4. Die Controller [global.asax, main.aspx]
Werfen wir einen Blick auf die MVC-Architektur unserer Anwendung:

Der Controller [main.aspx] muss drei Aktionen verarbeiten:
- init: entspricht der ersten Anfrage des Clients. Der Controller zeigt die Ansicht [form.aspx] an
- calcul: entspricht der Anfrage zur Steuerberechnung. Sind die Daten im Eingabeformular korrekt, wird die Steuer mithilfe der Business-Klasse [impotsODBC] berechnet. Der Controller gibt die Ansicht [simulations.aspx] mit dem Ergebnis der aktuellen Simulation sowie allen vorherigen Simulationen an den Client zurück. Sind die Daten im Eingabeformular fehlerhaft, gibt der Controller die Ansicht [erreurs.aspx] mit der Fehlerliste und einem Link zur Rückkehr zum Formular zurück.
- return: entspricht der Rückkehr zum Formular nach einem Fehler. Der Controller zeigt die Ansicht [form.aspx] so an, wie sie vor dem Fehler validiert wurde.
In dieser neuen Version hat sich nur die Aktion [calcul] geändert. Wenn die Daten gültig sind, muss sie nun zur Ansicht [simulations.aspx] führen, während sie zuvor zur Ansicht [form.aspx] führte. Der Controller [main.aspx.vb] sieht nun wie folgt aus:
Imports System
...
Public Class main
Inherits System.Web.UI.Page
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' first of all, we check whether the application has initialized correctly
...
' execute the action
Select Case action
Case "init"
' init application
initAppli()
Case "calcul"
' tax calculation
calculImpot()
Case "retour"
' back to form
retourFormulaire()
Case "effacer"
' init application
initAppli()
Case Else
' unknown action = init
initAppli()
End Select
End Sub
...
Private Sub calculImpot()
' save entries
Session.Item("formulaire") = Request.Form
' check the validity of the data entered
Dim erreurs As ArrayList = checkData()
' if there are errors, we report them
If erreurs.Count <> 0 Then
' prepare the error page
context.Items("href") = "main.aspx?action=retour"
context.Items("lien") = "Retour au formulaire"
context.Items("erreurs") = erreurs
Server.Transfer("erreurs.aspx")
End If
' no errors here - the tax is calculated
Dim impot As Long = CType(Application("objImpot"), impot).calculer( _
Request.Form("rdMarie") = "oui", _
CType(Request.Form("txtEnfants"), Integer), _
CType(Request.Form("txtSalaire"), Long))
' the result is added to existing simulations
Dim simulations As ArrayList
If Not Session.Item("simulations") Is Nothing Then
simulations = CType(Session.Item("simulations"), ArrayList)
Else
simulations = New ArrayList
End If
' add current simulation
Dim simulation() As String = New String() {Request.Form("rdMarie").ToString, _
Request.Form("txtEnfants").ToString, Request.Form("txtSalaire").ToString, _
impot.ToString}
simulations.Add(simulation)
' put simulations in session and context
context.Items("simulations") = simulations
Session.Item("simulations") = simulations
' the result page is displayed
context.Items("href") = "main.aspx?action=retour"
context.Items("lien") = "Retour au formulaire"
Server.Transfer("simulations.aspx", True)
End Sub
...
End Class
Wir haben oben nur das Nötigste aufgeführt, um die Änderungen zu verstehen, die ausschließlich in der Funktion [calculImpots] zu finden sind:
- Zunächst speichert die Funktion das Formular [Request.Form] in der Sitzung, damit das Formular in dem Zustand wiederhergestellt werden kann, in dem es validiert wurde. Dies muss in jedem Fall geschehen, da wir unabhängig davon, ob der Vorgang zur Antwort [erreurs.aspx] oder zur Antwort [simulations.aspx] führt, über den Link [Zurück zum Formular] zum Formular zurückkehren. Um das Formular korrekt wiederherzustellen, müssen seine Werte zuvor in der Sitzung gespeichert worden sein.
- Wenn die eingegebenen Daten korrekt sind, fügt die Funktion die aktuelle Simulation (verheiratet, Kinder, Gehalt, Steuern) zur Liste der Simulationen hinzu. Diese Liste befindet sich in der Sitzung, die mit dem Schlüssel „simulations“ verknüpft ist.
- Die Liste der Simulationen wird für die spätere Verwendung in der Sitzung gespeichert. Sie wird außerdem im aktuellen Kontext abgelegt, da die Ansicht [simulations.aspx] erwartet, sie dort zu finden
- Die Ansicht [simulations.aspx] wird angezeigt, sobald die anderen erforderlichen Informationen in den Kontext eingefügt wurden
6.4.5. Zusammenfassung der Änderungen
Die Anwendung ist bereit für Tests. Hier sind die Änderungen gegenüber früheren Versionen:
- Es wurde eine neue Ansicht erstellt
- Der Controller [main.aspx.vb] wurde an einer Stelle geändert: die Behandlung der Aktion [calcul]
6.4.6. Testen der Webanwendung
Der Leser ist eingeladen, die Tests durchzuführen. Hier eine Erinnerung an die Vorgehensweise. Alle Anwendungsdateien befinden sich in einem Ordner namens <application-path>. Innerhalb dieses Ordners wird ein Unterordner [bin] erstellt, der die Assembly [impot.dll] enthält, die aus der Kompilierung der Business-Class-Dateien generiert wurde: [impots.vb, impotsData.vb, impotsArray.vb, impotsODBC.vb]. Die durch diesen Befehl erzeugte Datei [impot.dll] muss in <application-path>\bin abgelegt werden, damit die Webanwendung darauf zugreifen kann. Der Cassini-Server wird mit den Parametern (<application-path>,/impots4) gestartet.
6.5. Fazit
Die vorangegangenen Beispiele haben in einem konkreten Szenario Mechanismen demonstriert, die in der Webentwicklung häufig zum Einsatz kommen. Wir haben aus pädagogischen Gründen durchgehend die MVC-Architektur verwendet. Wir hätten dieselben Beispiele auch anders und vielleicht einfacher ohne diese Architektur umsetzen können. Sie bietet jedoch erhebliche Vorteile, sobald die Anwendung mit mehreren Seiten etwas komplexer wird.
Wir könnten unsere Beispiele auf verschiedene Weise fortsetzen. Hier sind einige davon:
- Der Benutzer möchte möglicherweise seine Simulationen über einen längeren Zeitraum speichern. Er könnte beispielsweise am Tag D Simulationen ausführen und diese am Tag D+3 abrufen. Eine mögliche Lösung für dieses Problem ist die Verwendung von Cookies. Wir wissen, dass das Session-Token zwischen dem Server und einem Client über diesen Mechanismus übertragen wird. Wir könnten diesen Mechanismus auch nutzen, um die Simulationen zwischen dem Client und dem Server zu übertragen.
- Gleichzeitig mit dem Senden der Seite, die die Simulationsergebnisse enthält, sendet der Server in seinen HTTP-Headern ein Cookie, das eine Zeichenkette enthält, die die Simulationen repräsentiert. Da diese in einem [ArrayList]-Objekt vorliegen, muss dieses Objekt in einen [String] konvertiert werden. Der Server würde eine Lebensdauer für das Cookie festlegen, zum Beispiel 30 Tage.
- Der Browser des Clients speichert die empfangenen Cookies in einer Datei und sendet sie bei jeder Anfrage an den Server, der sie gesendet hat, zurück, sofern sie noch gültig sind (Lebensdauer nicht überschritten). Der Server erhält eine [String]-Zeichenkette für die Simulationen, die er in ein [ArrayList]-Objekt konvertieren muss.
Cookies werden von [Response.Cookies] verwaltet, wenn sie an den Client gesendet werden, und von [Request.Cookies], wenn sie auf dem Server empfangen werden.
- Der oben beschriebene Mechanismus kann bei einer großen Anzahl von Simulationen recht ressourcenintensiv werden. Zudem ist es üblich, dass ein Benutzer seine Cookies regelmäßig löscht, selbst wenn er ihrem Browser ansonsten die Verwendung erlaubt. Daher gehen die Simulations-Cookies früher oder später verloren. Daher möchten wir diese möglicherweise eher auf dem Server als auf dem Client speichern, beispielsweise in einer Datenbank. Um Simulationen mit einem bestimmten Benutzer zu verknüpfen, könnte die Anwendung mit einer Authentifizierungsphase beginnen, die einen Benutzernamen und ein Passwort erfordert, die wiederum in einer Datenbank oder einem anderen Datenspeicher abgelegt sind.
- Möglicherweise möchten wir auch den Betrieb unserer Anwendung absichern. Derzeit geht sie von zwei Annahmen aus:
- Der Benutzer geht immer über den Controller [main.aspx]
- und in diesem Fall immer die Aktionen nutzt, die auf der Seite verfügbar sind, die an ihn gesendet wurde
Was passiert beispielsweise, wenn der Benutzer direkt die URL [http://localhost/impots4/formulaire.aspx] anfordert? Dieses Szenario ist unwahrscheinlich, da der Benutzer diese URL nicht kennt. Es muss jedoch berücksichtigt werden. Dies kann vom Anwendungscontroller [global.asax] gehandhabt werden, der alle an die Anwendung gerichteten Anfragen verarbeitet. Er kann somit überprüfen, ob die angeforderte Ressource tatsächlich [main.aspx] ist.
Ein wahrscheinlichereres Szenario ist, dass ein Benutzer die auf der vom Server gesendeten Seite verfügbaren Aktionen nicht nutzt. Was passiert beispielsweise, wenn der Benutzer die URL [http://localhost/impots4/main.aspx?action=retour] direkt aufruft, ohne zuvor das Formular auszufüllen? Probieren wir es aus. Wir erhalten die folgende Antwort:

Der Server stürzt ab. Das ist normal. Für die Aktion [return] erwartet der Controller, in der Sitzung ein [NameValueCollection]-Objekt zu finden, das die Formularwerte enthält, die er anzeigen muss. Er findet sie nicht. Der Controller-Mechanismus bietet eine elegante Lösung für dieses Problem. Bei jeder Anfrage kann der [main.aspx]-Controller überprüfen, ob die angeforderte Aktion tatsächlich eine der Aktionen von der zuvor an den Benutzer gesendeten Seite ist. Wir können den folgenden Mechanismus verwenden:
- Bevor der Controller seine Antwort an den Client sendet, speichert er Informationen, die diese Seite identifizieren, in der Sitzung des Clients
- Wenn er eine neue Anfrage vom Client erhält, überprüft er, ob die angeforderte Aktion tatsächlich zu der letzten Seite gehört, die an diesen Client gesendet wurde
- Die Informationen, die Seiten und die auf diesen Seiten zulässigen Aktionen miteinander verknüpfen, können in die Konfigurationsdatei [web.config] der Anwendung eingetragen werden.
- Die Erfahrung zeigt, dass Anwendungscontroller eine breite gemeinsame Grundlage haben und dass es möglich ist, einen generischen Controller zu erstellen, dessen Spezialisierung für eine bestimmte Anwendung über eine Konfigurationsdatei erfolgt. Dies ist der Ansatz, den beispielsweise das [Struts]-Tool im Bereich der Java-Webprogrammierung verfolgt.



