Skip to content

6. Beispiele

In diesem Kapitel werden wir das bisher Gesehene anhand einer Reihe von Beispielen veranschaulichen.

6.1. Beispiel 1

6.1.1. Das Problem

Diese Anwendung muss es einem Benutzer ermöglichen, seine Steuern zu berechnen. Wir betrachten den vereinfachten Fall eines Steuerpflichtigen, der nur sein Gehalt anzugeben hat (Zahlen von 2004 für das Einkommen von 2003):

  • Wir berechnen die Anzahl der Steuerklassen für den Arbeitnehmer als nbParts = nbEnfants / 2 + 1, wenn er unverheiratet ist, und als nbEnfants / 2 + 2, wenn er verheiratet ist, wobei nbEnfants die Anzahl der Kinder ist.
  • Hat er mindestens drei Kinder, hat er Anspruch auf einen zusätzlichen halben Anteil
  • Sein zu versteuerndes Einkommen R berechnet sich als R = 0,72 * S, wobei S sein Jahresgehalt ist
  • Wir berechnen seinen Familienkoeffizienten QF = R / nbParts
  • Wir berechnen seine Steuer I. Betrachten Sie die folgende Tabelle:
4262
0
0
8382
0,0683
291,09
14.753
0,1914
1.322,92
23.888
0,2826
2.668,39
38.868
0,3738
4.846,98
47.932
0,4262
6.883,66
0
0,4809
9505,54

Jede Zeile hat 3 Felder. Um die Steuer I zu berechnen, suche die erste Zeile, in der QF <= Feld1 ist. Wenn beispielsweise QF = 5000 ist, lautet die gefundene Zeile

    8382        0.0683        291.09

Steuer I ist dann gleich 0,0683*R – 291,09*nbParts. Wenn QF so ist, dass die Bedingung QF <= Feld1 nie erfüllt ist, werden die Koeffizienten aus der letzten Zeile verwendet. Hier:

    0                0.4809    9505.54

was zu Steuer I = 0,4809*R - 9505,54*nbParts führt.

6.1.2. Die MVC-Struktur der Anwendung

Die MVC-Struktur der Anwendung sieht wie folgt aus:

Image

Die Controller-Rolle wird von der Seite [main.aspx] übernommen. Es gibt drei mögliche Aktionen:

  • init: entspricht der ersten Anfrage des Clients. Der Controller zeigt die Ansicht [formulaire.aspx] an
  • calcul: entspricht der Anfrage zur Berechnung der Steuer. Sind die Daten im Eingabeformular korrekt, wird die Steuer mithilfe der Business-Klasse [impots] berechnet. Der Controller gibt die Ansicht [form.aspx] so an den Client zurück, wie sie validiert wurde, zusammen mit der berechneten Steuer. Sind die Daten im Eingabeformular fehlerhaft, gibt der Controller die Ansicht [errors.aspx] mit einer Liste der Fehler und einem Link zur Rückkehr zum Formular zurück.
  • return: entspricht der Rückkehr zum Formular nach einem Fehler. Der Controller zeigt die Ansicht [form.aspx] so an, wie sie vor dem Fehler validiert wurde.

Der Controller [main.aspx] weiß nichts über Steuerberechnungen. Er ist lediglich für die Verwaltung des Client-Server-Dialogs und die Ausführung der vom Client angeforderten Aktionen zuständig. Für die Aktion [calculate] stützt er sich auf die Business-Klasse [tax].

6.1.3. Die Business-Klasse

Die Klasse **impot** wird wie folgt definiert:


' imported namespaces
Imports System
 
' class
Namespace st.istia.univangers.fr
    Public Class impot
        Private limites(), coeffR(), coeffN() As Decimal
 
        ' manufacturer
        Public Sub New(ByRef source As impotsData)
            ' data required for tax calculation
            ' come from an external source [source]
            ' we retrieve them - there may be an exception
            Dim data() As Object = source.getData
            limites = CType(data(0), Decimal())
            coeffR = CType(data(1), Decimal())
            coeffN = CType(data(2), Decimal())
        End Sub
 
        ' tAX CALCULATION
        Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Long) As Long
            ' calculating the number of shares
            Dim nbParts As Decimal
            If marié Then
                nbParts = CDec(nbEnfants) / 2 + 2
            Else
                nbParts = CDec(nbEnfants) / 2 + 1
            End If
            If nbEnfants >= 3 Then
                nbParts += 0.5D
            End If
            ' calculation of taxable income & family quota
            Dim revenu As Decimal = 0.72D * salaire
            Dim QF As Decimal = revenu / nbParts
            ' tAX CALCULATION
            limites((limites.Length - 1)) = QF + 1
            Dim i As Integer = 0
            While QF > limites(i)
                i += 1
            End While
            Return CLng(revenu * coeffR(i) - nbParts * coeffN(i))
        End Function
    End Class
End Namespace

Ein Steuerobjekt, indem sein Konstruktor mit einer Datenquelle vom Typ [impotsData] versorgt wird. Diese Klasse verfügt über eine öffentliche Methode [getData], die die drei zur Berechnung der Steuer erforderlichen Datenarrays abruft, wie zuvor beschrieben. Diese Methode kann eine Ausnahme behandeln, falls die Daten nicht abgerufen werden konnten oder sich als fehlerhaft erweisen. Sobald das [tax]-Objekt erstellt ist, kann seine calculate-Methode wiederholt aufgerufen werden, um die Steuer des Steuerzahlers basierend auf seinem Familienstand (verheiratet oder ledig), der Anzahl der Kinder und dem Jahresgehalt zu berechnen.

6.1.4. Die Datenzugriffsklasse

Die Klasse [impotsData] ist die Klasse, die den Zugriff auf die Daten ermöglicht. Es handelt sich um eine abstrakte Klasse. Für jede neue mögliche Datenquelle (Arrays, Flatfiles, Datenbanken, Konsole usw.) muss eine abgeleitete Klasse erstellt werden. Ihre Definition lautet wie folgt:


Imports System.Collections
 
Namespace st.istia.univangers.fr
    Public MustInherit Class impotsData
        Protected limites() As Decimal
        Protected coeffr() As Decimal
        Protected coeffn() As Decimal
        Protected checked As Boolean
        Protected valide As Boolean
 
        ' data access method
        Public MustOverride Function getData() As Object()
 
        ' data verification method
        Protected Function checkData() As Integer
            ' verifies acquired data
            ' we need data
            valide = Not limites Is Nothing AndAlso Not coeffr Is Nothing AndAlso Not coeffn Is Nothing
            If Not valide Then Return 1
            ' we must have 3 arrays of the same size
            If valide Then valide = limites.Length = coeffr.Length AndAlso limites.Length = coeffn.Length
            If Not valide Then Return 2
            ' tables must be non-empty
            valide = limites.Length <> 0
            If Not valide Then Return 3
            ' each array must contain elements >=0 in ascending order
            valide = check(limites, limites.Length - 1) AndAlso check(coeffr, coeffr.Length) AndAlso check(coeffn, coeffn.Length)
            If Not valide Then Return 4
            ' all is good
            Return 0
        End Function
 
        ' checks the validity of an array's contents
        Protected Function check(ByRef tableau() As Decimal, ByVal n As Integer) As Boolean
            ' array must have its first n elements >=0 and in strictly ascending order
            If tableau(0) < 0 Then Return False
            For i As Integer = 1 To n - 1
                If tableau(i) <= tableau(i - 1) Then Return False
            Next
            ' it's good
            Return True
        End Function
    End Class
End Namespace

Die Klasse verfügt über die folgenden geschützten Attribute:

limits
Array der Steuerklassengrenzen
coeffr
Array von Koeffizienten, die auf das zu versteuernde Einkommen angewendet werden
coeffn
Tabelle der Koeffizienten, die auf die Anzahl der Anteile angewendet werden
checked
Boolescher Wert, der angibt, ob die Daten (limits, coeffr, coeffn) überprüft wurden
valid
Boolescher Wert, der angibt, ob die Daten (limits, coeffr, coeffn) gültig sind

Die Klasse hat keinen Konstruktor. Sie verfügt über eine abstrakte Methode [getData], die abgeleitete Klassen implementieren müssen. Der Zweck dieser Methode ist es:

  • den drei Arrays limits, coeffr, coeffn Werte zuweisen
  • eine Ausnahme auszulösen, wenn die Daten nicht abgerufen werden konnten oder sich als ungültig herausstellten.

Die Klasse stellt die geschützten Methoden [checkData] und [check] bereit, die die Gültigkeit der Attribute (limits, coeffr, coeffn) überprüfen. Dadurch müssen abgeleitete Klassen diese nicht selbst implementieren. Sie müssen sie lediglich verwenden.

Die erste abgeleitete Klasse, die wir verwenden werden, lautet wie folgt:


Imports System.Collections
Imports System
 
Namespace st.istia.univangers.fr
    Public Class impotsArray
        Inherits impotsData
 
        ' constructor with no arguments
        Public Sub New()
            ' initializing tables with constants
            limites = New Decimal() {4262D, 8382D, 14753D, 23888D, 38868D, 47932D, 0D}
            coeffr = New Decimal() {0D, 0.0683D, 0.1914D, 0.2826D, 0.3738D, 0.4262D, 0.4809D}
            coeffn = New Decimal() {0D, 291.09D, 1322.92D, 2668.39D, 4846.98D, 6883.66D, 9505.54D}
            checked = True
            valide = True
        End Sub
 
        ' builder with three input tables
        Public Sub New(ByRef limites() As Decimal, ByRef coeffr() As Decimal, ByRef coeffn() As Decimal)
            ' data storage
            Me.limites = limites
            Me.coeffr = coeffr
            Me.coeffn = coeffn
            checked = False
        End Sub
 
        Public Overrides Function getData() As Object()
            ' check data if necessary
            Dim erreur As Integer
            If Not checked Then erreur = checkData() : checked = True
            ' if invalid, then throw an exception
            If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
            ' otherwise we return the three tables
            Return New Object() {limites, coeffr, coeffn}
        End Function
    End Class
End Namespace

Diese Klasse mit dem Namen [impotsArray] verfügt über zwei Konstruktoren:

  • einen Konstruktor ohne Argumente, der die Attribute (limits, coeffr, coeffn) der Basisklasse mit fest codierten Arrays initialisiert
  • einen Konstruktor, der die Attribute (limits, coeffr, coeffn) der Basisklasse mit Arrays initialisiert, die ihm als Parameter übergeben werden

Die Methode [getData], die es externen Klassen ermöglicht, die Arrays (limits, coeffr, coeffn) abzurufen, überprüft lediglich die Gültigkeit der drei Arrays mithilfe der Methode [checkData] der Basisklasse. Sie löst eine Ausnahme aus, wenn die Daten ungültig sind.

6.1.5. Testen von Geschäfts- und Datenzugriffsklassen

Es ist wichtig, in einer Webanwendung nur Geschäfts- und Datenzugriffsklassen einzubinden, deren Korrektheit überprüft wurde. Auf diese Weise kann sich die Debugging-Phase der Webanwendung auf die Controller- und View-Ebenen konzentrieren. Ein Testprogramm könnte wie folgt aussehen:


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System
Imports Microsoft.VisualBasic
 
Namespace st.istia.univangers.fr
    Module test
        Sub Main()
            ' interactive tax calculator
            ' the user enters three data points on the keyboard: married nbEnfants salary
            ' the program then displays the tax payable
            Const syntaxe As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"
 
            ' tax object creation
            Dim objImpôt As impot = Nothing
            Try
                objImpôt = New impot(New impotsArray)
            Catch ex As Exception
                Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
                Environment.Exit(1)
            End Try
            ' infinite loop
            Dim marié As String
            Dim nbEnfants As Integer
            Dim salaire As Long
            While True
                ' tax calculation parameters are requested
                Console.Out.Write("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :")
                Dim paramètres As String = Console.In.ReadLine().Trim()
                ' anything to do?
                If paramètres Is Nothing OrElse paramètres = "" Then
                    Exit While
                End If
                ' check the number of arguments in the input line
                Dim erreur As Boolean = False
                Dim args As String() = paramètres.Split(Nothing)
                Dim nbParamètres As Integer = args.Length
                If nbParamètres <> 3 Then
                    Console.Error.WriteLine(syntaxe)
                    erreur = True
                End If
                ' checking the validity of parameters
                If Not erreur Then
                    ' married
                    marié = args(0).ToLower()
                    If marié <> "o" And marié <> "n" Then
                        erreur = True
                    End If
                    ' nbEnfants
                    Try
                        nbEnfants = Integer.Parse(args(1))
                        If nbEnfants < 0 Then
                            Throw New Exception
                        End If
                    Catch
                        erreur = True
                    End Try
                    ' salary
                    Try
                        salaire = Integer.Parse(args(2))
                        If salaire < 0 Then
                            Throw New Exception
                        End If
                    Catch
                        erreur = True
                    End Try
                End If
                ' if the parameters are correct - the tax is calculated
                If Not erreur Then
                    Console.Out.WriteLine(("impôt=" & objImpôt.calculer(marié = "o", nbEnfants, salaire) & " euro(s)"))
                Else
                    Console.Error.WriteLine(syntaxe)
                End If
            End While
        End Sub
    End Module
End Namespace

Die Anwendung fordert den Benutzer auf, die drei für die Steuerberechnung erforderlichen Angaben einzugeben:

  • Familienstand: o für verheiratet, n für unverheiratet
  • Anzahl der Kinder
  • jährliches Gehalt

Die Steuerberechnung erfolgt anhand eines Objekts vom Typ [tax], das beim Start der Anwendung erstellt wird:


            ' tax object creation
            Dim objImpôt As impot = Nothing
            Try
                objImpôt = New impot(New impotsArray)
            Catch ex As Exception
                Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
                Environment.Exit(1)
            End Try

Als Datenquelle verwenden wir ein Objekt vom Typ [impotsArray]. Es wird der Konstruktor ohne Argumente für diese Klasse verwendet, wobei die drei Arrays (limites, coeffr, coeffn) mit fest codierten Werten versehen werden. Das Erstellen eines [impot]-Objekts kann theoretisch eine Ausnahme auslösen, da das Objekt zur eigenen Erstellung die Daten (limites, coeffr, coeffn) von seiner Datenquelle anfordert, die ihm als Parameter übergeben wurde, und dieser Datenabruf eine Ausnahme auslösen kann. In diesem Fall kann die Methode zum Abrufen der Daten (fest codierte Werte) jedoch keine Ausnahme auslösen. Wir haben die Ausnahmebehandlung jedoch beibehalten, um den Leser auf die Möglichkeit aufmerksam zu machen, dass das [impot]-Objekt möglicherweise falsch erstellt wurde.

Hier ist ein Beispiel für die Ausführung des vorherigen Programms:

dos>dir
05/04/2004  13:28                1 337 impots.vb
21/04/2004  08:23                1 311 impotsArray.vb
21/04/2004  08:26                1 634 impotsData.vb
21/04/2004  08:42                2 490 testimpots1.vb

Wir kompilieren alle Klassen [impot, impotsData, impotsArray] zu einer einzigen Assembly [impot.dll]:

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

Wir kompilieren das Testprogramm:

dos>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 ausführen:

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

6.1.6. Ansichten der Webanwendung

Die Anwendung verfügt über zwei Ansichten: [form.aspx] und [errors.aspx]. Lassen Sie uns anhand von Screenshots veranschaulichen, wie die Anwendung funktioniert. Die Ansicht [form.aspx] wird angezeigt, wenn die URL [main.aspx] zum ersten Mal aufgerufen wird:

Image

Der Benutzer füllt das Formular aus:

Image

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

Image

Möglicherweise gibt er falsche Daten ein:

Image

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

Image

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

Image

6.1.7. Die Ansicht [form.aspx]

Die Seite [form.aspx] sieht wie folgt aus:


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

Die dynamischen Felder auf dieser Seite sind wie folgt:

rdouichecked
„checked“, wenn das Kontrollkästchen [yes] aktiviert sein soll, andernfalls „“
rdnonchecked
Das Gleiche gilt für das Kontrollkästchen [nein]
txtChildren
Wert, der in das Eingabefeld [txtChildren] eingegeben werden soll
txtSalary
Wert, der in das Eingabefeld [txtSalary] eingegeben werden soll
txtTax
Wert, der in das Eingabefeld [txtTax] eingegeben werden soll

Die Seite enthält zwei Formulare, die jeweils über eine [submit]-Schaltfläche verfügen. Die Schaltfläche [Calculate] ist die [submit]-Schaltfläche für das folgende Formular:


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

Wir sehen, dass die Formularparameter mit [action=calcul] an den Controller gesendet werden. Die Schaltfläche [Clear] ist die [submit]-Schaltfläche für das folgende Formular:


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

Wir sehen, dass die Formularparameter mit [action=effacer] an den Controller gesendet werden. Hier hat das Formular keine Parameter. Nur die Aktion ist von Bedeutung.

Die Felder in [formulaire.aspx] werden von [formulaire.aspx.vb] berechnet:


Imports System.Collections.Specialized
 
Public Class formulaire
    Inherits System.Web.UI.Page
 
    ' page fields
    Protected rdouichecked As String
    Protected rdnonchecked As String
    Protected txtEnfants As String
    Protected txtSalaire As String
    Protected txtImpot As String
 
    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' we retrieve the previous request in the
        Dim form As NameValueCollection = Context.Items("formulaire")
        ' prepare the page to be displayed
        ' radio buttons
        rdouichecked = ""
        rdnonchecked = "checked"
        If form("rdMarie").ToString = "oui" Then
            rdouichecked = "checked"
            rdnonchecked = ""
        End If
        ' the rest
        txtEnfants = CType(form("txtEnfants"), String)
        txtSalaire = CType(form("txtSalaire"), String)
        txtImpot = CType(Context.Items("txtImpot"), String)
    End Sub
End Class

Die Felder in [main.aspx] werden auf der Grundlage von zwei Informationen berechnet, die vom Controller im Seitenkontext abgelegt werden:

  • Context.Items("form"): ein NameValueCollection-Wörterbuch, das die Werte der HTML-Felder [rdmarie,txtEnfants,txtSalaire] enthält
  • Context.Items("txtImpot"): Steuerwert

6.1.8. Die Ansicht [erreurs.aspx]

Die Ansicht [erreurs.aspx] zeigt alle Fehler an, die während der Laufzeit der Anwendung auftreten können. Der Darstellungscode lautet wie folgt:


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

Die Seite enthält drei dynamische Felder:

HTMLErrors
HTML-Code für eine Liste von Fehlern
href
URL eines Links
link
Linktext

Diese Felder werden vom Controller der Seite in [errors.aspx.vb] berechnet:


Imports System.Collections
Imports Microsoft.VisualBasic
 
Public Class erreurs
    Inherits System.Web.UI.Page
 
    ' page parameter
    Protected erreursHTML As String = ""
    Protected href As String
    Protected lien As String
 
    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' retrieve context elements
        Dim erreurs As ArrayList = CType(context.Items("erreurs"), ArrayList)
        href = context.Items("href").ToString
        lien = context.Items("lien").ToString
        ' we generate the HTML code from the list
        Dim i As Integer
        For i = 0 To erreurs.Count - 1
            erreursHTML += "<li> " + erreurs(i).ToString + "</li>" + ControlChars.CrLf
        Next
    End Sub
 
End Class

Der Seiten-Controller ruft Informationen ab, die vom Anwendungs-Controller im Seitenkontext abgelegt wurden:

Context.Items("errors"
ArrayList-Objekt, das die Liste der anzuzeigenden Fehlermeldungen enthält
Context.Items("href"
URL eines Links
Context.Items("link"
Linktext

Da wir nun wissen, was der Anwendungsbenutzer sieht, können wir mit dem Schreiben des Controllers der Anwendung fortfahren.

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

Lassen Sie uns die MVC-Architektur unserer Anwendung noch einmal betrachten:

Image

AnwendungslogikClient

Der Controller [main.aspx] muss drei Aktionen verarbeiten:

  • init: entspricht der ersten Anfrage des Clients. Der Controller zeigt die Ansicht [formulaire.aspx] an
  • calcul: entspricht der Anfrage zur Steuerberechnung. Sind die Daten im Eingabeformular korrekt, wird die Steuer mithilfe der Business-Klasse [taxes] berechnet. Der Controller gibt die Ansicht [form.aspx] in validiertem Zustand zusammen mit der berechneten Steuer an den Client zurück. Sind die Daten im Eingabeformular fehlerhaft, gibt der Controller die Ansicht [errors.aspx] mit einer Liste der Fehler und einem Link zur Rückkehr zum Formular zurück.
  • return: entspricht der Rückkehr zum Formular nach einem Fehler. Der Controller zeigt die Ansicht [form.aspx] so an, wie sie vor dem Fehler validiert wurde.

Es ist außerdem bekannt, dass jede Anfrage an die Anwendung den [global.asax]-Controller durchläuft, sofern dieser vorhanden ist. Wir haben daher am Einstiegspunkt der Anwendung eine Kette aus zwei Controllern:

  • [global.asax], der aufgrund der ASP.NET-Architektur alle Anfragen an die Anwendung empfängt
  • [main.aspx], der nach Wahl des Entwicklers ebenfalls alle Anfragen an die Anwendung empfängt

Die Notwendigkeit von [main.aspx] ergibt sich aus der Tatsache, dass wir eine Sitzung verwalten müssen. Wir haben gesehen, dass [global.asax] in diesem Fall nicht als Controller geeignet ist. Wir könnten hier ganz auf [global.asax] verzichten. Wir werden es jedoch verwenden, um Code beim Start der Anwendung auszuführen. Das obige MVC-Diagramm zeigt, dass wir ein [tax]-Objekt erstellen müssen, um die Steuer zu berechnen. Es ist nicht notwendig, dieses Objekt mehrfach zu erstellen; einmal reicht aus. Wir werden es daher beim Start der Anwendung während des [Application_Start]-Ereignisses erstellen, das vom [global.asax]-Controller abgewickelt wird. Der Code hierfür lautet wie folgt:

[global.asax]

<%@ 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)
        ' create an impot object
        Dim objImpot As impot
        Try
            objImpot = New impot(New impotsArray)
            ' put the object in the application
            Application("objImpot") = objImpot
            ' no error
            Application("erreur") = False
        Catch ex As Exception
            'there has been an error, we note it in the application
            Application("erreur") = True
        End Try
    End Sub
End Class

Nach der Erstellung wird das [import]-Objekt in der Anwendung gespeichert. Von dort wird es bei Anfragen verschiedener Clients abgerufen. Da die Erstellung des [import]-Objekts fehlschlagen kann, behandeln wir alle Ausnahmen und setzen einen [error]-Schlüssel in der Anwendung, um anzugeben, ob bei der Erstellung des [import]-Objekts ein Fehler aufgetreten ist oder nicht.

Der Controller-Code [main.aspx, main.aspx.vb] sieht wie folgt aus:

[main.aspx]

<%@ 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
        ' first of all, we check whether the application has initialized correctly
        If CType(Application("erreur"), Boolean) Then
            ' redirects to error page
            Dim erreurs As New ArrayList
            erreurs.Add("Application momentanément indisponible...")
            context.Items("erreurs") = erreurs
            context.Items("lien") = ""
            context.Items("href") = ""
            Server.Transfer("erreurs.aspx")
        End If
        ' retrieve the action to be performed
        Dim action As String
        If Request.QueryString("action") Is Nothing Then
            action = "init"
        Else
            action = Request.QueryString("action").ToString.ToLower
        End If
        ' execute the action
        Select Case action
            Case "init"
                ' init application
                initAppli()
            Case "calcul"
                ' tax calculation
                calculImpot()
            Case "retour"
                ' back to form
                retourFormulaire()
            Case "effacer"
                ' init application
                initAppli()
            Case Else
                ' unknown action = init
                initAppli()
        End Select
    End Sub
 
    Private Sub initAppli()
        ' the pre-filled form is displayed
        Context.Items("formulaire") = initForm()
        Context.Items("txtImpot") = ""
        Server.Transfer("formulaire.aspx", True)
    End Sub
 
    Private Function initForm() As NameValueCollection
        ' initialize the form
        Dim form As New NameValueCollection
        form.Set("rdMarie", "non")
        form.Set("txtEnfants", "")
        form.Set("txtSalaire", "")
        Return form
    End Function

    Private Sub calculImpot()
        ' check the validity of the data entered
        Dim erreurs As ArrayList = checkData()
        ' if there are errors, we report them
        If erreurs.Count <> 0 Then
            ' save entries
            Session.Item("formulaire") = Request.Form
            ' prepare the error page
            context.Items("href") = "main.aspx?action=retour"
            context.Items("lien") = "Retour au formulaire"
            context.Items("erreurs") = erreurs
            Server.Transfer("erreurs.aspx")
        End If
        ' no errors here - the tax is calculated
        Dim impot As Long = CType(Application("objImpot"), impot).calculer( _
        Request.Form("rdMarie") = "oui", _
        CType(Request.Form("txtEnfants"), Integer), _
        CType(Request.Form("txtSalaire"), Long))
        ' the result page is displayed
        context.Items("txtImpot") = impot.ToString + " euro(s)"
        context.Items("formulaire") = Request.Form
        Server.Transfer("formulaire.aspx", True)
    End Sub
 
    Private Sub retourFormulaire()
        ' displays the form with values taken from the session
        Context.Items("formulaire") = Session.Item("formulaire")
        Context.Items("txtImpot") = ""
        Server.Transfer("formulaire.aspx", True)
    End Sub
 
    Private Function checkData() As ArrayList
        ' initially no errors
        Dim erreurs As New ArrayList
        Dim erreur As Boolean = False
        ' married radio button
        Try
            Dim rdMarie As String = Request.Form("rdMarie").ToString
            If rdMarie <> "oui" And rdMarie <> "non" Then
                Throw New Exception
            End If
        Catch
            erreurs.Add("Vous n'avez pas indiqué votre statut marital")
        End Try
        ' no. of children
        Try
            Dim txtEnfants As String = Request.Form("txtEnfants").ToString
            Dim nbEnfants As Integer = CType(txtEnfants, Integer)
            If nbEnfants < 0 Then Throw New Exception
        Catch
            erreurs.Add("Le nombre d'enfants est incorrect")
        End Try
        ' salary
        Try
            Dim txtSalaire As String = Request.Form("txtSalaire").ToString
            Dim salaire As Integer = CType(txtSalaire, Long)
            If salaire < 0 Then Throw New Exception
        Catch
            erreurs.Add("Le salaire annuel est incorrect")
        End Try
        ' return the list of errors
        Return erreurs
    End Function
End Class

Der Controller überprüft zunächst, ob die Anwendung korrekt initialisiert wurde:


        ' first of all, we check whether the application has initialized correctly
        If CType(Application("erreur"), Boolean) Then
            ' redirects to error page
            Dim erreurs As New ArrayList
            erreurs.Add("Application momentanément indisponible...")
            context.Items("erreurs") = erreurs
            context.Items("lien") = ""
            context.Items("href") = ""
            Server.Transfer("erreurs.aspx")
        End If

Wenn der Controller feststellt, dass die Anwendung nicht korrekt initialisiert werden konnte (das für die Berechnung erforderliche [import]-Objekt konnte nicht erstellt werden), zeigt er die Fehlerseite mit den entsprechenden Parametern an. Hier ist es nicht erforderlich, den Rückkehrlink auf dem Formular zu platzieren, da die gesamte Anwendung nicht verfügbar ist. Eine allgemeine Fehlermeldung wird in [Context.Items("errors")] vom Typ [ArrayList] abgelegt.

Wenn der Controller feststellt, dass die Anwendung betriebsbereit ist, analysiert er die Aktion, die er über den [action]-Parameter ausführen soll. Diese Funktionsweise ist uns inzwischen schon oft begegnet. Die Verarbeitung jeder Art von Aktion wird an eine Funktion delegiert.

6.1.9.1. Die Aktionen „init“ und „delete“

Diese beiden Aktionen müssen das leere Eingabeformular anzeigen. Erinnern Sie sich daran, dass dieses Formular (siehe Ansichten) zwei Parameter hat:

  • Context.Items("form"): ein [NameValueCollection]-Wörterbuch, das die Werte der HTML-Felder [rdmarie,txtEnfants,txtSalaire] enthält
  • Context.Items("txtImpot"): Steuerwert

Die Funktion [initAppli] initialisiert diese beiden Parameter, um ein leeres Formular anzuzeigen.

6.1.9.2. Die Berechnungsaktion

Diese Aktion muss die fällige Steuer auf der Grundlage der im Formular eingegebenen Daten berechnen und das Formular mit den eingegebenen Werten und dem berechneten Steuerbetrag vorausgefüllt zurückgeben. Die für diese Aufgabe zuständige Funktion [calculImpot] überprüft zunächst, ob die Formulardaten korrekt sind:

  • Das Feld [rdMarie] muss vorhanden sein und den Wert [yes] oder [no] haben
  • das Feld [txtEnfants] muss vorhanden sein und eine ganze Zahl >=0 enthalten
  • Das Feld [txtSalaire] muss vorhanden sein und eine ganze Zahl >=0 enthalten

Sind die eingegebenen Daten ungültig, zeigt der Controller die Ansicht [erreurs.aspx] an, nachdem er zuvor die erwarteten Werte dafür im Kontext festgelegt hat:

  • Fehlermeldungen werden in ein [ArrayList]-Objekt gespeichert, das dann dem Kontext hinzugefügt wird [Context.Items("errors")]
  • Die URL des Rücklink und der Text dieses Links werden ebenfalls in den Kontext gesetzt.

Bevor die Steuerung an die Seite [erreurs.aspx] übergeben wird, die die Antwort an den Client sendet, werden die im Formular (Request.Form) eingegebenen Werte in die Sitzung abgelegt und dem Schlüssel „form“ zugeordnet. Dadurch können sie bei einer nachfolgenden Anfrage abgerufen werden.

Man könnte sich hier fragen, ob es sinnvoll ist, zu überprüfen, ob die Felder [rdMarie, txtEnfants, txtSalaire] in der vom Client gesendeten Anfrage vorhanden sind. Dies ist unnötig, wenn wir sicher sind, dass unser Client ein Browser ist, der die Ansicht [formulaire.aspx] mit diesen Feldern erhalten hat. Wir können uns dessen jedoch nie sicher sein. Wir werden etwas später ein Beispiel zeigen, bei dem der Client die bereits bekannte [curl]-Anwendung ist. Wir werden die Anwendung abfragen, ohne die von ihr erwarteten Felder zu senden, und sehen, wie sie reagiert. Dies ist eine Regel, die bereits mehrfach erwähnt wurde und die wir hier wiederholen: Eine Anwendung darf niemals Annahmen über den Typ des Clients treffen, der sie abfragt. Um auf der sicheren Seite zu sein, muss sie davon ausgehen, dass sie von einer programmierten Anwendung abgefragt werden könnte, die ihr unerwartete Parameterzeichenfolgen sendet. Sie muss sich in allen Fällen korrekt verhalten.

In unserem Fall haben wir überprüft, ob die Felder [rdMarie, txtEnfants, txtSalaire] in der Anfrage vorhanden waren, aber nicht, ob sie weitere enthalten könnte. In dieser Anwendung würden diese ignoriert werden. Dennoch wäre es, wiederum als Sicherheitsmaßnahme, sinnvoll, diese Art von Anfragen in einer Protokolldatei zu erfassen und eine Warnung an den Anwendungsadministrator zu senden, damit dieser weiß, dass die Anwendung „seltsame“ Anfragen erhält. Durch die Analyse dieser Einträge in der Protokolldatei könnte er einen potenziellen Angriff auf die Anwendung erkennen und dann die notwendigen Maßnahmen zu deren Schutz ergreifen.

Wenn die erwarteten Daten korrekt sind, leitet der Controller die Steuerberechnung unter Verwendung des in der Anwendung gespeicherten [tax]-Objekts ein. Anschließend speichert er die beiden von der Ansicht [form.aspx] erwarteten Informationen im Kontext:

  • Context.Items("formulaire"): ein [NameValueCollection]-Wörterbuch, das die Werte der HTML-Felder [rdmarie,txtEnfants,txtSalaire] enthält (hier [Request.Form]), d. h. die zuvor in das Formular eingegebenen Werte
  • Context.Items("txtImpot"): der soeben berechnete Steuerwert

Der aufmerksame Leser hat sich beim Lesen des Vorstehenden vielleicht gefragt: Da das beim Start der Anwendung erstellte [impot]-Objekt von allen Anfragen gemeinsam genutzt wird, könnte es dann zu Zugriffskonflikten kommen, die zu einer Beschädigung der Daten des [impot]-Objekts führen? Um diese Frage zu beantworten, müssen wir zum Code der [impot]-Klasse zurückkehren. Die Anfragen rufen die Methode [impot].calculateTax auf, um die fällige Steuer zu ermitteln. Daher müssen wir folgenden Code untersuchen:

        Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Long) As Long
            ' calculating the number of shares
            Dim nbParts As Decimal
            If marié Then
                nbParts = CDec(nbEnfants) / 2 + 2
            Else
                nbParts = CDec(nbEnfants) / 2 + 1
            End If
            If nbEnfants >= 3 Then
                nbParts += 0.5D
            End If
            ' calculation of taxable income & family quota
            Dim revenu As Decimal = 0.72D * salaire
            Dim QF As Decimal = revenu / nbParts
            ' tAX CALCULATION
            limites((limites.Length - 1)) = QF + 1
            Dim i As Integer = 0
            While QF > limites(i)
                i += 1
            End While
            Dim impot As Long = CLng(revenu * coeffR(i) - nbParts * coeffN(i))
            Return impot
        End Function

Angenommen, ein Thread führt die vorherige Methode aus und wird unterbrochen. Ein anderer Thread führt dann die Methode aus. Welche Risiken bestehen? Um dies herauszufinden, haben wir den folgenden Code hinzugefügt:


            Dim impot As Long = CLng(revenu * coeffR(i) - nbParts * coeffN(i))
            ' wait 10 seconds
            Thread.Sleep(10000)
            Return impot

Thread 1 wird nach der Berechnung des Werts [impot1] der lokalen Variablen [impot] unterbrochen. Anschließend wird Thread 2 ausgeführt und berechnet einen neuen Wert [impot2] für dieselbe Variable [impot], bevor er unterbrochen wird. Thread 1 erhält die Kontrolle zurück. Was findet er in der lokalen Variablen [impot]? Da diese Variable lokal zu einer Methode ist, wird sie in einer Speicherstruktur namens Stack gespeichert. Dieser Stack ist Teil des Thread-Kontexts, der gespeichert wird, wenn der Thread angehalten wird. Wenn Thread 2 startet, wird sein Kontext mit einem neuen Stack und somit einer neuen lokalen Variablen [impot] eingerichtet. Wenn Thread 2 seinerseits angehalten wird, wird auch sein Kontext gespeichert. Wenn Thread 1 neu gestartet wird, wird sein Kontext wiederhergestellt, einschließlich seines Stacks. Er ruft dann seine lokale Variable [impot] ab und nicht die von Thread 2. Wir befinden uns daher in einer Situation, in der es keine Zugriffskonflikte zwischen den Anfragen gibt. Die mit der oben beschriebenen 10-Sekunden-Pause durchgeführten Tests bestätigten, dass gleichzeitige Anfragen tatsächlich das erwartete Ergebnis lieferten.

6.1.9.3. Die Rückkehraktion

Diese Aktion entspricht dem Klicken auf den Link [Zurück zum Formular] in der Ansicht [errors.aspx], um zur Ansicht [form.aspx] zurückzukehren, die mit den zuvor eingegebenen und in der Sitzung gespeicherten Werten vorausgefüllt ist. Die Funktion [returnForm] ruft diese Informationen ab. Die beiden von der Ansicht [form.aspx] erwarteten Parameter werden initialisiert:

  • Context.Items("form") mit den zuvor eingegebenen und in der Sitzung gespeicherten Werten
  • Context.Items("txtImpot") mit der leeren Zeichenfolge

6.1.10. Testen der Webanwendung

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

Image

In diesem Ordner wird ein Unterordner [bin] erstellt, in dem die Assembly [impot.dll] – generiert aus der Kompilierung der Business-Class-Dateien: [impots.vb, impotsData.vb, impotsArray.vb] – abgelegt wird. Der erforderliche Kompilierungsbefehl lautet wie folgt:

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

Die oben genannte Datei [impot.dll] muss im Verzeichnis <Anwendungspfad>\bin abgelegt werden, damit die Webanwendung darauf zugreifen kann. Der Cassini-Server wird mit den Parametern (<Anwendungspfad>,/impots1) gestartet. Über einen Browser rufen wir die URL [http://localhost/impots1/main.aspx] auf:

Image

Wir füllen das Formular aus:

Image

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

Image

Anschließend geben wir falsche Daten ein:

Image

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

Image

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

Image

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

Image

6.1.11. Verwendung des [curl]-Clients

Es ist wichtig, Webanwendungen auch mit anderen Clients als Browsern zu testen. Wenn Sie einem Browser ein Formular mit Parametern senden, die beim Absenden übermittelt werden sollen, sendet der Browser die Werte dieser Parameter an den Server zurück. Ein anderer Client tut dies möglicherweise nicht, und der Server würde dann eine Anfrage mit fehlenden Parametern erhalten. Er muss wissen, wie er in diesem Fall vorgehen soll. Ein weiteres Beispiel ist die clientseitige Datenvalidierung. Wenn das Formular zu validierende Daten enthält, kann diese Validierung auf der Clientseite mithilfe von Skripten durchgeführt werden, die in dem Dokument enthalten sind, in dem sich das Formular befindet. Der Browser übermittelt das Formular nur, wenn alle clientseitig validierten Daten gültig sind. Man könnte dann auf der Serverseite versucht sein, davon auszugehen, dass wir validierte Daten erhalten, und diese Validierung nicht ein zweites Mal durchführen wollen. Das wäre ein Fehler. Tatsächlich könnte ein anderer Client als ein Browser ungültige Daten an den Server senden, und die Webanwendung könnte sich dann unerwartet verhalten. Wir werden diese Punkte anhand des [curl]-Clients veranschaulichen.

Zunächst rufen wir die URL [http://localhost/impots1/main.aspx] auf:

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

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


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

Der Server hat uns den HTML-Code des Formulars gesendet. In den HTTP-Headern finden wir das Sitzungscookie. Wir werden es in nachfolgenden Anfragen verwenden, um die Sitzung aufrechtzuerhalten. Fordern wir die Aktion [calcul] ohne Angabe von Parametern an:

dos>curl --cookie ASP.NET_SessionId=ivthkl45tjdjrzznevqsf255 --include --url  http://localhost/impots1/main.aspx?action=calcul 

HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Thu, 01 Apr 2004 15:22:42 GMT
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 380
Connection: Close


<HTML>
    <HEAD>
        <title>Impôt</title>
    </HEAD>
    <body>
        <P>Les erreurs suivantes se sont produites :</P>
        <HR>
        <ul>
            <li> Vous n'avez pas indiqué votre statut marital</li>
<li> Le nombre d'enfants est incorrect</li>
<li> Le salaire annuel est incorrect</li>
        </ul>
        <a href="main.aspx?action=retour">
            Retour au formulaire
        </a>
    </body>
</HTML>

Wir sehen, dass die Webanwendung die Ansicht [errors] mit drei Fehlermeldungen für die drei fehlenden Parameter zurückgegeben hat. Senden wir nun einige falsche Parameter:

dos>curl --cookie ASP.NET_SessionId=ivthkl45tjdjrzznevqsf255 --include --data rdMarie=xx --data txtEnfants=xx --data txtSalaire=xx --url http://localhost/impots1/main.aspx?action=calcul 

HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Thu, 01 Apr 2004 15:25:50 GMT
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 380
Connection: Close


<HTML>
    <HEAD>
        <title>Impôt</title>
    </HEAD>
    <body>
        <P>Les erreurs suivantes se sont produites :</P>
        <HR>
        <ul>
            <li> Vous n'avez pas indiqué votre statut marital</li>
<li> Le nombre d'enfants est incorrect</li>
<li> Le salaire annuel est incorrect</li>
        </ul>
        <a href="main.aspx?action=retour">
            Retour au formulaire
        </a>
    </body>
</HTML>

Die drei Fehler wurden korrekt erkannt. Senden wir nun gültige Parameter:

dos>curl --cookie ASP.NET_SessionId=ivthkl45tjdjrzznevqsf255 --include --data rdMarie=oui --data txtEnfants=2 --data txtSalaire=60000 --url http://localhost/impots1/main.aspx?action=calcul 

HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Thu, 01 Apr 2004 15:28:24 GMT
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 1000
Connection: Close


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

Wir haben die fällige Steuer erfolgreich abgerufen: 4.300 Euro. Die Lehre aus diesem Beispiel ist, dass wir uns nicht davon täuschen lassen dürfen, dass wir eine Webanwendung schreiben, die für Clients bestimmt ist, bei denen es sich um Browser handelt. Eine Webanwendung ist ein TCP/IP-Dienst, und dieses Netzwerkprotokoll gibt keinen Aufschluss über die Art der Client-Anwendung eines Dienstes. Daher können wir nicht wissen, ob der Client einer Webanwendung ein Browser ist oder nicht. Wir befolgen daher zwei Regeln:

  • Wenn wir eine Anfrage von einem Client erhalten, stellen wir keine Vermutungen über den Client an und überprüfen, ob die erwarteten Parameter in der Anfrage vorhanden und gültig sind
  • Wir erstellen eine für Browser bestimmte Antwort, die in der Regel aus HTML-Dokumenten besteht

Eine Webanwendung kann so aufgebaut sein, dass sie verschiedene Clients gleichzeitig bedient, beispielsweise Browser und Mobiltelefone. Wir können dann in jede Anfrage einen neuen Parameter aufnehmen, der den Client-Typ angibt. So fordert ein Browser eine Steuerberechnung über eine Anfrage an die URL http://machine/impots/main.aspx?client=navigateur&action=calcul an, während ein Mobiltelefon eine Anfrage an die URL http://machine/impots/main.aspx?client=mobile&action=calcul sendet. Die MVC-Struktur erleichtert die Entwicklung einer solchen Anwendung. Sie hat folgende Form:

Image

Der Block [Business Classes, Data Access Classes] bleibt unverändert. Dies liegt daran, dass es sich um eine clientunabhängige Komponente handelt. Der Block [Controller] ändert sich nur geringfügig, muss jedoch einen neuen Parameter in der Anfrage berücksichtigen: den Parameter [client], der den Typ des bearbeiteten Clients angibt. Der Block [Views] muss Ansichten für jeden Client-Typ generieren. Es könnte sich lohnen, das Vorhandensein des Parameters [client] in der Abfrage bereits in der Entwurfsphase der Anwendung zu berücksichtigen, auch wenn das kurz- oder mittelfristige Ziel auf Browser beschränkt ist. Wenn die Anwendung später einen neuen Client-Typ unterstützen muss, müssen nur noch auf diesen Client zugeschnittene Ansichten geschrieben werden.

6.2. Beispiel 2

6.2.1. Das Problem

Hier schlagen wir vor, dasselbe Problem wie zuvor anzugehen, jedoch durch eine Änderung der Datenquelle für das von der Webanwendung erstellte [tax]-Objekt. In der vorherigen Version lieferte die Datenquelle Werte aus Arrays, die fest im Code hinterlegt waren. Diesmal wird die neue Datenquelle diese Werte aus einer ODBC-Datenquelle abrufen, die mit einer MySQL-Datenbank verknüpft ist.

6.2.2. Die ODBC-Datenquelle

Die Daten befinden sich in einer Tabelle namens [IMPOTS] in einer MySQL-Datenbank namens [dbimpots]. Der Inhalt dieser Tabelle sieht wie folgt aus:

Image

Der Datenbankbesitzer ist der Benutzer [admimpots] mit dem Passwort [mdpimpots]. Wir verknüpfen eine ODBC-Datenquelle mit dieser Datenbank. Bevor wir dies tun, wollen wir zunächst die verschiedenen Möglichkeiten des Zugriffs auf eine Datenbank über die .NET-Plattform betrachten.

Für Windows-Plattformen stehen zahlreiche Datenbanken zur Verfügung. Um auf diese zuzugreifen, verwenden Anwendungen Programme, die als Treiber bezeichnet werden.

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 verhindern, dass eine für die Datenbank B1 geschriebene Anwendung bei einer Migration auf eine andere Datenbank B2 neu geschrieben werden muss, wurden Standardisierungsbemühungen für die Schnittstelle I1 unternommen. Wenn Datenbanken mit „standardisierten“ Treibern verwendet werden, wird die Datenbank B1 mit dem Treiber P1 und die Datenbank B2 mit dem Treiber P2 ausgestattet, wobei die I1-Schnittstelle dieser beiden Treiber identisch ist. Somit muss die Anwendung nicht neu geschrieben werden. So können Sie beispielsweise eine Access-Datenbank in eine MySQL-Datenbank migrieren, ohne die Anwendung zu ändern.

Es gibt zwei Arten von standardisierten Treibern:

  • ODBC-Treiber (Open DataBase Connectivity)
  • OLE DB-Treiber (Object Linking and Embedding DataBase)

ODBC-Treiber ermöglichen den Zugriff auf Datenbanken. Die Datenquellen für OLE DB-Treiber sind vielfältiger: Datenbanken, E-Mail-Systeme, Verzeichnisse usw. Es gibt keine Einschränkungen. Jede Datenquelle kann Gegenstand eines OLE DB-Treibers sein, wenn ein Anbieter dies beschließt. Der Vorteil liegt auf der Hand: Sie haben einheitlichen Zugriff auf eine Vielzahl von Daten.

Die .NET 1.1-Plattform enthält drei Arten von Datenzugriffsklassen:

  1. SQL Server.NET-Klassen für den Zugriff auf Microsoft SQL Server-Datenbanken
  2. die Ole Db.NET-Klassen für den Zugriff auf Datenbanken von DBMS, die einen OLE DB-Treiber anbieten
  3. ODBC.NET-Klassen für den Zugriff auf Datenbanken von DBMS, die einen ODBC-Treiber anbieten

Das MySQL-DBMS verfügt schon seit langem über einen ODBC-Treiber. Diesen werden wir nun verwenden. Unter Windows wählen wir [Startmenü/Systemsteuerung/Verwaltung/32-Bit-ODBC-Quellen]. Je nach Windows-Version kann dieser Pfad leicht variieren. Dadurch wird die folgende Anwendung geöffnet, mit der wir unsere ODBC-Quelle erstellen können:

Image

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

Image

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

Image

Sie müssen die folgenden Informationen angeben:

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

Sobald dies erledigt ist, testen wir die Gültigkeit unserer Verbindungseinstellungen über die Schaltfläche [Datenquelle testen]:

Image

Sobald dies erledigt ist, können wir uns auf unsere ODBC-Datenquelle verlassen. Wir können sie nun verwenden. Wir klicken so oft wie nötig auf [OK], um den ODBC-Assistenten zu verlassen.

Falls der Leser nicht über das MySQL-DBMS verfügt, kann er es kostenlos unter [http://www.mysql.com] herunterladen. Im Folgenden beschreiben wir die Schritte zum Erstellen einer ODBC-Quelle mit Access. Die ersten Schritte sind identisch mit den zuvor beschriebenen. Wir fügen eine neue Systemdatenquelle hinzu:

Image

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

Image

Die folgenden Informationen müssen angegeben werden:

Name der Datenquelle
Der Name, der die ODBC-Datenquelle identifiziert. Jede Windows-Anwendung kann über diesen Namen auf die Quelle zugreifen
Beschreibung
Beliebiger Text zur Beschreibung der Datenquelle
Datenbank
Der vollständige Name der zu verwendenden Access-Datei

6.2.3. Eine neue Datenzugriffsklasse

Werfen wir noch einmal einen Blick auf die MVC-Struktur unserer Anwendung:

Image

Im obigen Diagramm ist die Klasse [impotsData] für das Abrufen von Daten zuständig. In diesem Fall ruft sie Daten aus der MySQL-Datenbank [dbimpots] ab. Wie wir in der vorherigen Version dieser Anwendung gelernt haben, ist [impotsData] eine abstrakte Klasse, die immer dann abgeleitet werden muss, wenn wir sie an eine neue Datenquelle anpassen wollen. Sehen wir uns die Struktur dieser abstrakten Klasse noch einmal an:


Imports System.Collections
 
Namespace st.istia.univangers.fr
    Public MustInherit Class impotsData
        Protected limites() As Decimal
        Protected coeffr() As Decimal
        Protected coeffn() As Decimal
        Protected checked As Boolean
        Protected valide As Boolean
 
        ' data access method
        Public MustOverride Function getData() As Object()
 
        ' data verification method
        Protected Function checkData() As Integer
            ' verifies acquired data
...
        End Function
 
        ' checks the validity of an array's contents
        Protected Function check(ByRef tableau() As Decimal, ByVal n As Integer) As Boolean
        ...
        End Function
    End Class
End Namespace

Die Klasse, die von [impotsData] abgeleitet ist, muss zwei Methoden implementieren:

  • einen Konstruktor, falls der Standardkonstruktor von [impotsData] nicht geeignet ist
  • die Methode [getData], die die drei Arrays (limites, coeffr, coeffn) zurückgibt

Wir erstellen die Klasse [impotsODBC], die die Daten (limits, coeffr, coeffn) aus einer ODBC-Quelle abruft, deren Namen wir angeben:


Imports System.Data.Odbc
Imports System.Data
Imports System.Collections
Imports System
 
Namespace st.istia.univangers.fr
    Public Class impotsODBC
        Inherits impotsData
 
        ' instance variables
        Protected DSNimpots As String
 
        ' manufacturer
        Public Sub New(ByVal DSNimpots As String)
            ' we note the three pieces of information
            Me.DSNimpots = DSNimpots
        End Sub
 
        Public Overrides Function getdata() As Object()
            ' initializes the three limit tables, coeffr, coeffn from
            ' the contents of the [impots] table in the ODBC DSNimpots database
            ' limites, coeffr, coeffn are the three columns of this table
            ' can launch various exceptions
 
            Dim connectString As String = "DSN=" + DSNimpots + ";"         ' base connection chain
            Dim impotsConn As OdbcConnection = Nothing         ' the connection
            Dim sqlCommand As OdbcCommand = Nothing         ' the SQL command
            ' the SELECT query
            Dim selectCommand As String = "select limites,coeffr,coeffn from impots"
            ' tables to retrieve data
            Dim aLimites As New ArrayList
            Dim aCoeffR As New ArrayList
            Dim aCoeffN As New ArrayList
            Try
                ' attempt to access the database
                impotsConn = New OdbcConnection(connectString)
                impotsConn.Open()
                ' create a command object
                sqlCommand = New OdbcCommand(selectCommand, impotsConn)
                ' execute the query
                Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader()
                ' Using the recovered table
                While myReader.Read()
                    ' the data of the current line are put in the tables
                    aLimites.Add(myReader("limites"))
                    aCoeffR.Add(myReader("coeffr"))
                    aCoeffN.Add(myReader("coeffn"))
                End While
                ' freeing up resources
                myReader.Close()
                impotsConn.Close()
            Catch e As Exception
                Throw New Exception("Erreur d'accès à la base de données (" + e.Message + ")")
            End Try
            ' dynamic tables are placed in static tables
            Me.limites = New Decimal(aLimites.Count - 1) {}
            Me.coeffr = New Decimal(aLimites.Count - 1) {}
            Me.coeffn = New Decimal(aLimites.Count - 1) {}
            Dim i As Integer
            For i = 0 To aLimites.Count - 1
                limites(i) = Decimal.Parse(aLimites(i).ToString())
                coeffR(i) = Decimal.Parse(aCoeffR(i).ToString())
                coeffN(i) = Decimal.Parse(aCoeffN(i).ToString())
            Next i
            ' verify acquired data
            Dim erreur As Integer = checkData()
            ' if invalid data, throws an exception
            If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
            ' otherwise we return the three tables
            Return New Object() {limites, coeffr, coeffn}
        End Function
    End Class
End Namespace

Werfen wir einen Blick auf den Konstruktor:


        ' manufacturer
        Public Sub New(ByVal DSNimpots As String)
            ' we note the three pieces of information
            Me.DSNimpots = DSNimpots
        End Sub

Er nimmt als Parameter den Namen der ODBC-Datenquelle entgegen, die die abzurufenden Daten enthält. Der Konstruktor speichert diesen Namen lediglich. Die Methode [getData] ist dafür zuständig, die Daten aus der Tabelle [impots] zu lesen und in drei Arrays (limites, coeffr, coeffn) zu speichern. Lassen Sie uns den Code kommentieren:

  • Die Parameter für die Verbindung zur ODBC-Datenquelle sind definiert, aber die Verbindung ist nicht geöffnet
            ' base connection chain
            Dim connectString As String = "DSN=" + DSNimpots + ";"
            ' a database connection object is created - this connection is not open
            Dim impotsConn As OdbcConnection = New OdbcConnection(connectString)
  • Definieren Sie drei [ArrayList]-Objekte, um Daten aus der Tabelle [impots] abzurufen:

            ' tables to retrieve data
            Dim aLimites As New ArrayList
            Dim aCoeffR As New ArrayList
            Dim aCoeffN As New ArrayList
  • Der gesamte Code für den Datenbankzugriff ist in einem try/catch-Block eingeschlossen, um eventuelle Zugriffsfehler zu behandeln. Wir öffnen die Verbindung zur Datenbank:

                ' on tente d'accéder à la base de données
                impotsConn = New OdbcConnection(connectString)
                impotsConn.Open()
  • Wir führen den Befehl [select] auf der geöffneten Verbindung aus. Wir erhalten ein [OdbcDataReader]-Objekt, mit dem wir die Zeilen der durch den Befehl select erzeugten Tabelle durchlaufen können:

                ' on crée un objet command
                Dim sqlCommand As OdbcCommand = New OdbcCommand(selectCommand, impotsConn)
                ' on exécute la requête
                Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader()
  • Wir durchlaufen die Ergebnistabelle Zeile für Zeile. Dazu verwenden wir die [Read]-Methode des zuvor erhaltenen [OdbcDataReader]-Objekts. Diese Methode bewirkt zwei Dinge:
    • Sie springt eine Zeile in der Tabelle vor. Zu Beginn befindet sich der Cursor vor der ersten Zeile
    • Sie gibt den booleschen Wert [true] zurück, wenn sie vorrücken konnte, andernfalls [false], wobei der letztere Fall anzeigt, dass alle Zeilen verarbeitet wurden.

Die Spalten der aktuellen Zeile des [OdbcDataReader]-Objekts werden über OdbcDataReader abgerufen. Dies gibt ein Objekt zurück, das den Wert der Spalte darstellt. Wir durchlaufen die gesamte Tabelle, um ihren Inhalt in die drei [ArrayList]-Objekte zu übertragen:


                ' Exploitation de la table récupérée
                While myReader.Read()
                    ' les données de la ligne courante sont mis dans les tableaux
                    aLimites.Add(myReader("limites"))
                    aCoeffR.Add(myReader("coeffr"))
                    aCoeffN.Add(myReader("coeffn"))
  • Sobald dies erledigt ist, geben wir die mit der Verbindung verbundenen Ressourcen frei:
                ' libération des ressources
                myReader.Close()
                impotsConn.Close()
  • Der Inhalt der drei [ArrayList]-Objekte wird in drei Standard-Arrays übertragen:

            ' dynamic tables are placed in static tables
            limites = New Decimal(aLimites.Count - 1) {}
            coeffr = New Decimal(aLimites.Count - 1) {}
            coeffn = New Decimal(aLimites.Count - 1) {}
            Dim i As Integer
            For i = 0 To aLimites.Count - 1
                limites(i) = CType(aLimites(i), Decimal)
                coeffR(i) = CType(aCoeffR(i), Decimal)
                coeffN(i) = CType(aCoeffN(i), Decimal)
            Next i
  • Sobald die Daten aus der Tabelle [impots] in die drei Arrays geladen wurden, muss nur noch deren Inhalt mithilfe der Methode [checkData] der Basisklasse [impotsData] überprüft werden:
            ' verify acquired data
            Dim erreur As Integer = checkData()
            ' if invalid data, throws an exception
            If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
            ' otherwise we return the three tables
            Return New Object() {limites, coeffr, coeffn}

6.2.4. Tests für die Datenzugriffsklasse

Ein Testprogramm könnte wie folgt aussehen:

Option Explicit On 
Option Strict On

' namespaces
Imports System
Imports Microsoft.VisualBasic

Namespace st.istia.univangers.fr

    ' test pg
    Module testimpots
        Sub Main(ByVal arguments() As String)
            ' interactive tax calculator
            ' the user enters three data points on the keyboard: married nbEnfants salary
            ' the program then displays the tax payable
            Const syntaxe1 As String = "pg DSNimpots"
            Const syntaxe2 As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"

            ' checking program parameters
            If arguments.Length <> 1 Then
                ' error msg
                Console.Error.WriteLine(syntaxe1)
                ' end
                Environment.Exit(1)
            End If
            ' retrieve the arguments
            Dim DSNimpots As String = arguments(0)

            ' tax object creation
            Dim objImpot As impot = Nothing
            Try
                objImpot = New impot(New impotsODBC(DSNimpots))
            Catch ex As Exception
                Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
                Environment.Exit(2)
            End Try

            ' infinite loop
            While True
                ' initially no errors
                Dim erreur As Boolean = False

                ' tax calculation parameters are requested
                Console.Out.Write("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :")
                Dim paramètres As String = Console.In.ReadLine().Trim()

                ' anything to do?
                If paramètres Is Nothing Or paramètres = "" Then
                    Exit While
                End If

                ' check the number of arguments in the input line
                Dim args As String() = paramètres.Split(Nothing)
                Dim nbParamètres As Integer = args.Length
                If nbParamètres <> 3 Then
                    Console.Error.WriteLine(syntaxe2)
                    erreur = True
                End If
                Dim marié As String
                Dim nbEnfants As Integer
                Dim salaire As Integer
                If Not erreur Then
                    ' checking the validity of parameters
                    ' married
                    marié = args(0).ToLower()
                    If marié <> "o" And marié <> "n" Then
                        Console.Error.WriteLine((syntaxe2 + ControlChars.Lf + "Argument marié incorrect : tapez o ou n"))
                        erreur = True
                    End If
                    ' nbEnfants
                    nbEnfants = 0
                    Try
                        nbEnfants = Integer.Parse(args(1))
                        If nbEnfants < 0 Then
                            Throw New Exception
                        End If
                    Catch
                        Console.Error.WriteLine(syntaxe2 + "\nArgument nbEnfants incorrect : tapez un entier positif ou nul")
                        erreur = True
                    End Try
                    ' salary
                    salaire = 0
                    Try
                        salaire = Integer.Parse(args(2))
                        If salaire < 0 Then
                            Throw New Exception
                        End If
                    Catch
                        Console.Error.WriteLine(syntaxe2 + "\nArgument salaire incorrect : tapez un entier positif ou nul")
                        erreur = True
                    End Try
                End If
                If Not erreur Then
                    ' parameters are correct - tax is calculated
                    Console.Out.WriteLine(("impôt=" & objImpot.calculer(marié = "o", nbEnfants, salaire).ToString + " euro(s)"))
                End If
            End While
        End Sub
    End Module
End Namespace

Die Anwendung wird mit einem Parameter gestartet:

  • DSNimpots: Name der zu verwendenden ODBC-Datenquelle

Die Steuerberechnung erfolgt mithilfe eines Objekts vom Typ [tax], das beim Start der Anwendung erstellt wird:


            ' tax object creation
            Dim objImpôt As impot = Nothing
            Try
                objImpot = New impot(New impotsODBC(DSNimpots))
            Catch ex As Exception
                Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
                Environment.Exit(1)
            End Try

Nach der Initialisierung fordert die Anwendung den Benutzer wiederholt auf, die drei für die Steuerberechnung erforderlichen Angaben einzugeben:

  • Familienstand: o für verheiratet, n für unverheiratet
  • die Anzahl der Kinder
  • Jahresgehalt

Alle Klassen werden kompiliert:

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

Das Testprogramm wird dann kompiliert:

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 MySQL-ODBC-Datenquelle ausgeführt:

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

Wir wechseln die ODBC-Quelle zu einer Access-Quelle:

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

6.2.5. Ansichten der Webanwendung

Diese sind dieselben wie in der vorherigen Anwendung: formulaire.aspx und erreurs.aspx

6.2.6. Anwendungscontroller [global.asax, main.aspx]

Nur der Controller [global.asax] muss geändert werden. Er ist dafür zuständig, das Objekt [impot] beim Start der Anwendung zu erstellen. Der Konstruktor dieses Objekts hat einen einzigen Parameter: das Objekt [impotsData], das für das Abrufen der Daten zuständig ist. Dieser Parameter ändert sich daher für jeden neuen Typ von Datenquelle. Der Controller [global.asax.vb] sieht nun wie folgt aus:


Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr
Imports System.Configuration
 
Public Class Global
    Inherits System.Web.HttpApplication
 
    Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' create an impot object
        Dim objImpot As impot
        Try
            objImpot = New impot(New impotsODBC(ConfigurationSettings.AppSettings("DSNimpots")))
            ' put the object in the application
            Application("objImpot") = objImpot
            ' no error
            Application("erreur") = False
        Catch ex As Exception
            'there has been an error, we note it in the application
            Application("erreur") = True
            Application("message") = ex.Message
        End Try
    End Sub
End Class

Die Datenquelle für das [import]-Objekt ist nun ein [importODBC]-Objekt. Dieses Objekt nimmt den DSN-Namen der zu verwendenden ODBC-Datenquelle als Parameter entgegen. Anstatt diesen Namen fest im Code zu hinterlegen, speichern wir ihn in der Konfigurationsdatei [web.config] der Anwendung:


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

Wir wissen, dass der Wert eines Schlüssels C im Abschnitt <appSettings> der Datei [web.config] im Anwendungscode mit [ConfigurationSettings.AppSettings(C)] abgerufen wird.

Um die Ursache der Ausnahme zu ermitteln, protokollieren wir die Ausnahmemeldung in der Anwendung, damit sie für Abfragen verfügbar bleibt. Das Steuerelement [main.aspx.vb] nimmt diese Meldung in seine Fehlerliste auf:


    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' first of all, we check whether the application has initialized correctly
        If CType(Application("erreur"), Boolean) Then
            ' redirects to error page
            Dim erreurs As New ArrayList
            erreurs.Add("Application momentanément indisponible...(" + Application("message").ToString + ")")
            context.Items("erreurs") = erreurs
            context.Items("lien") = ""
            context.Items("href") = ""
            Server.Transfer("erreurs.aspx")
        End If
        ' retrieve the action to be performed
...

6.2.7. Zusammenfassung der Änderungen

Die Anwendung ist bereit für den Test. Hier sind die Änderungen gegenüber der vorherigen Version:

  1. Es wurde eine neue Datenzugriffsklasse erstellt
  2. Der Controller [global.asax.vb] wurde an zwei Stellen geändert: die Konstruktion des [import]-Objekts und die Protokollierung der Meldung zu Ausnahmen in der Anwendung
  3. Der Controller [main.aspx.vb] wurde an einer Stelle geändert, um die vorherige Ausnahmemeldung anzuzeigen
  4. Eine [web.config]-Datei wurde hinzugefügt

Die Änderungsarbeiten wurden hauptsächlich in 1 durchgeführt, d. h. außerhalb der Webanwendung. Dies wurde durch die MVC-Architektur der Anwendung ermöglicht, die den Controller von den Geschäftsklassen trennt. Das ist der Sinn dieser Architektur. Es konnte gezeigt werden, dass mit einer entsprechenden [web.config]-Datei jede Änderung am Anwendungscontroller hätte vermieden werden können. In [web.config] ist es möglich, den Namen der dynamisch zu instanziierenden Datenzugriffsklasse – wie z. B. – sowie die verschiedenen für diese Instanziierung erforderlichen Parameter anzugeben. Mit diesen Informationen kann [global.asax] das Datenzugriffsobjekt instanziieren. Das Ändern der Datenquelle läuft dann darauf hinaus,

  • Erstellen der Datenzugriffsklasse für diese Quelle, falls sie noch nicht existiert
  • die Datei [web.config] so anzupassen, dass die dynamische Erstellung einer Instanz dieser Klasse in [global.asax] möglich ist

6.2.8. Testen der Webanwendung

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

Image

In diesem Ordner wird ein Unterordner [bin] erstellt, in dem die Assembly [impot.dll] – die durch die Kompilierung der Business-Class-Dateien [impots.vb, impotsData.vb, impotsArray.vb, impotsODBC.vb] generiert wurde – abgelegt wird. Der erforderliche Kompilierungsbefehl lautet wie folgt:

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

Die oben genannte Datei [impot.dll] muss im Verzeichnis <Anwendungspfad>\bin abgelegt werden, damit die Webanwendung darauf zugreifen kann. Der Cassini-Server wird mit den Parametern (<Anwendungspfad>,/impots2) gestartet. Die Tests liefern dieselben Ergebnisse wie in der vorherigen Version, da das Vorhandensein der Datenbank für den Benutzer transparent ist. Um dieses Vorhandensein zu demonstrieren, stellen wir jedoch sicher, dass die ODBC-Quelle nicht verfügbar ist, indem wir das MySQL-DBMS anhalten und die URL [http://localhost/impots2/main.aspx] aufrufen. Wir erhalten die folgende Antwort:

Image

6.3. Beispiel 3

6.3.1. Das Problem

Hier schlagen wir vor, dasselbe Problem anzugehen, indem wir erneut die Datenquelle des von der Webanwendung erstellten [impot]-Objekts ändern. Diesmal wird die neue Datenquelle eine ACCESS-Datenbank sein, auf die über einen OLEDB-Treiber zugegriffen wird. Unser Ziel ist es, eine weitere Möglichkeit für den Zugriff auf eine Datenbank aufzuzeigen.

6.3.2. Die OLEDB-Datenquelle

Die Daten befinden sich in einer Tabelle namens [IMPOTS] in einer ACCESS-Datenbank. Der Inhalt dieser Tabelle sieht wie folgt aus:

Image

6.3.3. Die Datenzugriffsklasse

Werfen wir noch einmal einen Blick auf die MVC-Struktur unserer Anwendung:

Image

  • In der obigen Abbildung ist die Klasse [impotsData] für das Abrufen der Daten zuständig. Diesmal muss sie dies aus einer OLEDB-Quelle tun.

Wir erstellen die Klasse [impotsOLEDB], die die Daten (limits, coeffr, coeffn) aus einer ODBC-Quelle abruft, die wir wie folgt benennen:


Imports System.Data
Imports System.Collections
Imports System
Imports System.Xml
Imports System.Data.OleDb
 
Namespace st.istia.univangers.fr
    Public Class impotsOLEDB
        Inherits impotsData
 
        ' instance variables
        Protected chaineConnexion As String
 
        ' manufacturer
        Public Sub New(ByVal chaineConnexion As String)
            ' we note the three pieces of information
            Me.chaineConnexion = chaineConnexion
        End Sub
 
        Public Overrides Function getData() As Object()
            ' initializes the three limit tables, coeffr, coeffn from
            ' the contents of the [impots] table in the OLEDB [chaineConnexion] database
            ' limits, coeffr, coeffn are the three columns of this table
            ' can launch various exceptions
 
            ' create a DataAdapter object to read data from source OLEDB
            Dim adaptateur As New OleDbDataAdapter("select limites,coeffr,coeffn from impots", chaineConnexion)
            ' create a memory image of the select result
            Dim contenu As New DataTable("impots")
            Try
                adaptateur.Fill(contenu)
            Catch e As Exception
                Throw New Exception("Erreur d'accès à la base de données (" + e.Message + ")")
            End Try
            ' retrieve the contents of the impots table
            Dim lignesImpots As DataRowCollection = contenu.Rows
            ' dimensioning of reception panels
            Me.limites = New Decimal(lignesImpots.Count - 1) {}
            Me.coeffr = New Decimal(lignesImpots.Count - 1) {}
            Me.coeffn = New Decimal(lignesImpots.Count - 1) {}
            ' transfer the contents of the impots table to the tables
            Dim i As Integer
            Dim ligne As DataRow
            Try
                For i = 0 To lignesImpots.Count - 1
                    ' table line i
                    ligne = lignesImpots.Item(i)
                    ' retrieve the contents of the line
                    limites(i) = CType(ligne.Item(0), Decimal)
                    coeffr(i) = CType(ligne.Item(1), Decimal)
                    coeffn(i) = CType(ligne.Item(2), Decimal)
                Next
            Catch
                Throw New Exception("Les données des tranches d'impôts n'ont pas le bon type")
            End Try
            ' verify acquired data
            Dim erreur As Integer = checkData()
            ' if invalid data, throws an exception
            If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
            ' otherwise we return the three tables
            Return New Object() {limites, coeffr, coeffn}
        End Function
    End Class
End Namespace

Werfen wir einen Blick auf den Konstruktor:


        ' manufacturer
        Public Sub New(ByVal chaineConnexion As String)
            ' we note the three pieces of information
            Me.chaineConnexion = chaineConnexion
        End Sub

Er erhält als Parameter die Verbindungszeichenfolge für die OLEDB-Quelle, die die abzurufenden Daten enthält. Der Konstruktor speichert diese lediglich. Eine Verbindungszeichenfolge enthält alle Parameter, die der OLEDB-Treiber benötigt, um eine Verbindung zur OLEDB-Quelle herzustellen. Sie ist in der Regel recht komplex. Um die Verbindungszeichenfolge für ACCESS-Datenbanken zu ermitteln, können Sie das Tool [WebMatrix] verwenden. Starten Sie dieses Tool. Es bietet ein Fenster zum Herstellen einer Verbindung zu einer Datenquelle:

Über das oben mit dem Pfeil markierte Symbol können Sie eine Verbindung zu zwei Arten von Microsoft-Datenbanken herstellen: SQL Server und ACCESS. Wählen wir ACCESS:

Image

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

Image

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

Image

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

Image

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

Image

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

Image

Die OLEDB-Verbindungszeichenfolge zur ACCESS-Datenbank wird durch die Eigenschaft [ConnectionString] oben bereitgestellt:

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

Wir sehen, dass diese Zeichenfolge aus einem festen Teil und einem variablen Teil besteht, bei dem es sich einfach um den Namen der ACCESS-Datei handelt. Wir werden diese Tatsache nutzen, um die Verbindungszeichenfolge zu unserer OLEDB-Datenquelle zu generieren.

Kehren wir nun zu unserer [impotsOLEDB]-Klasse zurück. Die [getData]-Methode ist dafür zuständig, Daten aus der [impots]-Tabelle zu lesen und in drei Arrays (limites, coeffr, coeffn) zu speichern. Sehen wir uns den Code an:

  • Wir definieren das [DataAdapter]-Objekt, mit dem wir das Ergebnis einer SQL-SELECT-Abfrage in den Speicher übertragen können. Dazu definieren wir die auszuführende [select]-Abfrage und verknüpfen sie mit dem [DataAdapter]-Objekt. Der Konstruktor des [DataAdapter] benötigt außerdem die Verbindungszeichenfolge, über die er eine Verbindung zur OLEDB-Quelle herstellt

            ' on crée un objet DataAdapter pour lire les données de la source OLEDB
            Dim adaptateur As New OleDbDataAdapter("select limites,coeffr,coeffn from impots", chaineConnexion)
  • Wir führen den [select]-Befehl mithilfe der [Fill]-Methode des [DataAdapter]-Objekts aus. Das Ergebnis des [select]-Befehls wird in ein zu diesem Zweck erstelltes [DataTable]-Objekt geladen. Ein [DataTable]-Objekt ist die In-Memory-Darstellung einer Datenbanktabelle, d. h. eine Sammlung von Zeilen und Spalten. Wir behandeln eine Ausnahme, die auftreten kann, wenn beispielsweise die Verbindungszeichenfolge falsch ist.

            ' on crée une image en mémoire du résultat du select
            Dim contenu As New DataTable("impots")
            Try
                adaptateur.Fill(contenu)
            Catch e As Exception
                Throw New Exception("Erreur d'accès à la base de données (" + e.Message + ")")
            End Try
  • In [content] haben wir die Tabelle [taxes], die von der [select]-Anweisung zurückgegeben wird. Ein [DataTable]-Objekt ist eine Tabelle, d. h. eine Reihe von Zeilen. Auf diese kann über die [rows]-Eigenschaft von [DataTable] zugegriffen werden:

            ' retrieve the contents of the impots table
            Dim lignesImpots As DataRowCollection = contenu.Rows
  • Jedes Element der [taxRows]-Sammlung ist ein [DataRow]-Objekt, das eine Zeile in der Tabelle darstellt. Diese Zeile verfügt über Spalten, auf die über das [DataRow]-Objekt mittels seiner [Item]-Eigenschaft zugegriffen werden kann. [DataRow].[Item(i)] ist die Spalte Nummer i des [DataRow]. Durch Durchlaufen der Sammlung von Zeilen (die DataRows-Sammlung von taxRows) und der Sammlung von Spalten für jede Zeile können wir die gesamte Tabelle abrufen:
            ' dimensioning of reception panels
            Me.limites = New Decimal(lignesImpots.Count - 1) {}
            Me.coeffr = New Decimal(lignesImpots.Count - 1) {}
            Me.coeffn = New Decimal(lignesImpots.Count - 1) {}
            ' transfer the contents of the impots table to the tables
            Dim i As Integer
            Dim ligne As DataRow
            Try
                For i = 0 To lignesImpots.Count - 1
                    ' table line i
                    ligne = lignesImpots.Item(i)
                    ' retrieve the contents of the line
                    limites(i) = CType(ligne.Item(0), Decimal)
                    coeffr(i) = CType(ligne.Item(1), Decimal)
                    coeffn(i) = CType(ligne.Item(2), Decimal)
                Next
            Catch
                Throw New Exception("Les données des tranches d'impôts n'ont pas le bon type")
            End Try
  • Sobald die Daten aus der Tabelle [taxes] in die drei Arrays geladen wurden, muss nur noch deren Inhalt mithilfe der Methode [checkData] der Basisklasse [taxData] überprüft werden:
            ' verify acquired data
            Dim erreur As Integer = checkData()
            ' if invalid data, throws an exception
            If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
            ' otherwise we return the three tables
            Return New Object() {limites, coeffr, coeffn}

6.3.4. Tests für die Datenzugriffsklasse

Ein Testprogramm könnte wie folgt aussehen:

Option Explicit On 
Option Strict On

' namespaces
Imports System
Imports Microsoft.VisualBasic

Namespace st.istia.univangers.fr

    ' test pg
    Module testimpots
        Sub Main(ByVal arguments() As String)
            ' interactive tax calculator
            ' the user enters three data points on the keyboard: married nbEnfants salary
            ' the program then displays the tax payable
            Const syntaxe1 As String = "pg bdACCESS"
            Const syntaxe2 As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"

            ' checking program parameters
            If arguments.Length <> 1 Then
                ' error msg
                Console.Error.WriteLine(syntaxe1)
                ' end
                Environment.Exit(1)
            End If
            ' retrieve the arguments
            Dim chemin As String = arguments(0)
            ' prepare the connection chain
            Dim chaineConnexion As String = "Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=" + chemin

            ' tax object creation
            Dim objImpot As impot = Nothing
            Try
                objImpot = New impot(New impotsOLEDB(chaineConnexion))
            Catch ex As Exception
                Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
                Environment.Exit(2)
            End Try

            ' infinite loop
            While True
                ' initially no errors
                Dim erreur As Boolean = False

                ' tax calculation parameters are requested
                Console.Out.Write("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :")
                Dim paramètres As String = Console.In.ReadLine().Trim()

                ' anything to do?
                If paramètres Is Nothing Or paramètres = "" Then
                    Exit While
                End If

                ' check the number of arguments in the input line
                Dim args As String() = paramètres.Split(Nothing)
                Dim nbParamètres As Integer = args.Length
                If nbParamètres <> 3 Then
                    Console.Error.WriteLine(syntaxe2)
                    erreur = True
                End If
                Dim marié As String
                Dim nbEnfants As Integer
                Dim salaire As Integer
                If Not erreur Then
                    ' checking the validity of parameters
                    ' married
                    marié = args(0).ToLower()
                    If marié <> "o" And marié <> "n" Then
                        Console.Error.WriteLine((syntaxe2 + ControlChars.Lf + "Argument marié incorrect : tapez o ou n"))
                        erreur = True
                    End If
                    ' nbEnfants
                    nbEnfants = 0
                    Try
                        nbEnfants = Integer.Parse(args(1))
                        If nbEnfants < 0 Then
                            Throw New Exception
                        End If
                    Catch
                        Console.Error.WriteLine(syntaxe2 + "\nArgument nbEnfants incorrect : tapez un entier positif ou nul")
                        erreur = True
                    End Try
                    ' salary
                    salaire = 0
                    Try
                        salaire = Integer.Parse(args(2))
                        If salaire < 0 Then
                            Throw New Exception
                        End If
                    Catch
                        Console.Error.WriteLine(syntaxe2 + "\nArgument salaire incorrect : tapez un entier positif ou nul")
                        erreur = True
                    End Try
                End If
                If Not erreur Then
                    ' parameters are correct - tax is calculated
                    Console.Out.WriteLine(("impôt=" & objImpot.calculer(marié = "o", nbEnfants, salaire).ToString + " euro(s)"))
                End If
            End While
        End Sub
    End Module
End Namespace

Die Anwendung wird mit einem Parameter gestartet:

  • bdACCESS: Name der zu verwendenden ACCESS-Datei

Die Steuerberechnung erfolgt mithilfe eines Objekts vom Typ [tax], das beim Start der Anwendung erstellt wird:


            ' retrieve the arguments
            Dim chemin As String = arguments(0)
            ' prepare the connection chain
            Dim chaineConnexion As String = "Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=" + chemin
 
            ' tax object creation
            Dim objImpot As impot = Nothing
            Try
                objImpot = New impot(New impotsOLEDB(chaineConnexion))
            Catch ex As Exception
                Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
                Environment.Exit(2)
            End Try

Die Verbindungszeichenfolge zur OLEDB-Quelle wurde unter Verwendung von Informationen erstellt, die mit [WebMatrix] abgerufen wurden.

Nach der Initialisierung fordert die Anwendung den Benutzer wiederholt auf, die drei für die Berechnung seiner Steuer erforderlichen Angaben einzugeben:

  • Familienstand: o für verheiratet, n für unverheiratet
  • Anzahl der Kinder
  • Jahresgehalt

Alle Klassen werden kompiliert:

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

Die Datei [impots.mdb] wird im Ordner der Testanwendung abgelegt, und die Anwendung wird wie folgt gestartet:

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

Sie können die Anwendung mit einer fehlerhaften ACCESS-Datei ausführen:

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

6.3.5. Die Ansichten der Webanwendung

Diese sind dieselben wie in der vorherigen Anwendung: formulaire.aspx und erreurs.aspx

6.3.6. Anwendungscontroller [global.asax, main.aspx]

Nur der Controller [global.asax] muss geändert werden. Er ist dafür zuständig, das Objekt [impot] beim Start der Anwendung zu erstellen. Der Konstruktor dieses Objekts hat einen einzigen Parameter: das Objekt [impotsData], das für das Abrufen der Daten zuständig ist. Dieser Parameter ändert sich daher, da wir die Datenquelle wechseln. Der Controller [global.asax.vb] sieht nun wie folgt aus:


Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr
Imports System.Configuration
 
Public Class Global
    Inherits System.Web.HttpApplication
 
    Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' create an impot object
        Dim objImpot As impot
        Try
            objImpot = New impot(New impotsOLEDB(ConfigurationSettings.AppSettings("chaineConnexion")))
            ' put the object in the application
            Application("objImpot") = objImpot
            ' no error
            Application("erreur") = False
        Catch ex As Exception
            'there has been an error, we note it in the application
            Application("erreur") = True
            Application("message") = ex.Message
        End Try
    End Sub
End Class

Die Datenquelle für das [import]-Objekt ist nun ein [importOLEDB]-Objekt. Dieses Objekt nimmt als Parameter die Verbindungszeichenfolge für die zu verwendende OLEDB-Datenquelle entgegen. Diese Zeichenfolge ist in der Konfigurationsdatei [web.config] der Anwendung gespeichert:


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

Der Controller [main.aspx] bleibt unverändert.

6.3.7. Zusammenfassung der Änderungen

Die Anwendung ist bereit für den Test. Hier sind die Änderungen gegenüber der vorherigen Version:

  1. Es wurde eine neue Datenzugriffsklasse erstellt
  2. Der Controller [global.asax.vb] wurde an einer Stelle geändert: Erstellung des Objekts [import]
  3. Eine [web.config]-Datei wurde hinzugefügt

6.3.8. Testen der Webanwendung

Alle oben genannten Dateien befinden sich in einem Ordner namens <application-path>.

Image

In diesem Ordner wird ein Unterordner [bin] erstellt, in dem die Assembly [impot.dll] – die durch die Kompilierung der Business-Class-Dateien [impots.vb, impotsData.vb, impotsArray.vb, impotsOLEDB.vb] generiert wurde – abgelegt wird. Der erforderliche Kompilierungsbefehl lautet wie folgt:

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

Die durch diesen Befehl erzeugte Datei [impot.dll] muss im Verzeichnis <application-path>\bin abgelegt werden, damit die Webanwendung darauf zugreifen kann. Der Cassini-Server wird mit den Parametern (<application-path>,/impots3) gestartet. Die Tests liefern die gleichen Ergebnisse wie in der vorherigen Version.

6.4. Beispiel 4

6.4.1. Das Problem

Wir schlagen nun vor, unsere Anwendung in eine Anwendung zur Simulation von Steuerberechnungen umzuwandeln. Ein Benutzer kann aufeinanderfolgende Steuerberechnungen durchführen, die ihm in einer neuen Ansicht wie dieser angezeigt werden:

Image

6.4.2. Die MVC-Struktur der Anwendung

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

Image

Es erscheint eine neue Ansicht [simulations.aspx], von der wir gerade einen Screenshot bereitgestellt haben. Die Datenzugriffsklasse ist die Klasse [impotsODBC] aus Beispiel 2.

6.4.3. Die Ansichten der Webanwendung

Die Ansicht [erreurs.aspx] bleibt unverändert. Die Ansicht [formulaire.aspx] ändert sich geringfügig. Tatsächlich erscheint der Steuerbetrag nicht mehr in dieser Ansicht. Er befindet sich nun in der Ansicht [simulations.aspx]. Somit wird dem Benutzer beim Start folgende Seite angezeigt:

Image

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

Image

Der Präsentationscode lautet wie folgt:


<%@ page src="formulaire.aspx.vb" inherits="formulaire" AutoEventWireup="false"%>
<html>
    <head>
        <title>Impôt</title>
        <script language="javascript">
        function calculer(){
          // vérification des paramètres avant de les envoyer au serveur
        with(document.frmImpots){
          //nbre d'enfants
          champs=/^\s*(\d+)\s*$/.exec(txtEnfants.value);
          if(champs==null){
            // le modéle n'est pas vérifié
            alert("Le nombre d'enfants n'a pas été donné ou est incorrect");
            txtEnfants.focus();
            return;
          }//if
          //salaire
          champs=/^\s*(\d+)\s*$/.exec(txtSalaire.value);
          if(champs==null){
            // le modéle n'est pas vérifié
            alert("Le salaire n'a pas été donné ou est incorrect");
            txtSalaire.focus();
            return;
          }//if
          // c'est bon - on envoie le formulaire au serveur
          submit();
        }//with
      }//calculer  
        </script>
    </head>
    <body>
        <P>Calcul de votre impôt</P>
        <HR width="100%" SIZE="1">
        <form name="frmImpots" method="post" action="main.aspx?action=calcul">
            <TABLE border="0">
                <TR>
                    <TD>Etes-vous marié(e)</TD>
                    <TD>
                        <INPUT type="radio" value="oui" name="rdMarie" <%=rdouichecked%>>Oui 
                      <INPUT type="radio"  value="non" name="rdMarie" <%=rdnonchecked%>>Non</TD>
                </TR>
                <TR>
                    <TD>Nombre d'enfants</TD>
                    <TD><INPUT type="text" size="3" maxLength="3" name="txtEnfants" value="<%=txtEnfants%>"></TD>
                </TR>
                <TR>
                    <TD>Salaire annuel (euro)</TD>
                    <TD><INPUT type="text" maxLength="12" size="12" name="txtSalaire" value="<%=txtSalaire%>"></TD>
                </TR>
            </TABLE>
            <hr>
            <P>
                <INPUT type="button" value="Calculer" onclick="calculer()">
            </P>
        </form>
        <form method="post" action="main.aspx?action=effacer">
            <INPUT type="submit" value="Effacer">
        </form>
    </body>
</html>

Die dynamischen Felder auf der Seite sind dieselben wie in früheren Versionen. Das dynamische Feld für den Steuerbetrag wurde entfernt. Die Schaltfläche [Berechnen] ist keine [Submit]-Schaltfläche mehr. Es handelt sich um eine [Schaltfläche], und wenn sie angeklickt wird, wird die JavaScript-Funktion [calculate()] ausgeführt:


                <INPUT type="button" value="Calculer" onclick="calculer()">

Wir haben dem Formular den Namen [frmImpots] gegeben, damit wir im Skript [calculer] darauf verweisen können:


        <form name="frmImpots" method="post" action="main.aspx?action=calcul">

Die JavaScript-Funktion [calculate] verwendet reguläre Ausdrücke, um die Felder in den Formularen [document.frmImpots.txtEnfants] und [document.frmImpots.txtSalaire] zu validieren. Sind die eingegebenen Werte korrekt, werden sie über [document.frmImpots.submit()] an den Server gesendet.

Die Präsentationsseite bezieht ihre dynamischen Felder aus dem folgenden Controller [formulaire.aspx.vb]:


Imports System.Collections.Specialized
 
Public Class formulaire
    Inherits System.Web.UI.Page
 
    ' page fields
    Protected rdouichecked As String
    Protected rdnonchecked As String
    Protected txtEnfants As String
    Protected txtSalaire As String
    Protected txtImpot As String
 
    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' we retrieve the previous request in the
        Dim form As NameValueCollection = Context.Items("formulaire")
        ' prepare the page to be displayed
        ' radio buttons
        rdouichecked = ""
        rdnonchecked = "checked"
        If form("rdMarie").ToString = "oui" Then
            rdouichecked = "checked"
            rdnonchecked = ""
        End If
        ' the rest
        txtEnfants = CType(form("txtEnfants"), String)
        txtSalaire = CType(form("txtSalaire"), String)
    End Sub
End Class

Der Controller [formulaire.aspx.vb] ist identisch mit früheren Versionen, außer dass er das Feld [txtImpot] nicht mehr aus dem Kontext abrufen muss, da dieses Feld von der Seite entfernt wurde.

Die Ansicht [simulations.aspx] sieht wie folgt aus:

Image

und entspricht dem folgenden Präsentationscode:


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

Dieser Code enthält drei dynamische Felder:

simulationsHTML
HTML-Code für eine Liste von Simulationen in Form von HTML-Tabellenzeilen
href
URL eines Links
link
Linktext

Sie werden vom Controller [simulations.aspx.vb] generiert:


Imports System.Collections
Imports Microsoft.VisualBasic
 
Public Class simulations
    Inherits System.Web.UI.Page
 
    Protected simulationsHTML As String = ""
    Protected href As String
    Protected lien As String
 
    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        'simulations are retrieved from the context
        Dim simulations As ArrayList = CType(context.Items("simulations"), ArrayList)
        ' each simulation is an array of 4 string elements
        Dim simulation() As String
        Dim i, j As Integer
        For i = 0 To simulations.Count - 1
            simulation = CType(simulations(i), String())
            simulationsHTML += "<tr>"
            For j = 0 To simulation.Length - 1
                simulationsHTML += "<td>" + simulation(j) + "</td>"
            Next
            simulationsHTML += "</tr>" + ControlChars.CrLf
        Next
        ' recover the other elements of the context
        href = context.Items("href").ToString
        lien = context.Items("lien").ToString
    End Sub
End Class

Der Seiten-Controller ruft Informationen ab, die vom Anwendungs-Controller im Seitenkontext abgelegt wurden:

Context.Items("simulations")
ArrayList-Objekt, das die Liste der anzuzeigenden Simulationen enthält. Jedes Element ist ein Array aus 4 Zeichenfolgen, die die Informationen der Simulation darstellen (verheiratet, Kinder, Gehalt, Steuern).
Context.Items("href")
URL eines Links
Context.Items("link")
Linktext

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

Werfen wir einen Blick auf die MVC-Architektur unserer Anwendung:

Image

Der Controller [main.aspx] muss drei Aktionen verarbeiten:

  • init: entspricht der ersten Anfrage des Clients. Der Controller zeigt die Ansicht [form.aspx] an
  • calcul: entspricht der Anfrage zur Steuerberechnung. Sind die Daten im Eingabeformular korrekt, wird die Steuer mithilfe der Business-Klasse [impotsODBC] berechnet. Der Controller gibt die Ansicht [simulations.aspx] mit dem Ergebnis der aktuellen Simulation sowie allen vorherigen Simulationen an den Client zurück. Sind die Daten im Eingabeformular fehlerhaft, gibt der Controller die Ansicht [erreurs.aspx] mit der Fehlerliste und einem Link zur Rückkehr zum Formular zurück.
  • return: entspricht der Rückkehr zum Formular nach einem Fehler. Der Controller zeigt die Ansicht [form.aspx] so an, wie sie vor dem Fehler validiert wurde.

In dieser neuen Version hat sich nur die Aktion [calcul] geändert. Wenn die Daten gültig sind, muss sie nun zur Ansicht [simulations.aspx] führen, während sie zuvor zur Ansicht [form.aspx] führte. Der Controller [main.aspx.vb] sieht nun wie folgt aus:


Imports System
...
 
Public Class main
    Inherits System.Web.UI.Page
 
    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' first of all, we check whether the application has initialized correctly
...
        ' execute the action
        Select Case action
            Case "init"
                ' init application
                initAppli()
            Case "calcul"
                ' tax calculation
                calculImpot()
            Case "retour"
                ' back to form
                retourFormulaire()
            Case "effacer"
                ' init application
                initAppli()
            Case Else
                ' unknown action = init
                initAppli()
        End Select
    End Sub
 
...
    Private Sub calculImpot()
        ' save entries
        Session.Item("formulaire") = Request.Form
        ' check the validity of the data entered
        Dim erreurs As ArrayList = checkData()
        ' if there are errors, we report them
        If erreurs.Count <> 0 Then
            ' prepare the error page
            context.Items("href") = "main.aspx?action=retour"
            context.Items("lien") = "Retour au formulaire"
            context.Items("erreurs") = erreurs
            Server.Transfer("erreurs.aspx")
        End If
        ' no errors here - the tax is calculated
        Dim impot As Long = CType(Application("objImpot"), impot).calculer( _
        Request.Form("rdMarie") = "oui", _
        CType(Request.Form("txtEnfants"), Integer), _
        CType(Request.Form("txtSalaire"), Long))
        ' the result is added to existing simulations
        Dim simulations As ArrayList
        If Not Session.Item("simulations") Is Nothing Then
            simulations = CType(Session.Item("simulations"), ArrayList)
        Else
            simulations = New ArrayList
        End If
        ' add current simulation
        Dim simulation() As String = New String() {Request.Form("rdMarie").ToString, _
        Request.Form("txtEnfants").ToString, Request.Form("txtSalaire").ToString, _
        impot.ToString}
        simulations.Add(simulation)
        ' put simulations in session and context
        context.Items("simulations") = simulations
        Session.Item("simulations") = simulations
        ' the result page is displayed
        context.Items("href") = "main.aspx?action=retour"
        context.Items("lien") = "Retour au formulaire"
        Server.Transfer("simulations.aspx", True)
    End Sub
...
End Class

Wir haben oben nur das Nötigste aufgeführt, um die Änderungen zu verstehen, die ausschließlich in der Funktion [calculImpots] zu finden sind:

  • Zunächst speichert die Funktion das Formular [Request.Form] in der Sitzung, damit das Formular in dem Zustand wiederhergestellt werden kann, in dem es validiert wurde. Dies muss in jedem Fall geschehen, da wir unabhängig davon, ob der Vorgang zur Antwort [erreurs.aspx] oder zur Antwort [simulations.aspx] führt, über den Link [Zurück zum Formular] zum Formular zurückkehren. Um das Formular korrekt wiederherzustellen, müssen seine Werte zuvor in der Sitzung gespeichert worden sein.
  • Wenn die eingegebenen Daten korrekt sind, fügt die Funktion die aktuelle Simulation (verheiratet, Kinder, Gehalt, Steuern) zur Liste der Simulationen hinzu. Diese Liste befindet sich in der Sitzung, die mit dem Schlüssel „simulations“ verknüpft ist.
  • Die Liste der Simulationen wird für die spätere Verwendung in der Sitzung gespeichert. Sie wird außerdem im aktuellen Kontext abgelegt, da die Ansicht [simulations.aspx] erwartet, sie dort zu finden
  • Die Ansicht [simulations.aspx] wird angezeigt, sobald die anderen erforderlichen Informationen in den Kontext eingefügt wurden

6.4.5. Zusammenfassung der Änderungen

Die Anwendung ist bereit für Tests. Hier sind die Änderungen gegenüber früheren Versionen:

  1. Es wurde eine neue Ansicht erstellt
  2. Der Controller [main.aspx.vb] wurde an einer Stelle geändert: die Behandlung der Aktion [calcul]

6.4.6. Testen der Webanwendung

Der Leser ist eingeladen, die Tests durchzuführen. Hier eine Erinnerung an die Vorgehensweise. Alle Anwendungsdateien befinden sich in einem Ordner namens <application-path>. Innerhalb dieses Ordners wird ein Unterordner [bin] erstellt, der die Assembly [impot.dll] enthält, die aus der Kompilierung der Business-Class-Dateien generiert wurde: [impots.vb, impotsData.vb, impotsArray.vb, impotsODBC.vb]. Die durch diesen Befehl erzeugte Datei [impot.dll] muss in <application-path>\bin abgelegt werden, damit die Webanwendung darauf zugreifen kann. Der Cassini-Server wird mit den Parametern (<application-path>,/impots4) gestartet.

6.5. Fazit

Die vorangegangenen Beispiele haben in einem konkreten Szenario Mechanismen demonstriert, die in der Webentwicklung häufig zum Einsatz kommen. Wir haben aus pädagogischen Gründen durchgehend die MVC-Architektur verwendet. Wir hätten dieselben Beispiele auch anders und vielleicht einfacher ohne diese Architektur umsetzen können. Sie bietet jedoch erhebliche Vorteile, sobald die Anwendung mit mehreren Seiten etwas komplexer wird.

Wir könnten unsere Beispiele auf verschiedene Weise fortsetzen. Hier sind einige davon:

  • Der Benutzer möchte möglicherweise seine Simulationen über einen längeren Zeitraum speichern. Er könnte beispielsweise am Tag D Simulationen ausführen und diese am Tag D+3 abrufen. Eine mögliche Lösung für dieses Problem ist die Verwendung von Cookies. Wir wissen, dass das Session-Token zwischen dem Server und einem Client über diesen Mechanismus übertragen wird. Wir könnten diesen Mechanismus auch nutzen, um die Simulationen zwischen dem Client und dem Server zu übertragen.
    • Gleichzeitig mit dem Senden der Seite, die die Simulationsergebnisse enthält, sendet der Server in seinen HTTP-Headern ein Cookie, das eine Zeichenkette enthält, die die Simulationen repräsentiert. Da diese in einem [ArrayList]-Objekt vorliegen, muss dieses Objekt in einen [String] konvertiert werden. Der Server würde eine Lebensdauer für das Cookie festlegen, zum Beispiel 30 Tage.
    • Der Browser des Clients speichert die empfangenen Cookies in einer Datei und sendet sie bei jeder Anfrage an den Server, der sie gesendet hat, zurück, sofern sie noch gültig sind (Lebensdauer nicht überschritten). Der Server erhält eine [String]-Zeichenkette für die Simulationen, die er in ein [ArrayList]-Objekt konvertieren muss.

Cookies werden von [Response.Cookies] verwaltet, wenn sie an den Client gesendet werden, und von [Request.Cookies], wenn sie auf dem Server empfangen werden.

  • Der oben beschriebene Mechanismus kann bei einer großen Anzahl von Simulationen recht ressourcenintensiv werden. Zudem ist es üblich, dass ein Benutzer seine Cookies regelmäßig löscht, selbst wenn er ihrem Browser ansonsten die Verwendung erlaubt. Daher gehen die Simulations-Cookies früher oder später verloren. Daher möchten wir diese möglicherweise eher auf dem Server als auf dem Client speichern, beispielsweise in einer Datenbank. Um Simulationen mit einem bestimmten Benutzer zu verknüpfen, könnte die Anwendung mit einer Authentifizierungsphase beginnen, die einen Benutzernamen und ein Passwort erfordert, die wiederum in einer Datenbank oder einem anderen Datenspeicher abgelegt sind.
  • Möglicherweise möchten wir auch den Betrieb unserer Anwendung absichern. Derzeit geht sie von zwei Annahmen aus:
    1. Der Benutzer geht immer über den Controller [main.aspx]
    2. und in diesem Fall immer die Aktionen nutzt, die auf der Seite verfügbar sind, die an ihn gesendet wurde

Was passiert beispielsweise, wenn der Benutzer direkt die URL [http://localhost/impots4/formulaire.aspx] anfordert? Dieses Szenario ist unwahrscheinlich, da der Benutzer diese URL nicht kennt. Es muss jedoch berücksichtigt werden. Dies kann vom Anwendungscontroller [global.asax] gehandhabt werden, der alle an die Anwendung gerichteten Anfragen verarbeitet. Er kann somit überprüfen, ob die angeforderte Ressource tatsächlich [main.aspx] ist.

Ein wahrscheinlichereres Szenario ist, dass ein Benutzer die auf der vom Server gesendeten Seite verfügbaren Aktionen nicht nutzt. Was passiert beispielsweise, wenn der Benutzer die URL [http://localhost/impots4/main.aspx?action=retour] direkt aufruft, ohne zuvor das Formular auszufüllen? Probieren wir es aus. Wir erhalten die folgende Antwort:

Image

Der Server stürzt ab. Das ist normal. Für die Aktion [return] erwartet der Controller, in der Sitzung ein [NameValueCollection]-Objekt zu finden, das die Formularwerte enthält, die er anzeigen muss. Er findet sie nicht. Der Controller-Mechanismus bietet eine elegante Lösung für dieses Problem. Bei jeder Anfrage kann der [main.aspx]-Controller überprüfen, ob die angeforderte Aktion tatsächlich eine der Aktionen von der zuvor an den Benutzer gesendeten Seite ist. Wir können den folgenden Mechanismus verwenden:

  • Bevor der Controller seine Antwort an den Client sendet, speichert er Informationen, die diese Seite identifizieren, in der Sitzung des Clients
  • Wenn er eine neue Anfrage vom Client erhält, überprüft er, ob die angeforderte Aktion tatsächlich zu der letzten Seite gehört, die an diesen Client gesendet wurde
  • Die Informationen, die Seiten und die auf diesen Seiten zulässigen Aktionen miteinander verknüpfen, können in die Konfigurationsdatei [web.config] der Anwendung eingetragen werden.
  • Die Erfahrung zeigt, dass Anwendungscontroller eine breite gemeinsame Grundlage haben und dass es möglich ist, einen generischen Controller zu erstellen, dessen Spezialisierung für eine bestimmte Anwendung über eine Konfigurationsdatei erfolgt. Dies ist der Ansatz, den beispielsweise das [Struts]-Tool im Bereich der Java-Webprogrammierung verfolgt.