6. Exemples
Nous nous proposons dans ce chapitre d'illustrer ce qui a été vu précédemment par une série d'exemples.
6.1. Exemple 1
6.1.1. Le problème
Cette application doit permettre à un utilisateur de calculer son impôt. On se place dans le cas simplifié d'un contribuable n'ayant que son seul salaire à déclarer (chiffres 2004 pour revenus 2003) :
- on calcule le nombre de parts du salarié nbParts=nbEnfants/2 +1 s'il n'est pas marié, nbEnfants/2+2 s'il est marié, où nbEnfants est son nombre d'enfants.
- s'il a au moins trois enfants, il a une demi part de plus
- on calcule son revenu imposable R=0.72*S où S est son salaire annuel
- on calcule son coefficient familial QF=R/nbParts
- on calcule son impôt I. Considérons le tableau suivant :
|
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 |
Chaque ligne a 3 champs. Pour calculer l'impôt I, on recherche la première ligne où QF<=champ1. Par exemple, si QF=5000 on trouvera la ligne
L'impôt I est alors égal à 0.0683*R - 291.09*nbParts. Si QF est tel que la relation QF<=champ1 n'est jamais vérifiée, alors ce sont les coefficients de la dernière ligne qui sont utilisés. Ici :
ce qui donne l'impôt I=0.4809*R - 9505.54*nbParts.
6.1.2. La structure MVC de l'application
La structure MVC de l'application sera la suivante :

Le rôle du contrôleur sera joué par la page [main.aspx]. Il y aura trois actions possibles :
- init : correspond à la première requête du client. Le contrôleur affichera la vue [formulaire.aspx]
- calcul : correspond à la demande de calcul de l'impôt. Si les données du formulaire de saisie sont correctes, l'impôt est calculé grâce à la classe métier [impots]. Le contrôleur retourne au client la vue [formulaire.aspx] telle qu'elle avait été validée avec de plus l'impôt calculé. Si les données du formulaire de saisie sont incorrectes, le contrôleur retournera la vue [erreurs.aspx] avec la liste des erreurs et un lien pour retourner au formulaire.
- retour : correspond au retour au formulaire après une erreur. Le contrôleur affiche la vue [formulaire.aspx] telle qu'elle a été validée avant l'erreur.
Le contrôleur [main.aspx] ne connaît rien au calcul d'impots. Il est simplement en charge de gérer le dialogue client-serveur et de faire exécuter les actions demandées par le client. Pour l'action [calcul], il s'appuiera sur la classe métier [impot].
6.1.3. La classe métier
La classe impot sera définie comme suit :
' espaces de noms importés
Imports System
' classe
Namespace st.istia.univangers.fr
Public Class impot
Private limites(), coeffR(), coeffN() As Decimal
' constructeur
Public Sub New(ByRef source As impotsData)
' les données nécessaires au calcul de l'impôt
' proviennent d'une source extérieure [source]
' on les récupère - il peut y avoir une exception
Dim data() As Object = source.getData
limites = CType(data(0), Decimal())
coeffR = CType(data(1), Decimal())
coeffN = CType(data(2), Decimal())
End Sub
' calcul de l'impôt
Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Long) As Long
' calcul du nombre de parts
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
' calcul revenu imposable & Quotient familial
Dim revenu As Decimal = 0.72D * salaire
Dim QF As Decimal = revenu / nbParts
' calcul de l'impôt
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
Un objet impôt en fournissant à son constructeur une source de données de type [impotsData]. Cette classe a une méthode publique [getData] qui permet d'obtenir les trois tableaux de données nécessaires au calcul de l'impôt et qui ont été présentés précédemment. Cette méthode peut gérer une exception si les donnnées n'on pu être acquises ou si elles s'avèrent incorrectes. Une fois l'objet [impot] créé, on peut appeler de façon répétée sa méthode calculer qui calcule l'impôt du contribuable à partir de son statut marital (marié ou non), son nombre d'enfants et son salaire annuel.
6.1.4. La classe d'accès aux données
La classe [impotsData] est la classe qui permet d'accéder aux données. C'est une classe abstraite. On doit créer une classe dérivée pour chaque nouvelle source de données possible (tableaux, fichiers plats, bases de données, console, ...). Sa définition est la suivante :
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
' méthode d'accès aux données
Public MustOverride Function getData() As Object()
' méthode de vérification des données
Protected Function checkData() As Integer
' vérifie les données acquises
' on doit avoir des données
valide = Not limites Is Nothing AndAlso Not coeffr Is Nothing AndAlso Not coeffn Is Nothing
If Not valide Then Return 1
' on doit avoir 3 tableaux de même taille
If valide Then valide = limites.Length = coeffr.Length AndAlso limites.Length = coeffn.Length
If Not valide Then Return 2
' les tableaux doivent être non vides
valide = limites.Length <> 0
If Not valide Then Return 3
' chaque tableau doit contenir des éléments >=0 et en ordre croissant
valide = check(limites, limites.Length - 1) AndAlso check(coeffr, coeffr.Length) AndAlso check(coeffn, coeffn.Length)
If Not valide Then Return 4
' tout est bon
Return 0
End Function
' vérifie la validité du contenu d'un tableau
Protected Function check(ByRef tableau() As Decimal, ByVal n As Integer) As Boolean
' tableau doit avoir ses n premiers éléments >=0 et en ordre strictement croissant
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
' c'est bon
Return True
End Function
End Class
End Namespace
La classe a les attributs protégés suivants :
|
tableau des limites de tranches d'impôts |
|
tableau des coefficients appliqués au revenu imposable |
|
tableau des coefficients appliqués au nombre de parts |
|
booléen indiquant si les données (limites, coeffr, coeffn) ont été vérifiées |
|
booléen indiquant si les données (limites, coeffr, coeffn) sont valides |
La classe n'a pas de constructeur. Elle a une méthode abstraite [getData] que les classes dérivées devront implémenter. Cette méthode a pour rôle de :
- affecter des valeurs aux trois tableaux limites, coeffr, coeffn
- de lancer une exception si les données n'ont pu être acquises ou si elles se sont avérées invalides.
La classe fournit les méthodes protégées [checkData] et [check] qui vérifient la validité des attributs (limites, coeffr, coeffn). Cela dispense les classes dérivées de les implémenter. Elles n'auront qu'à les utiliser.
La première classe dérivée que nous utiliserons sera la suivante :
Imports System.Collections
Imports System
Namespace st.istia.univangers.fr
Public Class impotsArray
Inherits impotsData
' constructeur sans argument
Public Sub New()
' initialisations des tableaux avec des constantes
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
' constructeur avec trois tableaux en entrée
Public Sub New(ByRef limites() As Decimal, ByRef coeffr() As Decimal, ByRef coeffn() As Decimal)
' on mémorise les données
Me.limites = limites
Me.coeffr = coeffr
Me.coeffn = coeffn
checked = False
End Sub
Public Overrides Function getData() As Object()
' on vérifie éventuellement les données
Dim erreur As Integer
If Not checked Then erreur = checkData() : checked = True
' si pas valide, alors on lance une exception
If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
' sinon on rend les trois tableaux
Return New Object() {limites, coeffr, coeffn}
End Function
End Class
End Namespace
Cette classe appelée [impotsArray] a deux constructeurs :
- un constructeur sans argument qui initialise les attributs (limites,coeffr,coeffn) de la classe de base avec des tableaux codés en "dur"
- un constructeur qui initialise les attributs (limites,coeffr,coeffn) de la classe de base avec des tableaux qui lui sont passés en paramètres
La méthode [getData] qui permettra aux classes externes d'obtenir les tableaux (limites,coeffr,coeffn) se contente de vérifier la validité des trois tableaux à l'aide de la méthode [checkData] de la classe de base. Elle lance une exception si les données sont invalides.
6.1.5. Tests des classes métier et des classes d'accès aux données
Il est important de n'inclure dans une application web que des classes métier et d'accès aux données certifiées correctes. Ainsi la phase de débogage de l'application web pourra se concentrer sur la partie contrôleur et vues. Un programme de test pourait être le suivant :
' options
Option Strict On
Option Explicit On
' espaces de noms
Imports System
Imports Microsoft.VisualBasic
Namespace st.istia.univangers.fr
Module test
Sub Main()
' programme interactif de calcul d'impôt
' l'utilisateur tape trois données au clavier : marié nbEnfants salaire
' le programme affiche alors l'impôt à payer
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"
' création d'un objet impôt
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
' boucle infinie
Dim marié As String
Dim nbEnfants As Integer
Dim salaire As Long
While True
' on demande les paramètres du calcul de l'impôt
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()
' qq chose à faire ?
If paramètres Is Nothing OrElse paramètres = "" Then
Exit While
End If
' vérification du nombre d'arguments dans la ligne saisie
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
' vérification de la validité des paramètres
If Not erreur Then
' marié
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
' salaire
Try
salaire = Integer.Parse(args(2))
If salaire < 0 Then
Throw New Exception
End If
Catch
erreur = True
End Try
End If
' si les paramètres sont corrects - on calcule l'impôt
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
L'application demande à l'utilisateur de taper au clavier les trois informations dont on a besoin pour calculer son impôt :
- son statut marital : o pour marié, n pour non marié
- son nombre d'enfants
- son salaire annuel
Le calcul de l'impôt est fait à l'aide d'un objet de type [impot] créé dès le lancement de l'application :
' création d'un objet impôt
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
Comme source de données, on utilise un objet de type [impotsArray]. C'est le constructeur sans argument de cette classe qui est utilisé et qui fournit les trois tableaux (limites, coeffr, coeffn) avec des valeurs en "dur". La création d'un objet [impot] peut en théorie créer une exception puisque pour se créer, l'objet va demander les données (limites,coeffr,coeffn) à sa source de données qu'on lui a passé en paramètre, et que cette acquisition de données peut lancer une exception. Il se trouve qu'ici, la méthode d'obtention des données (codage en dur) ne peut provoquer d'exception. Nous avons cependant laissé la gestion de celle-ci afin d'attirer l'attention du lecteur sur cette possibilité que l'objet [impot] se construise mal.
Voici un exemple d'exécution du programme précédent :
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
Nous compilons l'ensemble des classes [impot, impotsData, impotsArray] dans un assembage [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
Nous compilons le programme de test :
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
Nous pouvons faire les tests :
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. Les vues de l'application web
L'application aura deux vues : [formulaire.aspx] et [erreurs.aspx]. Illustrons le fonctionnement de l'application par des copies d'écran. La vue [formulaire.aspx] est présentée lorsque l'url [main.aspx] est demandée la première fois :

L'utilisateur renseigne le formulaire :

et utilise le bouton [Calculer] pour obtenir la réponse suivante :

Il peut se tromper dans les données saisies :

L'utilisation du bouton [Calculer] amène alors une autre réponse [erreurs.aspx] :

Il peut utiliser le lien [Retour au formulaire] ci-dessus pour retrouver la vue [formulaire.aspx] telle qu'il l'a validée avant l'erreur :

6.1.7. La vue [formulaire.aspx]
La page [formulaire.aspx] sera la suivante :
<%@ 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>
Les champs dynamiques de cette page sont les suivants :
|
"checked" si la case [oui] doit être cochée, "" sinon |
|
idem pour la case [non] |
|
valeur à placer dans le champ de saisie [txtEnfants] |
|
valeur à placer dans le champ de saisie [txtSalaire] |
|
valeur à placer dans le champ de saisie [txtImpot] |
La page a deux formulaires, chacun ayant un bouton [submit]. Le bouton [Calculer] est le bouton [submit] du formulaire suivant :
<form method="post" action="main.aspx?action=calcul">
...
<P>
<INPUT type="submit" value="Calculer">
</P>
</form>
On voit que les paramètres du formulaire seront postés au contrôleur avec [action=calcul]. Le bouton [Effacer] est le bouton [submit] du formulaire suivant :
<form method="post" action="main.aspx?action=effacer">
<INPUT type="submit" value="Effacer">
</form>
On voit que les paramètres du formulaire seront postés au contrôleur avec [action=effacer]. Ici, le formulaire n'a aucun paramètre. Il n'y a que l'action qui importe.
Les champs de [formulaire.aspx] sont calculés par [formulaire.aspx.vb] :
Imports System.Collections.Specialized
Public Class formulaire
Inherits System.Web.UI.Page
' champs de la page
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
' on récupère la précédente requête dans le contexte
Dim form As NameValueCollection = Context.Items("formulaire")
' on prépare la page à afficher
' boutons radio
rdouichecked = ""
rdnonchecked = "checked"
If form("rdMarie").ToString = "oui" Then
rdouichecked = "checked"
rdnonchecked = ""
End If
' le reste
txtEnfants = CType(form("txtEnfants"), String)
txtSalaire = CType(form("txtSalaire"), String)
txtImpot = CType(Context.Items("txtImpot"), String)
End Sub
End Class
Le calcul des champs de [main.aspx] se fait à partir de deux informations placées par le contrôleur dans le contexte de la page :
- Context.Items("formulaire") : dictionnaire de type NameValueCollection contenant les valeurs des champs HTML [rdmarie,txtEnfants,txtSalaire]
- Context.Items("txtImpot") : valeur de l'impôt
6.1.8. La vue [erreurs.aspx]
La vue [erreurs.aspx] est celle qui affiche les erreurs éventuelles qui peuvent se produire lors de la vie de l'application. Son code de présentation est le suivant :
<%@ 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>
La page a trois champs dynamiques :
|
code HTML d'une liste d'erreurs |
|
url d'un lien |
|
texte du lien |
Ces champs sont calculés par la partie contrôleur de la page dans [erreurs.aspx.vb] :
Imports System.Collections
Imports Microsoft.VisualBasic
Public Class erreurs
Inherits System.Web.UI.Page
' paramètre de page
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
' on récupère les éléments du contexte
Dim erreurs As ArrayList = CType(context.Items("erreurs"), ArrayList)
href = context.Items("href").ToString
lien = context.Items("lien").ToString
' on génère le code HTML de la liste
Dim i As Integer
For i = 0 To erreurs.Count - 1
erreursHTML += "<li> " + erreurs(i).ToString + "</li>" + ControlChars.CrLf
Next
End Sub
End Class
Le contrôleur de la page récupère des informations placées par le contrôleur de l'application dans le contexte de la page :
|
objet ArrayList contenant la liste des messages d'erreurs à afficher |
|
url d'un lien |
|
texte du lien |
Maintenant que nous savons ce que voit l'utilisateur de l'application, nous pouvons passer à l'écriture du contrôleur de celle-ci.
6.1.9. Les contrôleurs [global.asax, main.aspx]
Rappelons le schéma MVC de notre application :

Le contrôleur [main.aspx] a à traiter trois actions :
- init : correspond à la première requête du client. Le contrôleur affiche la vue [formulaire.aspx]
- calcul : correspond à la demande de calcul de l'impôt. Si les données du formulaire de saisie sont correctes, l'impôt est calculé grâce à la classe métier [impots]. Le contrôleur retourne au client la vue [formulaire.aspx] telle qu'elle avait été validée avec de plus l'impôt calculé. Si les données du formulaire de saisie sont incorrectes, le contrôleur retourne la vue [erreurs.aspx] avec la liste des erreurs et un lien pour retourner au formulaire.
- retour : correspond au retour au formulaire après une erreur. Le contrôleur affiche la vue [formulaire.aspx] telle qu'elle a été validée avant l'erreur.
On sait par ailleurs, que toute requête vers l'application transite via le contrôleur [global.asax] s'il existe. Nous avons alors à l'entrée de l'application, une chaîne de deux contrôleurs :
- [global.asax] qui de par l'architecture ASP.NET reçoit toute requête vers l'application
- [main.aspx] qui de par la décision du développeur reçoit également toute requête vers l'application
La nécessité de [main.aspx] vient du fait que nous aurons une session à gérer. Nous avons vu que [global.asax] ne convenait pas comme contrôleur dans ce cas. On pourrait ici se passer totalement de [global.asax]. Nous allons cependant l'utiliser pour exécuter du code au démarrage de l'application. Le schéma MVC ci-dessus montre que nous allons avoir besoin de créer un objet [impot] pour calculer l'impôt. Il est inutile de créer celui-ci plusieurs fois, une fois suffit. Nous allons donc le créer au démarrage de l'application lors de l'événement [Application_Start] géré par le contrôleur [global.asax]. Le code de celui-ci est le suivant :
[global.asax]
[global.asax.vb]
Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr
Public Class Global
Inherits System.Web.HttpApplication
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
' on crée un objet impot
Dim objImpot As impot
Try
objImpot = New impot(New impotsArray)
' on met l'objet dans l'application
Application("objImpot") = objImpot
' pas d'erreur
Application("erreur") = False
Catch ex As Exception
'il y a eu erreur, on le note dans l'application
Application("erreur") = True
End Try
End Sub
End Class
Une fois créé, l'objet de type [impot] est mis dans l'application. C'est là que les différentes requêtes des différents clients iront le chercher. Comme la construction de l'objet [impot] peut échouer, nous gérons l'éventuelle exception et plaçons une clé [erreur] dans l'application pour signaler s'il y a eu ou non une erreur lors de la création de l'objet [impot].
Le code du contrôleur [main.aspx, main.aspx.vb] sera le suivant :
[main.aspx]
[main.aspx.vb]
Imports System
Imports System.Collections.Specialized
Imports System.Collections
Imports st.istia.univangers.fr
Public Class main
Inherits System.Web.UI.Page
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' avant tout, on regarde si l'application a pu s'initialiser correctement
If CType(Application("erreur"), Boolean) Then
' on redirige vers la page d'erreurs
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
' on récupère l'action à faire
Dim action As String
If Request.QueryString("action") Is Nothing Then
action = "init"
Else
action = Request.QueryString("action").ToString.ToLower
End If
' on exécute l'action
Select Case action
Case "init"
' init application
initAppli()
Case "calcul"
' calcul impot
calculImpot()
Case "retour"
' retour au formulaire
retourFormulaire()
Case "effacer"
' init application
initAppli()
Case Else
' action inconnue = init
initAppli()
End Select
End Sub
Private Sub initAppli()
' on affiche le formulaire pré-rempli
Context.Items("formulaire") = initForm()
Context.Items("txtImpot") = ""
Server.Transfer("formulaire.aspx", True)
End Sub
Private Function initForm() As NameValueCollection
' on initialise le formulaire
Dim form As New NameValueCollection
form.Set("rdMarie", "non")
form.Set("txtEnfants", "")
form.Set("txtSalaire", "")
Return form
End Function
Private Sub calculImpot()
' on vérifie la validité des données saisies
Dim erreurs As ArrayList = checkData()
' s'il y a des erreurs, on le signale
If erreurs.Count <> 0 Then
' on sauvegarde les saisies
Session.Item("formulaire") = Request.Form
' on prépare la page d'erreurs
context.Items("href") = "main.aspx?action=retour"
context.Items("lien") = "Retour au formulaire"
context.Items("erreurs") = erreurs
Server.Transfer("erreurs.aspx")
End If
' ici pas d'erreurs - on calcule l'impôt
Dim impot As Long = CType(Application("objImpot"), impot).calculer( _
Request.Form("rdMarie") = "oui", _
CType(Request.Form("txtEnfants"), Integer), _
CType(Request.Form("txtSalaire"), Long))
' on affiche la page de résultat
context.Items("txtImpot") = impot.ToString + " euro(s)"
context.Items("formulaire") = Request.Form
Server.Transfer("formulaire.aspx", True)
End Sub
Private Sub retourFormulaire()
' on affiche le formulaire avec des valeurs prises dans la session
Context.Items("formulaire") = Session.Item("formulaire")
Context.Items("txtImpot") = ""
Server.Transfer("formulaire.aspx", True)
End Sub
Private Function checkData() As ArrayList
' au départ pas d'erreurs
Dim erreurs As New ArrayList
Dim erreur As Boolean = False
' bouton radio marié
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
' nbre d'enfants
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
' salaire
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
' on rend la liste des erreurs
Return erreurs
End Function
End Class
Le contrôleur commence par vérifier que l'application s'est correctement initialisée :
' avant tout, on regarde si l'application a pu s'initialiser correctement
If CType(Application("erreur"), Boolean) Then
' on redirige vers la page d'erreurs
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
Si le contrôleur découvre que l'application n'a pas pu s'initialiser correctement (l'objet [impot] nécessaire au calcul n'a pu être créé), alors il fait afficher la page d'erreurs avec les paramètres adéquats. Ici, il n'y a pas lieu de placer le lien de retour sur le formulaire puisque la totalité de l'application s'avère indisponible. Un message d'erreur général est placé dans [Context.Items("erreurs")] de type [ArrayList].
Si le contrôleur découvre que l'application est opérationnelle, il analyse alors l'action qu'on lui demande d'exécuter via le paramètre [action]. Nous avons déjà rencontré souvent maintenant ce mode de fonctionnement. Le traitement de chaque type d'action est délégué à une fonction.
6.1.9.1. Les actions init, effacer
Ces deux actions doivent faire afficher le formulaire de saisie vide. On rappelle que celui-ci (cf vues) a deux paramètres :
- Context.Items("formulaire") : dictionnaire de type [NameValueCollection] contenant les valeurs des champs HTML [rdmarie,txtEnfants,txtSalaire]
- Context.Items("txtImpot") : valeur de l'impôt
La fonction [initAppli] initialise ces deux paramètres de façon à afficher un formulaire vide.
6.1.9.2. L'action calcul
Cette action doit calculer l'impôt à payer à partir des données saisies dans le formulaire et renvoyer celui-ci pré-rempli avec les valeurs saisies et avec de plus le montant de l'impôt calculé. La fonction [calculImpot] chargée de ce travail commence par vérifier que les données du formulaire sont bien correctes :
- le champ [rdMarie] doit être présent et avoir la valeur [oui] ou [non]
- le champ [txtEnfants] doit être présent et être un entier >=0
- le champ [txtSalaire] doit être présent et être un entier >=0
Si les données saisies se révèlent invalides, le contrôleur fait afficher la vue [erreurs.aspx] en ayant auparavant mis dans le contexte les valeurs attendues par celle-ci :
- les messages d'erreurs sont placés dans un objet [ArrayList], objet placé ensuite dans le contexte [Context.Items("erreurs")]
- l'url du lien de retour et le texte de ce lien sont également placés dans le contexte.
Avant de passer la main à la page [erreurs.aspx] qui va envoyer la réponse au client, les valeurs saisies dans le formulaire (Request.Form) sont placées dans la session, associées à la clé "formulaire". Ceci permettra à une requête ultérieure de les récupérer.
On peut se demander ici s'il est utile de vérifier que les champs [rdMarie, txtEnfants, txtSalaire] sont présents dans la requête envoyée par le client. C'est inutile si on est sûr que notre client est un navigateur ayant reçu la vue [formulaire.aspx] qui contient ces champs. On ne peut jamais être sûr de cela. Nous montrerons un peu plus tard un exemple où le client est l'application [curl] déjà rencontrée. Nous interrogerons l'application sans envoyer les champs qu'elle attend et nous verrons comment elle réagira. C'est une règle déjà énoncée à plusieurs reprises et que nous rappelons ici : une application ne doit jamais faire d'hypothèses sur le type de client qui l'interroge. Par sécurité, elle doit considérer qu'elle peut être interrogée par une application programmée qui peut lui envoyer des chaînes de paramètres inattendues. Elle doit correctement se comporter dans tous les cas.
Dans notre cas, nous avons vérifié que les champs [rdMarie, txtEnfants, txtSalaire] étaient présents dans la requête mais pas que celle-ci pouvait en contenir d'autres. Dans cette application, ils seraient ignorés. Néanmoins, toujours par mesure de sécurité, il serait intéressant d'enregistrer ce type de demande dans un fichier de logs et de remonter une alerte à l'administrateur de l'application afin que celui-ci sache que l'application reçoit des demandes "bizarres". En analysant celles-ci dans le fichier de logs, il pourrait détecter une éventuelle attaque de l'application et prendre alors les mesures nécessaires à la protection de celle-ci.
Si les données attendues sont correctes, le contrôleur lance le calcul de l'impôt avec l'objet [impot] stocké dans l'application. Puis il stocke dans le contexte les deux informations attendues par la vue [formulaire.aspx] :
- Context.Items("formulaire") : dictionnaire de type [NameValueCollection] contenant les valeurs des champs HTML [rdmarie,txtEnfants,txtSalaire], ici [Request.Form)], c.a.d. les valeurs saisies précédemment dans le formulaire
- Context.Items("txtImpot") : valeur de l'impôt qui vient d'être obtenue
Le lecteur attentif s'est peut être posé une question à la lecture de ce qui précède : puisque l'objet [impot] créé au démarrage de l'application est partagé entre toutes les requêtes, ne peut-il y avoir de conflits d'accès entraînant une corruption des données de l'objet [impot]. Pour répondre à cette question, il nous faut revenir au code de la classe [impot]. Les requêtes font appel à la méthode [impot].calculerImpot pour obtenir l'impôt à payer. C'est donc ce code qu'il nous faut examiner :
Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Long) As Long
' calcul du nombre de parts
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
' calcul revenu imposable & Quotient familial
Dim revenu As Decimal = 0.72D * salaire
Dim QF As Decimal = revenu / nbParts
' calcul de l'impôt
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
Supposons qu'un thread soit en train d'exécuter la méthode précédente et qu'il soit interrompu. Un autre thread exécute alors la méthode. Quels sont les risques ? Pour le savoir, nous avons rajouté le code suivant :
Dim impot As Long = CLng(revenu * coeffR(i) - nbParts * coeffN(i))
' on patiente 10 secondes
Thread.Sleep(10000)
Return impot
Le thread 1, après avoir calculé la valeur [impot1] de la variable locale [impot] est interrompu. Le thread 2 s'exécute alors et calcule une nouvelle valeur [impot2] pour cette même variable [impot] avant d'être interrompu. Le thread 1 récupère la main. Que retrouve-t-il dans la variable locale [impot] ? Cette variable étant locale à une méthode est stockée dans une structure de la mémoire appelée pile. Cette pile fait partie du contexte du thread qui est sauvegardé lorsque celui-ci est interrompu. Lorsque le thread 2 s'installe, son contexte est mis en place avec une nouvelle pile et donc une nouvelle variable locale [impot]. Lorsque le thread 2 va être interrompu à son tour, son contexte sera à son tour sauvegardé. Lorsque le thread 1 est relancé, son contexte est restauré dont sa pile. Il retrouve alors sa variable locale [impot] et non celle du thread 2. On est donc dans une situation où il n'y a pas de conflits d'accès entre requêtes. Les tests faits avec la pause de 10 secondes ci-dessus ont confirmé que les requêtes simultanées obtenaient bien le résultat attendu.
6.1.9.3. L'action retour
Cette action correspond à l'activation du lien [Retour vers le formulaire] de la vue [erreurs.aspx] pour revenir à la vue [formulaire.aspx] pré-rempli avec les valeurs saisies précédemment et sauvegardées dans la session. La fonction [retourFormulaire] récupère cette information. Les deux paramètres attendus par la vue [formulaire.aspx] sont initialisés :
- Context.Items("formulaire") avec les valeurs saisies précédemment et sauvegardées dans la session
- Context.Items("txtImpot") avec la chaîne vide
6.1.10. Test de l'application web
L'ensemble des fichiers précédents sont placés dans un dossier <application-path>.

Dans ce dossier, est créé un sous-dossier [bin] dans lequel est placé l'assemblage [impot.dll] issu de la compilation des fichiers des classes métier : [impots.vb, impotsData.vb, impotsArray.vb]. On rappelle ci-dessous, la commande de compilation nécessaire :
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
Le fichier [impot.dll] ci-dessus doit être placé dans <application-path>\bin afin que l'application web y ait accès. Le serveur Cassini est lancé avec les paramètres (<application-path>,/impots1). Avec un navigateur, nous demandons l'url [http://localhost/impots1/main.aspx] :

Nous remplissons le formulaire :

Puis nous lançons le calcul de l'impôt avec le bouton [Calculer]. Nous obtenons la réponse suivante :

Puis nous faisons des saisies erronées :

L'appui sur le bouton [Calculer] amène la réponse suivante :

L'utilisation du lien [Retour au formulaire] nous ramène au formulaire dans l'état où il était lorsqu'il a été validé :

Enfin, l'utilisation du bouton [Effacer] réinitialise la page :

6.1.11. Utilisation du client [curl]
Il est important de tester les applications web avec d'autres clients que les navigateurs. Si on envoie à un navigateur un formulaire avec des paramètres à poster lorsqu'il sera validé, le navigateur renverra les valeurs de ces paramètres au serveur. Un autre client pourrait ne pas le faire et alors le serveur aurait une requête où il manquerait des paramètres. Il doit savoir quoi faire dans ce cas. Un autre exemple est celui des vérifications de saisie faites côté client. Si le formulaire contient des données à vérifier, cette vérification peut être faite côté client grâce à des scripts inclus dans le document contenant le formulaire. Le navigateur ne postera le formulaire que si toutes les données vérifiées côté client sont valides. On pourrait alors être tenté, côté serveur, de considérer qu'on va recevoir des données vérifiées et ne pas vouloir faire cette vérification une seconde fois. Ce serait une erreur. En effet, un client autre qu'un navigateur pourrait au serveur des données invalides et alors l'application web risque d'avoir un comportement inattendu. Nous allons illustrer ces points en utilisant le client [curl].
Tout d'abord, nous demandons l'url [http://localhost/impots1/main.aspx] :
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>
Le serveur nous a envoyé le code HTML du formulaire. Dans les entêtes HTTP nous avons le cookie de session. Nous allons l'utiliser dans les requêtes suivantes afin de maintenir la session. Demandons l'action [calcul] sans fournir de paramètres :
dos>curl --cookie ASP.NET_SessionId=ivthkl45tjdjrzznevqsf255 --include --url http://localhost/impots1/main.aspx?action=calcul
HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Thu, 01 Apr 2004 15:22:42 GMT
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 380
Connection: Close
<HTML>
<HEAD>
<title>Impôt</title>
</HEAD>
<body>
<P>Les erreurs suivantes se sont produites :</P>
<HR>
<ul>
<li> Vous n'avez pas indiqué votre statut marital</li>
<li> Le nombre d'enfants est incorrect</li>
<li> Le salaire annuel est incorrect</li>
</ul>
<a href="main.aspx?action=retour">
Retour au formulaire
</a>
</body>
</HTML>
Nous pouvons constater que l'application web a renvoyé la vue [erreurs] avec trois messages d'erreurs pour les trois paramètres manquants. Envoyons maintenant des paramètres erronés :
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>
Les trois erreurs ont été correctement détectées. Maintenant envoyons des paramètres valides :
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>
Nous avons bien récupéré l'impôt à payer : 4300 euros. Nous retiendrons de cette illustration qu'il ne faut se laisser illusionner par le fait qu'on écrit une application web à destination de clients qui sont des navigateurs. Une application web est un service tcp-ip et ce protocole réseau ne permet pas de dire la nature de l'application cliente d'un service. Donc, on ne peut pas savoir si le client d'une application web est un navigateur ou non. On suit alors deux règles :
- à réception d'une requête d'un client, on ne fait aucune hypothèse sur le client et on vérifie que les paramètres attendus dans la requête sont bien présents et valides
- on élabore une réponse à destination des navigateurs, donc en général des documents HTML
Une application web peut être construite pour servir simultanément des clients différents, par exemple des navigateurs et des téléphones mobiles. On peut alors inclure dans chaque requête un nouveau paramètre indiquant le type du client. Ainsi un navigateur demandera le calcul de l'impôt par une requête à l'url http://machine/impots/main.aspx?client=navigateur&action=calcul alors que le téléphone mobile lui fera une requête à l'url http://machine/impots/main.aspx?client=mobile&action=calcul. La structure MVC facilite l'écriture d'une telle application. Elle devient la suivante :

Le bloc [Classes métier, Classes d'accès aux données] ne change pas. C'est en effet une partie indifférente au client. Le bloc [Contrôleur] change peu mais doit prendre un compte un nouveau paramètre dans la requête, le paramètre [client] indiquant à quel type de client il a affaire. Le bloc [vues] doit générer des vues pour chaque type de client. Il pourrait être intéressant de prendre en compte dès la conception de l'application la présence du paramètre [client] dans la requête, même si on n'a comme objectif à court ou moyen terme que les seuls navigateurs. Si l'application doit ultérieurement gérer un nouveau type de client, seules des vues adaptées à celui-ci sont à écrire.
6.2. Exemple 2
6.2.1. Le problème
Nous nous proposons ici de traiter le même problème que précédemment mais en modifiant la source des données de l'objet [impot] créé par l'application web. Dans la version précédente, celle utilisée délivrait les valeurs de tableaux écrits en "dur" dans le code. Cette fois-ci, la nouvelle source de données les prendra dans une source de données ODBC associée à une base MySQL.
6.2.2. La source de données ODBC
Les données se trouveront dans une table appelée [IMPOTS] d'une base MySQL appelée [dbimpots]. Le contenu de cette table sera le suivant :

Le propiétaire de la base est l'utilisateur [admimpots] de mot de passe [mdpimpots]. Nous associons une source de données ODBC à cette base. Avant de le faire, rappelons d'abord les différents moyens d'accéder à une base de données avec la plate-forme .NET.
Il existe de nombreuses bases de données pour les plate-formes windows. Pour y accéder, les applications passent au travers de programmes appelés pilotes (drivers).

Dans le schéma ci-dessus, le pilote présente deux interfaces :
- l'interface I1 présentée à l'application
- l'interface I2 vers la base de données
Afin d'éviter qu'une application écrite pour une base de données B1 doive être réécrite si on migre vers une base de données B2 différente, un effort de normalisation a été fait sur l'interface I1. Si on utilise des bases de données utilisant des pilotes "normalisés", la base B1 sera fournie avec un pilote P1, la base B2 avec un pilote P2, et l'interface I1 de ces deux pilotes sera identique. Aussi n'aura-t-on pas à réécrire l'application. On pourra ainsi, par exemple, migrer une base de données ACCESS vers une base de données MySQL sans changer l'application.
Il existe deux types de pilotes normalisés :
- les pilotes ODBC (Open DataBase Connectivity)
- les pilotes OLE DB (Object Linking and Embedding DataBase)
Les pilotes ODBC permettent l'accès à des bases de données. Les sources de données pour les pilotes OLE DB sont plus variées : bases de données, messageries, annuaires, ... Il n'y a pas de limite. Toute source de données peut faire l'objet d'un pilote Ole DB si un éditeur le décide. L'intérêt est évidemment grand : on a un accès uniforme à une grande variété de données.
La plate-forme .NET 1.1 est livrée avec trois types de classes d'accès aux données :
- les classes SQL Server.NET, pour accéder aux bases SQL Server de Microsoft
- les classes Ole Db.NET, pour accéder aux bases des SGBD offrant un pilote OLE DB
- les classes odbc.net, pour accéder aux bases des SGBD offrant un pilote ODBC
Le SGBD MySQL dispose depuis longtemps d'un pilote ODBC. C'est celui-ci que nous utilisons maintenant. Sous Windows, nous prenons l'option [Menu Démarrer/Panneau de configuration/Outils d'administration/Sources ODBC 32 bits]. Selon la version de windows, ce chemin peut légèrement varier. On obtient l'application suivante qui va nous permettre de créer notre source ODBC :

Nous allons créer une source de données Système, c.a.d. une source de données que tout utilisateur de la machine pourra utiliser.Aussi, ci-dessus sélectionnons-nous l'onglet [Source de données système]. La page présentée a un bouton [Ajouter] que nous utilisons pour créer une nouvelle source de données ODBC :

L'assistant demande de sélectionner le pilote ODBC à utiliser. Windows amène avec lui un certain nombre de pilotes ODBC pré-installés. Le pilote ODBC de MySQL ne fait pas partie du lot. Il faut donc auparavant l'installer. On le trouvera sur internet en tapant la chaîne clé "MySQL ODBC" ou encore "MyODBC" dans un moteur de recherche. Ici, nous avons installé le pilote [MySQL ODBC 3.51]. Nous le sélectionnons et faisons [Terminer] :

Un certain nombre de renseignements doivent être fournis :
|
le nom qui désignera la source de données ODBC. Toute application windows pourra avoir accès à la source via ce nom |
|
un texte arbitraire décrivant la source de données |
|
le nom de la machine hébergeant le SGBD MySQL. Ici c'est la machine locale. Ce pourrait être une machine distante. Cela permettrait à une application windows d'accéder à une base distante sans aucun codage particulier. C'est un grand intérêt de la source ODBC. |
|
un SGBD MySQL peut gérer plusieurs bases. Ici on précise laquelle on veut gérer : dbimpots |
|
nom d'un utilisateur déclaré au sein du SGBD MySQL. C'est sous son nom que se feront les accès à la source de données. Ici : admimpots |
|
le mot de passe de cet utilisateur. Ici : mdpimpots |
|
port de travail du SGBD MySQL. Par défaut c'est le port 3306. Nous ne l'avons pas changé |
Ceci fait, nous testons la validité de nos paramètres de connexion avec le bouton [Test Data Source] :

Ceci fait, nous sommes sûrs de notre source de données ODBC. Nous pouvons désormais l'exploiter. Nous faisons autant de fois que nécessaire [OK] pour sortir de l'assistant ODBC.
Si le lecteur ne dispose pas du SGBD mySQL, il peut se le procurer librement à l'url [http://www.mysql.com]. Nous présentons ci-dessous la démarche pour créer une source ODBC avec Access. Les premières étapes sont identiques à ce qui a été décrit précédemment. On ajoute une nouvelle source de données système :

Le pilote sélectionné sera [Microsoft Access Driver]. On fait [Terminer] pour passer à la définition de la source ODBC :

Les renseignements à fournir sont les suivants :
|
le nom qui désignera la source de données ODBC. Toute application windows pourra avoir accès à la source via ce nom |
|
un texte arbitraire décrivant la source de données |
|
le nom complet du fichier ACCESS à exploiter |
6.2.3. Une nouvelle classe d'accès aux données
Revenons la structure MVC de notre application :

Sur le schéma ci-dessus, la classe [impotsData] est chargée de récupérer les données. Elle devra le faire ici auprès de la base MySQL [dbimpots]. Nous savons depuis la version précédente de cette application, que [impotsData] est une classe abstraite qu'il faut dériver à chaque fois qu'on veut l'adapter à une nouvelle source de données. Rappelons la structure de cette classe abstraite :
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
' méthode d'accès aux données
Public MustOverride Function getData() As Object()
' méthode de vérification des données
Protected Function checkData() As Integer
' vérifie les données acquises
...
End Function
' vérifie la validité du contenu d'un tableau
Protected Function check(ByRef tableau() As Decimal, ByVal n As Integer) As Boolean
...
End Function
End Class
End Namespace
La classe qui dérive [impotsData] doit implémenter deux méthodes :
- un constructeur si le constructeur sans arguments de [impotsData] ne lui convient pas
- la méthode [getData] qui rend les trois tableaux (limites,coeffr,coeffn)
Nous créons la classe [impotsODBC] qui va aller chercher les données (limites,coeffr,coeffn) dans une source ODBC dont on lui donnera le nom :
Imports System.Data.Odbc
Imports System.Data
Imports System.Collections
Imports System
Namespace st.istia.univangers.fr
Public Class impotsODBC
Inherits impotsData
' variables d'instance
Protected DSNimpots As String
' constructeur
Public Sub New(ByVal DSNimpots As String)
' on note les trois informations
Me.DSNimpots = DSNimpots
End Sub
Public Overrides Function getdata() As Object()
' initialise les trois tableaux limites, coeffr, coeffn à partir
' du contenu de la table [impots] de la base ODBC DSNimpots
' limites, coeffr, coeffn sont les trois colonnes de cette table
' peut lancer diverses exceptions
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
' la requête SELECT
Dim selectCommand As String = "select limites,coeffr,coeffn from impots"
' tableaux pour récupérer les données
Dim aLimites As New ArrayList
Dim aCoeffR As New ArrayList
Dim aCoeffN As New ArrayList
Try
' on tente d'accéder à la base de données
impotsConn = New OdbcConnection(connectString)
impotsConn.Open()
' on crée un objet command
sqlCommand = New OdbcCommand(selectCommand, impotsConn)
' on exécute la requête
Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader()
' Exploitation de la table récupérée
While myReader.Read()
' les données de la ligne courante sont mis dans les tableaux
aLimites.Add(myReader("limites"))
aCoeffR.Add(myReader("coeffr"))
aCoeffN.Add(myReader("coeffn"))
End While
' libération des ressources
myReader.Close()
impotsConn.Close()
Catch e As Exception
Throw New Exception("Erreur d'accès à la base de données (" + e.Message + ")")
End Try
' les tableaux dynamiques sont mis dans des tableaux statiques
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
' on vérifie les données acquises
Dim erreur As Integer = checkData()
' si données pas valides, alors on lance une exception
If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
' sinon on rend les trois tableaux
Return New Object() {limites, coeffr, coeffn}
End Function
End Class
End Namespace
Intéressons-nous au constructeur :
' constructeur
Public Sub New(ByVal DSNimpots As String)
' on note les trois informations
Me.DSNimpots = DSNimpots
End Sub
Il reçoit en paramètre, le nom de la source ODBC dans laquelle se trouvent les données à acquérir. Le constructeur se contente de mémoriser ce nom. La méthode [getData] est chargée de lire les données de la table [impots] et de les mettre dans trois tableaux (limites, coeffr, coeffn). Commentons son code :
- les paramètres de la connexion à la source de données ODBC sont définis mais celle-ci n'est pas ouverte
- on définit trois objets [ArrayList] pour récupérer les données de la table [impots] :
' tableaux pour récupérer les données Dim aLimites As New ArrayList Dim aCoeffR As New ArrayList Dim aCoeffN As New ArrayList - Toute le code d'accès à la base est entouré d'un try/catch pour gérer une éventuelle erreur d'accès. On ouvre la connexion avec la base :
' on tente d'accéder à la base de données impotsConn = New OdbcConnection(connectString) impotsConn.Open() - on exécute la commande [select] sur la connexion ouverte. On obtient un objet [OdbcDataReader] qui va nous permettre de parcourir les lignes de la table résultat du select :
' on crée un objet command Dim sqlCommand As OdbcCommand = New OdbcCommand(selectCommand, impotsConn) ' on exécute la requête Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader() - on parcourt la table résultat, ligne par ligne. Pour cela on utilise la méthode [Read] de l'objet [OdbcDataReader] obtenu précédemment. Cette méthode fait deux choses :
- elle avance d'une ligne dans la table. Au départ, on est positionné avant la 1ère ligne
- elle rend le booléen [true] si elle a pu avancer, [false] sinon, ce dernier cas indiquant que toutes les lignes ont été exploitées.
Les colonnes de la ligne courante de l'objet [OdbcDataReader] sont obtenues par [OdbcDataReader](nomColonne). On obtient un objet représentant la valeur de la colonne. Nous parcourons la totalité de la table pour mettre son contenu dans les trois objets [ArrayList] :
' Exploitation de la table récupérée
While myReader.Read()
' les données de la ligne courante sont mis dans les tableaux
aLimites.Add(myReader("limites"))
aCoeffR.Add(myReader("coeffr"))
aCoeffN.Add(myReader("coeffn"))
- ceci fait, nous libérons les ressources associées à la connexion :
- le contenu des trois objets [ArrayList] sont transférées dans trois tableaux classiques :
' les tableaux dynamiques sont mis dans des tableaux statiques 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 - une fois les données de la table [impots] arrivées dans les trois tableaux, il ne reste plus qu'à vérifier le contenu de ceux-ci à l'aide de la méthode [checkData] de la classe de base [impotsData] :
' on vérifie les données acquises Dim erreur As Integer = checkData() ' si données pas valides, alors on lance une exception If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")") ' sinon on rend les trois tableaux Return New Object() {limites, coeffr, coeffn}
6.2.4. Tests de la classe d'accès aux données
Un programme de test pourrait être le suivant :
Option Explicit On
Option Strict On
' espaces de noms
Imports System
Imports Microsoft.VisualBasic
Namespace st.istia.univangers.fr
' pg de test
Module testimpots
Sub Main(ByVal arguments() As String)
' programme interactif de calcul d'impôt
' l'utilisateur tape trois données au clavier : marié nbEnfants salaire
' le programme affiche alors l'impôt à payer
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"
' vérification des paramètres du programme
If arguments.Length <> 1 Then
' msg d'erreur
Console.Error.WriteLine(syntaxe1)
' fin
Environment.Exit(1)
End If
' on récupère les arguments
Dim DSNimpots As String = arguments(0)
' création d'un objet impôt
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
' boucle infinie
While True
' au départ pas d'erreurs
Dim erreur As Boolean = False
' on demande les paramètres du calcul de l'impôt
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()
' qq chose à faire ?
If paramètres Is Nothing Or paramètres = "" Then
Exit While
End If
' vérification du nombre d'arguments dans la ligne saisie
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
' vérification de la validité des paramètres
' marié
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
' salaire
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
' les paramètres sont corrects - on calcule l'impôt
Console.Out.WriteLine(("impôt=" & objImpot.calculer(marié = "o", nbEnfants, salaire).ToString + " euro(s)"))
End If
End While
End Sub
End Module
End Namespace
L'application est lancée avec un paramètre :
- DSNimpots : nom de la source de données ODBC à exploiter
Le calcul de l'impôt est fait à l'aide d'un objet de type [impot] créé dès le lancement de l'application :
' création d'un objet impôt
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
Une fois initialisée, l'application demande de faon répétée à l'utilisateur de taper au clavier les trois informations dont on a besoin pour calculer son impôt :
- son statut marital : o pour marié, n pour non marié
- son nombre d'enfants
- son salaire annuel
L'ensemble des classes est compilé :
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
Le programme de test est compilé à son tour :
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
Le programme de test est exécuté d'abord avec la source de données 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)
On change de source ODBC pour prendre une source Access :
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. Les vues de l'application web
Ce sont celles de l'application précédente : formulaire.aspx et erreurs.aspx
6.2.6. Les contrôleurs d'application [global.asax, main.aspx]
Seul le contrôleur [global.asax] doit être modifié. Il est en effet en charge de créer l'objet [impot] lors du démarrage de l'application. Le constructeur de cet objet a pour unique paramètre l'objet de type [impotsData] chargé de récupérer les données. Ce paramètre change donc pour chaque nouveau type de sources de données. Le contrôleur [global.asax.vb] devient le suivant :
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)
' on crée un objet impot
Dim objImpot As impot
Try
objImpot = New impot(New impotsODBC(ConfigurationSettings.AppSettings("DSNimpots")))
' on met l'objet dans l'application
Application("objImpot") = objImpot
' pas d'erreur
Application("erreur") = False
Catch ex As Exception
'il y a eu erreur, on le note dans l'application
Application("erreur") = True
Application("message") = ex.Message
End Try
End Sub
End Class
La source de données de l'objet [impot] est maintenant un objet [impotODBC]. Ce dernier a pour paramètre le nom DSN de la source de données ODBC à exploiter. Plutôt que d'écrire ce nom en dur dans le code, on le met dans le fichier de configuration [web.config] de l'application :
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="DSNimpots" value="odbc-mysql-dbimpots" />
</appSettings>
</configuration>
On sait que la valeur d'une clé C de la section <appSettings> du fichier [web.config] est obtenue dans le code de l'application par [ConfigurationSettings.AppSettings(C)].
Afin de connaître la cause de l'exception, on enregistre le message de celle-ci dans l'application afin qu'il reste disponible pour les requêtes. Le contrôelir [main.aspx.vb] incluera ce message dans sa liste d'erreurs :
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' avant tout, on regarde si l'application a pu s'initialiser correctement
If CType(Application("erreur"), Boolean) Then
' on redirige vers la page d'erreurs
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
' on récupère l'action à faire
...
6.2.7. Bilan des modifications
L'application est prête à être testée. Listons les modifications amenées à la version précédente :
- une nouvelle classe d'accès aux données a été construite
- le contrôleur [global.asax.vb] a été modifié en deux endroits : construction de l'objet [impot] et enregistrement dans l'application du message lié à l'éventuelle exception
- le contrôleur [main.aspx.vb] a été modifié en un endroit pour afficher le message d'exception précédent
- un fichier [web.config] a été ajouté
Le travail de modification s'est fait essentiellement en 1, c.a.d. en-dehors de l'application web. Ceci a été rendu possible grâce à l'architecture MVC de l'application qui sépare le contrôleur des classes métier. C'est là tout l'intérêt de cette architecture. On pourrait montrer qu'avec un fichier de configuration [web.config] adéquat, on aurait pu éviter toute modification du contrôleur de l'application. Il est possible de mettre dans [web.config] le nom de la classe d'accès aux données à instancier dynamiquement ainsi que les divers paramètres nécessaires à cette instanciation. Avec ces informations, [global.asax] peut instancier l'objet d'accès aux données. Changer de source de données revient alors à :
- créer la classe d'accès à cette source si elle n'existe pas encore
- modifier le fichier [web.config] pour permettre la création dynamique d'une instance de cette classe dans [global.asax]
6.2.8. Test de l'application web
L'ensemble des fichiers précédents sont placés dans un dossier <application-path>.

Dans ce dossier, est créé un sous-dossier [bin] dans lequel est placé l'assemblage [impot.dll] issu de la compilation des fichiers des classes métier : [impots.vb, impotsData.vb, impotsArray.vb, impotsODBC.vb]. On rappelle ci-dessous, la commande de compilation nécessaire :
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
Le fichier [impot.dll] ci-dessus doit être placé dans <application-path>\bin afin que l'application web y ait accès. Le serveur Cassini est lancé avec les paramètres (<application-path>,/impots2). Les tests donnent les mêmes résultats que dans la version précédente, la présence de la base de données étant transparente pour l'utilisateur. Pour illustrer néanmoins cette présence, nous faisons en sorte que la source ODBC ne soit pas disponible en arrêtant le SGBD MySQL et nous demandons l'url [http://localhost/impots2/main.aspx]. Nous obtenons la réponse suivante :

6.3. Exemple 3
6.3.1. Le problème
Nous nous proposons ici de traiter le même problème en modifiant de nouveau la source des données de l'objet [impot] créé par l'application web. Cette fois-ci, la nouvelle source de données sera une base ACCESS à laquelle on accèdera via un pilote OLEDB. Notre objectif est de montrer une autre façon d'accéder à une base de données.
6.3.2. La source de données OLEDB
Les données se trouveront dans une table appelée [IMPOTS] d'une base ACCESS. Le contenu de cette table sera le suivant :

6.3.3. La classe d'accès aux données
Revenons la structure MVC de notre application :

- Sur le schéma ci-dessus, la classe [impotsData] est chargée de récupérer les données. Elle devra le faire cette fois-ci auprès d'une source OLEDB.
Nous créons la classe [impotsOLEDB] qui va aller chercher les données (limites,coeffr,coeffn) dans une source ODBC dont on lui donnera le nom :
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
' variables d'instance
Protected chaineConnexion As String
' constructeur
Public Sub New(ByVal chaineConnexion As String)
' on note les trois informations
Me.chaineConnexion = chaineConnexion
End Sub
Public Overrides Function getData() As Object()
' initialise les trois tableaux limites, coeffr, coeffn à partir
' du contenu de la table [impots] de la base OLEDB [chaineConnexion]
' limites, coeffr, coeffn sont les trois colonnes de cette table
' peut lancer diverses exceptions
' on crée un objet DataAdapter pour lire les données de la source OLEDB
Dim adaptateur As New OleDbDataAdapter("select limites,coeffr,coeffn from impots", chaineConnexion)
' on crée une image en mémoire du résultat du select
Dim contenu As New DataTable("impots")
Try
adaptateur.Fill(contenu)
Catch e As Exception
Throw New Exception("Erreur d'accès à la base de données (" + e.Message + ")")
End Try
' on récupère le contenu de la table impots
Dim lignesImpots As DataRowCollection = contenu.Rows
' on dimensionne les tableaux de réception
Me.limites = New Decimal(lignesImpots.Count - 1) {}
Me.coeffr = New Decimal(lignesImpots.Count - 1) {}
Me.coeffn = New Decimal(lignesImpots.Count - 1) {}
' on transfère le contenu de la table impots dans les tableaux
Dim i As Integer
Dim ligne As DataRow
Try
For i = 0 To lignesImpots.Count - 1
' ligne i de la table
ligne = lignesImpots.Item(i)
' on récupère le contenu de la ligne
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
' on vérifie les données acquises
Dim erreur As Integer = checkData()
' si données pas valides, alors on lance une exception
If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")")
' sinon on rend les trois tableaux
Return New Object() {limites, coeffr, coeffn}
End Function
End Class
End Namespace
Intéressons-nous au constructeur :
' constructeur
Public Sub New(ByVal chaineConnexion As String)
' on note les trois informations
Me.chaineConnexion = chaineConnexion
End Sub
Il reçoit en paramètre, la chaîne de connexion de la source OLEDB dans laquelle se trouvent les données à acquérir. Le constructeur se contente de la mémoriser. Une chaîne de connexion contient tous les paramètres nécessaires au pilote OLEDB pour se connecter à la source OLEDB. Elle est en général assez complexe. Pour découvrir celle des bases ACCESS, on peut se faire aider par l'outil [WebMatrix]. On lance cet outil. Il offre une fenêtre permettant de se connecter à une source de données :
![]()
|
![]()
|
Grâce à l'icone désignée par la flèche ci-dessus, il est possible de créer une connexion à deux types de bases de données Microsoft : SQL Server et ACCESS. Choisissons ACCESS :

Nous avons utilisé le bouton [...] pour désigner la base ACCESS. Nous validons l'assistant. Dans l'onglet [Data], des icônes symbolisent la connexion :

Maintenant, construisons un nouveau fichier .aspx par [Files/New File] :

Nous obtenons une feuille vierge sur laquelle on peut dessiner notre interface web :

Faisons glisser de l'onglet [Data] la table [impots] sur la feuille ci-dessus. Nous obtenons le résultat suivant :

Cliquons droit sur l'objet [AccessDataSourceControl] ci-dessous pour avoir accès à ces propriétés :

La chaîne de connexion OLEDB à la base ACCESS est donnée par la propriété [ConnectionString] ci-dessus :
Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=D:\data\serge\devel\aspnet\poly\chap5\impots\3\impots.mdb
On voit que cette chaîne est formée d'une partie fixe et d'une partie variable qui est tout simplement le nom du fichier ACCESS. Nous utiliserons ce fait pour générer la chaîne de connexion à notre source de données OLEDB.
Revenons maintenant à notre classe [impotsOLEDB]. La méthode [getData] est chargée de lire les données de la table [impots] et de les mettre dans trois tableaux (limites, coeffr, coeffn). Commentons son code :
- on définit l'objet [DataAdapter] qui va nous permettre de transférer en mémoire le résultat d'une requête SQL select. Pour cela, nous définissons la requête [select] à exécuter et l'associons à l'objet [DataAdapter]. Le constructeur de celui-ci réclame également la chaîne de connexion qu'il devra utiliser pour se connecter à la source OLEDB
' on crée un objet DataAdapter pour lire les données de la source OLEDB Dim adaptateur As New OleDbDataAdapter("select limites,coeffr,coeffn from impots", chaineConnexion) - on exécute la commande [select] grâce à la méthode [Fill] de l'objet [DataAdapter]. Le résultat du [select] est injecté dans un objet [DataTable] créé pour l'occasion. Un objet [DataTable] est l'image en mémoire d'une table de base de données, c.a.d. un ensemble de lignes et de colonnes. Nous gérons une exception qui peut survenir si par exemple la chaîne de connexion est incorrecte.
' on crée une image en mémoire du résultat du select Dim contenu As New DataTable("impots") Try adaptateur.Fill(contenu) Catch e As Exception Throw New Exception("Erreur d'accès à la base de données (" + e.Message + ")") End Try - dans [contenu], nous avons la table [impots]ramenée par le [select]. Un objet [DataTable] est une table donc un ensemble de lignes. Celles-ci sont accessibles via la propriété [rows] de [datatable] :
' on récupère le contenu de la table impots Dim lignesImpots As DataRowCollection = contenu.Rows - chaque élément de la collection [lignesImpots] est un objet de type [DataRow] représentant une ligne de la table. Celle-ci a des colonnes accessibles via l'objet [DataRow] via sa propriété [Item]. [DataRow].[Item(i)] est la colonne n° i de la ligne [DataRow]. En parcourant la collection des lignes (la collection DataRows de lignesImpots), et la collections des colonnes de chaque ligne, on est capable d'obtenir la totalité de la table :
' on dimensionne les tableaux de réception Me.limites = New Decimal(lignesImpots.Count - 1) {} Me.coeffr = New Decimal(lignesImpots.Count - 1) {} Me.coeffn = New Decimal(lignesImpots.Count - 1) {} ' on transfère le contenu de la table impots dans les tableaux Dim i As Integer Dim ligne As DataRow Try For i = 0 To lignesImpots.Count - 1 ' ligne i de la table ligne = lignesImpots.Item(i) ' on récupère le contenu de la ligne 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
- une fois les données de la table [impots] arrivées dans les trois tableaux, il ne reste plus qu'à vérifier le contenu de ceux-ci à l'aide de la méthode [checkData] de la classe de base [impotsData] :
' on vérifie les données acquises Dim erreur As Integer = checkData() ' si données pas valides, alors on lance une exception If Not valide Then Throw New Exception("Les données des tranches d'impôts sont invalides (" + erreur.ToString + ")") ' sinon on rend les trois tableaux Return New Object() {limites, coeffr, coeffn}
6.3.4. Tests de la classe d'accès aux données
Un programme de test pourait être le suivant :
Option Explicit On
Option Strict On
' espaces de noms
Imports System
Imports Microsoft.VisualBasic
Namespace st.istia.univangers.fr
' pg de test
Module testimpots
Sub Main(ByVal arguments() As String)
' programme interactif de calcul d'impôt
' l'utilisateur tape trois données au clavier : marié nbEnfants salaire
' le programme affiche alors l'impôt à payer
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"
' vérification des paramètres du programme
If arguments.Length <> 1 Then
' msg d'erreur
Console.Error.WriteLine(syntaxe1)
' fin
Environment.Exit(1)
End If
' on récupère les arguments
Dim chemin As String = arguments(0)
' on prépare la chaîne de connexion
Dim chaineConnexion As String = "Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=" + chemin
' création d'un objet impôt
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
' boucle infinie
While True
' au départ pas d'erreurs
Dim erreur As Boolean = False
' on demande les paramètres du calcul de l'impôt
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()
' qq chose à faire ?
If paramètres Is Nothing Or paramètres = "" Then
Exit While
End If
' vérification du nombre d'arguments dans la ligne saisie
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
' vérification de la validité des paramètres
' marié
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
' salaire
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
' les paramètres sont corrects - on calcule l'impôt
Console.Out.WriteLine(("impôt=" & objImpot.calculer(marié = "o", nbEnfants, salaire).ToString + " euro(s)"))
End If
End While
End Sub
End Module
End Namespace
L'application est lancée avec un paramètre :
- bdACCESS : nom du fichier ACCESS à exploiter
Le calcul de l'impôt est fait à l'aide d'un objet de type [impot] créé dès le lancement de l'application :
' on récupère les arguments
Dim chemin As String = arguments(0)
' on prépare la chaîne de connexion
Dim chaineConnexion As String = "Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=" + chemin
' création d'un objet impôt
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
La chaîne de connexion à la source OLEDB a été construite à partir des informations obtenues avec [WebMatrix].
Une fois initialisée, l'application demande de façon répétée à l'utilisateur de taper au clavier les trois informations dont on a besoin pour calculer son impôt :
- son statut marital : o pour marié, n pour non marié
- son nombre d'enfants
- son salaire annuel
L'ensemble des classes est compilé :
dos>vbc /r:system.dll /r:system.data.dll /t:library /out:impot.dll impots.vb impotsArray.vb impotsData.vb impotsOLEDB.vb
Le fichier [impots.mdb] est placé dans le dossier de l'application de test et celle-ci est lancée de la façon suivante :
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)
On peut lancer l'application avec un fichier ACCESS incorrect :
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. Les vues de l'application web
Ce sont celles de l'application précédente : formulaire.aspx et erreurs.aspx
6.3.6. Les contrôleurs d'application [global.asax, main.aspx]
Seul le contrôleur [global.asax] doit être modifié. Il est en effet en charge de créer l'objet [impot] lors du démarrage de l'application. Le constructeur de cet objet a pour unique paramètre l'objet de type [impotsData] chargé de récupérer les données. Ce paramètre change donc puisqu'on change de sources de données. Le contrôleur [global.asax.vb] devient le suivant :
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)
' on crée un objet impot
Dim objImpot As impot
Try
objImpot = New impot(New impotsOLEDB(ConfigurationSettings.AppSettings("chaineConnexion")))
' on met l'objet dans l'application
Application("objImpot") = objImpot
' pas d'erreur
Application("erreur") = False
Catch ex As Exception
'il y a eu erreur, on le note dans l'application
Application("erreur") = True
Application("message") = ex.Message
End Try
End Sub
End Class
La source de données de l'objet [impot] est maintenant un objet [impotOLEDB]. Ce dernier a pour paramètre la chaîne de connexion de la source de données OLEDB à exploiter. Celle-ci est placée dans le fichier de configuration [web.config] de l'application :
<?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>
Le contrôleur [main.aspx] ne change pas.
6.3.7. Bilan des modifications
L'application est prête à être testée. Listons les modifications amenées à la version précédente :
- une nouvelle classe d'accès aux données a été construite
- le contrôleur [global.asax.vb] a été modifié en un endroit : construction de l'objet [impot]
- un fichier [web.config] a été ajouté
6.3.8. Test de l'application web
L'ensemble des fichiers précédents sont placés dans un dossier <application-path>.

Dans ce dossier, est créé un sous-dossier [bin] dans lequel est placé l'assemblage [impot.dll] issu de la compilation des fichiers des classes métier : [impots.vb, impotsData.vb, impotsArray.vb, impotsOLEDB.vb]. On rappelle ci-dessous, la commande de compilation nécessaire :
dos>vbc /r:system.dll /r:system.data.dll /t:library /out:impot.dll impots.vb impotsArray.vb impotsData.vb impotsOLEDB.vb
Le fichier [impot.dll] produit par cette commande doit être placé dans <application-path>\bin afin que l'application web y ait accès. Le serveur Cassini est lancé avec les paramètres (<application-path>,/impots3). Les tests donnent les mêmes résultats que dans la version précédente.
6.4. Exemple 4
6.4.1. Le problème
Nous nous proposons maintenant de transformer notre application en application de simulation de calculs d'impôt. Un utilisateur pourra faire des calculs successifs d'impôts et ceux-ci lui seront présentés sur une nouvelle vue ressemblant à ceci :

6.4.2. La structure MVC de l'application
La structure MVC de l'application devient la suivante :

Apparaît une nouvelle vue [simulations.aspx] dont nous venons de donner une copie d'écran. La classe d'accès aux données sera la classe [impotsODBC] de l'exemple 2.
6.4.3. Les vues de l'application web
La vue [erreurs.aspx] ne change pas. La vue [formulaire.aspx] change légèrement. En effet, le montant de l'impôt n'apparaît désormais plus sur cette vue. Il est maintenant sur la vue [simulations.aspx]. Ainsi au démarrage, la page présentée à l'utilisateur est la suivante :

Par ailleurs, la vue [formulaire] transporte avec elle un script javascript qui vérifie la validité des données saisies avant de les envoyer au serveur comme le montre l'exemple suivant :

Le code de présentation est le suivant :
<%@ page src="formulaire.aspx.vb" inherits="formulaire" AutoEventWireup="false"%>
<html>
<head>
<title>Impôt</title>
<script language="javascript">
function calculer(){
// vérification des paramètres avant de les envoyer au serveur
with(document.frmImpots){
//nbre d'enfants
champs=/^\s*(\d+)\s*$/.exec(txtEnfants.value);
if(champs==null){
// le modéle n'est pas vérifié
alert("Le nombre d'enfants n'a pas été donné ou est incorrect");
txtEnfants.focus();
return;
}//if
//salaire
champs=/^\s*(\d+)\s*$/.exec(txtSalaire.value);
if(champs==null){
// le modéle n'est pas vérifié
alert("Le salaire n'a pas été donné ou est incorrect");
txtSalaire.focus();
return;
}//if
// c'est bon - on envoie le formulaire au serveur
submit();
}//with
}//calculer
</script>
</head>
<body>
<P>Calcul de votre impôt</P>
<HR width="100%" SIZE="1">
<form name="frmImpots" method="post" action="main.aspx?action=calcul">
<TABLE border="0">
<TR>
<TD>Etes-vous marié(e)</TD>
<TD>
<INPUT type="radio" value="oui" name="rdMarie" <%=rdouichecked%>>Oui
<INPUT type="radio" value="non" name="rdMarie" <%=rdnonchecked%>>Non</TD>
</TR>
<TR>
<TD>Nombre d'enfants</TD>
<TD><INPUT type="text" size="3" maxLength="3" name="txtEnfants" value="<%=txtEnfants%>"></TD>
</TR>
<TR>
<TD>Salaire annuel (euro)</TD>
<TD><INPUT type="text" maxLength="12" size="12" name="txtSalaire" value="<%=txtSalaire%>"></TD>
</TR>
</TABLE>
<hr>
<P>
<INPUT type="button" value="Calculer" onclick="calculer()">
</P>
</form>
<form method="post" action="main.aspx?action=effacer">
<INPUT type="submit" value="Effacer">
</form>
</body>
</html>
Les champs dynamiques de la page sont ceux des versions précédentes. Le champ dynamique du montant de l'impôt a disparu. Le bouton [Calculer] n'est plus un bouton de type [submit]. Il est de type [button] et lorsqu'il est cliqué, la fonction javascript [calculer()] est exécutée :
<INPUT type="button" value="Calculer" onclick="calculer()">
On a donné au formulaire un nom [frmImpots] afin de pouvoir le référencer dans le script [calculer] :
<form name="frmImpots" method="post" action="main.aspx?action=calcul">
La fonction javascript [calculer] utilise des expression régulières pour vérifier la validité des champs du formulaire [document.frmImpots.txtEnfants] et [document.frmImpots.txtSalaire]. Si les valeurs saisies sont correctes, elles sont envoyées au serveur par [document.frmImpots.submit()].
La page de présentation obtient ses champs dynamiques auprès de son contrôleur [formulaire.aspx.vb] suivant :
Imports System.Collections.Specialized
Public Class formulaire
Inherits System.Web.UI.Page
' champs de la page
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
' on récupère la précédente requête dans le contexte
Dim form As NameValueCollection = Context.Items("formulaire")
' on prépare la page à afficher
' boutons radio
rdouichecked = ""
rdnonchecked = "checked"
If form("rdMarie").ToString = "oui" Then
rdouichecked = "checked"
rdnonchecked = ""
End If
' le reste
txtEnfants = CType(form("txtEnfants"), String)
txtSalaire = CType(form("txtSalaire"), String)
End Sub
End Class
Le contrôleur [formulaire.aspx.vb] est identique aux versions précédentes si ce n'est qu'il n'a plus à récupérer le champ [txtImpot] dans le contexte, ce champ ayant disparu de la page.
La vue [simulations.aspx] se présente visuellement comme suit :

et correspond au code de présentation suivant :
<%@ 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>
Ce code présente trois champs dynamiques :
|
code HTML d'une liste de simulations sous la forme de lignes de table HTML |
|
url d'un lien |
|
texte du lien |
Ils sont générés par la partie contrôleur [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
'on récupère les simulations dans le contexte
Dim simulations As ArrayList = CType(context.Items("simulations"), ArrayList)
' chaque simulation est un tableau de 4 éléments string
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
' on récupère les autres éléments du contexte
href = context.Items("href").ToString
lien = context.Items("lien").ToString
End Sub
End Class
Le contrôleur de la page récupère des informations placées par le contrôleur de l'application dans le contexte de la page :
|
objet ArrayList contenant la liste des simulations à afficher. Chaque élément est un tableau de 4 chaînes de caractères représentant les informations (marié, enfants, salaire, impot) de la simulation. |
|
url d'un lien |
|
texte du lien |
6.4.4. Les contrôleurs [global.asax, main.aspx]
Rappelons le schéma MVC de notre application :

Le contrôleur [main.aspx] a à traiter trois actions :
- init : correspond à la première requête du client. Le contrôleur affiche la vue [formulaire.aspx]
- calcul : correspond à la demande de calcul de l'impôt. Si les données du formulaire de saisie sont correctes, l'impôt est calculé grâce à la classe métier [impotsODBC]. Le contrôleur retourne au client la vue [simulations.aspx] avec le résultat de la simulation courante plus toutes les précédentes. Si les données du formulaire de saisie sont incorrectes, le contrôleur retourne la vue [erreurs.aspx] avec la liste des erreurs et un lien pour retourner au formulaire.
- retour : correspond au retour au formulaire après une erreur. Le contrôleur affiche la vue [formulaire.aspx] telle qu'elle a été validée avant l'erreur.
Dans cette nouvelle version, seule l'action [calcul] a changé. En effet, si les données sont valides, elle doit aboutir à la vue [simulations.aspx] alors qu'auparavant elle aboutisait à la vue [formulaire.aspx]. Le contrôleur [main.aspx.vb] devient le suivant :
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
' avant tout, on regarde si l'application a pu s'initialiser correctement
...
' on exécute l'action
Select Case action
Case "init"
' init application
initAppli()
Case "calcul"
' calcul impot
calculImpot()
Case "retour"
' retour au formulaire
retourFormulaire()
Case "effacer"
' init application
initAppli()
Case Else
' action inconnue = init
initAppli()
End Select
End Sub
...
Private Sub calculImpot()
' on sauvegarde les saisies
Session.Item("formulaire") = Request.Form
' on vérifie la validité des données saisies
Dim erreurs As ArrayList = checkData()
' s'il y a des erreurs, on le signale
If erreurs.Count <> 0 Then
' on prépare la page d'erreurs
context.Items("href") = "main.aspx?action=retour"
context.Items("lien") = "Retour au formulaire"
context.Items("erreurs") = erreurs
Server.Transfer("erreurs.aspx")
End If
' ici pas d'erreurs - on calcule l'impôt
Dim impot As Long = CType(Application("objImpot"), impot).calculer( _
Request.Form("rdMarie") = "oui", _
CType(Request.Form("txtEnfants"), Integer), _
CType(Request.Form("txtSalaire"), Long))
' on rajoute le résultat aux simulations existantes
Dim simulations As ArrayList
If Not Session.Item("simulations") Is Nothing Then
simulations = CType(Session.Item("simulations"), ArrayList)
Else
simulations = New ArrayList
End If
' ajout de la simulation courante
Dim simulation() As String = New String() {Request.Form("rdMarie").ToString, _
Request.Form("txtEnfants").ToString, Request.Form("txtSalaire").ToString, _
impot.ToString}
simulations.Add(simulation)
' on met les simulations dans la session et le contexte
context.Items("simulations") = simulations
Session.Item("simulations") = simulations
' on affiche la page de résultat
context.Items("href") = "main.aspx?action=retour"
context.Items("lien") = "Retour au formulaire"
Server.Transfer("simulations.aspx", True)
End Sub
...
End Class
Nous n'avons conservé ci-dessus que ce qui était nécessaire pour comprendre les modifications qui se trouvent uniquement dans la fonction [calculImpots] :
- tout d'abord la fonction sauve le formulaire [Request.Form] dans la session, ceci afin de pouvoir régénérer le formulaire dans l'état où il a été validé. C'est à faire dans tous les cas, puisque que l'opération aboutisse sur la réponse [erreurs.aspx] ou sur la réponse [simulations.aspx], on revient au formulaire par le lien [Retour au formulaire]. Pour restituer correctement celui-ci, il faut avoir sauvegardé ses valeurs auparavant dans la session.
- si les données saisies s'avèrent correctes, la fonction met la simulation courante (marié, enfants, salaire, impôt) dans la liste des simulations. Celle-ci est trouvée dans la session associée à la clé "simulations".
- la liste des simulations est replacée dans la session pour un usage futur. Elle est également placée dans le contexte courant car c'est là que l'attend la vue [simulations.aspx]
- la vue [simulations.aspx] est affichée une fois mises dans le contextes les autres informations qu'elle attend
6.4.5. Bilan des modifications
L'application est prête à être testée. Listons les modifications amenées aux versions précédentes :
- une nouvelle vue a été construite
- le contrôleur [main.aspx.vb] a été modifié en un endroit : traitement de l'action [calcul]
6.4.6. Test de l'application web
Le lecteur est invité à faire les tests. On rappelle la démarche. L'ensemble des fichiers de l'application sont placés dans un dossier <application-path>. Dans ce dossier, est créé un sous-dossier [bin] dans lequel est placé l'assemblage [impot.dll] issu de la compilation des fichiers des classes métier : [impots.vb, impotsData.vb, impotsArray.vb, impotsODBC.vb. Le fichier [impot.dll] produit par cette commande doit être placé dans <application-path>\bin afin que l'application web y ait accès. Le serveur Cassini est lancé avec les paramètres (<application-path>,/impots4).
6.5. Conclusion
Les exemples précédents ont montré sur un cas concret des mécanismes couramment utilisés dans le développement web. Nous avons utilisé systématiquement l'architecture MVC pour son intérêt pédagogique. On aurait pu traiter ces mêmes exemples différemment et peut-être plus simplement sans cette architecture. Mais celle-ci apporte de grands avantages dès que l'application devient un peu complexe avec de multiples pages.
Nous pourrions poursuivre nos exemples de différentes façons. En voici quelques unes :
- l'utilisateur pourrait vouloir garder ses simulations au fil du temps. Il ferait des simulations un jour J et pourrait les retrouver le jour J+3 par exemple. Une solution possible à ce problème est l'utilisation de cookies. Nous savons que le jeton de session entre le serveur et un client est transporté par ce mécanisme. On pourrait utiliser également celui-ci pour transporter les simulations entre le client et le serveur.
- en même temps que le serveur envoie la page résultat des simulations, il envoie dans ses entêtes HTTP un cookie contenant une chaîne de caractères représentant les simulations. Comme celles-ci sont dans un objet [ArrayList] il y a un travail de transformation de cet objet en [String] à opérer. Le serveur donnerait au cookie une durée de vie, par exemple 30 jours.
- le navigateur client stocke les cookies reçus dans un fichier et les renvoie à chaque fois qu'il fait une requête à un serveurqui les lui a envoyés s'ils sont encore valides (durée de vie pas dépassée). Le serveur recevra pour les simulations une chaîne de caractères [String] qu'il devra transformer en objet [ArrayList].
Les cookies sont gérés par [Response.Cookies] lors de leur envoi vers le client et par [Request.Cookies] lors de leur réception sur le serveur.
- le mécanisme précédent peut devenir assez lourd si il y a un nombre important de simulations. Par ailleurs, il est fréquent qu'un utilisateur nettoie périodiquement ses cookies en les supprimant tous même si par ailleurs il autorise son navigateur à les utiliser. Donc un jour ou l'autre on perdra le cookie des simulations. On peut vouloir alors stocker celles-ci sur le serveur plutôt que sur le client, dans une base de données par exemple. Pour lier des simulations à un utilisateur particulier, l'application pourrait démarrer par une phase d'identification réclamant un login et un mot de passe eux-mêmes stockés dans une base de données ou tout autre type de dépôt de données.
- nous pourrions également vouloir sécuriser le fonctionnement de notre application. Celle-ci fait actuellement deux hypothèses :
- l'utilisateur passe toujours par le contrôleur [main.aspx]
- et dans ce cas, il utilise toujours les actions proposées dans la page qu'on lui a envoyée
Que se passe-t-il par exemple, si l'utilisateur demande directement l'url [http://localhost/impots4/formulaire.aspx] ? Ce cas est peu propable puisque l'utilisateur ne connaît pas l'existence de cette url. Cependant il doit être prévu. Il peut être géré par le contrôleur d'application [global.asax] qui voit passer toutes les requêtes faites à l'application. il peut ainsi vérifier que la ressource demandée est bien [main.aspx].
Un cas plus probable est qu'un utilisateur n'utilise pas les actions présentes sur la page que le serveur lui a envoyée. Par exemple, que se passe-t-il si l'utilisateur demande directement l'url [http://localhost/impots4/main.aspx?action=retour] sans passer auparavant par le remplissage du formulaire ? Essayons. Nous obtenons la réponse suivante :

On a un plantage du serveur. C'est normal. Pour l'action [retour] le contrôleur s'attend à trouver dans la session un objet [NameValueCollection] représentant les valeurs du formulaire qu'il doit afficher. Il ne les trouve pas. Le mécanisme du contrôleur permet de donner une solution élégante à ce problème. Pour chaque requête, le contrôleur [main.aspx] peut vérifier que l'action demandée est bien l'une des actions de la page précédemment envoyée à l'utilisateur. On peut utiliser le mécanisme suivant :
- le contrôleur avant d'envoyer sa réponse au client, stocke dans la session de celui-ci une information identifiant cette page
- lorsqu'il reçoit une nouvelle demande du client, il vérifie que l'action demandée appartient bien à la dernière page envoyée à ce client
- les informations liant pages et actions autorisées dans ces pages peuvent être introduites dans le fichier de configuration [web.config] de l'application.
- l'expérience montre que les contrôleurs d'applications ont une large base commune et qu'il est possible de construire un contrôleur générique, la spécialisation de celui-ci pour une application donnée se faisant via un fichier de configuration. C'est la voie suivie par exemple, par l'outil [Struts] dans le domaine de la programmation web en Java.

