6. Beispiele
In diesem Kapitel möchten wir das zuvor Besprochene anhand einer Reihe von Beispielen veranschaulichen.
6.1. Beispiel 1
6.1.1. Das Problem
Diese Anwendung soll es einem Benutzer ermöglichen, seine Steuer zu berechnen. Wir betrachten den vereinfachten Fall eines Steuerpflichtigen, der nur sein Gehalt anzugeben hat (Zahlen von 2004 für Einkünfte aus dem Jahr 2003):
- Die Anzahl der Anteile des Arbeitnehmers wird berechnet: nbParts = nbEnfants/2 + 1, wenn er unverheiratet ist, nbEnfants/2 + 2, wenn er verheiratet ist, wobei nbEnfants die Anzahl seiner Kinder ist.
- Hat er mindestens drei Kinder, erhält er einen halben Anteil zusätzlich
- sein steuerpflichtiges Einkommen R wird berechnet als R = 0,72 * S, wobei S sein Jahresgehalt ist
- Man berechnet seinen Familienkoeffizienten QF = R / nbParts
- Man berechnet seine Steuer I. Betrachten wir die folgende Tabelle:
4262 | 0 | 0 |
8382 | 0,0683 | 291,09 |
14753 | 0,1914 | 1322,92 |
23.888 | 0,2826 | 2668,39 |
38.868 | 0,3738 | 4846,98 |
47.932 | 0,4262 | 6883,66 |
0 | 0,4809 | 9505,54 |
Jede Zeile enthält 3 Felder. Um die Steuer I zu berechnen, wird die erste Zeile gesucht, in der QF <= Feld 1 gilt. Wenn beispielsweise QF = 5000 ist, wird die Zeile
Die Steuer I beträgt dann 0,0683*R – 291,09*nbParts. Wenn QF so ist, dass die Beziehung QF <= Feld1 niemals erfüllt ist, werden die Koeffizienten der letzten Zeile verwendet. Hier:
was die Steuer I = 0,4809 * R – 9505,54 * nbParts ergibt.
6.1.2. Die Struktur MVC der Anwendung
Die Struktur MVC der Anwendung sieht wie folgt aus:

Die Rolle des Controllers übernimmt die Seite [main.aspx]. 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 Steuerberechnung. Sind die Daten des Eingabeformulars korrekt, wird die Steuer mithilfe der Geschäftsklasse [impots] berechnet. Der Controller gibt dem Client die Ansicht [formulaire.aspx] zurück, wie sie validiert wurde, zusätzlich mit der berechneten Steuer. Sind die Daten des Eingabeformulars fehlerhaft, gibt der Controller die Ansicht [erreurs.aspx] mit der Fehlerliste und einem Link zur Rückkehr zum Formular zurück.
- Zurück: Bezieht sich auf die Rückkehr zum Formular nach einem Fehler. Der Controller zeigt die Ansicht [formulaire.aspx] so an, wie sie vor dem Fehler validiert wurde.
Der Controller [main.aspx] hat keine Kenntnisse über die Steuerberechnung. Er ist lediglich dafür zuständig, den Client-Server-Dialog zu verwalten und die vom Client angeforderten Aktionen auszuführen. Für die Aktion [calcul] stützt er sich auf die Geschäftsklasse [impot].
6.1.3. Die Fachklasse
Die Steuerklasse wird wie folgt definiert:
' importierte Namensräume
Imports System
' Klasse
Namespace st.istia.univangers.fr
Public Class impot
Private limites(), coeffR(), coeffN() As Decimal
' Konstruktor
Public Sub New(ByRef source As impotsData)
' die für die Steuerberechnung erforderlichen Daten
' stammen aus einer externen Quelle [source]
' sie werden abgerufen – es kann eine Ausnahme geben
Dim data() As Object = source.getData
limites = CType(data(0), Decimal())
coeffR = CType(data(1), Decimal())
coeffN = CType(data(2), Decimal())
End Sub
' Steuerberechnung
Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Long) As Long
' Berechnung der Anzahl der Anteile
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
' Berechnung des zu versteuernden Einkommens und des Familienquotienten
Dim revenu As Decimal = 0.72D * salaire
Dim QF As Decimal = revenu / nbParts
' Steuerberechnung
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 wird erstellt, indem dem Konstruktor eine Datenquelle vom Typ [impotsData] übergeben wird. Diese Klasse verfügt über eine öffentliche Methode [getData], mit der die drei für die Steuerberechnung erforderlichen Datentabellen abgerufen werden können, die zuvor vorgestellt wurden. Diese Methode kann eine Ausnahme auslösen, wenn die Daten nicht abgerufen werden konnten oder sich als fehlerhaft erweisen. Sobald das Objekt [impot] erstellt wurde, kann man wiederholt seine Methode „calculer“ aufrufen, die die Steuer des Steuerpflichtigen anhand seines Familienstands (verheiratet oder nicht), der Anzahl seiner Kinder und seines Jahresgehalts berechnet.
6.1.4. Die Klasse für den Datenzugriff
Die Klasse [impotsData] dient dem Zugriff auf die Daten. Es handelt sich um eine abstrakte Klasse. Für jede neue mögliche Datenquelle (Tabellen, 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
' Methode für den Datenzugriff
Public MustOverride Function getData() As Object()
' Methode zur Datenüberprüfung
Protected Function checkData() As Integer
' Überprüfung der erfassten Daten
' Es müssen Daten vorhanden sein
valide = Not limites Is Nothing AndAlso Not coeffr Is Nothing AndAlso Not coeffn Is Nothing
If Not valide Then Return 1
' Es müssen 3 Arrays gleicher Größe vorhanden sein
If valide Then valide = limites.Length = coeffr.Length AndAlso limites.Length = coeffn.Length
If Not valide Then Return 2
' Die Arrays dürfen nicht leer sein
valide = limites.Length <> 0
If Not valide Then Return 3
' Jedes Array muss Elemente >= 0 enthalten, die in aufsteigender Reihenfolge sortiert sind
valide = check(limites, limites.Length - 1) AndAlso check(coeffr, coeffr.Length) AndAlso check(coeffn, coeffn.Length)
If Not valide Then Return 4
' Alles ist in Ordnung
Return 0
End Function
' überprüft die Gültigkeit des Inhalts eines Arrays
Protected Function check(ByRef tableau() As Decimal, ByVal n As Integer) As Boolean
' Das Array muss die ersten n Elemente mit Werten >= 0 in streng aufsteigender Reihenfolge enthalten
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
' Das ist in Ordnung
Return True
End Function
End Class
End Namespace
Die Klasse verfügt über die folgenden geschützten Attribute:
Tabelle der Steuerklassengrenzen | |
Tabelle der auf das steuerpflichtige Einkommen angewandten Koeffizienten | |
Tabelle der auf die Anzahl der Anteile angewandten Koeffizienten | |
Boolescher Wert, der angibt, ob die Daten (Grenzwerte, Koeffizient a, Koeffizient b) überprüft wurden | |
Boolescher Wert, der angibt, ob die Daten (Grenzwerte, Koeffizient, Koeffizient) gültig sind |
Die Klasse verfügt über keinen Konstruktor. Sie enthält eine abstrakte Methode [getData], die von abgeleiteten Klassen implementiert werden muss. Diese Methode hat folgende Aufgabe:
- den drei Arrays „limites“, „coeffr“ und „coeffn“ Werte zuzuweisen
- eine Ausnahme auszulösen, wenn die Daten nicht erfasst werden konnten oder sich als ungültig erwiesen haben.
Die Klasse stellt die geschützten Methoden [checkData] und [check] bereit, die die Gültigkeit der Attribute (limites, coeffr, coeffn) überprüfen. Dadurch müssen die abgeleiteten 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
' Konstruktor ohne Argumente
Public Sub New()
' Initialisierung der Arrays mit Konstanten
limites = New Decimal() {4262D, 8382D, 14753D, 23888D, 38868D, 47932D, 0D}
coeffr = New Decimal() {0D, 0.0683D, 0.1914D, 0.2826D, 0.3738D, 0.4262D, 0.4809D}
coeffn = New Decimal() {0D, 291.09D, 1322.92D, 2668.39D, 4846.98D, 6883.66D, 9505.54D}
checked = True
valide = True
End Sub
' Konstruktor mit drei Arrays als Eingabe
Public Sub New(ByRef limites() As Decimal, ByRef coeffr() As Decimal, ByRef coeffn() As Decimal)
' Die Daten werden gespeichert
Me.limites = limites
Me.coeffr = coeffr
Me.coeffn = coeffn
checked = False
End Sub
Public Overrides Function getData() As Object()
' Die Daten werden gegebenenfalls überprüft
Dim erreur As Integer
If Not checked Then erreur = checkData() : checked = True
' Wenn sie ungültig sind, wird eine Ausnahme ausgelöst
If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
' ansonsten werden die drei Arrays zurückgegeben
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 (Grenzwerte, coeffr, coeffn) der Basisklasse mit fest codierten Arrays initialisiert
- ein Konstruktor, der die Attribute (limites, coeffr, coeffn) der Basisklasse mit Arrays initialisiert, die ihm als Parameter übergeben werden
Die Methode [getData], die es externen Klassen ermöglicht, die Arrays (limites, coeffr, coeffn) abzurufen, beschränkt sich darauf, die Gültigkeit der drei Arrays mithilfe der Methode [checkData] der Basisklasse zu überprüfen. Sie löst eine Ausnahme aus, wenn die Daten ungültig sind.
6.1.5. Tests der Fachklassen und der Datenzugriffsklassen
Es ist wichtig, in eine Webanwendung nur Geschäfts- und Datenzugriffsklassen aufzunehmen, deren Korrektheit bestätigt wurde. So kann sich die Debugging-Phase der Webanwendung auf den Controller- und den View-Teil konzentrieren. Ein Testprogramm könnte wie folgt aussehen:
' Optionen
Option Strict On
Option Explicit On
' Namensräume
Imports System
Imports Microsoft.VisualBasic
Namespace st.istia.univangers.fr
Module test
Sub Main()
' Interaktives Programm zur Steuerberechnung
' Der Benutzer gibt drei Daten über die Tastatur ein: verheiratet nbEnfants Gehalt
' Das Programm zeigt daraufhin die zu zahlende Steuer an
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"
' Erstellung eines Steuerobjekts
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
' Endlosschleife
Dim marié As String
Dim nbEnfants As Integer
Dim salaire As Long
While True
' Die Parameter für die Steuerberechnung werden abgefragt
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()
' Was ist zu tun?
If paramètres Is Nothing OrElse paramètres = "" Then
Exit While
End If
' Überprüfung der Anzahl der Argumente in der eingegebenen Zeile
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
' Überprüfung der Gültigkeit der Parameter
If Not erreur Then
' verheiratet
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
' Gehalt
Try
salaire = Integer.Parse(args(2))
If salaire < 0 Then
Throw New Exception
End If
Catch
erreur = True
End Try
End If
' Wenn die Parameter korrekt sind, wird die Steuer berechnet
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 Angaben, die zur Berechnung seiner Steuer benötigt werden, über die Tastatur einzugeben:
- seinen Familienstand: o für verheiratet, n für unverheiratet
- die Anzahl seiner Kinder
- sein Jahresgehalt
Die Berechnung der Steuer erfolgt mithilfe eines Objekts vom Typ [impot], das beim Start der Anwendung angelegt wird:
' Erstellung eines Steuerobjekts
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 wird ein Objekt vom Typ [impotsArray] verwendet. Es wird der Konstruktor ohne Argumente dieser Klasse verwendet, der die drei Arrays (limites, coeffr, coeffn) mit festgelegten Werten bereitstellt. Die Erstellung eines Objekts vom Typ [impot] kann theoretisch eine Ausnahme auslösen, da das Objekt bei seiner Erstellung die Daten (limites, coeffr, coeffn) von seiner Datenquelle anfordert, die ihm als Parameter übergeben wurde, und diese Datenabfrage eine Ausnahme auslösen kann. Tatsächlich kann die Methode zur Datenbeschaffung (feste Codierung) in diesem Fall keine Ausnahme auslösen. Wir haben die Behandlung einer solchen Ausnahme jedoch beibehalten, um den Leser auf die Möglichkeit aufmerksam zu machen, dass das Objekt [impot] fehlerhaft erstellt werden könnte.
Hier 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 fügen alle Klassen [impot, impotsData, impotsArray] in eine Assembly [impot.dll] zusammen:
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 durchfü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. Die Ansichten der Webanwendung
Die Anwendung verfügt über zwei Ansichten: [formulaire.aspx] und [erreurs.aspx]. Veranschaulichen wir die Funktionsweise der Anwendung anhand von Screenshots. Die Ansicht [formulaire.aspx] wird angezeigt, wenn die URL [main.aspx] zum ersten Mal aufgerufen wird:

Der Benutzer füllt das Formular aus:

und klickt auf die Schaltfläche [Calculer], um die folgende Antwort zu erhalten:

Er kann sich bei der Eingabe der Daten irren:

Die Verwendung der Schaltfläche [Calculer] führt dann zu einer anderen Antwort [erreurs.aspx]:

Er kann den obigen Link [Retour au formulaire] verwenden, um die Ansicht [formulaire.aspx] wieder aufzurufen, wie er sie vor dem Fehler bestätigt hat:

6.1.7. Die Ansicht [formulaire.aspx]
Die Seite [formulaire.aspx] sieht dann 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 dieser Seite sind folgende:
„checked“, wenn das Kontrollkästchen [oui] angekreuzt werden soll, andernfalls „“ | |
das Gleiche gilt für das Kontrollkästchen [non] | |
Wert, der in das Eingabefeld [txtEnfants] eingegeben werden soll | |
Wert, der in das Eingabefeld [txtSalaire] einzutragen ist | |
Wert, der in das Eingabefeld [txtImpot] eingegeben werden soll |
Die Seite enthält zwei Formulare, die jeweils eine Schaltfläche [submit] haben. Die Schaltfläche [Calculer] ist die Schaltfläche [submit] des folgenden Formulars:
<form method="post" action="main.aspx?action=calcul">
...
<P>
<INPUT type="submit" value="Calculer">
</P>
</form>
Man sieht, dass die Parameter des Formulars mit [action=calcul] an den Controller übermittelt werden. Die Schaltfläche [Effacer] ist die Schaltfläche [submit] des folgenden Formulars:
<form method="post" action="main.aspx?action=effacer">
<INPUT type="submit" value="Effacer">
</form>
Man sieht, dass die Parameter des Formulars mit [action=effacer] an den Controller übermittelt werden. In diesem Fall hat das Formular keine Parameter. Es ist nur die Aktion von Bedeutung.
Die Felder von [formulaire.aspx] werden von [formulaire.aspx.vb] berechnet:
Imports System.Collections.Specialized
Public Class formulaire
Inherits System.Web.UI.Page
' Felder der Seite
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
' Die vorherige Abfrage wird aus dem Kontext abgerufen
Dim form As NameValueCollection = Context.Items("formulaire")
' Die anzuzeigende Seite wird vorbereitet
' Optionsfelder
rdouichecked = ""
rdnonchecked = "checked"
If form("rdMarie").ToString = "oui" Then
rdouichecked = "checked"
rdnonchecked = ""
End If
' der Rest
txtEnfants = CType(form("txtEnfants"), String)
txtSalaire = CType(form("txtSalaire"), String)
txtImpot = CType(Context.Items("txtImpot"), String)
End Sub
End Class
Die Berechnung der Felder von [main.aspx] erfolgt anhand von zwei Informationen, die vom Controller im Seitenkontext abgelegt werden:
- Context.Items („Formular“): ein Wörterbuch vom Typ NameValueCollection, das die Werte der Felder HTML und [rdmarie,txtEnfants,txtSalaire] enthält
- Context.Items("txtImpot"): Steuerwert
6.1.8. Die Ansicht [erreurs.aspx]
Die Ansicht [erreurs.aspx] zeigt eventuelle Fehler an, die während der Laufzeit der Anwendung auftreten können. Ihr 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:
Code HTML aus einer Fehlerliste | |
URL eines Links | |
Text des Links |
Diese Felder werden vom Controller-Teil der Seite in [erreurs.aspx.vb] berechnet:
Imports System.Collections
Imports Microsoft.VisualBasic
Public Class erreurs
Inherits System.Web.UI.Page
' Seitenparameter
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
' Die Elemente des Kontexts werden abgerufen
Dim erreurs As ArrayList = CType(context.Items("erreurs"), ArrayList)
href = context.Items("href").ToString
lien = context.Items("lien").ToString
' Der Code HTML der Liste wird generiert
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:
Objekt ArrayList, das die Liste der anzuzeigenden Fehlermeldungen enthält | |
URL eines Links | |
Text des Links |
Da wir nun wissen, was der Nutzer der Anwendung sieht, können wir mit der Erstellung des Controllers für die Anwendung fortfahren.
6.1.9. Die Controller [global.asax, main.aspx]
Erinnern wir uns an das Schema MVC unserer Anwendung:

ClientAnwendungslogik
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 des Eingabeformulars korrekt, wird die Steuer mithilfe der Geschäftsklasse [impots] berechnet. Der Controller gibt dem Client die Ansicht [formulaire.aspx] so zurück, wie sie validiert wurde, zusätzlich mit der berechneten Steuer. Sind die Daten des Eingabeformulars fehlerhaft, gibt der Controller die Ansicht [erreurs.aspx] mit der Fehlerliste und einem Link zur Rückkehr zum Formular zurück.
- Rückkehr: Bezieht sich auf die Rückkehr zum Formular nach einem Fehler. Der Controller zeigt die Ansicht [formulaire.aspx] so an, wie sie vor dem Fehler validiert wurde.
Außerdem ist bekannt, dass jede Anfrage an die Anwendung über den Controller [global.asax] geleitet wird, sofern dieser vorhanden ist. Am Eingang der Anwendung haben wir somit eine Kette aus zwei Controllern:
- [global.asax], der aufgrund der Architektur alle Anfragen an die Anwendung ASP.NET empfängt
- [main.aspx], der aufgrund der Entscheidung des Entwicklers ebenfalls alle Anfragen an die Anwendung empfängt
Die Notwendigkeit von [main.aspx] ergibt sich daraus, dass wir eine Sitzung verwalten müssen. Wir haben gesehen, dass [global.asax] in diesem Fall nicht als Controller geeignet ist. Auf [global.asax] könnte man hier komplett verzichten. Wir werden es jedoch verwenden, um beim Start der Anwendung Code auszuführen. Das obige Schema MVC zeigt, dass wir ein Objekt [impot] erstellen müssen, um die Steuer zu berechnen. Es ist unnötig, dieses Objekt mehrmals zu erstellen – einmal reicht aus. Wir werden es daher beim Start der Anwendung im Rahmen des Ereignisses [Application_Start] anlegen, das vom Controller [global.asax] verwaltet 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)
' ein Impot-Objekt wird erstellt
Dim objImpot As impot
Try
objImpot = New impot(New impotsArray)
' Das Objekt wird in die Anwendung eingefügt
Application("objImpot") = objImpot
' kein Fehler
Application("erreur") = False
Catch ex As Exception
'Es ist ein Fehler aufgetreten, der in der Anwendung vermerkt wird
Application("erreur") = True
End Try
End Sub
End Class
Nach der Erstellung wird das Objekt vom Typ [impot] in die Anwendung eingefügt. Dort wird es von den verschiedenen Anfragen der verschiedenen Kunden abgerufen. Da die Erstellung des Objekts [impot] fehlschlagen kann, behandeln wir die mögliche Ausnahme und legen einen Schlüssel [erreur] in der Anwendung ab, um zu signalisieren, ob bei der Erstellung des Objekts [impot] ein Fehler aufgetreten ist oder nicht.
Der Code des Controllers [main.aspx, main.aspx.vb] lautet wie folgt:
[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
' Zunächst wird geprüft, ob die Anwendung korrekt initialisiert werden konnte
If CType(Application("erreur"), Boolean) Then
' Man leitet auf die Fehlerseite weiter
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
' Die auszuführende Aktion wird abgerufen
Dim action As String
If Request.QueryString("action") Is Nothing Then
action = "init"
Else
action = Request.QueryString("action").ToString.ToLower
End If
' Die Aktion wird ausgeführt
Select Case action
Case "init"
' Anwendung initialisieren
initAppli()
Case "calcul"
' Steuerberechnung
calculImpot()
Case "retour"
' Zurück zum Formular
retourFormulaire()
Case "effacer"
' Anwendung initialisieren
initAppli()
Case Else
' unbekannte Aktion = Initialisierung
initAppli()
End Select
End Sub
Private Sub initAppli()
' Das vorausgefüllte Formular wird angezeigt
Context.Items("formulaire") = initForm()
Context.Items("txtImpot") = ""
Server.Transfer("formulaire.aspx", True)
End Sub
Private Function initForm() As NameValueCollection
' Das Formular wird initialisiert
Dim form As New NameValueCollection
form.Set("rdMarie", "non")
form.Set("txtEnfants", "")
form.Set("txtSalaire", "")
Return form
End Function
Private Sub calculImpot()
' Die Gültigkeit der eingegebenen Daten wird überprüft
Dim erreurs As ArrayList = checkData()
' Bei Fehlern wird eine Fehlermeldung angezeigt
If erreurs.Count <> 0 Then
' Die Eingaben werden gespeichert
Session.Item("formulaire") = Request.Form
' Die Fehlerseite wird vorbereitet
context.Items("href") = "main.aspx?action=retour"
context.Items("lien") = "Retour au formulaire"
context.Items("erreurs") = erreurs
Server.Transfer("erreurs.aspx")
End If
' Hier keine Fehler – die Steuer wird berechnet
Dim impot As Long = CType(Application("objImpot"), impot).calculer( _
Request.Form("rdMarie") = "oui", _
CType(Request.Form("txtEnfants"), Integer), _
CType(Request.Form("txtSalaire"), Long))
' die Ergebnisseite wird angezeigt
context.Items("txtImpot") = impot.ToString + " euro(s)"
context.Items("formulaire") = Request.Form
Server.Transfer("formulaire.aspx", True)
End Sub
Private Sub retourFormulaire()
' Das Formular wird mit den aus der Sitzung übernommenen Werten angezeigt
Context.Items("formulaire") = Session.Item("formulaire")
Context.Items("txtImpot") = ""
Server.Transfer("formulaire.aspx", True)
End Sub
Private Function checkData() As ArrayList
' zunächst keine Fehler
Dim erreurs As New ArrayList
Dim erreur As Boolean = False
' Optionsfeld „verheiratet“
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
' Anzahl der Kinder
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
' Gehalt
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
' Die Fehlerliste wird ausgegeben
Return erreurs
End Function
End Class
Der Controller überprüft zunächst, ob die Anwendung korrekt initialisiert wurde:
' Zunächst wird geprüft, ob die Anwendung korrekt initialisiert werden konnte
If CType(Application("erreur"), Boolean) Then
' Weiterleitung zur Fehlerseite
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
Stellt der Controller fest, dass die Anwendung nicht ordnungsgemäß initialisiert werden konnte (das für die Berechnung erforderliche Objekt [impot] konnte nicht angelegt werden), lässt er die Fehlerseite mit den entsprechenden Parametern anzeigen. In diesem Fall ist es nicht erforderlich, den Rücklink auf das Formular zu setzen, da die gesamte Anwendung nicht verfügbar ist. Eine allgemeine Fehlermeldung vom Typ [ArrayList] wird in [Context.Items("erreurs")] abgelegt.
Stellt der Controller fest, dass die Anwendung betriebsbereit ist, analysiert er die Aktion, die über den Parameter [action] ausgeführt werden soll. Diese Funktionsweise ist uns mittlerweile bereits oft begegnet. Die Verarbeitung der einzelnen Aktionstypen wird an eine Funktion delegiert.
6.1.9.1. Die Aktionen „init“ und „effacer“
Diese beiden Aktionen müssen das leere Eingabeformular anzeigen. Zur Erinnerung: Dieses (siehe Ansichten) hat zwei Parameter:
- Context.Items("Formular"): Dictionary vom Typ [NameValueCollection], der die Werte der Felder HTML und [rdmarie,txtEnfants,txtSalaire] enthält
- Context.Items("txtImpot"): Steuerwert
Die Funktion [initAppli] initialisiert diese beiden Parameter so, dass ein leeres Formular angezeigt wird.
6.1.9.2. Die Aktion „Berechnen“
Diese Aktion muss die zu zahlende Steuer anhand der im Formular eingegebenen Daten berechnen und das Formular mit den eingegebenen Werten sowie dem berechneten Steuerbetrag vorausgefüllt zurückgeben. Die dafür zuständige Funktion [calculImpot] überprüft zunächst, ob die Daten im Formular korrekt sind:
- Das Feld [rdMarie] muss vorhanden sein und den Wert [oui] oder [non] 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
Sollten sich die eingegebenen Daten als ungültig erweisen, lässt der Controller die Ansicht [erreurs.aspx] anzeigen, nachdem er zuvor die von dieser erwarteten Werte in den Kontext gesetzt hat:
- Die Fehlermeldungen werden in ein Objekt [ArrayList] gespeichert, das anschließend in den Kontext [Context.Items("erreurs")] eingefügt wird
- Die URL des Rücklink und der Text dieses Links werden ebenfalls in den Kontext gestellt.
Bevor die Kontrolle 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 „Formular“ zugeordnet. Dadurch können sie bei einer späteren Anfrage wieder abgerufen werden.
Man kann 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 man sicher ist, dass es sich bei unserem Client um einen Browser handelt, der die Ansicht [formulaire.aspx] erhalten hat, die diese Felder enthält. Davon kann man jedoch nie sicher sein. Wir werden etwas später ein Beispiel zeigen, bei dem der Client die bereits bekannte Anwendung [curl] ist. Wir werden die Anwendung abfragen, ohne die von ihr erwarteten Felder zu senden, und sehen, wie sie darauf reagiert. Dies ist eine Regel, die bereits mehrfach erwähnt wurde und die wir hier noch einmal in Erinnerung rufen: Eine Anwendung darf niemals Annahmen über die Art des Clients treffen, der sie abfragt. Aus Sicherheitsgründen muss sie davon ausgehen, dass sie von einer programmierten Anwendung abgefragt werden kann, die ihr unerwartete Parameterfolgen senden könnte. Sie muss sich in jedem Fall korrekt verhalten.
In unserem Fall haben wir überprüft, ob die Felder [rdMarie, txtEnfants, txtSalaire] in der Anfrage vorhanden waren, nicht jedoch, ob diese noch weitere enthalten könnte. In dieser Anwendung würden sie ignoriert werden. Dennoch wäre es aus Sicherheitsgründen sinnvoll, diese Art von Anfragen in einer Protokolldatei zu speichern und eine Warnmeldung an den Anwendungsadministrator weiterzuleiten, damit dieser weiß, dass die Anwendung „seltsame“ Anfragen erhält. Durch die Analyse dieser Einträge in der Protokolldatei könnte er einen möglichen Angriff auf die Anwendung erkennen und dann die notwendigen Maßnahmen zu deren Schutz ergreifen.
Sind die erwarteten Daten korrekt, startet der Controller die Steuerberechnung mit dem in der Anwendung gespeicherten Objekt [impot]. Anschließend speichert er im Kontext die beiden von der Ansicht [formulaire.aspx] erwarteten Informationen:
- Context.Items („Formular“): ein Dictionary vom Typ [NameValueCollection], das die Werte der Felder HTML, [rdmarie,txtEnfants,txtSalaire], hier [Request.Form)] und c.a.d enthält. die zuvor im Formular eingegebenen Werte
- Context.Items("txtImpot“): der soeben ermittelte Steuerwert
Der aufmerksame Leser hat sich beim Lesen des Vorstehenden vielleicht eine Frage gestellt: Da das beim Start der Anwendung erstellte Objekt [impot] von allen Abfragen gemeinsam genutzt wird, könnte es dann nicht zu Zugriffskonflikten kommen, die eine Beschädigung der Daten des Objekts [impot] zur Folge haben? Um diese Frage zu beantworten, müssen wir uns den Code der Klasse [impot] noch einmal ansehen. Die Abfragen rufen die Methode [impot].calculerImpot auf, um die zu zahlende Steuer zu ermitteln. Daher müssen wir diesen Code untersuchen:
Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Long) As Long
' Berechnung der Anzahl der Anteile
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
' Berechnung des steuerpflichtigen Einkommens und des Familienquotienten
Dim revenu As Decimal = 0.72D * salaire
Dim QF As Decimal = revenu / nbParts
' Berechnung der Steuer
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
Nehmen wir an, ein Thread führt gerade die vorherige Methode aus und wird unterbrochen. Ein anderer Thread führt dann die Methode aus. Welche Risiken bestehen dabei? Um dies herauszufinden, haben wir den folgenden Code hinzugefügt:
Dim impot As Long = CLng(revenu * coeffR(i) - nbParts * coeffN(i))
' 10 Sekunden Wartezeit
Thread.Sleep(10000)
Return impot
Thread 1 wird unterbrochen, nachdem er den Wert [impot1] der lokalen Variablen [impot] berechnet hat. 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 wieder die Kontrolle. Was findet er in der lokalen Variablen [impot] vor? Da es sich um eine methodenlokale Variable handelt, wird sie in einer Speicherstruktur namens Stack gespeichert. Dieser Stack ist Teil des Thread-Kontexts, der bei einer Unterbrechung des Threads gesichert wird. Wenn Thread 2 gestartet wird, wird sein Kontext mit einem neuen Stack und somit einer neuen lokalen Variablen [impot] eingerichtet. Wenn Thread 2 seinerseits unterbrochen wird, wird sein Kontext ebenfalls gesichert. Wenn Thread 1 wieder gestartet wird, wird sein Kontext einschließlich seines Stacks wiederhergestellt. Er findet dann seine lokale Variable [impot] wieder und nicht die von Thread 2. Wir befinden uns also in einer Situation, in der es keine Zugriffskonflikte zwischen den Anfragen gibt. Die Tests mit der oben genannten Pause von 10 Sekunden haben bestätigt, dass die gleichzeitigen Abfragen tatsächlich das erwartete Ergebnis lieferten.
6.1.9.3. Die Rückkehraktion
Diese Aktion entspricht dem Anklicken des Links [Retour vers le formulaire] in der Ansicht [erreurs.aspx], um zur Ansicht [formulaire.aspx] zurückzukehren, die mit den zuvor eingegebenen und in der Sitzung gespeicherten Werten vorausgefüllt ist. Die Funktion [retourFormulaire] ruft diese Informationen ab. Die beiden von der Ansicht [formulaire.aspx] erwarteten Parameter werden initialisiert:
- Context.Items("Formular") mit den zuvor eingegebenen und in der Sitzung gespeicherten Werten
- Context.Items("txtImpot") mit der leeren Zeichenkette
6.1.10. Testen der Webanwendung
Alle oben genannten Dateien werden in einem Ordner <application-path> abgelegt.

In diesem Ordner wird ein Unterordner [bin] angelegt, in dem die Assembly [impot.dll] abgelegt wird, die aus der Kompilierung der Geschäftsclass-Dateien [impots.vb, impotsData.vb, impotsArray.vb] hervorgeht. Nachfolgend wird der erforderliche Kompilierungsbefehl noch einmal aufgeführt:
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 <application-path>\bin abgelegt werden, damit die Webanwendung darauf zugreifen kann. Der Cassini-Server wird mit den Parametern (<application-path>,/impots1) gestartet. Mit einem 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 [Calculer]. Wir erhalten folgende Antwort:

Anschließend geben wir falsche Daten ein:

Ein Klick auf die Schaltfläche [Calculer] führt zu folgender Antwort:

Über den Link [Retour au formulaire] gelangen wir zurück zum Formular in dem Zustand, in dem es sich zum Zeitpunkt der Absendung befand:

Schließlich wird durch Klicken auf die Schaltfläche „[Effacer]“ die Seite zurückgesetzt:

6.1.11. Verwendung des Clients [curl]
Es ist wichtig, Webanwendungen auch mit anderen Clients als Browsern zu testen. Wenn man einem Browser ein Formular mit Parametern übermittelt, die bei der Absendung gesendet werden sollen, sendet der Browser die Werte dieser Parameter an den Server zurück. Ein anderer Client tut dies möglicherweise nicht, sodass der Server eine Anfrage erhält, in der Parameter fehlen. Er muss wissen, wie er in diesem Fall vorgehen soll. Ein weiteres Beispiel sind clientseitige Eingabevalidierungen. Wenn das Formular zu überprüfende Daten enthält, kann diese Überprüfung clientseitig mithilfe von Skripten erfolgen, die in das Dokument mit dem Formular eingebunden sind. Der Browser sendet das Formular erst ab, wenn alle clientseitig überprüften Daten gültig sind. Man könnte nun auf Serverseite versucht sein, davon auszugehen, dass man bereits geprüfte Daten erhält, und diese Prüfung nicht ein zweites Mal durchführen wollen. Das wäre ein Fehler. Denn ein anderer Client als ein Browser könnte dem Server ungültige Daten übermitteln, wodurch die Webanwendung möglicherweise ein unerwartetes Verhalten zeigt. Wir werden diese Punkte anhand des Clients [curl] 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 Code HTML des Formulars gesendet. In den Headern HTTP finden wir das Session-Cookie. Wir werden es in den folgenden Anfragen verwenden, um die Sitzung aufrechtzuerhalten. Rufen wir die Aktion [calcul] auf, ohne Parameter anzugeben:
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 können feststellen, dass die Webanwendung die Ansicht [erreurs] mit drei Fehlermeldungen für die drei fehlenden Parameter zurückgegeben hat. Senden wir nun fehlerhafte 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. Nun senden wir 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 zu zahlende Steuer korrekt ermittelt: 4.300 Euro. Aus diesem Beispiel lernen wir, dass man sich nicht davon täuschen lassen darf, dass man eine Webanwendung für Clients entwickelt, bei denen es sich um Browser handelt. Eine Webanwendung ist ein TCP/IP-Dienst, und dieses Netzwerkprotokoll lässt keine Rückschlüsse auf die Art der Client-Anwendung eines Dienstes zu. Man kann also nicht wissen, ob der Client einer Webanwendung ein Browser ist oder nicht. Daher befolgen wir zwei Regeln:
- Beim Empfang einer Anfrage eines Clients stellen wir keine Annahmen über den Client an und überprüfen, ob die in der Anfrage erwarteten Parameter vorhanden und gültig sind
- man erstellt eine Antwort, die für Browser bestimmt ist, also in der Regel Dokumente vom Typ HTML
Eine Webanwendung kann so konzipiert werden, dass sie gleichzeitig verschiedene Clients bedient, beispielsweise Browser und Mobiltelefone. In diesem Fall kann in jede Anfrage ein neuer Parameter aufgenommen werden, der den Client-Typ angibt. So fordert ein Browser die Berechnung der Steuer über eine Anfrage an die URL http://machine/impots/main.aspx?client=Browser&action=Berechnung, während das Mobiltelefon eine Anfrage an die URL http://machine/impots/main.aspx?client=mobile&action=calcul sendet. Die Struktur MVC erleichtert die Erstellung einer solchen Anwendung. Sie sieht dann wie folgt aus:

Der Block [Classes métier, Classes d'accès aux données] bleibt unverändert. Er ist nämlich für den Client irrelevant. Der Block [Contrôleur] ändert sich nur geringfügig, muss jedoch einen neuen Parameter in der Anfrage berücksichtigen: den Parameter [client], der angibt, um welchen Client-Typ es sich handelt. Der Block [vues] muss Ansichten für jeden Kundentyp generieren. Es könnte sinnvoll sein, bereits bei der Konzeption der Anwendung das Vorhandensein des Parameters [client] in der Anfrage zu berücksichtigen, auch wenn kurz- oder mittelfristig nur Browser im Fokus stehen. Sollte die Anwendung später einen neuen Client-Typ unterstützen müssen, müssen lediglich die dafür geeigneten Ansichten geschrieben werden.
6.2. Beispiel 2
6.2.1. Das Problem
Wir wollen hier dasselbe Problem wie zuvor behandeln, jedoch unter Änderung der Datenquelle des von der Webanwendung erstellten Objekts [impot]. In der vorherigen Version lieferte die verwendete Datenquelle die Werte von Tabellen, die fest im Code hinterlegt waren. Dieses Mal bezieht die neue Datenquelle diese Werte aus einer Datenquelle namens ODBC, die mit einer Datenbank namens MySQL verknüpft ist.
6.2.2. Die Datenquelle ODBC
Die Daten befinden sich in einer Tabelle namens [IMPOTS] einer Datenbank namens MySQL, die wiederum den Namen [dbimpots] trägt. Der Inhalt dieser Tabelle sieht wie folgt aus:

Der Eigentümer der Datenbank ist der Benutzer [admimpots] mit dem Passwort [mdpimpots]. Wir ordnen dieser Datenbank eine Datenquelle mit dem Namen ODBC zu. Bevor wir dies tun, wollen wir zunächst die verschiedenen Möglichkeiten des Zugriffs auf eine Datenbank mit der Plattform .NET in Erinnerung rufen.
Es gibt zahlreiche Datenbanken für Windows-Plattformen. Um darauf zuzugreifen, nutzen die Anwendungen sogenannte Treiber (Drivers).

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 vermeiden, dass eine für eine Datenbank vom Typ B1 geschriebene Anwendung bei einer Migration zu einer anderen Datenbank vom Typ B2 neu geschrieben werden muss, wurden Standardisierungsbemühungen an der Schnittstelle I1 unternommen. Bei der Verwendung von Datenbanken mit „normalisierten“ Treibern wird die Datenbank B1 mit einem Treiber P1 bereitgestellt, die Datenbank B2 mit einem Treiber P2, und die Schnittstelle I1 dieser beiden Treiber ist identisch. Daher muss die Anwendung nicht neu geschrieben werden. So kann man beispielsweise eine Datenbank vom Typ ACCESS auf eine Datenbank vom Typ MySQL migrieren, ohne die Anwendung zu ändern.
Es gibt zwei Arten von standardisierten Treibern:
- die Treiber ODBC (Open DataBase Connectivity)
- die Treiber OLE und DB (Object Linking and Embedding DataBase)
Die Treiber ODBC ermöglichen den Zugriff auf Datenbanken. Die Datenquellen für die Treiber OLE und DB sind vielfältiger: Datenbanken, E-Mail-Systeme, Verzeichnisse usw. Es gibt keine Einschränkungen. Jede Datenquelle kann Gegenstand eines OLE-Treibers DB sein, wenn ein Hersteller dies beschließt. Der Nutzen ist offensichtlich groß: Man erhält einen einheitlichen Zugriff auf eine große Vielfalt an Daten.
Die Plattform .NET 1.1 wird mit drei Arten von Datenzugriffsklassen ausgeliefert:
- die Klassen SQL und Server.NET für den Zugriff auf Microsoft SQL Server-Datenbanken
- die Ole-Klassen Db.NET für den Zugriff auf Datenbanken von SGBD, die einen Treiber OLE DB bereitstellen
- die Klassen odbc.net, für den Zugriff auf die Datenbanken von SGBD, die einen Treiber ODBC bereitstellen
Für die Klassen SGBD und MySQL gibt es schon seit Langem einen Treiber ODBC. Diesen verwenden wir nun. Unter Windows wählen wir die Option [Menu Démarrer/Panneau de configuration/Outils d'administration/Sources ODBC 32 bits]. Je nach Windows-Version kann dieser Pfad leicht variieren. Man erhält die folgende Anwendung, mit der wir unsere Quelle ODBC erstellen können:

Wir erstellen nun eine Systemdatenquelle mit dem Namen c.a.d. Diese Datenquelle steht allen Benutzern des Computers zur Verfügung (utiliser.Aussi). Wählen wir oben die Registerkarte [Source de données système] aus. Die angezeigte Seite enthält eine Schaltfläche [Ajouter], mit der wir eine neue Datenquelle ODBC anlegen:

Der Assistent fordert Sie auf, den zu verwendenden Treiber „ODBC“ auszuwählen. Windows enthält eine Reihe vorinstallierter Treiber vom Typ „ODBC“. Der Treiber ODBC von MySQL ist nicht im Lieferumfang enthalten. Er muss daher zuvor installiert werden. Man findet ihn im Internet, indem man die Suchbegriffe „MySQL ODBC“ oder auch „MyODBC“ in eine Suchmaschine eingibt. Hier haben wir den Treiber [MySQL ODBC 3.51] installiert. Wir wählen ihn aus und führen [Terminer] aus:

Es müssen einige Angaben gemacht werden:
den Namen, der die Datenquelle ODBC bezeichnet. Jede Windows-Anwendung kann über diesen Namen auf die Quelle zugreifen | |
Ein beliebiger Text zur Beschreibung der Datenquelle | |
Der Name des Rechners, auf dem die Datenquelle SGBD und MySQL gehostet wird. Hier handelt es sich um den lokalen Rechner. Es könnte sich aber 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 großer Vorteil der Datenquelle ODBC. | |
Ein SGBD MySQL kann mehrere Datenbanken verwalten. Hier wird angegeben, welche verwaltet werden soll: dbimpots | |
Name eines Benutzers, der in den Systemen SGBD und MySQL registriert ist. Der Zugriff auf die Datenquelle erfolgt unter diesem Namen. Hier: admimpots | |
Das Passwort dieses Benutzers. Hier: mdpimpots | |
Arbeitsport des SGBD MySQL. Standardmäßig ist dies Port 3306. Wir haben ihn nicht geändert |
Anschließend testen wir die Gültigkeit unserer Verbindungsparameter mit der Schaltfläche [Test Data Source]:

Damit sind wir uns unserer Datenquelle ODBC sicher. Wir können sie nun nutzen. Wir führen den Vorgang so oft wie nötig durch [OK], um den Assistenten zu verlassen ODBC.
Falls der Leser nicht über SGBD mySQL verfügt, kann er es kostenlos unter der URL [http://www.mysql.com] herunterladen. Im Folgenden wird die Vorgehensweise zum Erstellen einer Datenquelle ODBC mit Access beschrieben. Die ersten Schritte entsprechen den zuvor beschriebenen. Es wird eine neue Systemdatenquelle hinzugefügt:

Als Treiber wird [Microsoft Access Driver] ausgewählt. Mit [Terminer] gelangt man zur Definition der Datenquelle ODBC:

Folgende Angaben sind erforderlich:
Der Name, der die Datenquelle ODBC bezeichnet. Jede Windows-Anwendung kann über diesen Namen auf die Quelle zugreifen | |
Ein beliebiger Text zur Beschreibung der Datenquelle | |
Der vollständige Name der zu verarbeitenden Datei ACCESS |
6.2.3. Eine neue Klasse für den Datenzugriff
Kehren wir zur Struktur MVC unserer Anwendung zurück:

In dem obigen Schema ist die Klasse [impotsData] für das Abrufen der Daten zuständig. Dies muss sie hier aus der Datenbank MySQL [dbimpots] tun. Seit der vorherigen Version dieser Anwendung wissen wir, dass [impotsData] eine abstrakte Klasse ist, die jedes Mal abgeleitet werden muss, wenn man sie an eine neue Datenquelle anpassen möchte. Hier noch einmal die Struktur dieser abstrakten Klasse:
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
' Methode für den Datenzugriff
Public MustOverride Function getData() As Object()
' Methode zur Datenüberprüfung
Protected Function checkData() As Integer
' überprüft die erfassten Daten
...
End Function
' überprüft die Gültigkeit des Inhalts eines Arrays
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 Konstruktor ohne Argumente 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 (limites, coeffr, coeffn) aus einer Quelle ODBC abruft, der wir den Namen
Imports System.Data.Odbc
Imports System.Data
Imports System.Collections
Imports System
Namespace st.istia.univangers.fr
Public Class impotsODBC
Inherits impotsData
' Instanzvariablen
Protected DSNimpots As String
' Konstruktor
Public Sub New(ByVal DSNimpots As String)
' die drei Informationen werden notiert
Me.DSNimpots = DSNimpots
End Sub
Public Overrides Function getdata() As Object()
' initialisiert die drei Arrays „limites“, „coeffr“ und „coeffn“ anhand
' des Inhalts der Tabelle [impots] aus der Datenbank ODBC DSNimpots
' „limites“, „coeffr“ und „coeffn“ sind die drei Spalten dieser Tabelle
' kann verschiedene Ausnahmen auslösen
Dim connectString As String = "DSN=" + DSNimpots + ";" ' chaîne de connexion à la base
Dim impotsConn As OdbcConnection = Nothing ' la connexion
Dim sqlCommand As OdbcCommand = Nothing ' la commande SQL
' die Abfrage SELECT
Dim selectCommand As String = "select limites,coeffr,coeffn from impots"
' Tabellen zum Abrufen der Daten
Dim aLimites As New ArrayList
Dim aCoeffR As New ArrayList
Dim aCoeffN As New ArrayList
Try
' Es wird versucht, auf die Datenbank zuzugreifen
impotsConn = New OdbcConnection(connectString)
impotsConn.Open()
' ein Befehlsobjekt wird erstellt
sqlCommand = New OdbcCommand(selectCommand, impotsConn)
' Die Abfrage wird ausgeführt
Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader()
' Auswertung der abgerufenen Tabelle
While myReader.Read()
' Die Daten der aktuellen Zeile werden in die Arrays übertragen
aLimites.Add(myReader("limites"))
aCoeffR.Add(myReader("coeffr"))
aCoeffN.Add(myReader("coeffn"))
End While
' Freigabe der Ressourcen
myReader.Close()
impotsConn.Close()
Catch e As Exception
Throw New Exception("Erreur d'accès à la base de données (" + e.Message + ")")
End Try
' Die dynamischen Arrays werden in statische Arrays übertragen
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
' Die erfassten Daten werden überprüft
Dim erreur As Integer = checkData()
' Wenn die Daten ungültig sind, wird eine Ausnahme ausgelöst
If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
' Andernfalls werden die drei Arrays zurückgegeben
Return New Object() {limites, coeffr, coeffn}
End Function
End Class
End Namespace
Werfen wir einen Blick auf den Hersteller:
' Konstruktor
Public Sub New(ByVal DSNimpots As String)
' die drei Informationen werden notiert
Me.DSNimpots = DSNimpots
End Sub
Als Parameter erhält sie den Namen der Quelle ODBC, in der sich die zu erfassenden Daten befinden. Der Konstruktor speichert lediglich diesen Namen. Die Methode [getData] ist dafür zuständig, die Daten aus der Tabelle [impots] auszulesen und in drei Arrays (limites, coeffr, coeffn) zu speichern. Erläutern wir den Code:
- Die Parameter für die Verbindung zur Datenquelle ODBC sind definiert, diese ist jedoch nicht geöffnet
' Verbindungszeichenfolge zur Datenbank
Dim connectString As String = "DSN=" + DSNimpots + ";"
' Es wird ein Datenbankverbindungsobjekt erstellt – diese Verbindung ist nicht geöffnet
Dim impotsConn As OdbcConnection = New OdbcConnection(connectString)
- Es werden drei Objekte [ArrayList] definiert, um die Daten aus der Tabelle [impots] abzurufen:
' Tabellen zum Abrufen der Daten
Dim aLimites As New ArrayList
Dim aCoeffR As New ArrayList
Dim aCoeffN As New ArrayList
- Der gesamte Code für den Datenbankzugriff ist von einer try/catch-Anweisung umgeben, um einen möglichen Zugriffsfehler zu behandeln. Wir stellen die Verbindung zur Datenbank her:
' Es wird versucht, auf die Datenbank zuzugreifen
impotsConn = New OdbcConnection(connectString)
impotsConn.Open()
- Wir führen den Befehl [select] über die geöffnete Verbindung aus. Wir erhalten ein Objekt [OdbcDataReader], mit dem wir die Zeilen der Ergebnistabelle der SELECT-Anweisung durchlaufen können:
' Es wird ein Befehlsobjekt angelegt
Dim sqlCommand As OdbcCommand = New OdbcCommand(selectCommand, impotsConn)
' Die Abfrage wird ausgeführt
Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader()
- Wir durchlaufen die Ergebnistabelle Zeile für Zeile. Dazu verwenden wir die Methode [Read] des zuvor erhaltenen Objekts [OdbcDataReader]. Diese Methode führt zwei Schritte aus:
- Sie springt um eine Zeile in der Tabelle weiter. Zu Beginn befindet man sich vor der ersten Zeile
- sie gibt den Booleschen Wert [true] zurück, wenn ein Zeilensprung möglich war, andernfalls [false]; letzterer Fall zeigt an, dass alle Zeilen verarbeitet wurden.
Die Spalten der aktuellen Zeile des Objekts [OdbcDataReader] werden über OdbcDataReader abgerufen. Man erhält ein Objekt, das den Wert der Spalte darstellt. Wir durchlaufen die gesamte Tabelle, um ihren Inhalt in die drei Objekte [ArrayList] zu übertragen:
' Auswertung der abgerufenen Tabelle
While myReader.Read()
' Die Daten der aktuellen Zeile werden in die Arrays geschrieben
aLimites.Add(myReader("limites"))
aCoeffR.Add(myReader("coeffr"))
aCoeffN.Add(myReader("coeffn"))
- Anschließend geben wir die mit der Verbindung verbundenen Ressourcen frei:
- Der Inhalt der drei Objekte [ArrayList] wird in drei herkömmliche Arrays übertragen:
' Die dynamischen Arrays werden in statische Arrays übertragen
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 den drei Arrays angekommen sind, muss nur noch deren Inhalt mithilfe der Methode [checkData] der Basisklasse [impotsData] überprüft werden:
' Die erfassten Daten werden überprüft
Dim erreur As Integer = checkData()
' Wenn die Daten ungültig sind, wird eine Ausnahme ausgelöst
If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
' Andernfalls werden die drei Arrays zurückgegeben
Return New Object() {limites, coeffr, coeffn}
6.2.4. Tests der Datenzugriffsklasse
Ein Testprogramm könnte wie folgt aussehen:
Option Explicit On
Option Strict On
' Namensräume
Imports System
Imports Microsoft.VisualBasic
Namespace st.istia.univangers.fr
' Testseite
Module testimpots
Sub Main(ByVal arguments() As String)
' Interaktives Programm zur Steuerberechnung
' Der Benutzer gibt drei Daten über die Tastatur ein: verheiratet nbEnfants Gehalt
' Das Programm zeigt daraufhin die zu zahlende Steuer an
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"
' Überprüfung der Programmparameter
If arguments.Length <> 1 Then
' Fehlermeldung
Console.Error.WriteLine(syntaxe1)
' Ende
Environment.Exit(1)
End If
' die Argumente werden abgerufen
Dim DSNimpots As String = arguments(0)
' Erstellung eines Steuerobjekts
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
' Endlosschleife
While True
' zunächst keine Fehler
Dim erreur As Boolean = False
' die Parameter für die Steuerberechnung werden abgefragt
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()
' Was ist zu tun?
If paramètres Is Nothing Or paramètres = "" Then
Exit While
End If
' Überprüfung der Anzahl der Argumente in der eingegebenen Zeile
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
' Überprüfung der Gültigkeit der Parameter
' verheiratet
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
' Gehalt
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
' Die Parameter sind korrekt – die Steuer wird berechnet
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 verarbeitenden Datenquelle ODBC
Die Steuerberechnung erfolgt mithilfe eines Objekts vom Typ [impot], das beim Start der Anwendung angelegt wird:
' Erstellung eines Steuerobjekts
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 dem Start fordert die Anwendung den Benutzer wiederholt auf, die drei Angaben, die zur Berechnung seiner Steuer benötigt werden, über die Tastatur einzugeben:
- seinen Familienstand: o für verheiratet, n für unverheiratet
- die Anzahl seiner Kinder
- sein 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 nun ebenfalls 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 Datenquelle ODBC MySQL 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)
Die Datenquelle wird von ODBC auf eine Access-Datenquelle umgestellt:
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. Die Ansichten der Webanwendung
Es handelt sich um die Ansichten der vorherigen Anwendung: formulaire.aspx und erreurs.aspx
6.2.6. Die Anwendungs-Controller [global.asax, main.aspx]
Nur der Controller [global.asax] muss geändert werden. Er ist nämlich dafür zuständig, beim Start der Anwendung das Objekt [impot] zu erstellen. Der Konstruktor dieses Objekts hat als einzigen Parameter das Objekt vom Typ [impotsData], das für das Abrufen der Daten zuständig ist. Dieser Parameter ändert sich daher für jeden neuen Typ von Datenquellen. 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)
' ein Steuerobjekt wird angelegt
Dim objImpot As impot
Try
objImpot = New impot(New impotsODBC(ConfigurationSettings.AppSettings("DSNimpots")))
' Das Objekt wird in die Anwendung übernommen
Application("objImpot") = objImpot
' kein Fehler
Application("erreur") = False
Catch ex As Exception
'Es ist ein Fehler aufgetreten, der in der Anwendung vermerkt wird
Application("erreur") = True
Application("message") = ex.Message
End Try
End Sub
End Class
Die Datenquelle des Objekts [impot] ist nun ein Objekt [impotODBC]. Dieses hat als Parameter den Namen DSN der zu verarbeitenden Datenquelle ODBC. Anstatt diesen Namen fest im Code zu hinterlegen, wird er in die Konfigurationsdatei [web.config] der Anwendung aufgenommen:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="DSNimpots" value="odbc-mysql-dbimpots" />
</appSettings>
</configuration>
Es ist bekannt, dass der Wert eines Schlüssels C aus dem Abschnitt <appSettings> der Datei [web.config] im Anwendungscode durch [ConfigurationSettings.AppSettings(C)] ermittelt wird.
Um die Ursache der Ausnahme zu ermitteln, wird die Ausnahmemeldung in der Anwendung protokolliert, 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
' Zunächst wird geprüft, ob die Anwendung korrekt initialisiert werden konnte
If CType(Application("erreur"), Boolean) Then
' Man leitet auf die Fehlerseite weiter
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
' Die auszuführende Aktion wird abgerufen
...
6.2.7. Übersicht über die Änderungen
Die Anwendung ist bereit für den Test. Hier sind die Änderungen gegenüber der vorherigen Version aufgeführt:
- Es wurde eine neue Klasse für den Datenzugriff erstellt
- Der Controller [global.asax.vb] wurde an zwei Stellen geändert: beim Erstellen des Objekts [impot] und beim Speichern der Meldung zu einer möglichen Ausnahme in der Anwendung
- Der Controller [main.aspx.vb] wurde an einer Stelle geändert, um die oben genannte Ausnahmemeldung anzuzeigen
- Eine Datei mit dem Namen [web.config] wurde hinzugefügt
Die Änderungsarbeiten erfolgten im Wesentlichen in 1, c.a.d, außerhalb der Webanwendung. Dies wurde durch die Architektur MVC der Anwendung ermöglicht, die den Controller von den Geschäftsklassen trennt. Darin liegt der wesentliche Vorteil dieser Architektur. Man könnte zeigen, dass mit einer geeigneten Konfigurationsdatei jegliche Änderung am Controller der Anwendung hätte vermieden werden können. Es ist möglich, in der Konfigurationsdatei den Namen der dynamisch zu instanziierenden Datenzugriffsklasse sowie die verschiedenen für diese Instanziierung erforderlichen Parameter anzugeben. Mit diesen Informationen kann [global.asax] das Datenzugriffsobjekt instanziieren. Ein Wechsel der Datenquelle läuft dann darauf hinaus,
- die Zugriffsklasse für diese Quelle anzulegen, 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 <application-path> abgelegt.

In diesem Ordner wird ein Unterordner [bin] angelegt, in dem die Assembly [impot.dll] abgelegt wird, die aus der Kompilierung der Geschäftsclass-Dateien ([impots.vb, impotsData.vb, impotsArray.vb, impotsODBC.vb]) stammt. Nachfolgend wird der erforderliche Kompilierungsbefehl noch einmal aufgeführt:
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 <application-path>\bin abgelegt werden, damit die Webanwendung darauf zugreifen kann. Der Cassini-Server wird mit den Parametern (<application-path>,/impots2) gestartet. Die Tests liefern dieselben Ergebnisse wie in der vorherigen Version, wobei das Vorhandensein der Datenbank für den Benutzer transparent ist. Um dies dennoch zu veranschaulichen, sorgen wir dafür, dass die Quelle ODBC nicht verfügbar ist, indem wir SGBD und MySQL anhalten und die URL [http://localhost/impots2/main.aspx] aufrufen. Wir erhalten folgende Antwort:

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

6.3.3. Die Klasse für den Datenzugriff
Kehren wir zur Struktur MVC unserer Anwendung zurück:

- In dem obigen Schema ist die Klasse [impotsData] für das Abrufen der Daten zuständig. Diesmal muss sie die Daten aus einer Quelle OLEDB beziehen.
Wir erstellen die Klasse [impotsOLEDB], die die Daten (Grenzwerte, Koeffizient a, Koeffizient n) aus einer Quelle ODBC abruft, der wir den Namen
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
' Instanzvariablen
Protected chaineConnexion As String
' Konstruktor
Public Sub New(ByVal chaineConnexion As String)
' die drei Informationen werden notiert
Me.chaineConnexion = chaineConnexion
End Sub
Public Overrides Function getData() As Object()
' Initialisiert die drei Arrays „limites“, „coeffr“ und „coeffn“ anhand
' des Inhalts der Tabelle [impots] aus der Datenbank OLEDB [chaineConnexion]
' „limites“, „coeffr“ und „coeffn“ sind die drei Spalten dieser Tabelle
' kann verschiedene Ausnahmen auslösen
' Es wird ein Objekt DataAdapter erstellt, um die Daten aus der Quelle OLEDB zu lesen
Dim adaptateur As New OleDbDataAdapter("select limites,coeffr,coeffn from impots", chaineConnexion)
' Es wird ein Speicherabbild des Ergebnisses der SELECT-Anweisung erstellt
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
' Der Inhalt der Tabelle „impots“ wird abgerufen
Dim lignesImpots As DataRowCollection = contenu.Rows
' Die Empfangstabellen werden dimensioniert
Me.limites = New Decimal(lignesImpots.Count - 1) {}
Me.coeffr = New Decimal(lignesImpots.Count - 1) {}
Me.coeffn = New Decimal(lignesImpots.Count - 1) {}
' Der Inhalt der Tabelle „impots“ wird in die Arrays übertragen
Dim i As Integer
Dim ligne As DataRow
Try
For i = 0 To lignesImpots.Count - 1
' Zeile i der Tabelle
ligne = lignesImpots.Item(i)
' Der Inhalt der Zeile wird abgerufen
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
' Die abgerufenen Daten werden überprüft
Dim erreur As Integer = checkData()
' Wenn die Daten ungültig sind, wird eine Ausnahme ausgelöst
If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
' ansonsten werden die drei Arrays zurückgegeben
Return New Object() {limites, coeffr, coeffn}
End Function
End Class
End Namespace
Werfen wir einen Blick auf den Hersteller:
' Konstruktor
Public Sub New(ByVal chaineConnexion As String)
' Die drei Informationen werden notiert
Me.chaineConnexion = chaineConnexion
End Sub
Er erhält als Parameter die Verbindungszeichenfolge der Quelle OLEDB, in der sich die zu erfassenden Daten befinden. Der Konstruktor speichert diese lediglich ab. Eine Verbindungszeichenfolge enthält alle Parameter, die der Treiber OLEDB benötigt, um eine Verbindung zur Quelle OLEDB herzustellen. Sie ist in der Regel recht komplex. Um die Verbindungszeichenfolge der Datenbanken ACCESS zu ermitteln, kann man das Tool [WebMatrix] zu Hilfe nehmen. Man startet dieses Tool. Es öffnet ein Fenster, über das man eine Verbindung zu einer Datenquelle herstellen kann:
![]() ![]() | ![]() ![]() |
Über das mit dem Pfeil oben markierte Symbol kann eine Verbindung zu zwei Arten von Microsoft-Datenbanken hergestellt werden: SQL Server und ACCESS. Wählen wir ACCESS aus:

Wir haben die Schaltfläche „[...]“ verwendet, um die Datenbank „ACCESS“ auszuwählen. Wir bestätigen den Assistenten. Auf der Registerkarte „[Data]“ symbolisieren Symbole die Verbindung:

Erstellen wir nun eine neue .aspx-Datei mit [Files/New File]:

Wir erhalten ein leeres Blatt, auf dem wir unsere Weboberfläche entwerfen können:

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

Klicken wir mit der rechten Maustaste auf das Objekt [AccessDataSourceControl] unten, um Zugriff auf dessen Eigenschaften zu erhalten:

Die Verbindungszeichenfolge OLEDB zur Datenbank ACCESS wird durch die oben genannte Eigenschaft [ConnectionString] angegeben:
Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=D:\data\serge\devel\aspnet\poly\chap5\impots\3\impots.mdb
Man sieht, dass diese Zeichenfolge aus einem festen Teil und einem variablen Teil besteht, bei dem es sich ganz einfach um den Dateinamen ACCESS handelt. Diese Tatsache werden wir nutzen, um die Verbindungszeichenfolge zu unserer Datenquelle OLEDB zu generieren.
Kehren wir nun zu unserer Klasse [impotsOLEDB] zurück. Die Methode [getData] ist dafür zuständig, die Daten aus der Tabelle [impots] zu lesen und sie in drei Arrays (limites, coeffr, coeffn) zu speichern. Kommentieren wir den Code:
- Wir definieren das Objekt [DataAdapter], mit dem wir das Ergebnis einer SELECT-Abfrage SQL in den Speicher übertragen können. Dazu definieren wir die auszuführende Abfrage [select] und verknüpfen sie mit dem Objekt [DataAdapter]. Der Konstruktor dieses Objekts benötigt außerdem die Verbindungszeichenfolge, die für die Verbindung zur Quelle OLEDB verwendet werden soll
' Es wird ein Objekt DataAdapter erstellt, um die Daten aus der Quelle OLEDB zu lesen
Dim adaptateur As New OleDbDataAdapter("select limites,coeffr,coeffn from impots", chaineConnexion)
- Der Befehl [select] wird mithilfe der Methode [Fill] des Objekts [DataAdapter] ausgeführt. Das Ergebnis von [select] wird in ein eigens dafür erstelltes Objekt [DataTable] eingefügt. Ein Objekt [DataTable] ist das Speicherabbild einer Datenbanktabelle, c.a.d, also eine Ansammlung von Zeilen und Spalten. Wir behandeln eine Ausnahme, die auftreten kann, wenn beispielsweise die Verbindungszeichenfolge falsch ist.
' Es wird ein Speicherabbild des Ergebnisses der SELECT-Abfrage erstellt
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 [contenu] befindet sich die Tabelle [impots], die von [select] abgerufen wurde. Ein Objekt [DataTable] ist eine Tabelle und somit eine Sammlung von Zeilen. Auf diese kann über die Eigenschaft [rows] von [datatable] zugegriffen werden:
' Der Inhalt der Tabelle „impots“ wird abgerufen
Dim lignesImpots As DataRowCollection = contenu.Rows
- Jedes Element der Sammlung [lignesImpots] ist ein Objekt vom Typ [DataRow], das eine Zeile der Tabelle darstellt. Diese verfügt über Spalten, auf die über das Objekt [DataRow] mittels seiner Eigenschaft [Item] zugegriffen werden kann. [DataRow].[Item(i)] ist die Spalte Nr. i der Zeile [DataRow]. Durch Durchlaufen der Zeilensammlung (die Sammlung DataRows von lignesImpots) und der Spaltensammlungen jeder Zeile lässt sich die gesamte Tabelle abrufen:
' Die Empfangstabellen werden dimensioniert
Me.limites = New Decimal(lignesImpots.Count - 1) {}
Me.coeffr = New Decimal(lignesImpots.Count - 1) {}
Me.coeffn = New Decimal(lignesImpots.Count - 1) {}
' Der Inhalt der Tabelle „impots“ wird in die Arrays übertragen
Dim i As Integer
Dim ligne As DataRow
Try
For i = 0 To lignesImpots.Count - 1
' Zeile i der Tabelle
ligne = lignesImpots.Item(i)
' Der Inhalt der Zeile wird abgerufen
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 [impots] in den drei Arrays angekommen sind, muss nur noch deren Inhalt mithilfe der Methode [checkData] der Basisklasse [impotsData] überprüft werden:
' Die abgerufenen Daten werden überprüft
Dim erreur As Integer = checkData()
' Wenn die Daten ungültig sind, wird eine Ausnahme ausgelöst
If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
' ansonsten werden die drei Arrays zurückgegeben
Return New Object() {limites, coeffr, coeffn}
6.3.4. Tests der Datenzugriffsklasse
Ein Testprogramm könnte wie folgt aussehen:
Option Explicit On
Option Strict On
' Namensräume
Imports System
Imports Microsoft.VisualBasic
Namespace st.istia.univangers.fr
' Testseite
Module testimpots
Sub Main(ByVal arguments() As String)
' Interaktives Programm zur Steuerberechnung
' Der Benutzer gibt drei Daten über die Tastatur ein: verheiratet nbEnfants Gehalt
' Das Programm zeigt daraufhin die zu zahlende Steuer an
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"
' Überprüfung der Programmparameter
If arguments.Length <> 1 Then
' Fehlermeldung
Console.Error.WriteLine(syntaxe1)
' Ende
Environment.Exit(1)
End If
' Die Argumente werden abgerufen
Dim chemin As String = arguments(0)
' Vorbereitung der Verbindungszeichenfolge
Dim chaineConnexion As String = "Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=" + chemin
' Erstellung eines Steuerobjekts
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
' Endlosschleife
While True
' zunächst keine Fehler
Dim erreur As Boolean = False
' Es werden die Parameter für die Steuerberechnung abgefragt
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()
' Muss etwas unternommen werden?
If paramètres Is Nothing Or paramètres = "" Then
Exit While
End If
' Überprüfung der Anzahl der Argumente in der eingegebenen Zeile
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
' Überprüfung der Gültigkeit der Parameter
' verheiratet
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
' Gehalt
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
' Die Parameter sind korrekt – die Steuer wird berechnet
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 verarbeitenden Datei ACCESS
Die Steuerberechnung erfolgt mithilfe eines Objekts vom Typ [impot], das beim Start der Anwendung angelegt wird:
' die Argumente werden abgerufen
Dim chemin As String = arguments(0)
' die Verbindungszeichenfolge wird vorbereitet
Dim chaineConnexion As String = "Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=" + chemin
' Erstellung eines Steuerobjekts
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 Verbindungskette zur Quelle OLEDB wurde anhand der mit [WebMatrix] ermittelten Informationen erstellt.
Nach der Initialisierung fordert die Anwendung den Benutzer wiederholt auf, die drei Angaben, die zur Berechnung seiner Steuer benötigt werden, über die Tastatur einzugeben:
- seinen Familienstand: o für verheiratet, n für unverheiratet
- die Anzahl seiner Kinder
- sein 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 diese 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)
Die Anwendung kann auch mit einer fehlerhaften ACCESS-Datei gestartet werden:
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
Es handelt sich um die Ansichten der vorherigen Anwendung: formulaire.aspx und erreurs.aspx
6.3.6. Die Anwendungs-Controller [global.asax, main.aspx]
Nur der Controller [global.asax] muss geändert werden. Er ist nämlich dafür zuständig, beim Start der Anwendung das Objekt [impot] zu erstellen. Der Konstruktor dieses Objekts hat als einzigen Parameter das Objekt vom Typ [impotsData], das für das Abrufen der Daten zuständig ist. Dieser Parameter ändert sich also, da die Datenquelle gewechselt wird. 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)
' Ein Steuerobjekt wird angelegt
Dim objImpot As impot
Try
objImpot = New impot(New impotsOLEDB(ConfigurationSettings.AppSettings("chaineConnexion")))
' Das Objekt wird in die Anwendung eingefügt
Application("objImpot") = objImpot
' Kein Fehler
Application("erreur") = False
Catch ex As Exception
'Es ist ein Fehler aufgetreten, der in der Anwendung vermerkt wird
Application("erreur") = True
Application("message") = ex.Message
End Try
End Sub
End Class
Die Datenquelle des Objekts [impot] ist nun ein Objekt [impotOLEDB]. Letzteres hat als Parameter die Verbindungszeichenfolge der zu verwendenden Datenquelle OLEDB. Diese ist in der Konfigurationsdatei [web.config] der Anwendung hinterlegt:
<?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 aufgeführt:
- Es wurde eine neue Klasse für den Datenzugriff erstellt
- Der Controller [global.asax.vb] wurde an einer Stelle geändert: Erstellung des Objekts [impot]
- eine Datei [web.config] wurde hinzugefügt
6.3.8. Test der Webanwendung
Alle oben genannten Dateien werden in einem Ordner <application-path> abgelegt.

In diesem Ordner wird ein Unterordner [bin] angelegt, in dem die Assembly [impot.dll] abgelegt wird, die aus der Kompilierung der Geschäftsclass-Dateien [impots.vb, impotsData.vb, impotsArray.vb, impotsOLEDB.vb] stammt. Nachfolgend wird der erforderliche Kompilierungsbefehl noch einmal aufgeführt:
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 dieselben Ergebnisse wie in der vorherigen Version.
6.4. Beispiel 4
6.4.1. Das Problem
Wir wollen nun unsere Anwendung in eine Anwendung zur Simulation von Steuerberechnungen umwandeln. Ein Benutzer kann nacheinander Steuerberechnungen durchführen, die ihm in einer neuen Ansicht wie dieser angezeigt werden:

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

Es erscheint eine neue Ansicht [simulations.aspx], von der wir gerade einen Screenshot gezeigt 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. Der Steuerbetrag wird nun nicht mehr in dieser Ansicht angezeigt. Er befindet sich nun in der Ansicht [simulations.aspx]. Beim Start wird dem Benutzer somit folgende Seite angezeigt:

Außerdem enthält die Ansicht [formulaire] ein JavaScript-Skript, das die Gültigkeit der eingegebenen Daten überprüft, bevor diese an den Server gesendet werden, wie das folgende Beispiel zeigt:

Der Darstellungscode lautet wie folgt:
<%@ page src="formulaire.aspx.vb" inherits="formulaire" AutoEventWireup="false"%>
<html>
<head>
<title>Impôt</title>
<script language="javascript">
function calculer(){
// Überprüfung der Parameter vor dem Senden an den Server
with(document.frmImpots){
//Anzahl der Kinder
champs=/^\s*(\d+)\s*$/.exec(txtEnfants.value);
if(champs==null){
// Das Modell wurde nicht überprüft
alert("Le nombre d'enfants n'a pas été donné ou est incorrect");
txtEnfants.focus();
return;
}//if
//Gehalt
champs=/^\s*(\d+)\s*$/.exec(txtSalaire.value);
if(champs==null){
// Das Modell wurde nicht überprüft
alert("Le salaire n'a pas été donné ou est incorrect");
txtSalaire.focus();
return;
}//if
// Alles in Ordnung – das Formular wird an den Server gesendet
submit();
}//mit
}//berechnen
</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 der Seite entsprechen denen der Vorgängerversionen. Das dynamische Feld für den Steuerbetrag ist nicht mehr vorhanden. Die Schaltfläche [Calculer] ist keine Schaltfläche vom Typ [submit] mehr. Sie ist vom Typ [button], und wenn man darauf klickt, wird die JavaScript-Funktion [calculer()] ausgeführt:
<INPUT type="button" value="Calculer" onclick="calculer()">
Dem Formular wurde der Name [frmImpots] zugewiesen, um es im Skript [calculer] referenzieren zu können:
<form name="frmImpots" method="post" action="main.aspx?action=calcul">
Die JavaScript-Funktion [calculer] verwendet reguläre Ausdrücke, um die Gültigkeit der Felder der Formulare [document.frmImpots.txtEnfants] und [document.frmImpots.txtSalaire] zu überprüfen. Sind die eingegebenen Werte korrekt, werden sie von [document.frmImpots.submit()] an den Server gesendet.
Die Anzeigeseite bezieht ihre dynamischen Felder von ihrem Controller [formulaire.aspx.vb] wie folgt:
Imports System.Collections.Specialized
Public Class formulaire
Inherits System.Web.UI.Page
' Felder der Seite
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
' die vorherige Anfrage wird aus dem Kontext abgerufen
Dim form As NameValueCollection = Context.Items("formulaire")
' die anzuzeigende Seite wird vorbereitet
' Optionsfelder
rdouichecked = ""
rdnonchecked = "checked"
If form("rdMarie").ToString = "oui" Then
rdouichecked = "checked"
rdnonchecked = ""
End If
' der Rest
txtEnfants = CType(form("txtEnfants"), String)
txtSalaire = CType(form("txtSalaire"), String)
End Sub
End Class
Der Controller [formulaire.aspx.vb] ist identisch mit den vorherigen Versionen, mit der Ausnahme, 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 optisch wie folgt aus:

und entspricht dem folgenden Darstellungscode:
<%@ 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:
Code HTML aus einer Liste von Simulationen in Form von Tabellenzeilen HTML | |
URL eines Links | |
Text des Links |
Sie werden vom Controller-Teil [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
': Die Simulationen werden aus dem Kontext abgerufen
Dim simulations As ArrayList = CType(context.Items("simulations"), ArrayList)
' Jede Simulation ist ein Array mit 4 String-Elementen
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
' Die übrigen Elemente des Kontexts werden abgerufen
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:
Objekt ArrayList, das die Liste der anzuzeigenden Simulationen enthält. Jedes Element ist ein Array aus 4 Zeichenketten, die die Informationen (verheiratet, Kinder, Gehalt, Steuern) der Simulation darstellen. | |
URL eines Links | |
Text des Links |
6.4.4. Die Controller [global.asax, main.aspx]
Zur Erinnerung: Das Schema MVC unserer Anwendung:

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 des Eingabeformulars korrekt, wird die Steuer mithilfe der Geschäftsklasse [impotsODBC] berechnet. Der Controller gibt die Ansicht [simulations.aspx] mit dem Ergebnis der aktuellen Simulation sowie aller vorherigen Simulationen an den Client zurück. Sind die Daten im Eingabeformular fehlerhaft, gibt der Controller die Ansicht [erreurs.aspx] mit einer Fehlerliste und einem Link zur Rückkehr zum Formular zurück.
- Zurück: Bezieht sich auf die Rückkehr zum Formular nach einem Fehler. Der Controller zeigt die Ansicht [formulaire.aspx] so an, wie sie vor dem Fehler validiert wurde.
In dieser neuen Version hat sich lediglich die Aktion [calcul] geändert. Sind die Daten gültig, muss sie nun zur Ansicht [simulations.aspx] führen, während sie zuvor zur Ansicht [formulaire.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
' Zunächst wird geprüft, ob die Anwendung korrekt initialisiert werden konnte
...
' Die Aktion wird ausgeführt
Select Case action
Case "init"
' Anwendung initialisieren
initAppli()
Case "calcul"
' Steuerberechnung
calculImpot()
Case "retour"
' Zurück zum Formular
retourFormulaire()
Case "effacer"
' Anwendung initialisieren
initAppli()
Case Else
' unbekannte Aktion = Initialisierung
initAppli()
End Select
End Sub
...
Private Sub calculImpot()
' Eingaben werden gespeichert
Session.Item("formulaire") = Request.Form
' Gültigkeit der eingegebenen Daten wird überprüft
Dim erreurs As ArrayList = checkData()
' Falls Fehler vorliegen, werden diese gemeldet
If erreurs.Count <> 0 Then
' Die Fehlerseite wird vorbereitet
context.Items("href") = "main.aspx?action=retour"
context.Items("lien") = "Retour au formulaire"
context.Items("erreurs") = erreurs
Server.Transfer("erreurs.aspx")
End If
' Hier keine Fehler – die Steuer wird berechnet
Dim impot As Long = CType(Application("objImpot"), impot).calculer( _
Request.Form("rdMarie") = "oui", _
CType(Request.Form("txtEnfants"), Integer), _
CType(Request.Form("txtSalaire"), Long))
' Das Ergebnis wird zu den bestehenden Simulationen hinzugefügt
Dim simulations As ArrayList
If Not Session.Item("simulations") Is Nothing Then
simulations = CType(Session.Item("simulations"), ArrayList)
Else
simulations = New ArrayList
End If
' Hinzufügen der aktuellen Simulation
Dim simulation() As String = New String() {Request.Form("rdMarie").ToString, _
Request.Form("txtEnfants").ToString, Request.Form("txtSalaire").ToString, _
impot.ToString}
simulations.Add(simulation)
' Die Simulationen werden in die Sitzung und den Kontext übernommen
context.Items("simulations") = simulations
Session.Item("simulations") = simulations
' die Ergebnisseite wird angezeigt
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 beibehalten, was zum Verständnis der Änderungen notwendig war, die ausschließlich in der Funktion [calculImpots] enthalten sind:
- Zunächst speichert die Funktion das Formular [Request.Form] in der Sitzung, um das Formular in dem Zustand neu generieren zu können, in dem es freigegeben wurde. Dies ist in jedem Fall erforderlich, da man – unabhängig davon, ob der Vorgang zur Antwort [erreurs.aspx] oder zur Antwort [simulations.aspx] führt – über den Link [Retour au formulaire] zum Formular zurückkehrt. Um das Formular korrekt wiederherzustellen, müssen die Werte zuvor in der Sitzung gespeichert worden sein.
- Sind die eingegebenen Daten korrekt, fügt die Funktion die aktuelle Simulation (verheiratet, Kinder, Gehalt, Steuern) in die Liste der Simulationen ein. Diese befindet sich in der Sitzung, die mit dem Schlüssel „simulations“ verknüpft ist.
- Die Liste der Simulationen wird zur späteren Verwendung wieder in die Sitzung zurückgeschrieben. Sie wird außerdem in den aktuellen Kontext gestellt, da sie dort von der Ansicht [simulations.aspx] erwartet wird.
- Die Ansicht [simulations.aspx] wird angezeigt, sobald die übrigen Informationen, auf die sie wartet, in den Kontext eingefügt wurden
6.4.5. Zusammenfassung der Änderungen
Die Anwendung ist bereit zum Testen. Hier sind die Änderungen gegenüber den Vorgängerversionen aufgeführt:
- Es wurde eine neue Ansicht erstellt
- Der Controller [main.aspx.vb] wurde an einer Stelle geändert: Verarbeitung der Aktion [calcul]
6.4.6. Test der Webanwendung
Der Leser wird gebeten, die Tests durchzuführen. Hier noch einmal die Vorgehensweise: Alle Dateien der Anwendung befinden sich in einem Ordner <application-path>. In diesem Ordner wird ein Unterordner [bin] angelegt, in dem sich die Assembly [impot.dll] befindet, die aus der Kompilierung der Geschäftsclass-Dateien stammt: [impots.vb, impotsData.vb, impotsArray.vb, impotsODBC.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>,/impots4) gestartet.
6.5. Conclusion
Die vorangegangenen Beispiele haben anhand eines konkreten Falls die in der Webentwicklung häufig verwendeten Mechanismen veranschaulicht. Wir haben aus didaktischen Gründen durchgehend die Architektur MVC verwendet. Man hätte dieselben Beispiele auch anders und vielleicht einfacher ohne diese Architektur bearbeiten können. Diese bietet jedoch große 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 hinweg speichern. Er würde beispielsweise am Tag J Simulationen durchführen und könnte diese dann am Tag J+3 wiederfinden. 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. Man könnte diesen Mechanismus auch nutzen, um die Simulationen zwischen dem Client und dem Server zu übertragen.
- Gleichzeitig mit dem Versand der Ergebnisseite der Simulationen sendet der Server in seinen Headern „HTTP“ ein Cookie, das eine Zeichenkette enthält, die die Simulationen repräsentiert. Da sich diese in einem Objekt vom Typ [ArrayList] befinden, muss dieses Objekt in das Format [String] umgewandelt werden. Der Server würde dem Cookie eine Gültigkeitsdauer zuweisen, beispielsweise 30 Tage.
- Der Client-Browser speichert die empfangenen Cookies in einer Datei und sendet sie bei jeder Anfrage an den Server, der sie ihm gesendet hat, zurück, sofern sie noch gültig sind (Lebensdauer noch nicht abgelaufen). Der Server erhält für die Simulationen eine Zeichenfolge „[String]“, die er in das Objekt „[ArrayList]“ umwandeln muss.
Die Cookies werden beim Versand an den Client von [Response.Cookies] und beim Empfang auf dem Server von [Request.Cookies] verwaltet.
- Der oben beschriebene Mechanismus kann bei einer großen Anzahl von Simulationen recht aufwendig werden. Außerdem kommt es häufig vor, dass ein Nutzer seine Cookies regelmäßig bereinigt, indem er sie alle löscht, auch wenn er seinem Browser ansonsten die Verwendung dieser Cookies erlaubt. Daher geht das Cookie für die Simulationen früher oder später verloren. Man könnte diese daher lieber auf dem Server als auf dem Client speichern, beispielsweise in einer Datenbank. Um Simulationen einem bestimmten Nutzer zuzuordnen, könnte die Anwendung mit einer Identifizierungsphase beginnen, bei der ein Benutzername und ein Passwort abgefragt werden, die wiederum in einer Datenbank oder einem anderen Datenspeicher abgelegt sind.
- Außerdem möchten wir möglicherweise den Betrieb unserer Anwendung absichern. Diese geht derzeit von zwei Annahmen aus:
- Der Benutzer durchläuft immer den Controller [main.aspx]
- und nutzt in diesem Fall immer die Aktionen, die auf der Seite angeboten werden, die ihm gesendet wurde
Was passiert beispielsweise, wenn der Benutzer direkt die URL [http://localhost/impots4/formulaire.aspx] aufruft? Dieser Fall ist zwar unwahrscheinlich, da der Benutzer die Existenz dieser URL nicht kennt. Dennoch muss er einkalkuliert werden. Er kann durch den Anwendungs-Controller [global.asax] abgewickelt werden, der alle an die Anwendung gerichteten Anfragen abfängt. So kann er überprüfen, ob die angeforderte Ressource tatsächlich [main.aspx] ist.
Wahrscheinlicher ist jedoch, dass ein Benutzer die auf der Seite vorhandenen Aktionen nicht nutzt, die der Server ihm gesendet hat. 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 folgende Antwort:

Der Server ist abgestürzt. Das ist normal. Für die Aktion [retour] erwartet der Controller, in der Sitzung ein Objekt [NameValueCollection] zu finden, das die Werte des Formulars enthält, das er anzeigen soll. Er findet es jedoch nicht. Der Mechanismus des Controllers ermöglicht eine elegante Lösung für dieses Problem. Bei jeder Anfrage kann der Controller [main.aspx] überprüfen, ob die angeforderte Aktion tatsächlich eine der Aktionen der zuvor an den Benutzer gesendeten Seite ist. Man kann den folgenden Mechanismus verwenden:
- Der Controller speichert, bevor er seine Antwort an den Client sendet, eine Information zur Identifizierung dieser Seite 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 Basis haben und dass es möglich ist, einen generischen Controller zu erstellen, dessen Spezialisierung für eine bestimmte Anwendung über eine Konfigurationsdatei erfolgt. Diesen Ansatz verfolgt beispielsweise das Tool [Struts] im Bereich der Java-Webprogrammierung.



