Skip to content

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

    8382        0.0683        291.09

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:

    0                0.4809    9505.54

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:

Image

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:

limites
Tabelle der Steuerklassengrenzen
coeffr
Tabelle der auf das steuerpflichtige Einkommen angewandten Koeffizienten
coeffn
Tabelle der auf die Anzahl der Anteile angewandten Koeffizienten
checked
Boolescher Wert, der angibt, ob die Daten (Grenzwerte, Koeffizient a, Koeffizient b) überprüft wurden
valide
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>vbc /r:impot.dll testimpots1.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4
dos>dir
05/04/2004  13:28                1 337 impots.vb
21/04/2004  08:23                1 311 impotsArray.vb
21/04/2004  08:26                1 634 impotsData.vb
21/04/2004  08:42                2 490 testimpots1.vb
21/04/2004  09:21                5 632 impot.dll
21/04/2004  09:23                4 608 testimpots1.exe

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:

Image

Der Benutzer füllt das Formular aus:

Image

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

Image

Er kann sich bei der Eingabe der Daten irren:

Image

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

Image

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:

Image

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:

rdouichecked
„checked“, wenn das Kontrollkästchen [oui] angekreuzt werden soll, andernfalls „“
rdnonchecked
das Gleiche gilt für das Kontrollkästchen [non]
txtEnfants
Wert, der in das Eingabefeld [txtEnfants] eingegeben werden soll
txtSalaire
Wert, der in das Eingabefeld [txtSalaire] einzutragen ist
txtImpot
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:

erreursHTML
Code HTML aus einer Fehlerliste
href
URL eines Links
lien
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:

Context.Items("erreurs"
Objekt ArrayList, das die Liste der anzuzeigenden Fehlermeldungen enthält
Context.Items("href"
URL eines Links
Context.Items("lien"
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:

Image

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]

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

[global.asax.vb]


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

Public Class Global
    Inherits System.Web.HttpApplication

    Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' 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]

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

[main.aspx.vb]


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

Public Class main
    Inherits System.Web.UI.Page

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' 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.

Image

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:

Image

Wir füllen das Formular aus:

Image

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

Image

Anschließend geben wir falsche Daten ein:

Image

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

Image

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

Image

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

Image

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:

Image

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:

Image

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).

Image

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:

  1. die Klassen SQL und Server.NET für den Zugriff auf Microsoft SQL Server-Datenbanken
  2. die Ole-Klassen Db.NET für den Zugriff auf Datenbanken von SGBD, die einen Treiber OLE DB bereitstellen
  3. 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:

Image

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:

Image

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:

Image

Es müssen einige Angaben gemacht werden:

Data Source Name
den Namen, der die Datenquelle ODBC bezeichnet. Jede Windows-Anwendung kann über diesen Namen auf die Quelle zugreifen
Description
Ein beliebiger Text zur Beschreibung der Datenquelle
Host Name
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.
Database Name
Ein SGBD MySQL kann mehrere Datenbanken verwalten. Hier wird angegeben, welche verwaltet werden soll: dbimpots
User
Name eines Benutzers, der in den Systemen SGBD und MySQL registriert ist. Der Zugriff auf die Datenquelle erfolgt unter diesem Namen. Hier: admimpots
Password
Das Passwort dieses Benutzers. Hier: mdpimpots
Port
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]:

Image

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:

Image

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

Image

Folgende Angaben sind erforderlich:

Nom de la source de données
Der Name, der die Datenquelle ODBC bezeichnet. Jede Windows-Anwendung kann über diesen Namen auf die Quelle zugreifen
Description
Ein beliebiger Text zur Beschreibung der Datenquelle
Base de données
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:

Image

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:
                 ' Freigabe der Ressourcen
                myReader.Close()
                impotsConn.Close()
  • 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:

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

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:

  1. Es wurde eine neue Klasse für den Datenzugriff erstellt
  2. 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
  3. Der Controller [main.aspx.vb] wurde an einer Stelle geändert, um die oben genannte Ausnahmemeldung anzuzeigen
  4. 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.

Image

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:

Image

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:

Image

6.3.3. Die Klasse für den Datenzugriff

Kehren wir zur Struktur MVC unserer Anwendung zurück:

Image

  • 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:

Image

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:

Image

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

Image

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

Image

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

Image

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

Image

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
dos>vbc /r:impot.dll testimpots.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:

  1. Es wurde eine neue Klasse für den Datenzugriff erstellt
  2. Der Controller [global.asax.vb] wurde an einer Stelle geändert: Erstellung des Objekts [impot]
  3. eine Datei [web.config] wurde hinzugefügt

6.3.8. Test der Webanwendung

Alle oben genannten Dateien werden in einem Ordner <application-path> abgelegt.

Image

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:

Image

6.4.2. Die Struktur MVC der Anwendung

Die Struktur MVC der Anwendung sieht nun wie folgt aus:

Image

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:

Image

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:

Image

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:

Image

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:

simulationsHTML
Code HTML aus einer Liste von Simulationen in Form von Tabellenzeilen HTML
href
URL eines Links
lien
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:

Context.Items("simulations")
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.
Context.Items("href")
URL eines Links
Context.Items("lien")
Text des Links

6.4.4. Die Controller [global.asax, main.aspx]

Zur Erinnerung: Das Schema MVC unserer Anwendung:

Image

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:

  1. Es wurde eine neue Ansicht erstellt
  2. 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:

Image

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.