Skip to content

6. Voorbeelden

In dit hoofdstuk willen we hetgeen we eerder hebben besproken illustreren aan de hand van een reeks voorbeelden.

6.1. Voorbeeld 1

6.1.1. Het probleem

Deze applicatie moet een gebruiker in staat stellen zijn belasting te berekenen. We gaan uit van het vereenvoudigde geval van een belastingplichtige die alleen zijn salaris hoeft aan te geven (cijfers uit 2004 voor inkomsten uit 2003):

  • we berekenen het aantal aandelen van de werknemer nbParts=nbEnfants/2 +1 als hij ongehuwd is, nbEnfants/2 + 2 als hij gehuwd is, waarbij nbEnfants het aantal kinderen is.
  • als hij ten minste drie kinderen heeft, krijgt hij een half deel extra
  • zijn belastbaar inkomen wordt berekend als R = 0,72 * S, waarbij S zijn jaarsalaris is
  • we berekenen zijn gezinscoëfficiënt QF = R / nbParts
  • zijn belasting I wordt berekend. Laten we de volgende tabel bekijken:
4262
0
0
8382
0,0683
291,09
14753
0,1914
1322,92
23888
0,2826
2668,39
38868
0,3738
4846,98
47932
0,4262
6883,66
0
0,4809
9505,54

Elke regel heeft 3 velden. Om belasting I te berekenen, zoeken we de eerste regel waar QF <= veld1. Als bijvoorbeeld QF = 5000 is, vinden we de regel

    8382        0.0683        291.09

Belasting I is dan gelijk aan 0,0683*R - 291,09*nbParts. Als QF zodanig is dat de relatie QF<=veld1 nooit geldt, dan worden de coëfficiënten van de laatste regel gebruikt. In dit geval:

    0                0.4809    9505.54

wat de belasting I = 0,4809*R - 9505,54*nbParts oplevert.

6.1.2. De structuur MVC van de applicatie

De structuur MVC van de applicatie ziet er als volgt uit:

Image

De rol van de controller wordt vervuld door de pagina [main.aspx]. Er zijn drie mogelijke acties:

  • init: komt overeen met het eerste verzoek van de client. De controller geeft de weergave [formulaire.aspx] weer
  • calcul: komt overeen met het verzoek om de belasting te berekenen. Als de gegevens in het invoerformulier correct zijn, wordt de belasting berekend met behulp van de businessklasse [impots]. De controller stuurt de client de weergave [formulaire.aspx] terug zoals deze was gevalideerd, aangevuld met de berekende belasting. Als de gegevens in het invoerformulier onjuist zijn, stuurt de controller de weergave [erreurs.aspx] terug met de lijst met fouten en een link om terug te keren naar het formulier.
  • terug: dit betreft de terugkeer naar het formulier na een fout. De controller geeft de weergave [formulaire.aspx] weer zoals deze vóór de fout was gevalideerd.

De controller [main.aspx] heeft geen kennis van belastingberekeningen. Hij is uitsluitend verantwoordelijk voor het beheer van de client-servercommunicatie en het uitvoeren van de door de client gevraagde acties. Voor de actie [calcul] zal hij gebruikmaken van de businessklasse [impot].

6.1.3. De businessklasse

De belastingklasse wordt als volgt gedefinieerd:


' geïmporteerde naamruimten
Imports System

' klasse
Namespace st.istia.univangers.fr
    Public Class impot
        Private limites(), coeffR(), coeffN() As Decimal

        ' constructor
        Public Sub New(ByRef source As impotsData)
            ' de gegevens die nodig zijn voor de berekening van de belasting
             ' zijn afkomstig van een externe bron [source]
             ' worden deze gegevens opgehaald – er kan een uitzondering zijn
            Dim data() As Object = source.getData
            limites = CType(data(0), Decimal())
            coeffR = CType(data(1), Decimal())
            coeffN = CType(data(2), Decimal())
        End Sub

         ' berekening van de belasting
        Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Long) As Long
             ' berekening van het aantal aandelen
            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
             ' berekening van het belastbaar inkomen en het gezinsquotiënt
            Dim revenu As Decimal = 0.72D * salaire
            Dim QF As Decimal = revenu / nbParts
             ' berekening van de belasting
            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

Een belastingobject door aan de constructor een gegevensbron van het type [impotsData] te verstrekken. Deze klasse heeft een openbare methode [getData] waarmee de drie gegevenstabellen kunnen worden opgehaald die nodig zijn voor de berekening van de belasting en die eerder zijn gepresenteerd. Deze methode kan een uitzondering afhandelen als de gegevens niet konden worden opgehaald of als ze onjuist blijken te zijn. Zodra het object [impot] is aangemaakt, kan de methode **calculer** herhaaldelijk worden aangeroepen; deze berekent de belasting van de belastingplichtige op basis van zijn burgerlijke staat (gehuwd of ongehuwd), het aantal kinderen en zijn jaarsalaris.

6.1.4. De klasse voor gegevenstoegang

De klasse [impotsData] is de klasse die toegang tot de gegevens mogelijk maakt. Het is een abstracte klasse. Voor elke nieuwe mogelijke gegevensbron (tabellen, platte bestanden, databases, console, ...) moet een afgeleide klasse worden aangemaakt. De definitie ervan is als volgt:


Imports System.Collections

Namespace st.istia.univangers.fr
    Public MustInherit Class impotsData
        Protected limites() As Decimal
        Protected coeffr() As Decimal
        Protected coeffn() As Decimal
        Protected checked As Boolean
        Protected valide As Boolean

        ' methode voor toegang tot gegevens
        Public MustOverride Function getData() As Object()

        ' methode voor gegevenscontrole
        Protected Function checkData() As Integer
            ' controleert de verkregen gegevens
            ' er moeten gegevens aanwezig zijn
            valide = Not limites Is Nothing AndAlso Not coeffr Is Nothing AndAlso Not coeffn Is Nothing
            If Not valide Then Return 1
            ' er moeten 3 tabellen van dezelfde grootte zijn
            If valide Then valide = limites.Length = coeffr.Length AndAlso limites.Length = coeffn.Length
            If Not valide Then Return 2
            ' de tabellen mogen niet leeg zijn
            valide = limites.Length <> 0
            If Not valide Then Return 3
            ' elke tabel moet elementen bevatten die >=0 zijn en in oplopende volgorde staan
            valide = check(limites, limites.Length - 1) AndAlso check(coeffr, coeffr.Length) AndAlso check(coeffn, coeffn.Length)
            If Not valide Then Return 4
            ' alles is in orde
            Return 0
        End Function

        ' controleert de geldigheid van de inhoud van een array
        Protected Function check(ByRef tableau() As Decimal, ByVal n As Integer) As Boolean
            ' de eerste n elementen van de array moeten >=0 zijn en in strikt oplopende volgorde staan
            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
            ' dit is goed
            Return True
        End Function
    End Class
End Namespace

De klasse heeft de volgende beschermde attributen:

limites
tabel met de grenzen van de belastingschijven
coeffr
tabel met coëfficiënten die op het belastbaar inkomen worden toegepast
coeffn
tabel met coëfficiënten die worden toegepast op het aantal aandelen
checked
booleaanse waarde die aangeeft of de gegevens (limieten, coeffr, coeffn) zijn gecontroleerd
valide
booleaanse waarde die aangeeft of de gegevens (limieten, coeffr, coeffn) geldig zijn

De klasse heeft geen constructor. Ze heeft een abstracte methode [getData] die afgeleide klassen moeten implementeren. Deze methode heeft als taak:

  • waarden toewijzen aan de drie arrays limieten, coeffr, coeffn
  • een uitzondering te genereren als de gegevens niet konden worden verkregen of als ze ongeldig bleken te zijn.

De klasse biedt de beschermde methoden [checkData] en [check] die de geldigheid van de attributen (limites, coeffr, coeffn) controleren. Hierdoor hoeven de afgeleide klassen deze niet zelf te implementeren. Ze hoeven ze alleen maar te gebruiken.

De eerste afgeleide klasse die we zullen gebruiken, is de volgende:


Imports System.Collections
Imports System

Namespace st.istia.univangers.fr
    Public Class impotsArray
        Inherits impotsData

        ' constructor zonder argumenten
        Public Sub New()
            ' initialisatie van arrays met constanten
            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

        ' constructor met drie arrays als invoer
        Public Sub New(ByRef limites() As Decimal, ByRef coeffr() As Decimal, ByRef coeffn() As Decimal)
            ' de gegevens worden opgeslagen
            Me.limites = limites
            Me.coeffr = coeffr
            Me.coeffn = coeffn
            checked = False
        End Sub

        Public Overrides Function getData() As Object()
            ' eventueel de gegevens controleren
            Dim erreur As Integer
            If Not checked Then erreur = checkData() : checked = True
            ' als ze ongeldig zijn, dan wordt er een uitzondering gegenereerd
            If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
            ' anders worden de drie arrays teruggegeven
            Return New Object() {limites, coeffr, coeffn}
        End Function
    End Class
End Namespace

Deze klasse, genaamd [impotsArray], heeft twee constructors:

  • een constructor zonder argumenten die de attributen (limites, coeffr, coeffn) van de basisklasse initialiseert met 'hardgecodeerde' arrays
  • een constructor die de attributen (limites, coeffr, coeffn) van de basisklasse initialiseert met arrays die als parameters worden doorgegeven

De methode [getData], waarmee externe klassen de arrays (limites, coeffr, coeffn) kunnen opvragen, controleert alleen de geldigheid van de drie arrays met behulp van de methode [checkData] van de basisklasse. Ze genereert een uitzondering als de gegevens ongeldig zijn.

6.1.5. Testen van de bedrijfs- en datatoegangsklassen

Het is belangrijk om in een webapplicatie alleen bedrijfs- en gegevenstoegangs-klassen op te nemen waarvan de juistheid is gecertificeerd. Zo kan de debugfase van de webapplicatie zich concentreren op het controller- en view-gedeelte. Een testprogramma zou er als volgt uit kunnen zien:


' opties
Option Strict On
Option Explicit On 

' naamruimten
Imports System
Imports Microsoft.VisualBasic

Namespace st.istia.univangers.fr
    Module test
        Sub Main()
             ' interactief programma voor belastingberekening
             ' de gebruiker voert drie gegevens in via het toetsenbord: gehuwd nbEnfants salaris
             ' het programma geeft vervolgens de te betalen belasting weer
            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"

             ' aanmaken van een belastingobject
            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
            ' oneindige lus
            Dim marié As String
            Dim nbEnfants As Integer
            Dim salaire As Long
            While True
                ' er wordt gevraagd om de parameters voor de belastingberekening
                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()
                ' moet er iets gebeuren?
                If paramètres Is Nothing OrElse paramètres = "" Then
                    Exit While
                End If
                ' controle van het aantal argumenten in de ingevoerde regel
                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
                ' controle van de geldigheid van de parameters
                If Not erreur Then
                    ' getrouwd
                    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
                    ' salaris
                    Try
                        salaire = Integer.Parse(args(2))
                        If salaire < 0 Then
                            Throw New Exception
                        End If
                    Catch
                        erreur = True
                    End Try
                End If
                 ' als de parameters correct zijn, wordt de belasting berekend
                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

De applicatie vraagt de gebruiker om de drie gegevens die nodig zijn om zijn belasting te berekenen via het toetsenbord in te voeren:

  • zijn burgerlijke staat: o voor gehuwd, n voor ongehuwd
  • het aantal kinderen
  • zijn jaarsalaris

De belasting wordt berekend met behulp van een object van het type [impot] dat bij het opstarten van de applicatie wordt aangemaakt:


             ' aanmaken van een belastingobject
            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 gegevensbron wordt een object van het type [impotsArray] gebruikt. Hierbij wordt de constructor zonder argumenten van deze klasse gebruikt, die de drie tabellen (limites, coeffr, coeffn) met vaste waarden levert. Het aanmaken van een object van het type [impot] kan in theorie een uitzondering veroorzaken, aangezien het object bij het aanmaken de gegevens (limites, coeffr, coeffn) opvraagt bij de gegevensbron die als parameter is doorgegeven, en het ophalen van deze gegevens een uitzondering kan veroorzaken. In dit geval kan de methode voor het ophalen van de gegevens (hardgecodeerde waarden) echter geen uitzondering veroorzaken. We hebben de afhandeling van de uitzondering echter wel behouden om de aandacht van de lezer te vestigen op de mogelijkheid dat het object [impot] onjuist wordt aangemaakt.

Hier volgt een voorbeeld van de uitvoering van het vorige programma:

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

We voegen alle klassen [impot, impotsData, impotsArray] samen in een 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

We compileren het testprogramma:

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

We kunnen de tests uitvoeren:

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. De weergaven van de webapplicatie

De applicatie zal twee weergaven hebben: [formulaire.aspx] en [erreurs.aspx]. Laten we de werking van de applicatie illustreren aan de hand van schermafbeeldingen. De weergave [formulaire.aspx] wordt getoond wanneer de URL [main.aspx] voor het eerst wordt opgevraagd:

Image

De gebruiker vult het formulier in:

Image

en klikt op de knop [Calculer] om het volgende antwoord te krijgen:

Image

Hij kan zich vergissen bij het invoeren van de gegevens:

Image

Als hij vervolgens de knop [Calculer] gebruikt, krijgt hij een ander antwoord: [erreurs.aspx]:

Image

Hij kan de bovenstaande link [Retour au formulaire] gebruiken om de weergave [formulaire.aspx] terug te vinden zoals hij die vóór de fout had bevestigd:

Image

6.1.7. De weergave [formulaire.aspx]

De pagina [formulaire.aspx] ziet er als volgt uit:


<%@ 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>

De dynamische velden op deze pagina zijn de volgende:

rdouichecked
"checked" als het vakje [oui] moet worden aangevinkt, "" anders
rdnonchecked
idem voor het selectievakje [non]
txtEnfants
waarde die in het invoerveld [txtEnfants] moet worden ingevuld
txtSalaire
waarde die in het invoerveld moet worden ingevuld [txtSalaire]
txtImpot
waarde die in het invoerveld moet worden geplaatst [txtImpot]

De pagina bevat twee formulieren, elk met een knop [submit]. De knop [Calculer] is de knop [submit] van het volgende formulier:


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

We zien dat de parameters van het formulier met [action=calcul] naar de controller worden verzonden. De knop [Effacer] is de knop [submit] van het volgende formulier:


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

We zien dat de parameters van het formulier met [action=effacer] naar de controller worden verzonden. In dit geval heeft het formulier geen parameters. Alleen de actie is van belang.

De velden van [formulaire.aspx] worden berekend door [formulaire.aspx.vb]:


Imports System.Collections.Specialized

Public Class formulaire
    Inherits System.Web.UI.Page

    ' velden op de pagina
    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
        ' de vorige aanvraag wordt opgehaald uit de context
        Dim form As NameValueCollection = Context.Items("formulaire")
        ' de pagina wordt voorbereid voor weergave
        ' keuzerondjes
        rdouichecked = ""
        rdnonchecked = "checked"
        If form("rdMarie").ToString = "oui" Then
            rdouichecked = "checked"
            rdnonchecked = ""
        End If
        ' de rest
        txtEnfants = CType(form("txtEnfants"), String)
        txtSalaire = CType(form("txtSalaire"), String)
        txtImpot = CType(Context.Items("txtImpot"), String)
    End Sub
End Class

De berekening van de velden van [main.aspx] gebeurt op basis van twee gegevens die door de controller in de context van de pagina zijn geplaatst:

  • Context.Items("formulier"): een woordenboek van het type NameValueCollection dat de waarden bevat van de velden HTML [rdmarie,txtEnfants,txtSalaire]
  • Context.Items("txtImpot"): belastingbedrag

6.1.8. De weergave [erreurs.aspx]

De weergave [erreurs.aspx] toont eventuele fouten die tijdens de levensduur van de applicatie kunnen optreden. De weergavecode is als volgt:


<%@ 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>

De pagina bevat drie dynamische velden:

erreursHTML
code HTML uit een foutenlijst
href
URL van een link
lien
tekst van de link

Deze velden worden berekend door het controllergedeelte van de pagina in [erreurs.aspx.vb]:


Imports System.Collections
Imports Microsoft.VisualBasic

Public Class erreurs
    Inherits System.Web.UI.Page

    ' paginaparameter
    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
        ' de elementen uit de context worden opgehaald
        Dim erreurs As ArrayList = CType(context.Items("erreurs"), ArrayList)
        href = context.Items("href").ToString
        lien = context.Items("lien").ToString
        ' de code HTML van de lijst wordt gegenereerd
        Dim i As Integer
        For i = 0 To erreurs.Count - 1
            erreursHTML += "<li> " + erreurs(i).ToString + "</li>" + ControlChars.CrLf
        Next
    End Sub

End Class

De paginacontroller haalt informatie op die door de applicatiecontroller in de paginacontext is geplaatst:

Context.Items("erreurs"
object ArrayList met de lijst van weer te geven foutmeldingen
Context.Items("href"
URL van een link
Context.Items("lien"
tekst van de link

Nu we weten wat de gebruiker van de applicatie te zien krijgt, kunnen we verdergaan met het schrijven van de controller ervan.

6.1.9. De controllers [global.asax, main.aspx]

Laten we het schema MVC van onze applicatie nog eens bekijken:

Image

ClientApplicatie-logica

De controller [main.aspx] moet drie acties verwerken:

  • init: komt overeen met het eerste verzoek van de client. De controller geeft de weergave [formulaire.aspx] weer
  • calcul: dit is het verzoek om de belasting te berekenen. Als de gegevens in het invoerformulier correct zijn, wordt de belasting berekend met behulp van de businessklasse [impots]. De controller stuurt de client de weergave [formulaire.aspx] terug zoals deze was gevalideerd, aangevuld met de berekende belasting. Als de gegevens in het invoerformulier onjuist zijn, stuurt de controller de weergave [erreurs.aspx] terug met de lijst met fouten en een link om terug te keren naar het formulier.
  • terug: dit betreft de terugkeer naar het formulier na een fout. De controller geeft de weergave [formulaire.aspx] weer zoals deze vóór de fout was gevalideerd.

Bovendien is bekend dat elk verzoek aan de applicatie via de controller [global.asax] verloopt, indien deze bestaat. We hebben dus bij de ingang van de applicatie een keten van twee controllers:

  • [global.asax], die vanwege de architectuur van ASP.NET elk verzoek aan de applicatie ontvangt
  • [main.aspx], die op basis van de keuze van de ontwikkelaar eveneens alle verzoeken aan de applicatie ontvangt

De noodzaak van [main.aspx] vloeit voort uit het feit dat we een sessie moeten beheren. We hebben gezien dat [global.asax] in dit geval niet geschikt was als controller. We zouden hier volledig zonder [global.asax] kunnen. We gaan het echter wel gebruiken om code uit te voeren bij het opstarten van de applicatie. Het bovenstaande schema MVC laat zien dat we een object [impot] moeten aanmaken om de belasting te berekenen. Het heeft geen zin om dit meerdere keren aan te maken; één keer is voldoende. We zullen het dus bij het opstarten van de applicatie aanmaken tijdens de gebeurtenis [Application_Start], die wordt afgehandeld door de controller [global.asax]. De code hiervan is als volgt:

[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)
        ' een imp-object aanmaken
        Dim objImpot As impot
        Try
            objImpot = New impot(New impotsArray)
            ' het object wordt in de applicatie geplaatst
            Application("objImpot") = objImpot
            ' geen fout
            Application("erreur") = False
        Catch ex As Exception
            'er is een fout opgetreden, dit wordt in de applicatie genoteerd
            Application("erreur") = True
        End Try
    End Sub
End Class

Zodra het object van het type [impot] is aangemaakt, wordt het in de applicatie geplaatst. Daar zullen de verschillende verzoeken van de verschillende klanten het ophalen. Aangezien het aanmaken van het object [impot] kan mislukken, vangen we de eventuele uitzondering op en plaatsen we een sleutel [erreur] in de applicatie om aan te geven of er al dan niet een fout is opgetreden bij het aanmaken van het object [impot].

De code van de controller [main.aspx, main.aspx.vb] ziet er als volgt uit:

[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
        ' Allereerst wordt gecontroleerd of de applicatie correct is opgestart
        If CType(Application("erreur"), Boolean) Then
            ' we leiden de gebruiker door naar de foutpagina
            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
        ' we halen de uit te voeren actie op
        Dim action As String
        If Request.QueryString("action") Is Nothing Then
            action = "init"
        Else
            action = Request.QueryString("action").ToString.ToLower
        End If
        ' we voeren de actie uit
        Select Case action
            Case "init"
                ' applicatie initialiseren
                initAppli()
            Case "calcul"
                ' belasting berekenen
                calculImpot()
            Case "retour"
                ' terug naar het formulier
                retourFormulaire()
            Case "effacer"
                ' applicatie initialiseren
                initAppli()
            Case Else
                ' onbekende actie = initialiseren
                initAppli()
        End Select
    End Sub

    Private Sub initAppli()
        ' het vooraf ingevulde formulier wordt weergegeven
        Context.Items("formulaire") = initForm()
        Context.Items("txtImpot") = ""
        Server.Transfer("formulaire.aspx", True)
    End Sub

    Private Function initForm() As NameValueCollection
        ' het formulier wordt geïnitialiseerd
        Dim form As New NameValueCollection
        form.Set("rdMarie", "non")
        form.Set("txtEnfants", "")
        form.Set("txtSalaire", "")
        Return form
    End Function

    Private Sub calculImpot()
        ' de geldigheid van de ingevoerde gegevens wordt gecontroleerd
        Dim erreurs As ArrayList = checkData()
        ' als er fouten zijn, wordt dit gemeld
        If erreurs.Count <> 0 Then
            ' de invoer wordt opgeslagen
            Session.Item("formulaire") = Request.Form
            ' de foutpagina wordt voorbereid
            context.Items("href") = "main.aspx?action=retour"
            context.Items("lien") = "Retour au formulaire"
            context.Items("erreurs") = erreurs
            Server.Transfer("erreurs.aspx")
        End If
        ' hier zijn geen fouten – de belasting wordt berekend
        Dim impot As Long = CType(Application("objImpot"), impot).calculer( _
        Request.Form("rdMarie") = "oui", _
        CType(Request.Form("txtEnfants"), Integer), _
        CType(Request.Form("txtSalaire"), Long))
        ' de resultatenpagina wordt weergegeven
        context.Items("txtImpot") = impot.ToString + " euro(s)"
        context.Items("formulaire") = Request.Form
        Server.Transfer("formulaire.aspx", True)
    End Sub

    Private Sub retourFormulaire()
        ' het formulier wordt weergegeven met waarden uit de sessie
        Context.Items("formulaire") = Session.Item("formulaire")
        Context.Items("txtImpot") = ""
        Server.Transfer("formulaire.aspx", True)
    End Sub

    Private Function checkData() As ArrayList
        ' in eerste instantie geen fouten
        Dim erreurs As New ArrayList
        Dim erreur As Boolean = False
        ' keuzerondje ‘getrouwd’
        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
        ' aantal kinderen
        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
        ' salaris
        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
        ' de lijst met fouten wordt weergegeven
        Return erreurs
    End Function
End Class

De controller controleert eerst of de applicatie correct is geïnitialiseerd:


         ' allereerst wordt gecontroleerd of de applicatie correct is opgestart
        If CType(Application("erreur"), Boolean) Then
            ' we sturen door naar de foutpagina
            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

Als de controller vaststelt dat de applicatie niet correct kon worden geïnitialiseerd (het voor de berekening benodigde object [impot] kon niet worden aangemaakt), dan laat hij de foutpagina met de juiste parameters weergeven. In dit geval is het niet nodig om de terugkeerkoppeling op het formulier te plaatsen, aangezien de gehele applicatie niet beschikbaar blijkt te zijn. Er wordt een algemene foutmelding geplaatst in [Context.Items("erreurs")] van het type [ArrayList].

Als de controller vaststelt dat de applicatie operationeel is, analyseert hij vervolgens de actie die hij via de parameter [action] moet uitvoeren. We zijn deze werkwijze inmiddels al vaak tegengekomen. De verwerking van elk type actie wordt gedelegeerd aan een functie.

6.1.9.1. De acties init en effacer

Deze twee acties moeten ervoor zorgen dat het lege invoerformulier wordt weergegeven. Ter herinnering: dit formulier (zie weergaven) heeft twee parameters:

  • Context.Items("formulier"): een woordenboek van het type [NameValueCollection] dat de waarden van de velden HTML en [rdmarie,txtEnfants,txtSalaire] bevat
  • Context.Items("txtImpot"): belastingbedrag

De functie [initAppli] initialiseert deze twee parameters zodat een leeg formulier wordt weergegeven.

6.1.9.2. De berekeningsactie

Deze actie moet de te betalen belasting berekenen op basis van de gegevens die in het formulier zijn ingevoerd en het formulier vervolgens terugsturen, vooraf ingevuld met de ingevoerde waarden en bovendien met het berekende belastingbedrag. De functie [calculImpot], die hiervoor verantwoordelijk is, controleert eerst of de gegevens in het formulier correct zijn:

  • het veld [rdMarie] moet aanwezig zijn en de waarde [oui] of [non] hebben
  • het veld [txtEnfants] moet aanwezig zijn en een geheel getal >=0 bevatten
  • het veld [txtSalaire] moet aanwezig zijn en een geheel getal >=0 zijn

Als de ingevoerde gegevens ongeldig blijken te zijn, laat de controller de weergave [erreurs.aspx] weergeven, nadat hij eerst de door deze weergave verwachte waarden in de context heeft geplaatst:

  • de foutmeldingen worden in een object [ArrayList] geplaatst, dat vervolgens in de context [Context.Items("erreurs")] wordt geplaatst
  • de URL van de teruglink en de tekst van deze link worden eveneens in de context geplaatst.

Voordat de controle wordt overgedragen aan de pagina [erreurs.aspx], die het antwoord naar de klant zal verzenden, worden de in het formulier (Request.Form) ingevoerde waarden in de sessie geplaatst, gekoppeld aan de sleutel "formulier". Hierdoor kunnen deze bij een volgend verzoek worden opgehaald.

Men kan zich hier afvragen of het zinvol is om te controleren of de velden [rdMarie, txtEnfants, txtSalaire] aanwezig zijn in het verzoek dat door de client is verzonden. Dit is overbodig als we er zeker van zijn dat onze client een browser is die de weergave [formulaire.aspx] heeft ontvangen, waarin deze velden zijn opgenomen. Daar kunnen we echter nooit zeker van zijn. We zullen straks een voorbeeld laten zien waarin de client de reeds eerder genoemde applicatie [curl] is. We zullen de applicatie opvragen zonder de velden te verzenden die zij verwacht, en we zullen zien hoe zij hierop reageert. Dit is een regel die al meerdere malen is genoemd en die we hier nogmaals benadrukken: een applicatie mag nooit aannames doen over het type client dat haar opvraagt. Uit veiligheidsoverwegingen moet ze ervan uitgaan dat ze kan worden opgevraagd door een geprogrammeerde applicatie die haar onverwachte parameterreeksen kan sturen. Ze moet zich in alle gevallen correct gedragen.

In ons geval hebben we gecontroleerd of de velden [rdMarie, txtEnfants, txtSalaire] in de aanvraag aanwezig waren, maar niet of deze nog andere velden zou kunnen bevatten. In deze applicatie zouden deze worden genegeerd. Niettemin zou het, nog steeds uit veiligheidsoverwegingen, interessant zijn om dit soort verzoeken in een logbestand vast te leggen en een waarschuwing door te geven aan de beheerder van de applicatie, zodat deze weet dat de applicatie „vreemde” verzoeken ontvangt. Door deze verzoeken in het logbestand te analyseren, zou hij een mogelijke aanval op de applicatie kunnen detecteren en vervolgens de nodige maatregelen kunnen nemen om deze te beschermen.

Als de verwachte gegevens correct zijn, start de controller de belastingberekening met het object [impot] dat in de applicatie is opgeslagen. Vervolgens slaat hij in de context de twee gegevens op die door de weergave [formulaire.aspx] worden verwacht:

  • Context.Items("formulier"): een woordenboek van het type [NameValueCollection] met de waarden van de velden HTML, [rdmarie,txtEnfants,txtSalaire], hier [Request.Form)] en c.a.d. de eerder in het formulier ingevoerde waarden
  • Context.Items("txtImpot"): de zojuist berekende belastingwaarde

De oplettende lezer heeft zich bij het lezen van het bovenstaande wellicht een vraag gesteld: aangezien het object [impot], dat bij het opstarten van de applicatie wordt aangemaakt, door alle verzoeken wordt gedeeld, kunnen er dan geen toegangskonflicten ontstaan die leiden tot beschadiging van de gegevens van het object [impot]? Om deze vraag te beantwoorden, moeten we terugkeren naar de code van de klasse [impot]. De verzoeken roepen de methode [impot].calculerImpot aan om de te betalen belasting te verkrijgen. Het is dus deze code die we moeten onderzoeken:

        Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Long) As Long
             ' berekening van het aantal aandelen
            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
             ' berekening van het belastbaar inkomen en de gezinsquotiënt
            Dim revenu As Decimal = 0.72D * salaire
            Dim QF As Decimal = revenu / nbParts
             ' berekening van de belasting
            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

Stel dat een thread de vorige methode aan het uitvoeren is en wordt onderbroken. Een andere thread voert de methode dan uit. Wat zijn de risico's? Om dat te achterhalen, hebben we de volgende code toegevoegd:


            Dim impot As Long = CLng(revenu * coeffR(i) - nbParts * coeffN(i))
            ' er wordt 10 seconden gewacht
            Thread.Sleep(10000)
            Return impot

Thread 1 wordt onderbroken nadat de waarde [impot1] van de lokale variabele [impot] is berekend. Vervolgens wordt thread 2 uitgevoerd en berekent deze een nieuwe waarde [impot2] voor dezelfde variabele [impot], voordat hij wordt onderbroken. Thread 1 krijgt weer de controle. Wat vindt hij terug in de lokale variabele [impot]? Aangezien deze variabele lokaal is voor een methode, wordt ze opgeslagen in een geheugenstructuur die de stack wordt genoemd. Deze stack maakt deel uit van de context van de thread, die wordt opgeslagen wanneer deze wordt onderbroken. Wanneer thread 2 wordt gestart, wordt zijn context opgezet met een nieuwe stack en dus een nieuwe lokale variabele [impot]. Wanneer thread 2 op zijn beurt wordt onderbroken, wordt zijn context eveneens opgeslagen. Wanneer thread 1 opnieuw wordt gestart, wordt zijn context hersteld, inclusief zijn stack. Hij vindt dan zijn lokale variabele [impot] terug en niet die van thread 2. We bevinden ons dus in een situatie waarin er geen toegangsconflicten tussen verzoeken zijn. Tests met de hierboven genoemde pauze van 10 seconden hebben bevestigd dat gelijktijdige verzoeken inderdaad het verwachte resultaat opleverden.

6.1.9.3. De terugkeeractie

Deze actie komt overeen met het klikken op de link [Retour vers le formulaire] in het scherm [erreurs.aspx] om terug te keren naar het scherm [formulaire.aspx], dat vooraf is ingevuld met de eerder ingevoerde waarden die in de sessie zijn opgeslagen. De functie [retourFormulaire] haalt deze informatie op. De twee parameters die door de weergave [formulaire.aspx] worden verwacht, worden geïnitialiseerd:

  • Context.Items("formulier") met de eerder ingevoerde waarden die in de sessie zijn opgeslagen
  • Context.Items("txtImpot") met de lege tekenreeks

6.1.10. Testen van de webapplicatie

Alle bovenstaande bestanden worden in een map <application-path> geplaatst.

Image

In deze map wordt een submap [bin] aangemaakt, waarin de assembly [impot.dll] wordt geplaatst, die is voortgekomen uit de compilatie van de bestanden van de bedrijfsspecifieke klassen: [impots.vb, impotsData.vb, impotsArray.vb]. Hieronder wordt het benodigde compilatiecommando nogmaals weergegeven:

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

Het bovenstaande bestand [impot.dll] moet in <application-path>\bin worden geplaatst, zodat de webapplicatie er toegang toe heeft. De Cassini-server wordt gestart met de parameters (<application-path>,/impots1). Met een browser roepen we de url [http://localhost/impots1/main.aspx] op:

Image

We vullen het formulier in:

Image

Vervolgens starten we de belastingberekening met de knop [Calculer]. We krijgen het volgende antwoord:

Image

Vervolgens voeren we onjuiste gegevens in:

Image

Als we op de knop [Calculer] klikken, krijgen we het volgende antwoord:

Image

Als we de link [Retour au formulaire] gebruiken, keren we terug naar het formulier in de staat waarin het zich bevond toen het werd gevalideerd:

Image

Ten slotte wordt de pagina opnieuw geladen wanneer de knop [Effacer] wordt gebruikt:

Image

6.1.11. Gebruik van de client [curl]

Het is belangrijk om webapplicaties te testen met andere clients dan browsers. Als we een formulier naar een browser sturen met parameters die moeten worden verzonden wanneer het wordt gevalideerd, stuurt de browser de waarden van deze parameters terug naar de server. Een andere client doet dit mogelijk niet, waardoor de server een verzoek zou ontvangen waarin parameters ontbreken. De server moet weten wat hij in dat geval moet doen. Een ander voorbeeld betreft de invoercontroles die aan de clientzijde worden uitgevoerd. Als het formulier gegevens bevat die moeten worden gecontroleerd, kan deze controle aan de clientzijde worden uitgevoerd met behulp van scripts die zijn opgenomen in het document waarin het formulier zich bevindt. De browser zal het formulier alleen verzenden als alle aan de clientzijde gecontroleerde gegevens geldig zijn. Men zou dan aan de serverzijde in de verleiding kunnen komen om ervan uit te gaan dat er geverifieerde gegevens worden ontvangen en deze controle niet nogmaals uit te voeren. Dat zou een vergissing zijn. Een client die geen browser is, zou namelijk ongeldige gegevens naar de server kunnen sturen, waardoor de webapplicatie onvoorspelbaar gedrag zou kunnen vertonen. We zullen deze punten illustreren aan de hand van de client [curl].

Allereerst vragen we de URL [http://localhost/impots1/main.aspx] op:

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>

De server heeft ons de code HTML van het formulier gestuurd. In de headers HTTP staat de sessiecookie. We gaan deze in de volgende verzoeken gebruiken om de sessie in stand te houden. Laten we de actie [calcul] aanvragen zonder parameters op te geven:

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

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>

We zien dat de webapplicatie de weergave [erreurs] heeft teruggestuurd met drie foutmeldingen voor de drie ontbrekende parameters. Laten we nu onjuiste parameters verzenden:

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>

De drie fouten zijn correct gedetecteerd. Laten we nu geldige parameters verzenden:

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>

We hebben de verschuldigde belasting inderdaad geïnd: 4300 euro. Wat we uit dit voorbeeld kunnen leren, is dat we ons niet moeten laten misleiden door het feit dat we een webapplicatie schrijven voor clients die browsers zijn. Een webapplicatie is een TCP/IP-dienst en dit netwerkprotocol laat niet toe om de aard van de clientapplicatie van een dienst te bepalen. We kunnen dus niet weten of de client van een webapplicatie een browser is of niet. We volgen daarom twee regels:

  • bij ontvangst van een verzoek van een client doen we geen aannames over de client en controleren we of de verwachte parameters in het verzoek aanwezig en geldig zijn
  • we stellen een antwoord op dat bestemd is voor browsers, dus over het algemeen documenten van het type HTML

Een webapplicatie kan zo worden gebouwd dat deze tegelijkertijd verschillende clients bedient, bijvoorbeeld browsers en mobiele telefoons. We kunnen dan in elk verzoek een nieuwe parameter opnemen die het type client aangeeft. Zo zal een browser de berekening van de belasting aanvragen via een verzoek naar de url http://machine/impots/main.aspx?client=browser&action=calcul, terwijl de mobiele telefoon een verzoek zal sturen naar de URL http://machine/impots/main.aspx?client=mobile&action=calcul. De structuur MVC maakt het schrijven van een dergelijke applicatie eenvoudiger. Deze ziet er als volgt uit:

Image

Het blok [Classes métier, Classes d'accès aux données] verandert niet. Dit is namelijk een onderdeel dat voor de klant niet van belang is. Het blok [Contrôleur] verandert weinig, maar moet rekening houden met een nieuwe parameter in het verzoek, namelijk de parameter [client], die aangeeft met welk type klant het te maken heeft. Het blok [vues] moet weergaven genereren voor elk type klant. Het zou interessant kunnen zijn om bij het ontwerpen van de applicatie al rekening te houden met de aanwezigheid van de parameter [client] in de aanvraag, ook al zijn op korte of middellange termijn alleen browsers het doel. Als de applicatie later een nieuw type client moet ondersteunen, hoeven alleen de daarvoor aangepaste weergaven te worden geschreven.

6.2. Voorbeeld 2

6.2.1. Het probleem

We willen hier hetzelfde probleem aanpakken als eerder, maar dan door de gegevensbron van het door de webapplicatie aangemaakte object [impot] aan te passen. In de vorige versie leverde de gebruikte gegevensbron de waarden van tabellen die „hard“ in de code waren vastgelegd. Deze keer haalt de nieuwe gegevensbron deze waarden uit een gegevensbron ODBC die is gekoppeld aan een database MySQL.

6.2.2. De gegevensbron ODBC

De gegevens bevinden zich in een tabel met de naam [IMPOTS] in een database met de naam MySQL, die op haar beurt de naam [dbimpots] draagt. De inhoud van deze tabel is als volgt:

Image

De eigenaar van de database is de gebruiker [admimpots] met wachtwoord [mdpimpots]. We koppelen een gegevensbron ODBC aan deze database. Laten we, voordat we dit doen, eerst even de verschillende manieren op een rijtje zetten om toegang te krijgen tot een database met het platform .NET.

Er bestaan talrijke databases voor Windows-platforms. Om hier toegang toe te krijgen, maken applicaties gebruik van programma’s die drivers worden genoemd.

Image

In het bovenstaande schema heeft de driver twee interfaces:

  • de interface I1 die aan de applicatie wordt aangeboden
  • de interface I2 naar de database

Om te voorkomen dat een applicatie die is geschreven voor een database met de naam B1 opnieuw moet worden geschreven bij een migratie naar een andere database met de naam B2, is er gewerkt aan de standaardisatie van de interface I1. Als er databases worden gebruikt die gebruikmaken van „gestandaardiseerde” stuurprogramma’s, wordt de database B1 geleverd met een stuurprogramma P1, de database B2 met een driver P2, en de interface I1 van deze twee drivers zal identiek zijn. Daardoor hoeft de applicatie niet te worden herschreven. Zo kan bijvoorbeeld een database met de naam ACCESS worden gemigreerd naar een database met de naam MySQL zonder de applicatie te wijzigen.

Er zijn twee soorten gestandaardiseerde stuurprogramma's:

  • de stuurprogramma’s ODBC (Open DataBase Connectivity)
  • de stuurprogramma’s OLE en DB (Object Linking and Embedding DataBase)

De stuurprogramma's ODBC bieden toegang tot databases. De gegevensbronnen voor de stuurprogramma's OLE en DB zijn veelzijdiger: databases, e-mailsystemen, adresboeken, ... Er zijn geen beperkingen. Elke gegevensbron kan het onderwerp zijn van een Ole-driver DB als een ontwikkelaar daarvoor kiest. Het voordeel is uiteraard groot: men heeft uniforme toegang tot een grote verscheidenheid aan gegevens.

Het .NET 1.1-platform wordt geleverd met drie soorten klassen voor gegevenstoegang:

  1. de SQL- en Server.NET-klassen, voor toegang tot Microsoft SQL Server-databases
  2. de Ole-klassen Db.NET, voor toegang tot de databases van SGBD die een stuurprogramma OLE DB bieden
  3. de odbc.net-klassen, voor toegang tot de SGBD-databases die een stuurprogramma ODBC bieden

De SGBD MySQL beschikt al geruime tijd over een stuurprogramma ODBC. Dit is het stuurprogramma dat we nu gebruiken. Onder Windows kiezen we voor de optie [Menu Démarrer/Panneau de configuration/Outils d'administration/Sources ODBC 32 bits]. Afhankelijk van de Windows-versie kan dit pad enigszins afwijken. We krijgen dan de volgende toepassing waarmee we onze bron ODBC kunnen aanmaken:

Image

We gaan een systeemgegevensbron aanmaken, c.a.d. Een gegevensbron die elke gebruiker van de computer kan gebruiken utiliser.Aussi; hierboven selecteren we het tabblad [Source de données système]. De weergegeven pagina bevat een knop [Ajouter] die we gebruiken om een nieuwe gegevensbron ODBC aan te maken:

Image

De wizard vraagt om de te gebruiken driver ODBC te selecteren. Windows wordt geleverd met een aantal vooraf geïnstalleerde drivers ODBC. Het stuurprogramma ODBC van MySQL zit er niet bij. Deze moet dus eerst worden geïnstalleerd. Je kunt deze op internet vinden door de zoektermen "MySQL ODBC" of "MyODBC" in een zoekmachine in te voeren. Hier hebben we het stuurprogramma [MySQL ODBC 3.51] geïnstalleerd. We selecteren dit en voeren [Terminer] in:

Image

Er moet een aantal gegevens worden ingevuld:

Data Source Name
de naam waarmee de gegevensbron ODBC wordt aangeduid. Elke Windows-toepassing kan via deze naam toegang krijgen tot de bron
Description
een willekeurige tekst die de gegevensbron beschrijft
Host Name
de naam van de machine waarop de SGBD MySQL draait. In dit geval is dat de lokale machine. Het zou ook een externe machine kunnen zijn. Hierdoor zou een Windows-toepassing zonder speciale codering toegang kunnen krijgen tot een externe database. Dit is een groot voordeel van de bron ODBC.
Database Name
Een SGBD MySQL kan meerdere databases beheren. Hier wordt aangegeven welke database men wil beheren: dbimpots
User
naam van een gebruiker die is aangemeld binnen SGBD MySQL. Onder deze naam zal toegang tot de gegevensbron plaatsvinden. Hier: admimpots
Password
het wachtwoord van deze gebruiker. Hier: mdpimpots
Port
werkpoort van de SGBD MySQL. Standaard is dit poort 3306. We hebben deze niet gewijzigd

Nu we dit hebben gedaan, testen we of onze inloggegevens kloppen met de knop [Test Data Source]:

Image

Nu we dit hebben gedaan, zijn we zeker van onze gegevensbron ODBC. We kunnen deze nu gebruiken. We voeren [OK] zo vaak als nodig uit om de wizard ODBC te verlaten.

Als de lezer niet beschikt over de SGBD mySQL, kan hij deze gratis downloaden via de url [http://www.mysql.com]. Hieronder wordt de procedure beschreven voor het aanmaken van een ODBC-bron met Access. De eerste stappen zijn identiek aan wat eerder is beschreven. Er wordt een nieuwe systeemgegevensbron toegevoegd:

Image

De geselecteerde driver is [Microsoft Access Driver]. We voeren [Terminer] in om door te gaan naar de definitie van de bron ODBC:

Image

De volgende gegevens moeten worden opgegeven:

Nom de la source de données
de naam waarmee de gegevensbron ODBC wordt aangeduid. Elke Windows-toepassing kan via deze naam toegang krijgen tot de bron
Description
een willekeurige tekst die de gegevensbron beschrijft
Base de données
de volledige bestandsnaam ACCESS die moet worden verwerkt

6.2.3. Een nieuwe klasse voor gegevenstoegang

Laten we terugkeren naar de structuur MVC van onze toepassing:

Image

In het bovenstaande schema is de klasse [impotsData] verantwoordelijk voor het ophalen van de gegevens. Dit moet hier gebeuren uit de database MySQL [dbimpots]. We weten al sinds de vorige versie van deze applicatie dat [impotsData] een abstracte klasse is die telkens moet worden afgeleid wanneer men deze aan een nieuwe gegevensbron wil aanpassen. Laten we de structuur van deze abstracte klasse nog eens bekijken:


Imports System.Collections

Namespace st.istia.univangers.fr
    Public MustInherit Class impotsData
        Protected limites() As Decimal
        Protected coeffr() As Decimal
        Protected coeffn() As Decimal
        Protected checked As Boolean
        Protected valide As Boolean

        ' methode voor toegang tot gegevens
        Public MustOverride Function getData() As Object()

        ' methode voor gegevenscontrole
        Protected Function checkData() As Integer
            ' controleert de verkregen gegevens
...
        End Function

        ' controleert de geldigheid van de inhoud van een tabel
        Protected Function check(ByRef tableau() As Decimal, ByVal n As Integer) As Boolean
        ...
        End Function
    End Class
End Namespace

De klasse die afstamt van [impotsData] moet twee methoden implementeren:

  • een constructor, indien de constructor zonder argumenten van [impotsData] niet geschikt is
  • de methode [getData] die de drie arrays (limites, coeffr, coeffn) retourneert

We maken de klasse [impotsODBC] die de gegevens (limites, coeffr, coeffn) zal ophalen uit een bron ODBC waaraan we de naam zullen geven:


Imports System.Data.Odbc
Imports System.Data
Imports System.Collections
Imports System

Namespace st.istia.univangers.fr
    Public Class impotsODBC
        Inherits impotsData

        ' instantievariabelen
        Protected DSNimpots As String

        ' constructor
        Public Sub New(ByVal DSNimpots As String)
            ' we noteren de drie gegevens
            Me.DSNimpots = DSNimpots
        End Sub

        Public Overrides Function getdata() As Object()
            ' initialiseert de drie tabellen limites, coeffr en coeffn op basis van
            ' de inhoud van de tabel [impots] uit de database ODBC DSNimpots
            ' limites, coeffr en coeffn zijn de drie kolommen van deze tabel
            ' kan verschillende uitzonderingen genereren

            Dim connectString As String = "DSN=" + DSNimpots + ";"         ' chaîne de connexion à la base
            Dim impotsConn As OdbcConnection = Nothing         ' la connexion
            Dim sqlCommand As OdbcCommand = Nothing         ' la commande SQL
            ' de query SELECT
            Dim selectCommand As String = "select limites,coeffr,coeffn from impots"
            ' tabellen om de gegevens op te halen
            Dim aLimites As New ArrayList
            Dim aCoeffR As New ArrayList
            Dim aCoeffN As New ArrayList
            Try
                ' er wordt geprobeerd toegang te krijgen tot de database
                impotsConn = New OdbcConnection(connectString)
                impotsConn.Open()
                ' er wordt een command-object aangemaakt
                sqlCommand = New OdbcCommand(selectCommand, impotsConn)
                ' de query wordt uitgevoerd
                Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader()
                ' De opgehaalde tabel wordt verwerkt
                While myReader.Read()
                    ' de gegevens van de huidige rij worden in de tabellen geplaatst
                    aLimites.Add(myReader("limites"))
                    aCoeffR.Add(myReader("coeffr"))
                    aCoeffN.Add(myReader("coeffn"))
                End While
                ' vrijgeven van de bronnen
                myReader.Close()
                impotsConn.Close()
            Catch e As Exception
                Throw New Exception("Erreur d'accès à la base de données (" + e.Message + ")")
            End Try
            ' de dynamische tabellen worden in statische tabellen geplaatst
            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
            ' De verkregen gegevens worden gecontroleerd
            Dim erreur As Integer = checkData()
            ' als de gegevens ongeldig zijn, wordt er een uitzondering gegenereerd
            If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
            ' anders worden de drie tabellen teruggegeven
            Return New Object() {limites, coeffr, coeffn}
        End Function
    End Class
End Namespace

Laten we eens kijken naar de fabrikant:


        ' constructor
        Public Sub New(ByVal DSNimpots As String)
            ' de drie gegevens worden genoteerd
            Me.DSNimpots = DSNimpots
        End Sub

Als parameter ontvangt deze methode de naam van de bron ODBC waarin de op te halen gegevens zich bevinden. De constructor slaat deze naam alleen op. De methode [getData] is verantwoordelijk voor het lezen van de gegevens uit de tabel [impots] en het plaatsen ervan in drie arrays (limites, coeffr, coeffn). Laten we de code toelichten:

  • de parameters voor de verbinding met de gegevensbron ODBC zijn gedefinieerd, maar deze is niet geopend
             ' verbindingsstring naar de database
            Dim connectString As String = "DSN=" + DSNimpots + ";"
            ' we maken een databaseverbindingsobject aan – deze verbinding is niet geopend
            Dim impotsConn As OdbcConnection = New OdbcConnection(connectString)
  • Er worden drie objecten [ArrayList] gedefinieerd om de gegevens uit de tabel [impots] op te halen:

             ' tabellen om de gegevens op te halen
            Dim aLimites As New ArrayList
            Dim aCoeffR As New ArrayList
            Dim aCoeffN As New ArrayList
  • De volledige code voor de toegang tot de database is omgeven door een try/catch-blok om eventuele toegangsfouten op te vangen. We openen de verbinding met de database:

                 ' er wordt geprobeerd toegang te krijgen tot de database
                impotsConn = New OdbcConnection(connectString)
                impotsConn.Open()
  • we voeren de opdracht [select] uit op de geopende verbinding. We krijgen een object [OdbcDataReader] waarmee we de rijen van de tabel met de resultaten van de select-opdracht kunnen doorlopen:

                 ' er wordt een command-object aangemaakt
                Dim sqlCommand As OdbcCommand = New OdbcCommand(selectCommand, impotsConn)
                ' de query wordt uitgevoerd
                Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader()
  • We doorlopen de resultatentabel regel voor regel. Hiervoor gebruiken we de methode [Read] van het eerder verkregen object [OdbcDataReader]. Deze methode doet twee dingen:
    • ze gaat één rij verder in de tabel. In het begin bevinden we ons vóór de eerste rij
    • ze retourneert de booleaanse waarde [true] als ze een regel verder is gegaan, en [false] anders; dit laatste geval geeft aan dat alle regels zijn verwerkt.

De kolommen van de huidige regel van het object [OdbcDataReader] worden verkregen via OdbcDataReader. We krijgen een object dat de waarde van de kolom vertegenwoordigt. We doorlopen de gehele tabel om de inhoud ervan in de drie objecten [ArrayList] op te nemen:


                 ' De opgehaalde tabel wordt verwerkt
                While myReader.Read()
                    ' de gegevens van de huidige rij worden in de tabellen geplaatst
                    aLimites.Add(myReader("limites"))
                    aCoeffR.Add(myReader("coeffr"))
                    aCoeffN.Add(myReader("coeffn"))
  • Zodra dit is gebeurd, geven we de aan de verbinding gekoppelde bronnen vrij:
                 ' vrijgeven van de bronnen
                myReader.Close()
                impotsConn.Close()
  • De inhoud van de drie objecten [ArrayList] wordt overgebracht naar drie gewone arrays:

             ' de dynamische tabellen worden in statische tabellen geplaatst
            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
  • Zodra de gegevens uit tabel [impots] in de drie tabellen zijn opgeslagen, hoeft u alleen nog maar de inhoud ervan te controleren met behulp van de methode [checkData] van de basisklasse [impotsData]:
             ' De verkregen gegevens worden gecontroleerd
            Dim erreur As Integer = checkData()
             ' als de gegevens ongeldig zijn, wordt er een uitzondering gegenereerd
            If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
             ' anders worden de drie tabellen teruggegeven
            Return New Object() {limites, coeffr, coeffn}

6.2.4. Tests van de klasse voor gegevenstoegang

Een testprogramma zou er als volgt uit kunnen zien:

Option Explicit On 
Option Strict On

' naamruimten
Imports System
Imports Microsoft.VisualBasic

Namespace st.istia.univangers.fr

     ' testpagina
    Module testimpots
        Sub Main(ByVal arguments() As String)
             ' interactief programma voor belastingberekening
             ' de gebruiker voert drie gegevens in via het toetsenbord: gehuwd nbEnfants salaris
             ' het programma geeft vervolgens de te betalen belasting weer
            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"

             ' controle van de programma-instellingen
            If arguments.Length <> 1 Then
                 ' foutmelding
                Console.Error.WriteLine(syntaxe1)
                 ' einde
                Environment.Exit(1)
            End If
             ' de argumenten worden opgehaald
            Dim DSNimpots As String = arguments(0)

             ' een belastingobject aanmaken
            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

             ' oneindige lus
            While True
                 ' in eerste instantie geen fouten
                Dim erreur As Boolean = False

                 ' de parameters voor de belastingberekening worden opgevraagd
                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()

                 ' moet er iets gebeuren?
                If paramètres Is Nothing Or paramètres = "" Then
                    Exit While
                End If

                 ' controle van het aantal argumenten in de ingevoerde regel
                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
                     ' controle van de geldigheid van de parameters
                     ' getrouwd
                    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
                     ' salaris
                    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
                     ' de parameters zijn correct – de belasting wordt berekend
                    Console.Out.WriteLine(("impôt=" & objImpot.calculer(marié = "o", nbEnfants, salaire).ToString + " euro(s)"))
                End If
            End While
        End Sub
    End Module
End Namespace

De toepassing wordt gestart met één parameter:

  • DSNimpots: naam van de te gebruiken gegevensbron ODBC

De belastingberekening wordt uitgevoerd met behulp van een object van het type [impot] dat bij het starten van de applicatie wordt aangemaakt:


             ' een belastingobject aanmaken
            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

Zodra de applicatie is opgestart, vraagt deze de gebruiker herhaaldelijk om de drie gegevens in te voeren die nodig zijn om zijn belasting te berekenen:

  • zijn burgerlijke staat: o voor gehuwd, n voor ongehuwd
  • het aantal kinderen
  • zijn jaarsalaris

Alle klassen worden gecompileerd:

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

Het testprogramma wordt op zijn beurt gecompileerd:

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

Het testprogramma wordt eerst uitgevoerd met de gegevensbron ODBC MySQL:

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

We wisselen van bron naar ODBC om een Access-bron te gebruiken:

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. De weergaven van de webapplicatie

Dit zijn dezelfde als die van de vorige applicatie: formulaire.aspx en erreurs.aspx

6.2.6. De applicatiecontrollers [global.asax, main.aspx]

Alleen de controller [global.asax] moet worden aangepast. Deze is namelijk verantwoordelijk voor het aanmaken van het object [impot] bij het opstarten van de applicatie. De constructor van dit object heeft als enige parameter het object van het type [impotsData], dat verantwoordelijk is voor het ophalen van de gegevens. Deze parameter verandert dus voor elk nieuw type gegevensbron. De controller [global.asax.vb] ziet er dan als volgt uit:


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)
        ' er wordt een belastingobject aangemaakt
        Dim objImpot As impot
        Try
            objImpot = New impot(New impotsODBC(ConfigurationSettings.AppSettings("DSNimpots")))
            ' het object wordt in de toepassing geplaatst
            Application("objImpot") = objImpot
            ' geen fout
            Application("erreur") = False
        Catch ex As Exception
            'er is een fout opgetreden, dit wordt in de applicatie genoteerd
            Application("erreur") = True
            Application("message") = ex.Message
        End Try
    End Sub
End Class

De gegevensbron van het object [impot] is nu een object [impotODBC]. Dit laatste heeft als parameter de naam DSN van de te gebruiken gegevensbron ODBC. In plaats van deze naam hard te coderen in de code, wordt deze opgenomen in het configuratiebestand [web.config] van de applicatie:


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

Het is bekend dat de waarde van een sleutel C uit de sectie <appSettings> van het bestand [web.config] in de applicatiecode wordt verkregen via [ConfigurationSettings.AppSettings(C)].

Om de oorzaak van de uitzondering te achterhalen, wordt het foutbericht in de applicatie opgeslagen, zodat het beschikbaar blijft voor opvragingen. Het controleveld [main.aspx.vb] zal dit bericht opnemen in zijn foutenlijst:


    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' Allereerst wordt gecontroleerd of de applicatie correct is opgestart
        If CType(Application("erreur"), Boolean) Then
            ' we leiden de gebruiker door naar de foutpagina
            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
        ' we halen de uit te voeren actie op
...

6.2.7. Overzicht van de wijzigingen

De applicatie is klaar om getest te worden. Hieronder volgt een overzicht van de wijzigingen ten opzichte van de vorige versie:

  1. er is een nieuwe klasse voor gegevenstoegang gemaakt
  2. de controller [global.asax.vb] is op twee plaatsen gewijzigd: het aanmaken van het object [impot] en het vastleggen in de applicatie van het bericht met betrekking tot een eventuele uitzondering
  3. de controller [main.aspx.vb] is op één plaats aangepast om het voorgaande uitzonderingsbericht weer te geven
  4. er is een bestand [web.config] toegevoegd

Het aanpassingswerk vond grotendeels plaats buiten de webapplicatie om, in c.a.d. Dit werd mogelijk gemaakt door de architectuur van de applicatie, die de controller scheidt van de businessklassen. Daarin ligt het grote voordeel van deze architectuur. Men zou kunnen aantonen dat met een geschikt configuratiebestand [web.config] elke wijziging aan de controller van de applicatie had kunnen worden vermeden. Het is mogelijk om in [web.config] de naam van de klasse voor gegevenstoegang op te nemen die dynamisch moet worden geïnstantieerd, evenals de diverse parameters die nodig zijn voor deze instantiëring. Met deze informatie kan [global.asax] het object voor gegevenstoegang instantiëren. Het wijzigen van de gegevensbron komt dan neer op:

  • de toegangsklasse voor deze bron aanmaken als deze nog niet bestaat
  • het bestand [web.config] aanpassen om het dynamisch aanmaken van een instantie van deze klasse in [global.asax] mogelijk te maken

6.2.8. Testen van de webapplicatie

Alle bovengenoemde bestanden worden in een map <application-path> geplaatst.

Image

In deze map wordt een submap [bin] aangemaakt, waarin de assembly [impot.dll] wordt geplaatst, die is voortgekomen uit de compilatie van de bestanden van de bedrijfsspecifieke klassen: [impots.vb, impotsData.vb, impotsArray.vb, impotsODBC.vb]. Hieronder wordt het benodigde compilatiecommando nogmaals weergegeven:

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

Het bovenstaande bestand [impot.dll] moet in <application-path>\bin worden geplaatst, zodat de webapplicatie er toegang toe heeft. De Cassini-server wordt gestart met de parameters (<application-path>,/impots2). De tests leveren dezelfde resultaten op als in de vorige versie, waarbij de aanwezigheid van de database voor de gebruiker transparant is. Om dit toch te illustreren, zorgen we ervoor dat de bron ODBC niet beschikbaar is door SGBD en MySQL te stoppen, en vragen we de URL [http://localhost/impots2/main.aspx] op. We krijgen het volgende antwoord:

Image

6.3. Voorbeeld 3

6.3.1. Het probleem

We willen hier hetzelfde probleem aanpakken door de gegevensbron van het door de webapplicatie aangemaakte object [impot] opnieuw te wijzigen. Deze keer zal de nieuwe gegevensbron een database ACCESS zijn, die we via een driver OLEDB zullen benaderen. Ons doel is om een andere manier te laten zien om toegang te krijgen tot een database.

6.3.2. De gegevensbron OLEDB

De gegevens bevinden zich in een tabel met de naam [IMPOTS] in een database ACCESS. De inhoud van deze tabel is als volgt:

Image

6.3.3. De klasse voor gegevenstoegang

Laten we terugkeren naar de structuur MVC van onze applicatie:

Image

  • In het bovenstaande schema is de klasse [impotsData] verantwoordelijk voor het ophalen van de gegevens. Dit keer moet zij dit doen vanuit een bron OLEDB.

We maken de klasse [impotsOLEDB] aan, die de gegevens (limites, coeffr, coeffn) gaat ophalen uit een bron ODBC, waaraan we de naam zullen geven:


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

        ' instantievariabelen
        Protected chaineConnexion As String

        ' constructor
        Public Sub New(ByVal chaineConnexion As String)
            ' de drie gegevens worden genoteerd
            Me.chaineConnexion = chaineConnexion
        End Sub

        Public Overrides Function getData() As Object()
            ' initialiseert de drie arrays ‘limites’, ‘coeffr’ en ‘coeffn’ op basis van
            ' de inhoud van de tabel [impots] uit de database OLEDB [chaineConnexion]
            ' limites, coeffr en coeffn zijn de drie kolommen van deze tabel
            ' kan verschillende uitzonderingen genereren

            ' er wordt een object DataAdapter aangemaakt om de gegevens uit de bron OLEDB te lezen
            Dim adaptateur As New OleDbDataAdapter("select limites,coeffr,coeffn from impots", chaineConnexion)
            ' er wordt een geheugenafbeelding gemaakt van het resultaat van de 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
            ' de inhoud van de tabel impots wordt opgehaald
            Dim lignesImpots As DataRowCollection = contenu.Rows
            ' de ontvangsttabellen worden gedimensioneerd
            Me.limites = New Decimal(lignesImpots.Count - 1) {}
            Me.coeffr = New Decimal(lignesImpots.Count - 1) {}
            Me.coeffn = New Decimal(lignesImpots.Count - 1) {}
            ' de inhoud van de tabel impots wordt naar de arrays overgebracht
            Dim i As Integer
            Dim ligne As DataRow
            Try
                For i = 0 To lignesImpots.Count - 1
                    ' rij i van de tabel
                    ligne = lignesImpots.Item(i)
                    ' de inhoud van de rij wordt opgehaald
                    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
            ' de verkregen gegevens worden gecontroleerd
            Dim erreur As Integer = checkData()
            ' als de gegevens ongeldig zijn, wordt er een uitzondering gegenereerd
            If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
            ' anders worden de drie arrays teruggegeven
            Return New Object() {limites, coeffr, coeffn}
        End Function
    End Class
End Namespace

Laten we eens kijken naar de fabrikant:


         ' constructor
        Public Sub New(ByVal chaineConnexion As String)
             ' de drie gegevens worden genoteerd
            Me.chaineConnexion = chaineConnexion
        End Sub

Hij ontvangt als parameter de verbindingsstring van de bron OLEDB, waarin de te verzamelen gegevens zich bevinden. De constructor slaat deze alleen op. Een verbindingsstring bevat alle parameters die de driver OLEDB nodig heeft om verbinding te maken met de bron OLEDB. Deze is doorgaans vrij complex. Om de verbindingsstring van de databases ACCESS te achterhalen, kun je gebruikmaken van de tool [WebMatrix]. Start deze tool op. Er verschijnt een venster waarin je verbinding kunt maken met een gegevensbron:

Met het pictogram dat hierboven met de pijl is aangegeven, kun je een verbinding maken met twee soorten Microsoft-databases: SQL Server en ACCESS. Laten we ACCESS kiezen:

Image

We hebben de knop [...] gebruikt om de database ACCESS aan te wijzen. We bevestigen de wizard. Op het tabblad [Data] geven pictogrammen de verbinding weer:

Image

Laten we nu een nieuw .aspx-bestand aanmaken via [Files/New File]:

Image

We krijgen een leeg blad waarop we onze webinterface kunnen ontwerpen:

Image

Laten we vanuit het tabblad [Data] de tabel [impots] naar het bovenstaande blad slepen. We krijgen het volgende resultaat:

Image

Laten we met de rechtermuisknop op het object [AccessDataSourceControl] hieronder klikken om toegang te krijgen tot deze eigenschappen:

Image

De verbindingsstring OLEDB naar de database ACCESS wordt aangegeven door de eigenschap [ConnectionString] hierboven:

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

We zien dat deze tekenreeks bestaat uit een vast deel en een variabel deel, dat simpelweg de bestandsnaam ACCESS is. We zullen hiervan gebruikmaken om de verbindingsstring naar onze gegevensbron OLEDB te genereren.

Laten we nu teruggaan naar onze klasse [impotsOLEDB]. De methode [getData] is verantwoordelijk voor het inlezen van de gegevens uit de tabel [impots] en het opslaan ervan in drie arrays (limites, coeffr, coeffn). Laten we de code ervan toelichten:

  • we definiëren het object [DataAdapter] waarmee we het resultaat van een SQL select-query naar het geheugen kunnen overbrengen. Hiervoor definiëren we de uit te voeren query [select] en koppelen we deze aan het object [DataAdapter]. De constructor van dit object vraagt ook om de verbindingsstring die moet worden gebruikt om verbinding te maken met de bron OLEDB

             ' er wordt een object DataAdapter aangemaakt om de gegevens uit de bron OLEDB te lezen
            Dim adaptateur As New OleDbDataAdapter("select limites,coeffr,coeffn from impots", chaineConnexion)
  • We voeren de opdracht [select] uit met behulp van de methode [Fill] van het object [DataAdapter]. Het resultaat van [select] wordt ingevoerd in een object [DataTable] dat speciaal voor dit doel is aangemaakt. Een object [DataTable] is de weergave in het geheugen van een databasetabel, c.a.d: een verzameling rijen en kolommen. We vangen een uitzondering op die kan optreden als bijvoorbeeld de verbindingsstring onjuist is.

             ' er wordt een geheugenafbeelding gemaakt van het resultaat van de select-opdracht
            Dim contenu As New DataTable("impots")
            Try
                adaptateur.Fill(contenu)
            Catch e As Exception
                Throw New Exception("Erreur d'accès à la base de données (" + e.Message + ")")
            End Try
  • in [contenu] hebben we de tabel [impots], die wordt aangeroepen door [select]. Een object [DataTable] is een tabel en dus een verzameling rijen. Deze zijn toegankelijk via de eigenschap [rows] van [datatable]:

             ' de inhoud van de tabel impots wordt opgehaald
            Dim lignesImpots As DataRowCollection = contenu.Rows
  • elk element van de verzameling [lignesImpots] is een object van het type [DataRow] dat een rij van de tabel vertegenwoordigt. Deze rij heeft kolommen die toegankelijk zijn via het object [DataRow] via de eigenschap [Item]. [DataRow].[Item(i)] is kolom nr. i van rij [DataRow]. Door de verzameling van rijen (de verzameling DataRows van lignesImpots) en de verzameling van kolommen van elke rij te doorlopen, kunnen we de volledige tabel verkrijgen:
             ' de ontvangsttabellen worden gedimensioneerd
            Me.limites = New Decimal(lignesImpots.Count - 1) {}
            Me.coeffr = New Decimal(lignesImpots.Count - 1) {}
            Me.coeffn = New Decimal(lignesImpots.Count - 1) {}
             ' de inhoud van de tabel impots wordt naar de arrays overgebracht
            Dim i As Integer
            Dim ligne As DataRow
            Try
                For i = 0 To lignesImpots.Count - 1
                     ' rij i van de tabel
                    ligne = lignesImpots.Item(i)
                     ' de inhoud van de rij wordt opgehaald
                    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
  • Zodra de gegevens uit tabel [impots] in de drie tabellen zijn opgeslagen, hoeft u alleen nog maar de inhoud ervan te controleren met behulp van de methode [checkData] van de basisklasse [impotsData]:
            ' de verkregen gegevens worden gecontroleerd
            Dim erreur As Integer = checkData()
             ' als de gegevens ongeldig zijn, wordt er een uitzondering gegenereerd
            If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
             ' anders worden de drie arrays teruggegeven
            Return New Object() {limites, coeffr, coeffn}

6.3.4. Tests van de klasse voor gegevenstoegang

Een testprogramma zou er als volgt uit kunnen zien:

Option Explicit On 
Option Strict On

' naamruimten
Imports System
Imports Microsoft.VisualBasic

Namespace st.istia.univangers.fr

     ' testpagina
    Module testimpots
        Sub Main(ByVal arguments() As String)
             ' interactief programma voor belastingberekening
             ' de gebruiker voert drie gegevens in via het toetsenbord: gehuwd nbEnfants salaris
             ' het programma geeft vervolgens de te betalen belasting weer
            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"

             ' controle van de programma-instellingen
            If arguments.Length <> 1 Then
                 ' foutmelding
                Console.Error.WriteLine(syntaxe1)
                 ' einde
                Environment.Exit(1)
            End If
             ' de argumenten worden opgehaald
            Dim chemin As String = arguments(0)
             ' de verbindingsstring wordt voorbereid
            Dim chaineConnexion As String = "Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=" + chemin

             ' een belastingobject aanmaken
            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

             ' oneindige lus
            While True
                 ' in eerste instantie geen fouten
                Dim erreur As Boolean = False

                 ' er wordt gevraagd om de parameters voor de belastingberekening
                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()

                 ' moet er iets gebeuren?
                If paramètres Is Nothing Or paramètres = "" Then
                    Exit While
                End If

                 ' controle van het aantal argumenten in de ingevoerde regel
                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
                     ' controle van de geldigheid van de parameters
                     ' getrouwd
                    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
                     ' salaris
                    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
                     ' de parameters zijn correct – de belasting wordt berekend
                    Console.Out.WriteLine(("impôt=" & objImpot.calculer(marié = "o", nbEnfants, salaire).ToString + " euro(s)"))
                End If
            End While
        End Sub
    End Module
End Namespace

De toepassing wordt gestart met één parameter:

  • bdACCESS: naam van het te verwerken bestand ACCESS

De belastingberekening wordt uitgevoerd met behulp van een object van het type [impot] dat bij het starten van de applicatie wordt aangemaakt:


             ' de argumenten worden opgehaald
            Dim chemin As String = arguments(0)
            ' de verbindingsstring wordt voorbereid
            Dim chaineConnexion As String = "Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=" + chemin

             ' een belastingobject aanmaken
            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

De verbindingsketen naar de bron OLEDB is samengesteld op basis van de informatie die is verkregen met [WebMatrix].

Zodra de applicatie is opgestart, vraagt deze de gebruiker herhaaldelijk om de drie gegevens in te voeren die nodig zijn om zijn belasting te berekenen:

  • zijn burgerlijke staat: o voor gehuwd, n voor ongehuwd
  • het aantal kinderen
  • zijn jaarsalaris

Alle klassen worden gecompileerd:

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

Het bestand [impots.mdb] wordt in de map van de testtoepassing geplaatst en de toepassing wordt als volgt gestart:

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)

De applicatie kan worden gestart met een onjuist ACCESS-bestand:

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. De weergaven van de webapplicatie

Dit zijn die van de vorige applicatie: formulaire.aspx en erreurs.aspx

6.3.6. De applicatiecontrollers [global.asax, main.aspx]

Alleen de controller [global.asax] moet worden aangepast. Deze is namelijk verantwoordelijk voor het aanmaken van het object [impot] bij het opstarten van de applicatie. De constructor van dit object heeft als enige parameter het object van het type [impotsData], dat verantwoordelijk is voor het ophalen van de gegevens. Deze parameter verandert dus omdat we van gegevensbron wisselen. De controller [global.asax.vb] ziet er dan als volgt uit:


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)
        ' er wordt een belastingobject aangemaakt
        Dim objImpot As impot
        Try
            objImpot = New impot(New impotsOLEDB(ConfigurationSettings.AppSettings("chaineConnexion")))
            ' het object wordt in de applicatie geplaatst
            Application("objImpot") = objImpot
            ' geen fout
            Application("erreur") = False
        Catch ex As Exception
            'er is een fout opgetreden, dit wordt in de applicatie genoteerd
            Application("erreur") = True
            Application("message") = ex.Message
        End Try
    End Sub
End Class

De gegevensbron van het object [impot] is nu een object [impotOLEDB]. Dit laatste object heeft als parameter de verbindingsstring van de te gebruiken gegevensbron OLEDB. Deze is opgenomen in het configuratiebestand [web.config] van de applicatie:


<?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>

De controller [main.aspx] blijft ongewijzigd.

6.3.7. Overzicht van de wijzigingen

De applicatie is klaar om getest te worden. Hieronder volgt een overzicht van de wijzigingen ten opzichte van de vorige versie:

  1. er is een nieuwe klasse voor gegevenstoegang gemaakt
  2. de controller [global.asax.vb] is op één punt gewijzigd: aanmaak van het object [impot]
  3. er is een bestand [web.config] toegevoegd

6.3.8. Testen van de webapplicatie

Alle bovengenoemde bestanden worden in een map <application-path> geplaatst.

Image

In deze map wordt een submap [bin] aangemaakt, waarin de assembly [impot.dll] wordt geplaatst, die is voortgekomen uit de compilatie van de bestanden van de bedrijfsspecifieke klassen: [impots.vb, impotsData.vb, impotsArray.vb, impotsOLEDB.vb]. Hieronder wordt het benodigde compilatiecommando nogmaals weergegeven:

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

Het bestand [impot.dll] dat door dit commando wordt gegenereerd, moet in <application-path>\bin worden geplaatst, zodat de webapplicatie er toegang toe heeft. De Cassini-server wordt gestart met de parameters (<application-path>,/impots3). De tests leveren dezelfde resultaten op als in de vorige versie.

6.4. Voorbeeld 4

6.4.1. Het probleem

We willen onze applicatie nu omzetten in een applicatie voor het simuleren van belastingberekeningen. Een gebruiker kan achtereenvolgende belastingberekeningen uitvoeren en deze worden hem gepresenteerd in een nieuw scherm dat er als volgt uitziet:

Image

6.4.2. De structuur MVC van de applicatie

De structuur MVC van de applicatie ziet er als volgt uit:

Image

Er verschijnt een nieuw scherm [simulations.aspx], waarvan we zojuist een schermafbeelding hebben getoond. De klasse voor gegevenstoegang is de klasse [impotsODBC] uit voorbeeld 2.

6.4.3. De weergaven van de webapplicatie

De weergave [erreurs.aspx] verandert niet. De weergave [formulaire.aspx] verandert enigszins. Het belastingbedrag wordt namelijk niet meer in deze weergave weergegeven. Het staat nu in de weergave [simulations.aspx]. Bij het opstarten wordt de gebruiker dus de volgende pagina getoond:

Image

Bovendien bevat de weergave [formulaire] een JavaScript-script dat de geldigheid van de ingevoerde gegevens controleert voordat deze naar de server worden verzonden, zoals in het volgende voorbeeld te zien is:

Image

De weergavecode is als volgt:


<%@ page src="formulaire.aspx.vb" inherits="formulaire" AutoEventWireup="false"%>
<html>
    <head>
        <title>Impôt</title>
        <script language="javascript">
        function calculer(){
          // controle van de parameters voordat deze naar de server worden verzonden
        with(document.frmImpots){
          //aantal kinderen
          champs=/^\s*(\d+)\s*$/.exec(txtEnfants.value);
          if(champs==null){
            // het model is niet gecontroleerd
            alert("Le nombre d'enfants n'a pas été donné ou est incorrect");
            txtEnfants.focus();
            return;
          }//if
          //salaris
          champs=/^\s*(\d+)\s*$/.exec(txtSalaire.value);
          if(champs==null){
            // het model is niet gecontroleerd
            alert("Le salaire n'a pas été donné ou est incorrect");
            txtSalaire.focus();
            return;
          }//als
          // alles in orde – het formulier wordt naar de server verzonden
          submit();
        }//met
      }//berekenen  
        </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>

De dynamische velden op de pagina zijn dezelfde als in eerdere versies. Het dynamische veld voor het belastingbedrag is verdwenen. De knop [Calculer] is niet langer een knop van het type [submit]. Het is nu een knop van het type [button] en wanneer erop wordt geklikt, wordt de JavaScript-functie [calculer()] uitgevoerd:


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

We hebben het formulier de naam [frmImpots] gegeven, zodat we ernaar kunnen verwijzen in het script [calculer]:


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

De JavaScript-functie [calculer] gebruikt reguliere expressies om de geldigheid van de velden in de formulieren [document.frmImpots.txtEnfants] en [document.frmImpots.txtSalaire] te controleren. Als de ingevoerde waarden correct zijn, worden ze door [document.frmImpots.submit()] naar de server verzonden.

De weergavepagina haalt haar dynamische velden op via de volgende controller [formulaire.aspx.vb]:


Imports System.Collections.Specialized

Public Class formulaire
    Inherits System.Web.UI.Page

    ' velden van de pagina
    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 halen het vorige verzoek op uit de context
        Dim form As NameValueCollection = Context.Items("formulaire")
        ' we maken de pagina klaar om weer te geven
        ' keuzerondjes
        rdouichecked = ""
        rdnonchecked = "checked"
        If form("rdMarie").ToString = "oui" Then
            rdouichecked = "checked"
            rdnonchecked = ""
        End If
        ' de rest
        txtEnfants = CType(form("txtEnfants"), String)
        txtSalaire = CType(form("txtSalaire"), String)
    End Sub
End Class

De controller [formulaire.aspx.vb] is identiek aan de vorige versies, behalve dat hij het veld [txtImpot] niet meer uit de context hoeft op te halen, aangezien dit veld van de pagina is verdwenen.

De weergave [simulations.aspx] ziet er visueel als volgt uit:

Image

en komt overeen met de volgende weergavecode:


<%@ 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>

Deze code bevat drie dynamische velden:

simulationsHTML
code HTML van een lijst met simulaties in de vorm van tabelrijen HTML
href
URL van een link
lien
tekst van de link

Deze worden gegenereerd door de controller [simulations.aspx.vb]:


Imports System.Collections
Imports Microsoft.VisualBasic

Public Class simulations
    Inherits System.Web.UI.Page

    Protected simulationsHTML As String = ""
    Protected href As String
    Protected lien As String

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        'de simulaties worden opgehaald uit de context
        Dim simulations As ArrayList = CType(context.Items("simulations"), ArrayList)
        ' elke simulatie is een array van 4 string-elementen
        Dim simulation() As String
        Dim i, j As Integer
        For i = 0 To simulations.Count - 1
            simulation = CType(simulations(i), String())
            simulationsHTML += "<tr>"
            For j = 0 To simulation.Length - 1
                simulationsHTML += "<td>" + simulation(j) + "</td>"
            Next
            simulationsHTML += "</tr>" + ControlChars.CrLf
        Next
        ' we halen de overige elementen uit de context op
        href = context.Items("href").ToString
        lien = context.Items("lien").ToString
    End Sub
End Class

De paginacontroller haalt informatie op die door de applicatiecontroller in de paginacontext is geplaatst:

Context.Items("simulations")
object ArrayList met de lijst van weer te geven simulaties. Elk element is een array van 4 tekenreeksen die de gegevens (gehuwd, kinderen, salaris, belasting) van de simulatie weergeven.
Context.Items("href")
URL van een link
Context.Items("lien")
tekst van de link

6.4.4. De controllers [global.asax, main.aspx]

Laten we het schema MVC van onze applicatie nog eens bekijken:

Image

De controller [main.aspx] moet drie acties verwerken:

  • init: komt overeen met het eerste verzoek van de client. De controller geeft de weergave [formulaire.aspx] weer
  • calcul: komt overeen met het verzoek om de belasting te berekenen. Als de gegevens in het invoerformulier correct zijn, wordt de belasting berekend met behulp van de businessklasse [impotsODBC]. De controller stuurt de client de weergave [simulations.aspx] terug met het resultaat van de huidige simulatie plus alle voorgaande simulaties. Als de gegevens in het invoerformulier onjuist zijn, stuurt de controller de weergave [erreurs.aspx] terug met de lijst met fouten en een link om terug te keren naar het formulier.
  • terug: dit betreft de terugkeer naar het formulier na een fout. De controller geeft de weergave [formulaire.aspx] weer zoals deze vóór de fout was gevalideerd.

In deze nieuwe versie is alleen de actie [calcul] gewijzigd. Als de gegevens namelijk geldig zijn, moet deze actie leiden naar de weergave [simulations.aspx], terwijl deze voorheen leidde naar de weergave [formulaire.aspx]. De controller [main.aspx.vb] ziet er nu als volgt uit:


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
        ' allereerst kijken we of de applicatie correct is opgestart
...
        ' we voeren de actie uit
        Select Case action
            Case "init"
                ' toepassing initialiseren
                initAppli()
            Case "calcul"
                ' belasting berekenen
                calculImpot()
            Case "retour"
                ' terug naar het formulier
                retourFormulaire()
            Case "effacer"
                ' applicatie initialiseren
                initAppli()
            Case Else
                ' onbekende actie = initialiseren
                initAppli()
        End Select
    End Sub

...
    Private Sub calculImpot()
        ' de invoer wordt opgeslagen
        Session.Item("formulaire") = Request.Form
        ' de geldigheid van de ingevoerde gegevens wordt gecontroleerd
        Dim erreurs As ArrayList = checkData()
        ' indien er fouten zijn, wordt dit gemeld
        If erreurs.Count <> 0 Then
            ' de foutpagina wordt voorbereid
            context.Items("href") = "main.aspx?action=retour"
            context.Items("lien") = "Retour au formulaire"
            context.Items("erreurs") = erreurs
            Server.Transfer("erreurs.aspx")
        End If
        ' hier zijn geen fouten – de belasting wordt berekend
        Dim impot As Long = CType(Application("objImpot"), impot).calculer( _
        Request.Form("rdMarie") = "oui", _
        CType(Request.Form("txtEnfants"), Integer), _
        CType(Request.Form("txtSalaire"), Long))
        ' het resultaat wordt toegevoegd aan de bestaande simulaties
        Dim simulations As ArrayList
        If Not Session.Item("simulations") Is Nothing Then
            simulations = CType(Session.Item("simulations"), ArrayList)
        Else
            simulations = New ArrayList
        End If
        ' de huidige simulatie toevoegen
        Dim simulation() As String = New String() {Request.Form("rdMarie").ToString, _
        Request.Form("txtEnfants").ToString, Request.Form("txtSalaire").ToString, _
        impot.ToString}
        simulations.Add(simulation)
        ' de simulaties worden in de sessie en de context geplaatst
        context.Items("simulations") = simulations
        Session.Item("simulations") = simulations
        ' de resultatenpagina wordt weergegeven
        context.Items("href") = "main.aspx?action=retour"
        context.Items("lien") = "Retour au formulaire"
        Server.Transfer("simulations.aspx", True)
    End Sub
...
End Class

We hebben hierboven alleen datgene behouden wat nodig was om de wijzigingen te begrijpen die uitsluitend in de functie [calculImpots] voorkomen:

  • allereerst slaat de functie het formulier [Request.Form] op in de sessie, zodat het formulier opnieuw kan worden gegenereerd in de staat waarin het is gevalideerd. Dit moet in alle gevallen gebeuren, want of de bewerking nu resulteert in het antwoord [erreurs.aspx] of in het antwoord [simulations.aspx], keert men via de link [Retour au formulaire] terug naar het formulier. Om het formulier correct te herstellen, moeten de waarden ervan eerder in de sessie zijn opgeslagen.
  • Als de ingevoerde gegevens correct blijken te zijn, voegt de functie de huidige simulatie (gehuwd, kinderen, salaris, belasting) toe aan de lijst met simulaties. Deze lijst is te vinden in de sessie die gekoppeld is aan de sleutel "simulaties".
  • De lijst met simulaties wordt teruggeschreven naar de sessie voor toekomstig gebruik. Ze wordt ook in de huidige context geplaatst, omdat de weergave [simulations.aspx] daar op haar wacht
  • De weergave [simulations.aspx] wordt weergegeven zodra de overige informatie die deze nodig heeft in de context is geplaatst

6.4.5. Overzicht van de wijzigingen

De applicatie is klaar om getest te worden. Laten we de wijzigingen ten opzichte van eerdere versies opsommen:

  1. er is een nieuwe weergave gebouwd
  2. de controller [main.aspx.vb] is op één punt aangepast: verwerking van de actie [calcul]

6.4.6. Testen van de webapplicatie

De lezer wordt uitgenodigd om de tests uit te voeren. We herhalen de werkwijze. Alle bestanden van de applicatie worden in een map <application-path> geplaatst. In deze map wordt een submap [bin] aangemaakt, waarin de assembly [impot.dll] wordt geplaatst, die is voortgekomen uit de compilatie van de bestanden van de bedrijfsspecifieke klassen: [impots.vb, impotsData.vb, impotsArray.vb, impotsODBC.vb. Het bestand [impot.dll] dat door deze opdracht wordt gegenereerd, moet in <application-path>\bin worden geplaatst, zodat de webapplicatie er toegang toe heeft. De Cassini-server wordt gestart met de parameters (<application-path>,/impots4).

6.5. Conclusion

De voorgaande voorbeelden hebben aan de hand van een concreet geval de mechanismen geïllustreerd die vaak worden gebruikt bij webontwikkeling. We hebben systematisch de architectuur MVC gebruikt vanwege het pedagogische nut ervan. We hadden deze voorbeelden ook anders en wellicht eenvoudiger kunnen behandelen zonder deze architectuur. Maar deze biedt grote voordelen zodra de applicatie wat complexer wordt met meerdere pagina’s.

We zouden onze voorbeelden op verschillende manieren kunnen voortzetten. Hier volgen er enkele:

  • de gebruiker zou zijn simulaties in de loop van de tijd willen bewaren. Hij zou op dag D simulaties uitvoeren en deze bijvoorbeeld op dag D+3 weer kunnen terugvinden. Een mogelijke oplossing voor dit probleem is het gebruik van cookies. We weten dat het sessietoken tussen de server en een client via dit mechanisme wordt overgedragen. We zouden dit ook kunnen gebruiken om de simulaties tussen de client en de server over te dragen.
    • Tegelijkertijd met het verzenden van de pagina met de simulatieresultaten stuurt de server in zijn headers HTTP een cookie mee die een tekenreeks bevat die de simulaties vertegenwoordigt. Aangezien deze zich in een object [ArrayList] bevinden, moet dit object worden omgezet naar [String]. De server zou de cookie een geldigheidsduur toekennen, bijvoorbeeld 30 dagen.
    • De browser van de klant slaat de ontvangen cookies op in een bestand en stuurt ze telkens terug wanneer hij een verzoek doet aan een server die ze naar hem heeft gestuurd, mits ze nog geldig zijn (de geldigheidsduur is nog niet verstreken). De server ontvangt voor de simulaties een tekenreeks [String], die hij moet omzetten in het object [ArrayList].

De cookies worden beheerd door [Response.Cookies] wanneer ze naar de client worden verzonden en door [Request.Cookies] wanneer ze op de server worden ontvangen.

  • Het bovenstaande mechanisme kan behoorlijk traag worden als er een groot aantal simulaties is. Bovendien komt het vaak voor dat een gebruiker zijn cookies periodiek opschoont door ze allemaal te verwijderen, ook al staat hij zijn browser toe om ze te gebruiken. Dus vroeg of laat gaan de cookies van de simulaties verloren. Het kan dan wenselijk zijn om deze op de server op te slaan in plaats van op de client, bijvoorbeeld in een database. Om simulaties aan een bepaalde gebruiker te koppelen, zou de applicatie kunnen beginnen met een identificatiefase waarbij een gebruikersnaam en wachtwoord worden gevraagd, die zelf zijn opgeslagen in een database of een andere vorm van gegevensopslag.
  • We zouden ook de werking van onze applicatie willen beveiligen. Deze gaat momenteel uit van twee aannames:
    • de gebruiker gaat altijd via de controller [main.aspx]
    • en in dat geval maakt hij altijd gebruik van de acties die worden aangeboden op de pagina die naar hem is verzonden

Wat gebeurt er bijvoorbeeld als de gebruiker rechtstreeks de URL [http://localhost/impots4/formulaire.aspx] opvraagt? Dit scenario is onwaarschijnlijk, aangezien de gebruiker het bestaan van deze URL niet kent. Toch moet hiermee rekening worden gehouden. Dit kan worden afgehandeld door de applicatiecontroller [global.asax], die alle verzoeken aan de applicatie verwerkt. Zo kan deze controleren of de opgevraagde bron inderdaad [main.aspx] is.

Een waarschijnlijker scenario is dat een gebruiker geen gebruik maakt van de acties op de pagina die de server hem heeft gestuurd. Wat gebeurt er bijvoorbeeld als de gebruiker rechtstreeks de URL [http://localhost/impots4/main.aspx?action=retour] opvraagt zonder eerst het formulier in te vullen? Laten we het eens proberen. We krijgen het volgende antwoord:

Image

De server is gecrasht. Dat is normaal. Voor de actie [retour] verwacht de controller in de sessie een object [NameValueCollection] aan te treffen dat de waarden van het formulier vertegenwoordigt dat hij moet weergeven. Hij vindt deze echter niet. Het mechanisme van de controller biedt een elegante oplossing voor dit probleem. Voor elk verzoek kan de controller [main.aspx] controleren of de gevraagde actie inderdaad een van de acties is van de pagina die eerder naar de gebruiker is verzonden. We kunnen het volgende mechanisme gebruiken:

  • de controller slaat, voordat hij zijn antwoord naar de klant verstuurt, informatie op in de sessie van de klant waarmee deze pagina kan worden geïdentificeerd
  • wanneer hij een nieuw verzoek van de klant ontvangt, controleert hij of de gevraagde actie inderdaad behoort tot de laatste pagina die naar deze klant is verzonden
  • de informatie die pagina's koppelt aan de toegestane acties op die pagina's kan worden ingevoerd in het configuratiebestand [web.config] van de applicatie.
  • De ervaring leert dat applicatiecontrollers een brede gemeenschappelijke basis hebben en dat het mogelijk is om een generieke controller te bouwen, waarbij de aanpassing aan een bepaalde applicatie via een configuratiebestand plaatsvindt. Dit is bijvoorbeeld de aanpak die wordt gevolgd door de tool [Struts] op het gebied van webprogrammering in Java.