Skip to content

8. Composants serveur ASP - 2

8.1. Introduction

Nous continuons notre travail sur l'interface utilisateur en découvrant :

  • des composants de validation de données
  • la façon de lier des données à des composants serveur
  • les composants serveur HTML

8.2. Les composants de validation de données

8.2.1. Introduction

Dans des applications à formulaires, il est primordial de vérifier la validité des données saisies dans ceux-ci. Dans l'exemple sur le calcul d'impôt, nous avions le formulaire suivant :

Image

A réception des valeurs de ce formulaire, il nous faut vérifier la validité des données saisies : le nombre d'enfants et le salaire doivent être des entiers positifs ou nuls. Si ce n'est pas le cas, le formulaire est renvoyé au client tel qu'il a été saisi avec des messages indiquant les erreurs. Nous avions traité ce cas avec deux vues, l'une pour le formulaire ci-dessus et l'autre pour les erreurs :

Image

ASP.NET offre des composants appelés composants de validation qui permettent de vérifier les cas suivants :

Composant

Rôle


RequiredFieldValidator

vérifie qu'un champ est non vide


CompareValidator

vérifie deux valeurs entre-elles


RangeValidator

vérifie q'une valeur est entre deux bornes


RegularExpressionValidator

vérifie qu'un champ vérifie une expression régulière


CustomValidator

permet au développeur de donner ses propres règles de validation - ce composant pourrait remplacer tous les autres


ValidationSummary

permet de rassembler les messages d'erreurs émis par les contrôles précédents en un unique endroit de la page

Nous allons maintenant présenter chacun de ces composants.

8.2.2. RequiredFieldValidator

Nous construisons la page [requiredfieldvalidator1.aspx] suivante :

Image

nom

type

propriétés

rôle

1


txtNom

TextBox

EnableViewState=true

champ de saisie

2


RequiredFieldValidator1

RequiredFieldValidator

EnableViewState=false

EnableClientScript=true

ErrorMessage=Le champ [nom] est obligatoire

composant de validation

3


btnEnvoyer

Button

EnableViewState=false

CausesValidation=true

bouton [submit]

Le champ [RequiredFieldValidator1] sert à afficher un message d'erreur si le champ [txtNom] est vide ou contient une suite d'espaces. Ses propriétés sont les suivantes :


ControlToValidate

champ dont la valeur doit être contrôlée par le composant. Que signifie la valeur d'un composant ? C'est la valeur de l'attribut [value] de la balise HTML correspondante. Pour un [TextBox] ce sera le contenu du champ de saisie, pour un [DropDownList] la valeur de l'élément sélectionné.


EnableClientScript

booléen - à vrai indique que le contenu du champ précédent doit être contrôlé également côté client. Dans ce cas, le formulaire ne sera posté par le navigateur que si le formulaire ne comporte pas d'erreurs. Néanmoins, les tests de validation sont faits également sur le serveur pour le cas où le client ne serait pas un navigateur par exemple.


ErrorMessage

le message d'erreur que le composant doit afficher en cas d'erreur détectée

La vérification des données de la page par les contrôles de validation n'est faite que si le bouton ou le lien ayant provoqué le [POST] de la page a la propriété [CausesValidation=true]. [true] est la valeur par défaut de cette propriété.

Exécutons cette application. Nous utilisons tout d'abord un navigateur [Mozilla] :

Image

Le code HTML reçu par [Mozilla] est le suivant :

<html>
<head>
</head>
<body>
    <form name="_ctl0" method="post" action="requiredfieldvalidator1.aspx" id="_ctl0">
<input type="hidden" name="__VIEWSTATE" value="dDwxNDI1MDc1NTU1Ozs+SGtdZvVxefDCDxnsqbDnqCaROsk=" />

        <p>
            Demande du dossier de candidature au DESS 
        </p>
        <fieldset>
            <legend>[--Identité--]</legend>

            <table>
                <tbody>
                    <tr>
                        <td>
                            Nom*</td>
                        <td>
                            <input name="txtNom" type="text" id="txtNom" />
                            &nbsp;
                        </td>
                    </tr>
                </tbody>
            </table>
        </fieldset>
        <p>
            <input type="submit" name="btnEnvoyer" value="Envoyer" onclick="if (typeof(Page_ClientValidate) == 'function') Page_ClientValidate(); " language="javascript" id="btnEnvoyer" />
        </p>
    </form>

</body>
</html>

On voit que le bouton [btnEnvoyer] est lié à une fonction Javascript via son attribut [onclick]. On peut remarquer également que la page ne contient aucun code [Javascript]. Le test [typeof(Page_ClientValidate) == 'function'] va échouer et la fonction javascript ne sera pas appelée. Le formulaire sera alors posté au serveur et c'est lui qui fera les contrôles de validité. Utilisons le bouton [Envoyer] sans mettre de nom. La réponse du serveur est la suivante :

Image

Que s'est-il passé ? Le formulaire a été posté au serveur. Celui-ci a fait exécuter le code de tous les composants de validation se trouvant sur la page. Ici il n'y en a qu'un. Si au moins un composant de validation détecte une erreur, le serveur renvoie le formulaire tel qu'il a été saisi (en fait pour les composants ayant [EnableViewState=true], avec de plus un message d'erreur pour chaque composant de validation ayant détecté une erreur. Ce message est l'attribut [ErrorMessage] du composant de validation. C'est ce mécanisme que nous voyons à l'oeuvre ci-dessus. Si nous mettons quelque chose dans le champ [nom], le message d'erreur n'apparaît plus lorsqu'on valide le formulaire :

Image

Maintenant, utilisons Internet Explorer et demandons l'url [http://localhost/requiredfieldvalidator1.aspx]. Nous obtenons la page suivante :

Image

Le code HTML reçu par IE est le suivant :

<html>
<head>
</head>
<body>
    <form name="_ctl0" method="post" action="requiredfieldvalidator1.aspx" language="javascript" onsubmit="ValidatorOnSubmit();" id="_ctl0">
<input type="hidden" name="__VIEWSTATE" value="dDwxNDI1MDc1NTU1Ozs+SGtdZvVxefDCDxnsqbDnqCaROsk=" />

<script language="javascript" src="/aspnet_client/system_web/1_1_4322/WebUIValidation.js"></script>


        <p>
            Demande du dossier de candidature au DESS 
        </p>
        <fieldset>
            <legend>[--Identité--]</legend>
            <table>
                <tbody>
                    <tr>
                        <td>
                            Nom*</td>
                        <td>
                            <input name="txtNom" type="text" id="txtNom" />
                            <span id="RequiredFieldValidator1" controltovalidate="txtNom" errormessage="Le champ [nom] est obligatoire" evaluationfunction="RequiredFieldValidatorEvaluateIsValid" initialvalue="" style="color:Red;visibility:hidden;">Le champ [nom] est obligatoire</span>
                        </td>
                    </tr>
                </tbody>
            </table>
        </fieldset>
        <p>
            <input type="submit" name="btnEnvoyer" value="Envoyer" onclick="if (typeof(Page_ClientValidate) == 'function') Page_ClientValidate(); " language="javascript" id="btnEnvoyer" />
        </p>

<script language="javascript">
<!--
    var Page_Validators =  new Array(document.all["RequiredFieldValidator1"]);
        // -->
</script>


<script language="javascript">
<!--
var Page_ValidationActive = false;
if (typeof(clientInformation) != "undefined" && clientInformation.appName.indexOf("Explorer") != -1) {
    if (typeof(Page_ValidationVer) == "undefined")
        alert("Impossible de trouver la bibliothèque de scripts /aspnet_client/system_web/1_1_4322/WebUIValidation.js. Essayez de placer ce fichier manuellement ou effectuez une réinstallation en exécutant 'aspnet_regiis -c'.");
    else if (Page_ValidationVer != "125")
        alert("Cette page utilise une version incorrecte de WebUIValidation.js. La page requiert la version 125. La bibliothèque de scripts est " + Page_ValidationVer + ".");
    else
        ValidatorOnLoad();
}

function ValidatorOnSubmit() {
    if (Page_ValidationActive) {
        ValidatorCommonOnSubmit();
    }
}
// -->
</script>


        </form>
</body>
</html>

On voit que ce code est beaucoup plus important que celui reçu par [Mozilla]. Nous n'entrerons pas dans les détails. La différence vient du fait que le serveur a inclus des fonctions javascript dans le document HTML envoyé. Pourquoi deux codes HTML différents alors que l'url demandée par les deux navigateurs est la même ? Nous avons eu l'occasion de dire que la technologie ASP.NET faisait que le serveur adaptait le document HTML envoyé au client au niveau de ce client. Il existe sur le marché différents navigateurs n'ayant pas tous les mêmes capacités. Les navigateurs Microsoft et Netscape par exemple, n'utilisent pas la même modélisation objet pour le document qu'ils reçoivent. Aussi le code Javascript pour manipuler ce document côté navigateur présente-t-il des différences entre les deux navigateurs. De même, les éditeurs ont créé des versions successives de leur navigateur améliorant sans cesse leurs capacités. Aussi un document HTML compris par IE5 peut ne pas l'être par IE3. Nous avons ci-dessus un exemple de cette adaptation du serveur au niveau de son client. Pour une raison qui n'a pas été approfondie ici, le serveur web a estimé que le client [Mozilla] n'avait pas la capacité de gérer le code javascript de validation. Aussi ce code n'a-t-il pas été placé dans le document HTML qui lui a été envoyé et la validation s'est faite côté serveur. Pour [IE6], ce code javascript a été inclus dans le document HTML envoyé, comme nous pouvons le voir. Pour le voir à l'oeuvre, faisons l'expérience suivante :

  • arrêtons le serveur web
  • utilisons le bouton [Envoyer] sans remplir le champ [nom]

Nous obtenons la nouvelle page suivante :

Image

C'est bien le navigateur qui a placé le message d'erreur. En effet, le serveur est arrêté. On peut s'en assurer en mettant quelque chose dans le champ [nom] et en validant. Cette fois la réponse est la suivante :

Image

Que s'est-il passé ? Le navigateur a exécuté le code Javascript de validation. Ce code a dit que la page était valide. Le navigateur a donc posté le formulaire au serveur web qui était arrêté. Le navigateur s'en est aperçu et a affiché la page ci-dessus. Si on relance le serveur, tout redevient normal.

Que retenir de cet exemple ?

  • l'intérêt de la notion de composant de validation qui permet de renvoyer au client tout formulaire incorrect
  • l'intérêt du [VIEWSTATE] qui permet de renvoyer ce formulaire tel qu'il a été saisi
  • la faculté d'adaptation du serveur à son client. Celui-ci est identifié par l'entête HTTP [User-Agent:] qu'il envoie au serveur. Ce n'est donc pas le serveur qui "devine" à qui il a affaire. Cette faculté d'adaptation présente un grand intérêt pour le développeur qui n'a pas à se préoccuper du type de client de son application.

8.2.3. CompareValidator

Nous construisons la page [comparevalidator1.aspx] suivante :

Image

nom

type

propriétés

rôle

1


cmbChoix1

DropDownList

EnableViewState=true

liste déroulante

2


cmbChoix2

DropDownList

EnableViewState=true

liste déroulante

3


CompareValidator1

CompareValidator

EnableViewState=false

EnableClientScript=true

ErrorMessage=Choix 1 invalide

ValeurToCompare=?

Operator=NotEqual

Type=string

composant de validation

4


CompareValidator2

CompareValidator

EnableViewState=false

EnableClientScript=true

ErrorMessage=Choix 2 invalide

ControlToCompare=?

Operator=NotEqual

Type=string

composant de validation

5


btnEnvoyer

Button

EnableViewState=false

CausesValidation=true

bouton [submit]

Le composant [CompareValidator] sert à comparer deux valeurs entre-elles. Les opérateurs utilisables sont [Equal, NotEqual, LessThan, LessThanEqual, GreaterThan, GreaterThanEqual]. La première valeur est fixée par la propriété [ControlToValidate], la seconde par [ValueToCompare] si la valeur précédente doit être comparée à une constante, ou par [ControlToCompare] si elle doit être comparée à la valeur d'un autre composant. Les propriétés importantes du composant [CompareValidator] sont les suivantes :


ControlToValidate

champ dont le contenu doit être contrôlé par le composant


EnableClientScript

booléen - à vrai indique que le contenu du champ précédent doit être contrôlé également côté client


ErrorMessage

le message d'erreur que le composant doit afficher en cas d'erreur détectée


ValeurToCompare

valeur à laquelle doit être comparée la valeur du champ [ControlToValidate]


ControlToCompare

composant à la valeur duquel doit être comparée la valeur du champ [ControlToValidate]


Operator

opérateur de comparaison entre les deux valeurs


Type

type des valeurs à comparer

Le code de présentation de cette page est le suivant :

<html>
<head>
</head>
<body>
    <form runat="server">
        <p>
            Demande du dossier de candidature au DESS 
        </p>
        <fieldset>
            <legend>[--Options du DESS pour lesquelles vous candidatez--]</legend>
            <table>
                <tbody>
                    <tr>
                        <td>
                            Option1*</td>
                        <td>
                            Option2</td>
                    </tr>
                    <tr>
                        <td>
                            <asp:DropDownList id="cmbChoix1" runat="server">
                                <asp:ListItem Value="?" Selected="True">?</asp:ListItem>
                                <asp:ListItem Value="Automatique">Automatique</asp:ListItem>
                                <asp:ListItem Value="Informatique">Informatique</asp:ListItem>
                            </asp:DropDownList>
                        </td>
                        <td>
                            <asp:DropDownList id="cmbChoix2" runat="server">
                                <asp:ListItem Value="?">?</asp:ListItem>
                                <asp:ListItem Value="Automatique">Automatique</asp:ListItem>
                                <asp:ListItem Value="Informatique">Informatique</asp:ListItem>
                            </asp:DropDownList>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            <asp:CompareValidator id="CompareValidator1" runat="server" ErrorMessage="Choix 1 invalide" ControlToValidate="cmbChoix1" ValueToCompare="?" Operator="NotEqual"></asp:CompareValidator>
                        </td>
                        <td>
                            <asp:CompareValidator id="CompareValidator2" runat="server" ErrorMessage="Choix 2 invalide" ControlToValidate="cmbChoix2" Operator="NotEqual" ControlToCompare="cmbChoix1"></asp:CompareValidator>
                        </td>
                    </tr>
                </tbody>
            </table>
        </fieldset>
        <p>
            <asp:Button id="btnEnvoyer" runat="server" Text="Envoyer"></asp:Button>
        </p>
    </form>
</body>
</html>

Les contraintes de validation de la page sont les suivantes :

  • la valeur sélectionnée dans [cmbChoix1] doit être différente de la chaîne "?". Ceci implique les propriétés suivantes pour le composant [CompareValidator1] : Operator=NotEqual, ValueToCompare=?, Type=string
  • la valeur sélectionnée dans [cmbChoix2] doit être différente de celle sélectionnée dans [cmbChoix1]. Ceci implique les propriétés suivantes pour le composant [CompareValidator2] : Operator=NotEqual, ControlToCompare=cmbChoix1, Type=string

Nous exécutons cette application. Voici un exemple de page reçue au cours des échanges client-serveur :

Image

Si la même page est demandée par Internet Explorer 6, du code Javascript est inclus dans celle-ci qui amène à un fonctionnement légèrement différent. Les éventuelles erreurs sont signalées dès que l'utilisateur change de valeur dans l'une des listes déroulantes. C'est une amélioration du confort de l'utilisateur.

8.2.4. CustomValidator, RangeValidator

Nous construisons la page [customvalidator1.aspx] suivante :

Image

nom

type

propriétés

rôle


1

cmbDiplomes

DropDownList

EnableViewState=true

liste déroulante


2

CompareValidator2

CompareValidator

EnableViewState=false

EnableClientScript=true

ErrorMessage=Diplôme invalide

Operator=NotEqual

ValueToCompare=?

Type=String

composant de validation du contrôle [1]


3

txtAutreDiplome

TextBox

EnableViewState=false

champ de saisie


4

CustomValidator1

CustomValidator

EnableViewState=false

EnableClientScript=true

ErrorMessage=Précision diplôme invalide

ClientValidationFunction=chkAutreDiplome

composant de validation du champ [3]


5

txtAnDiplome

TextBox

EnableViewState=false

champ de saisie


6

RangeValidator1

RangeValidator

EnableViewState=false

EnableClientScript=true

ErrorMessage=Année diplôme doit être dans l'intervalle [1990,2004]

MinValue=1990

MaxValue=2004

Type=Integer

ControlToValidate=txtAnDiplome

composant de validation du champ [5]


7

RequiredFieldValidator2

RequiredFieldValidator

EnableViewState=false

EnableClientScript=true

ErrorMessage=Année diplôme requise

ControlToValidate=txtAnDiplome

composant de validation du champ [5]


8

btnEnvoyer

Button

EnableViewState=false

CausesValidation=true

bouton [submit]

Le champ [RangeValidator] sert à vérifier que la valeur d'un contrôle se trouve entre deux bornes [MinValue] et [MaxValue]. Ses propriétés sont les suivantes :


ControlToValidate

champ dont la valeur doit être contrôlée par le composant


EnableClientScript

booléen - à vrai indique que le contenu du champ précédent doit être contrôlé également côté client


ErrorMessage

le message d'erreur que le composant doit afficher en cas d'erreur détectée


MinValue

valeur min de la valeur du champ à contrôler


MaxValue

valeur max de la valeur du champ à contrôler


Type

type de la valeur du champ à contrôler

Le champ [CustomValidator] permet de faire des validations que ne savent pas faire les composants de validation proposés par ASP.NET. Cette validation est faite par une fonction écrite par le développeur. Celle-ci est exécutée côté serveur. Comme le contrôle peut être fait également côté client, le développeur peut être amené à développer une fonction javascript qu'il inclura dans le document HTML. Ses propriétés sont les suivantes :


ControlToValidate

champ dont la valeur doit être contrôlée par le composant


EnableClientScript

booléen - à vrai indique que le contenu du champ précédent doit être contrôlé également côté client


ErrorMessage

le message d'erreur que le composant doit afficher en cas d'erreur détectée


ClientValidationFunction

la fonction à exécuter côté client

Le code de présentation de la page est le suivant :

<%@ Page Language="VB" %>
<script runat="server">
...
</script>
<html>
<head>
</head>
<body>
    <form runat="server">
        <p align="left">
            Demande du dossier de candidature au DESS 
        </p>
        <fieldset>
            <legend>[--Votre dernier diplôme--]</legend>
            <table>
                <tbody>
                    <tr>
                        <td>
                            Diplôme*</td>
                        <td>
                            <asp:DropDownList id="cmbDiplomes" runat="server">
                                <asp:ListItem Value="?">?</asp:ListItem>
                                <asp:ListItem Value="[Autre]">[Autre]</asp:ListItem>
                                <asp:ListItem Value="Ma&#238;trise">Ma&#238;trise</asp:ListItem>
                                <asp:ListItem Value="DESS">DESS</asp:ListItem>
                                <asp:ListItem Value="DEA">DEA</asp:ListItem>
                            </asp:DropDownList>
                        </td>
                        <td>
                            Si [Autre], précisez</td>
                        <td>
                            <asp:TextBox id="txtAutreDiplome" runat="server" EnableViewState="False"></asp:TextBox>
                        </td>
                    </tr>
                    <tr>
                        <td>
                        </td>
                        <td>
                            <p>
                                <asp:CompareValidator id="CompareValidator2" runat="server" ErrorMessage="Diplôme invalide" ControlToValidate="cmbDiplomes" ValueToCompare="?" Operator="NotEqual" ></asp:CompareValidator>
                            </p>
                        </td>
                        <td>
                        </td>
                        <td>
                            <asp:CustomValidator id="CustomValidator1" runat="server" ErrorMessage="Précision diplôme invalide" OnServerValidate="CustomValidator1_ServerValidate_1" EnableViewState="False" ClientValidationFunction="chckAutreDiplome"></asp:CustomValidator>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            Année d'obtention*</td>
                        <td colspan="3">
                            <asp:TextBox id="txtAnDiplome" runat="server" Columns="4"></asp:TextBox>
                            <asp:RangeValidator id="RangeValidator1" runat="server" ErrorMessage="Année diplôme doit être dans l'intervalle [1990,2004]" ControlToValidate="txtAnDiplome" MinimumValue="1990" MaximumValue="2004" Type="Integer"></asp:RangeValidator>
                            <asp:RequiredFieldValidator id="RequiredFieldValidator2" runat="server" ErrorMessage="Année diplôme requise" ControlToValidate="txtAnDiplome"></asp:RequiredFieldValidator>
                        </td>
                    </tr>
                </tbody>
            </table>
        </fieldset>
        <p>
            <asp:Button id="btnEnvoyer" runat="server" Text="Envoyer"></asp:Button>
        </p>
    </form>
</body>
</html>

L'attribut [OnServerValidate] du composant [CustomValidator] permet de préciser la fonction chargée de la valisation côté serveur. Ci-dessus le composant [CustomValidator1] fait appel à la procédure [CustomValidator1_ServerValidate_1] suivante :

<%@ Page Language="VB" %>
<script runat="server">

    Sub CustomValidator1_ServerValidate_1(sender As Object, e As ServerValidateEventArgs)
        ' le champ [txtAutreDiplome] doit être non vide si [cmbDiplomes]=[autre]
        e.isvalid=not (cmbDiplomes.selecteditem.text="[Autre]" and txtAutreDiplome.text.trim="") _
            and not (cmbDiplomes.selecteditem.text<>"[Autre]" and cmbDiplomes.selecteditem.text<>"?" and txtAutreDiplome.text.trim<>"")
    End Sub

</script>
....

Une fonction de validation associée à un composant [CustomValidator] reçoit deux paramètres :

  • sender : objet ayant provoqué l'événement
  • e : l'événement. La procédure doit positionner l'attribut [e.IsValid] à vrai si les données vérifiées sont correctes, à faux sinon.

Ici, les vérifications faites sont les suivantes :

  • la liste déroulante [cmbDiplomes] ne peut avoir [?] pour valeur. C'est vérifié par le composant [CompareValidator2]
  • l'utilisateur sélectionne un diplôme dans la liste déroulante [cmbDiplomes]. Si son diplôme n'existe pas dans la liste, il a la possibilité de sélectionner l'option [Autre] de la liste. Il doit alors indiquer son diplôme dans le champ de saisie [txtAutreDiplome]. Si [Autre] est sélectionnée dans [cmbDiplomes] alors le champ [txtAutreDiplome] doit être non vide. Si ni [Autre], ni [?] n'est sélectionnée dans [cmbDiplomes] alors le champ [txtAutreDiplome] doit être vide. Ceci est vérifié par la fonction associée au composant [CustomValidator1].
  • l'année d'obtention du diplôme doit être dans l'intervalle [1900-2004]. Ceci est vérifié par [RangeValidator1]. Cependant si l'utilisateur ne met rien dans le champ, la fonction de validation de [RangeValidator1] n'est pas utilisée. Aussi ajoute-t-on le composant [RequiredFieldValidator2] pour vérifier la présence d'un contenu. Ceci est une règle générale. Le contenu d'un champ n'est pas vérifié si celui-ci est vide. Le seul cas où il l'est, est celui où il est associé à un composant [RequiredFieldValidator].

Voici un exemple d'exécution dans le navigateur [Mozilla] :

Image

Si nous utilisons le navigateur [IE6], nous pouvons ajouter une fonction de validation côté client pour le composant [CustomValidator1]. Pour cela, ce composant a les propriétés suivantes :

  • EnableClientScript=true
  • ClientValidationFunction=chkAutreDiplome

La fonction [chkAutreDiplome ] est la suivante :

<head>
    <meta http-equiv="pragma" content="no-cache" />
    <script language="javascript">
        function chkAutreDiplome(source,args){
            // vérifie la validité du champ txtAutreDiplome
            with(document.frmCandidature){
                diplome=cmbDiplomes.options[cmbDiplomes.selectedIndex].text;
                    args.IsValid= !(diplome=="[Autre]" && txtAutreDiplome.value=="")
                        && ! (diplome!="[Autre]" && diplome!="?" && txtAutreDiplome.value!="");
            }
        }
        </script>
</head>

La fonction [chkAutreDiplome] reçoit les mêmes deux paramètres que la fonction serveur [CustomValidator1_ServerValidate_1] :

  • source : l'objet qui a provoqué l'événement
  • args : l'événement. Celui-ci a un attribut [IsValid] qui doit être positionné à vrai par la fonction si les données vérifiées sont correctes. Une valeur à faux affichera à l'endroit du composant de validation le message d'erreur qui lui est associé et le formulaire ne sera pas posté au serveur.

8.2.5. RegularExpressionValidator

Nous construisons la page [regularexpressionvalidator1.aspx] suivante :

Image

nom

type

propriétés

rôle


1

txtMel

TextBox

EnableViewState=true

champ de saisie


2

RequiredFieldValidator3

RequiredFieldValidator

ControlToValidate=txtMel

Display=Dynamic

composant de validation du contrôle [1]


3

RegularExpressionValidator1

RegularExpressionValidator

ControlToValidate=txtMel

Display=Dynamic

composant de validation du contrôle [1]


4

btnEnvoyer

Button

EnableViewState=false

CausesValidation=true

bouton [submit]

Ici, nous voulons vérifier le format d'une adresse électronique. Nous le faisons avec un composant [RegularExpressionValidator] qui permet de vérifier la validité d'un champ à l'aide d'une expression régulière. Rappelons-ici qu'une expression régulière est un modèle de chaîne de caractères. On vérifie donc le contenu d'un champ vis à vis d'un modèle. Comme le contenu d'un champ n'est pas vérifié s'il est vide, il nous faut également un composant [RequiredFieldValidator] pour vérifier que l'adresse électronique n'est pas vide. La classe [RegularExpressionValidator] a des propriétés analogues aux classes des composants déjà étudiés :


ControlToValidate

champ dont la valeur doit être contrôlée par le composant


EnableClientScript

booléen - à vrai indique que le contenu du champ précédent doit être contrôlé également côté client


ErrorMessage

le message d'erreur que le composant doit afficher en cas d'erreur détectée


RegularExpression

l'expression régulière à laquelle le contenu de [ControlToValidate] va être comparé


Display

mode d'affichage : Static : le champ est toujours présent même s'il n'affiche pas de message d'erreur, Dynamic : le champ n'est présent que s'il y a un message d'erreur, None : le msg d'erreur n'est pas affiché. Ce champ existe également pour les autres composants mais il n'avait pas été utilisé jusqu'à maintenant.

L'expression régulière modèle d'une adresse électronique pourrait être la suivante : \s*[\w-]+(.[\w-]+)*@[\w-]+(.[\w-]+)+\s*

Une adresse électronique est de la forme [champ1.champ2....@champA.champB...]. Il y a obligatoirement au moins un champ avant le signe @ et au moins deux champs derrière. Ces champs sont constitués de caractères alphanumériques et du signe -. Le caractère alphanumérique est représenté par le symbole \w. La séquence [\w-] signifie le caractère \w ou le caractère -. Le signe + derrière une séquence S signifie que celle-ci peut se répéter 1 ou plusieurs fois, le signe * signifie ? qu'elle peut se répéter 0 ou plusieurs fois, le signe ? qu'elle peut se répéter 0 ou 1 fois.

Un champ de l'adresse électronique correspond au modèle [\w-]+. Le fait qu'il y ait obligatoirement au moins un champ avant le signe @ et au moins deux après, séparés par le signe . correspond au modèle : [\w-]+(.[\w-]+)*@[\w-]+(.[\w-]+)+. On peut laisser l'utilisateur mettre des espaces devant et après l'adresse. On les enlèvera lors du traitement. Une suite d'espaces éventuellement vide est représentée par le modèle \s*. D'où l'expression régulière de l'adresse électonique : \s*[\w-]+(.[\w-]+)*@[\w-]+(.[\w-]+)+\s*.

Le code de présentation de la page est alors le suivant :

<html>
<head>
</head>
<body>
    <form runat="server">
        <p align="left">
            Demande du dossier de candidature au DESS
        </p>
        <fieldset>
            <legend>[--Votre adresse électronique où sera envoyé le dossier de candidature--]</legend>
            <table>
                <tbody>
                    <tr>
                        <td>
                            <asp:TextBox id="txtMel" runat="server" Columns="60"></asp:TextBox>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            <p>
                                <asp:RequiredFieldValidator id="RequiredFieldValidator3" runat="server" Display="Dynamic" ControlToValidate="txtMel" ErrorMessage="Votre adresse électronique est requise"></asp:RequiredFieldValidator>
                            </p>
                            <p>
                                <asp:RegularExpressionValidator id="RegularExpressionValidator1" runat="server" Display="Dynamic" ControlToValidate="txtMel" ErrorMessage="Votre adresse électronique n'a pas un format valide" ValidationExpression="\s*[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+\s*"></asp:RegularExpressionValidator>
                            </p>
                        </td>
                    </tr>
                </tbody>
            </table>
        </fieldset>
        <p>
            <asp:Button id="btnEnvoyer" runat="server" Text="Envoyer"></asp:Button>
        </p>
    </form>
</body>
</html>

8.2.6. ValidationSummary

Pour des raisons esthétiques, on peut vouloir rassembler les messages d'erreurs en un unique endroit comme dans l'exemple suivant [summaryvalidator1.aspx] où nous avons réuni dans une unique page tous les exemples précédents :

Image

Tous les contrôles de validation ont les propriétés suivantes :

  • [Display=Dynamic] qui fait que les contrôles n'occupent pas d'espace dans la page si leur message d'erreur est vide
  • [EnableClientScript=false] pour interdire toute validation côté client
  • [Text=*]. Ce sera le message affiché en cas d'erreur, le contenu de l'attribut [ErrorMessage] étant lui affiché par le contrôle [ValidationSummary] que nous présentons ci-dessous.

Cet ensemble de contrôles est placée dans un composant [Panel] appelée [vueFormulaire]. Dans un autre composant [Panel] appelé [vueErreurs], nous plaçons un contrôle [ValidationSummary] :

Image

nom

type

propriétés

rôle

1


ValidationSummary1

ValidationSummary

HeaderText=Les erreurs suivantes se sont produites, EnableClientScript=false, ShowSummary=true

affiche les erreurs de tous les contrôles de validation de la page

2


lnkErreursToFormulaire

LinkButton

CausesValidation=false

lien de retour au formulaire

Pour le lien [lnkErreursToFormulaire], il n'y a pas lieu d'activer la validation puisque ce lien ne poste aucune valeur à vérifier.

Lorsque toutes les données sont valides, on présente à l'utilisateur la vue [infos] suivante :

Image

nom

type

propriétés

rôle

1


lblInfo

Label

 

message d'information à destination de l'utilisateur

Le code de présentation de cette page est le suivant :

<html>
<head>
</head>
<body>
    <form runat="server">
        <p align="left">
            Demande du dossier de candidature au DESS 
        </p>
        <p>
            <hr />
            <asp:panel id="vueErreurs" runat="server">
                <p align="left">
                    <asp:ValidationSummary id="ValidationSummary1" runat="server" ShowMessageBox="True" BorderColor="#C04000" BorderWidth="1px" BackColor="#FFFFC0" HeaderText="Les erreurs suivantes se sont produites"></asp:ValidationSummary>
                </p>
                <p>
                    <asp:LinkButton id="lnkErreursToFormulaire" onclick="lnkErreursToFormulaire_Click" runat="server" CausesValidation="False">Retour au formulaire</asp:LinkButton>
                </p>
            </asp:panel>
            <asp:panel id="vueFormulaire" runat="server">
....
                    <asp:Button id="btnEnvoyer" onclick="btnEnvoyer_Click" runat="server" Text="Envoyer"></asp:Button>
                </p>
            </asp:panel>
        <asp:panel id="vueInfos" runat="server">
            <asp:Label id="lblInfo" runat="server"></asp:Label>
        </asp:panel>
    </form>
</body>
</html>

Voici quelques exemples de résultats obtenus. Nous validons la vue [formulaire] sans saisir de valeurs :

Image

Le bouton [Envoyer] nous donne la réponse suivante :

Image

C'est la vue [erreurs] qui a été affichée. Si nous utilisons le lien de retour au formulaire, nous retrouvons celui-ci dans l'état suivant :

Image

C'est la vue [formulaire]. On remarquera le caractère [*] près des données erronées. C'est le champ [Text] des contrôles de validation qui a été affiché. Si nous remplissons correctement les champs, nous obtiendrons la vue [infos] :

Image

Le code de contrôle de la page est le suivant :

<%@ Page Language="VB" %>
<script runat="server">

    ' procédure exécutée au chargement de la page
    Sub page_Load(sender As Object, e As EventArgs)
        ' à la 1ère requête, on présente la vue [formulaire]
        if not ispostback then
            afficheVues(true,false,false)
        end if
    end sub

    sub afficheVues(byval formulaireVisible as boolean, _
        erreursVisible as boolean, infosVisible as boolean)
        ' jeu de vues
        vueFormulaire.visible=formulaireVisible
        vueErreurs.visible=erreursVisible
        vueInfos.visible=infosVisible
    end sub

    Sub CustomValidator1_ServerValidate(sender As Object, e As ServerValidateEventArgs)
...
    End Sub

    Sub CustomValidator2_ServerValidate(sender As Object, e As ServerValidateEventArgs)
...
    End Sub

    Sub CustomValidator1_ServerValidate_1(sender As Object, e As ServerValidateEventArgs)
...
    End Sub

    Sub lnkErreursToFormulaire_Click(sender As Object, e As EventArgs)
        ' affiche la vue formulaire
        afficheVues(true,false,false)
        ' refait les contrôles de validité
        Page.validate
    End Sub

    Sub btnEnvoyer_Click(sender As Object, e As EventArgs)
        ' la page est-elle valide ?
        if not Page.IsValid then
            ' on affiche la vue [erreurs]
            afficheVues(false,true,false)
        else
            ' sinon la vue [infos]
            lblInfo.Text="Le dossier de candidature au DESS IAIE a été envoyé à l'adresse ["+ _
                        txtMel.Text + "]. Nous vous en souhaitons bonne réception.<br><br>Le secrétariat du DESS."
            afficheVues(false,false,true)
        end if
    end sub

</script>
<html>
...
</html>
  • Dans la procédure [Page_Load] exécutée à chaque requête du client, nous affichons la vue [formulaire], les autres étant cachées. Ceci est fait seulement lors de la première requête. La procédure fait appel à une procédure utilitaire [afficheVues] à qui on passe trois booléens pour faire afficher ou non les trois vues.
  • Lorsque la procédure [btnEnvoyer_Click] est appelée, les vérifications de données ont été faites. Le bouton [btnEnvoyer] à la propriété [CausesValidation=true] qui force cette vérification des données. Tous les contrôles de validation ont été exécutés et leur propriété [IsValid] positionnée. Celle-ci indique si la donnée vérifiée par le contrôle était valide ou non. Par ailleurs, la propriété [IsValid] de la page elle-même a été égaleemnt positionnée. Elle est à [vrai] uniquement si la propriété [IsValid] des tous les contrôles de validation de la page ont la valeur [vrai]. Ainsi la procédure [btnEnvoyer_Click] commence-t-elle par vérifier si la page est valide ou non. Si elle est invalide, on fait afficher la vue [erreurs]. Celle-ci contient le contrôle [ValidationSummary] qui reprend les attributs [ErrorMessage] de tous les contrôles (voir copie d'écran plus haut). Si la page est valide, c'est la vue [infos] qui est affichée avec un message d'information.
  • La procédure [lnkErreursToFormulaire_Click] est chargée de faire afficher la vue [formulaire] dans l'état où elle a été validée. Tous les champs de saisie de la vue [formulaire] ayant la propriété [EnableViewState=true], leur état est automatiquement régénéré. Curieusement, l'état des composants de validation n'est lui pas restitué. On pouvait s'attendre en effet, au retour de la vue [erreurs] à voir les contrôles erronés afficher leur champ [Text]. Ce n'est pas le cas. On a donc forcé la validation des données en utilisant la méthode [Page.Validate] de la page. Ceci doit être fait une fois que le panel [vueFormulaire] a été rendu visible. Il y a donc au total deux validations. Ceci serait à éviter dans la pratique. Ici, l'exemple nous permettait d'introduire de nouvelles notions sur la validation de page.

8.3. Composants ListControl et liaison de données

Un certain nombre des composants serveur étudiés permettent d'afficher une liste de valeurs (DropDownList, ListBox). D'autres que nous n'avons pas encore présentés permettent d'afficher plusieurs listes de valeurs dans des tables HTML. Pour tous, il est possible, par programme, d'associer une par une les valeurs des listes au composant associé. Il est possible également d'associer des objets plus complexes à ces composants tels des objets de type [Array], [ArrayList], [DataSet], [HashTable], ... ce qui simplifie le code qui associe les données au composant. On appelle cette association, une liaison de données.

Tous les composants dérivés de la classe [ListControl] peuvent être associés à une liste de données. Il s'agit des composants [DropDownList], [ListBox], [CheckButtonList], [RadioButtonList]. Chacun de ces composants peut être lié à une source de données. Celle-ci peut être diverse : [Array], [ArrayList], [DataTable], [DataSet], [HashTable],...., de façon générale un objet implémentant l'une des interfaces IEnumerable, ICollection, IListSource. Nous ne présenterons que quelques-uns s'entre-eux. Un objet [DataSet] est un objet image d'une base de données relationnelles. C'est donc un ensemble de tables liées par des relations. L'objet [DataTable] représente une telle table. La source de données affecte les propriétés [Text] et [Value] de chacun des membres [Item] de l'objet [ListControl]. Si T est la valeur de [Text] et V la valeur de [Value], la balise HTML générée pour chaque élément de [ListControl] est la suivante :


DropDownList, ListBox

<option value="V">T</option>


CheckButtonList

<input type="checkbox" value="V">T


RadioButtonList

<input type="radio" value="V">T

L'association d'un composant [ListControl] à une source de données se fait au travers des propriétés suivantes :


DataSource

une source de données [Array], [ArrayList], [DataTable], [DataSet], [HashTable], ...


DataMember

dans le cas où la source de données est un [DataSet], représente le nom de la table à utiliser comme source de données. La véritable source de données est alors une table.


DataTextField

dans le cas où la source de données est une table ([DataTable],[DataSet]), représente le nom de la colonne de la table qui va donner ses valeurs au champ [Text] des éléments du [ListControl]


DataValueField

dans le cas où la source de données est une table ([DataTable],[DataSet]), représente le nom de la colonne de la table qui va donner ses valeurs au champ [Value] des éléments du [ListControl]

L'association d'un composant [ListControl] à une source de données n'initialise pas le composant avec les valeurs de la source de données. C'est l'opération [ListControl].DataBind qui le fait.

Selon la nature de la source de données, l'association de celle-ci à un composant [ListControl] se fera de façon différente :


Array A

[ListControl].DataSource=A

les champs [Text] et [Value] des éléments de [ListControl] auront pour valeurs les éléments de A


ArrayList AL

[ListControl].DataSource=AL

les champs [Text] et [Value] des éléments de [ListControl] auront pour valeurs les éléments de AL


DataTable DT

[ListControl].DataSource=DT, [ListControl].DataTextField="col1", [ListControl]. DatavalueField ="col2"

où col1 et col2 sont deux colonnes de la table DT. Les champs [Text] et [Value] des éléments de [ListControl] auront pour valeurs celles des colonnes col1 et col2 de la table DT


DataSet DS

[ListControl].DataSource=DS , [ListControl].DataSource="table" où "table" est le nom d'une des tables de DS.

[ListControl].DataTextField="col1", [ListControl]. DatavalueField ="col2" où col1 et col2 sont deux colonnes de la table "table". Les champs [Text] et [Value] des éléments de [ListControl] auront pour valeurs celles des colonnes col1 et col2 de la table "table"


HashTable HT

[ListControl].DataSource=HT , [ListControl].DataTextField="key", [ListControl]. DatavalueField ="value" où [key] et [value] sont respectivement les clés et les valeurs de HT.

Nous mettons en pratique ces informations sur l'exemple [databind1.aspx] suivant :

Image

Nous avons là cinq liaisons de données avec à chaque fois quatre contrôles de type [DropDownList], [ListBox], [CheckBoxList] et [RadioButtonList]. Les cinq liaisons étudiées diffèrent par leurs sources de données :

liaison

source de données


1

Array


2

ArrayList


3

DataTable


4

DataSet


5

HashTable

8.3.1. Code de présentation des composants

Le code de présentation des contrôles de la liaison 1 est le suivant :

<td>
<asp:DropDownList id="DropDownList1" runat="server"></asp:DropDownList>
</td>
<td>
<asp:ListBox id="ListBox1" runat="server" SelectionMode="Multiple"></asp:ListBox>
</td>
<td>
<asp:CheckBoxList id="CheckBoxList1" runat="server"></asp:CheckBoxList>
</td>
<td>
<asp:RadioButtonList id="RadioButtonList1" runat="server"></asp:RadioButtonList>
</td>

Celui des liaisons 2 à 5 est identique au numéro de liaison près.

8.3.2. Liaison à une source de données de type Array

La liaison des quatre contrôles [ListBox] ci-dessus se fait de la façon suivante dans la procédure [Page_Load] du code de contrôle :

' les données globales
dim textes() as string={"un","deux","trois","quatre"}
dim valeurs() as string={"1","2","3","4"}
dim myDataListe as new ArrayList
dim myDataTable as new DataTable("table1")
dim myDataSet as new DataSet
dim myHashTable as new HashTable

' procédure exécutée au chargement de la page
Sub page_Load(sender As Object, e As EventArgs)
    if not IsPostBack then
      ' on crée les sources  des données qu'on va lier aux composants
      createDataSources
      ' liaison à un tableau [Array]
      bindToArray
      ' liaison à une liste [ArrayList]
      bindToArrayList
      ' liaison à une table [DataTable]
      bindToDataTable
      ' liaison à un groupe de données [DataSet]
      bindToDataSet
      ' liaison à un dictionnaire [HashTable]
      bindToHashTable
    end if
End Sub

sub createDataSources
  ' crée les sources de données qui seront liées aux composants
  ' arraylist
  dim i as integer
  for i=0 to textes.length-1
    myDataListe.add(textes(i))
  next
  ' datatable
  ' on définit ses deux colonnes
  myDataTable.Columns.Add("id",Type.GetType("System.Int32"))
  myDataTable.Columns.Add("texte",Type.GetType("System.String"))
  ' on remplit la table
  dim ligne as DataRow
  for i=0 to textes.length-1
    ligne=myDataTable.NewRow
    ligne("id")=i
    ligne("texte")=textes(i)
    myDataTable.Rows.Add(ligne)
  next
  ' dataset - une seule table
  myDataSet.Tables.Add(myDataTable)
  ' hashtable
  for i=0 to textes.length-1
    myHashTable.add(valeurs(i),textes(i))
  next
end sub

' liaison tableau
sub bindToArray
        ' l'association aux composants
        with DropDownList1
            .DataSource=textes
            .DataBind
        end with
        with ListBox1
            .DataSource=textes
            .DataBind
        end with
        with CheckBoxList1
            .DataSource=textes
            .DataBind
        end with
        with RadioButtonList1
            .DataSource=textes
            .DataBind
        end with
        ' la sélection des éléments
        ListBox1.Items(1).Selected=true
        ListBox1.Items(3).Selected=true
        CheckBoxList1.Items(0).Selected=true
        CheckBoxList1.Items(3).Selected=true
        DropDownList1.SelectedIndex=2
        RadioButtonList1.SelectedIndex=1
end sub

sub bindToArrayList
....
end sub

sub bindToDataTable
...
end sub

sub bindToDataSet
...
end sub

La liaison des contrôles aux données ne se fait ici qu'à la première requête. Ensuite les contrôles garderont leurs éléments par le mécanisme du [VIEWSTATE]. La liaison est faite dans la procédure [bindToArray]. La source de données étant de type [Array], seul le champ [DataSource] des composants [ListControl] est initialisé. Le remplissage du contrôle avec les valeurs de la source de données associée est réalisée par la méthode [ListControl].DataBind. Ce n'est qu'après, que les objets [ListControl] ont des éléments. On peut alors sélectionner certains d'entre-eux.

8.3.3. Liaison à une source de données de type ArrayList

La source de données [myDataListe] est initialisée dans la procédure [createDataSources] :

' les données globales
dim textes() as string={"un","deux","trois","quatre"}
dim myDataListe as new ArrayList
..

' procédure exécutée au chargement de la page
Sub page_Load(sender As Object, e As EventArgs)
    if not IsPostBack then
      ' on crée les sources  des données qu'on va lier aux composants
      createDataSources
...
    end if
End Sub

sub createDataSources
  ' crée les sources de données qui seront liées aux composants
  ' arraylist
  dim i as integer
  for i=0 to textes.length-1
    myDataListe.add(textes(i))
  next
...
end sub

La liaison des quatre contrôles [ListBox] de la liaison 2 se fait de la façon suivante dans la procédure [bindToArrayList] du code de contrôle :

' liaison arraylist
sub bindToArrayList
        ' l'association aux composants
        with DropDownList2
            .DataSource=myDataListe
            .DataBind
        end with
        with ListBox2
            .DataSource=myDataListe
            .DataBind
        end with
        with CheckBoxList2
            .DataSource=myDataListe
            .DataBind
        end with
        with RadioButtonList2
            .DataSource=myDataListe
            .DataBind
        end with
        ' la sélection des éléments
        ListBox2.Items(1).Selected=true
        ListBox2.Items(3).Selected=true
        CheckBoxList2.Items(0).Selected=true
        CheckBoxList2.Items(3).Selected=true
        DropDownList2.SelectedIndex=2
        RadioButtonList2.SelectedIndex=1
end sub

La source de données étant de type [ArrayList], seul le champ [DataSource] des composants [ListControl] est initialisé.

8.3.4. Source de données de type DataTable

La source de données [myDataTable] est initialisée dans la procédure [createDataSources] :

' les données globales
dim textes() as string={"un","deux","trois","quatre"}
dim myDataTable as new DataTable("table1")
...

' procédure exécutée au chargement de la page
Sub page_Load(sender As Object, e As EventArgs)
    if not IsPostBack then
      ' on crée les sources  des données qu'on va lier aux composants
      createDataSources
...
    end if
End Sub

sub createDataSources
  ' crée les sources de données qui seront liées aux composants
...
  ' datatable
  ' on définit ses deux colonnes
  myDataTable.Columns.Add("id",Type.GetType("System.Int32"))
  myDataTable.Columns.Add("texte",Type.GetType("System.String"))
  ' on remplit la table
  dim ligne as DataRow
  for i=0 to textes.length-1
    ligne=myDataTable.NewRow
    ligne("id")=i
    ligne("texte")=textes(i)
    myDataTable.Rows.Add(ligne)
  next
...
end sub

On commence par construire un objet [DataTable] avec deux colonnes [id] et [texte]. La colonne [id] alimentera le champ [Value] des éléments des [ListControl] et la colonne [texte] leurs champs [Text]. La construction du [DataTable] avec deux colonnes se fait de la façon suivante :

  myDataTable.Columns.Add("id",Type.GetType("System.Int32"))
  myDataTable.Columns.Add("texte",Type.GetType("System.String"))

On crée donc une table à deux colonnes :

  • la première appelée "id" est de type entier
  • la seconde appelée "texte" est de type chaîne de caractères

La structure de la table étant créée, on peut la remplir avec le code suivant :

  dim ligne as DataRow
  for i=0 to textes.length-1
    ligne=myDataTable.NewRow
    ligne("id")=i
    ligne("texte")=textes(i)
    myDataTable.Rows.Add(ligne)
  next

La colonne [id] va contenir des entiers [0,1,..,n] alors que la colonne [texte] va contenir les valeurs du tableau [data]. Ceci fait, la table [dataListe] est remplie. La liaison des quatre contrôles [ListBox] de la liaison 3 se fait de la façon suivante dans la procédure [bindToDataTable] du code de contrôle :

sub bindToDataTable
        ' l'association aux composants
        with DropDownList3
            .DataSource=myDataTable
            .DataValueField="id"
            .DataTextField="texte"
            .DataBind
        end with
        with ListBox3
            .DataSource=myDataTable
            .DataValueField="id"
            .DataTextField="texte"
            .DataBind
        end with
        with CheckBoxList3
            .DataSource=myDataTable
            .DataValueField="id"
            .DataTextField="texte"
            .DataBind
        end with
        with RadioButtonList3
            .DataSource=myDataTable
            .DataValueField="id"
            .DataTextField="texte"
            .DataBind
        end with
        ' la sélection des éléments
        ListBox3.Items(1).Selected=true
        ListBox3.Items(3).Selected=true
        CheckBoxList3.Items(0).Selected=true
        CheckBoxList3.Items(3).Selected=true
        DropDownList3.SelectedIndex=2
        RadioButtonList3.SelectedIndex=1
end sub

Chaque composant [ListControl] est lié à la source [myDataTable] en affectant pour chacun d'entre-eux :

            .DataSource= myDataTable
            .DataValueField="id"
            .DataTextField="texte"

La table [myDataTable] est la source de données. La colonne [id] de cette table va alimenter les champs [Value] des éléments des composants alors que la colonne [texte] va alimenter leurs champs [Text].

8.3.5. Source de données de type DataSet

La source de données [myDataSet] est initialisée dans la procédure [createDataSources] :

' les données globales
dim myDataTable as new DataTable("table1")
dim myDataSet as new DataSet
...


' procédure exécutée au chargement de la page
Sub page_Load(sender As Object, e As EventArgs)
    if not IsPostBack then
      ' on crée les sources  des données qu'on va lier aux composants
      createDataSources
...
    end if
End Sub

sub createDataSources
  ' crée les sources de données qui seront liées aux composants
  ' dataset - une seule table
  myDataSet.Tables.Add(myDataTable)
...
end sub

Un objet [DataSet] représente un ensemble de tables de type [DataTable]. On ajoute la table [myDataTable] construite précédemment au [DataSet]. La liaison des quatre contrôles [ListBox] de la liaison 4 se fait de la façon suivante dans la procédure [bindToDataSet] du code de contrôle :

sub bindToDataSet
        ' l'association aux composants
        with DropDownList4
            .DataSource=myDataSet
            .DataMember="table1"
            .DataValueField="id"
            .DataTextField="texte"
            .DataBind
        end with
        with ListBox4
            .DataSource=myDataSet
            .DataMember="table1"
            .DataValueField="id"
            .DataTextField="texte"
            .DataBind
        end with
        with CheckBoxList4
            .DataSource=myDataSet
            .DataMember="table1"
            .DataValueField="id"
            .DataTextField="texte"
            .DataBind
        end with
        with RadioButtonList4
            .DataSource=myDataSet
            .DataMember="table1"
            .DataValueField="id"
            .DataTextField="texte"
            .DataBind
        end with
        ' la sélection des éléments
        ListBox4.Items(1).Selected=true
        ListBox4.Items(3).Selected=true
        CheckBoxList4.Items(0).Selected=true
        CheckBoxList4.Items(3).Selected=true
        DropDownList4.SelectedIndex=2
        RadioButtonList4.SelectedIndex=1
end sub

Chaque composant [ListControl] est lié à la source de données de la façon suivante :

            .DataSource= myDataSet
            .DataMember="table1"
            .DataValueField="id"
            .DataTextField="texte"

Le groupe de données [myDataSet] est la source de données. Comme celle-ci peut comprendre plusieurs tables, on précise dans [DataMember] le nom de celle que l'on va utiliser. La colonne [id] de cette table va alimenter les champs [Value] des éléments des composants alors que la colonne [texte] va alimenter leurs champs [Text].

8.3.6. Source de données de type HashTable

La source de données [myHashTable] est initialisée dans la procédure [createDataSources] :

' les données globales
dim textes() as string={"un","deux","trois","quatre"}
dim valeurs() as string={"1","2","3","4"}
dim myHashTable as new HashTable
...

' procédure exécutée au chargement de la page
Sub page_Load(sender As Object, e As EventArgs)
    if not IsPostBack then
      ' on crée les sources  des données qu'on va lier aux composants
      createDataSources
...
    end if
End Sub

sub createDataSources
  ' crée les sources de données qui seront liées aux composants
...
  ' hashtable
  for i=0 to textes.length-1
    myHashTable.add(valeurs(i),textes(i))
  next
end sub

Le dictionnaire [myHashTable] peut être vu comme une table à deux colonnes appelées "key" et "value". La colonne [key] représente les clés du dictionnaire et la colonne [value] les valeurs associées à celles-ci. La colonne [key] est ici formée du contenu du tableau [valeurs] et la colonne [value] du contenu du tableau [textes]. La liaison de cette source aux contrôles est faite dans la procédure [bindToHashTable] :

sub bindToHashTable
        ' l'association aux composants
        with DropDownList5
            .DataSource=myHashTable
            .DataValueField="key"
            .DataTextField="value"
            .DataBind
        end with
        with ListBox5
            .DataSource=myHashTable
            .DataValueField="key"
            .DataTextField="value"
            .DataBind
        end with
        with CheckBoxList5
            .DataSource=myHashTable
            .DataValueField="key"
            .DataTextField="value"
            .DataBind
        end with
        with RadioButtonList5
            .DataSource=myHashTable
            .DataValueField="key"
            .DataTextField="value"
            .DataBind
        end with
        ' la sélection des éléments
        ListBox5.Items(1).Selected=true
        ListBox5.Items(3).Selected=true
        CheckBoxList5.Items(0).Selected=true
        CheckBoxList5.Items(3).Selected=true
        DropDownList5.SelectedIndex=2
        RadioButtonList5.SelectedIndex=1
end sub

Pour chacun des composants, la liaison est faite par les instructions :

            .DataSource=myHashTable
            .DataValueField="key"
            .DataTextField="value"

La source de données est le dictionnaire [myHashTable]. Les valeurs du contrôle sont fournies par la colonne [key] du dictionnaire et les textes par la colonne [value]. L'insertion des éléments du dictionnaire dans les contrôles se fait dans l'ordre des clés qui est à priori aléatoire.

8.3.7. Les directives d'importation d'espaces de noms

Un certain nombre d'espaces de noms sont automatiquement importés dans une page ASP.NET. Ce n'est pas le cas de "System.Data" où se trouve les classes [DataTable] et [DataSet]. Aussi faut-il importer cette classe. Cela se fait de la façon suivante :

<%@ Page Language="VB" %>
<%@ import Namespace="System.Data" %>
<script runat="server">
...
</script>
<html>
...
</html>

8.4. Composant DataGrid et liaison de données

Le composant [DataGrid] permet d'afficher des données sous forme de tableaux mais il va bien au-delà de ce simple affichage :

  • il offre la possibilité de paramétrer de façon précise le "rendu visuel" du tableau
  • il permet la mise à jour de la source de données

Le composant [DataGrid] est un composant à la fois puissant et complexe. Nous allons le présenter par touches successives.

8.4.1. Affichage d'une source de données Array, ArrayList, DataTable, DataSet

Le composant [DataGrid] permet d'afficher dans un tableau HTML des sources de données de type [Array], [ArrayList], [DataTable], [DataSet]. Pour ces quatre type de données, il suffit d'associer la source à la propriété [DataSource] du composant [DataGrid] :


DataSource

une source de données [Array], [ArrayList], [DataTable], [DataSet], ...


DataMember

dans le cas où la source de données est un [DataSet], représente le nom de la table à utiliser comme source de données. La véritable source de données est alors une table. Si ce champ n'est pas renseigné, toutes les tables du [DataSet] sont affichées.

Nous présentons maintenant la page [datagrid1.aspx] qui montre l'association d'un [DataGrid] à quatre sources de données différentes :

Image

La page contient quatre composants [DataGrid] construits avec [WebMatrix] de la façon suivante. On dépose le composant à son emplacement dans l'onglet [Design] :

Image

Un tableau HTML générique est alors dessiné. Les propriétés d'un [DataGrid] peuvent être définies à la conception. C'est ce que nous faisons ici pour ses propriétés de mise en forme. Pour cela, nous sélectionnons le [DataGrid] à configurer. Ses propriétés apparaissent dans une fenêtre en bas à droite :

Image

Nous allons utiliser les deux liens ci-dessus. Le lien [Générateur de propriétés] donne accès aux principales propriétés du [DataGrid] :

Image

Nous décochons la phrase [Afficher l'en-tête] pour les quatre composants [DataGrid] et validons la page. L'autre lien [Mise en forme automatique] permet de choisir parmi plusieurs styles pour le tableau HTML qui sera affiché :

Image

Nous choisissons [couleur i] pour le [DataGrid] n° i. Ces choix de conception sont traduits dans le code de présentation de la page :

<html>
<head>
</head>
<body>
    <form runat="server">
        <p>
            Liaison de données avec un DataGrid 
        </p>
        <hr />
        <p>
            <table>
                <tbody>
                </tbody>
            </table>
            <table border="1">
                <tbody>
                    <tr>
                        <td>
                            Array</td>
                        <td>
                            ArrayList</td>
                        <td>
                            DataTable</td>
                        <td>
                            DataSet</td>
                    </tr>
                    <tr>
                        <td>
                            <asp:DataGrid id="DataGrid1" runat="server" ShowHeader="False" CellPadding="4" BackColor="White" BorderColor="#CC9966" BorderWidth="1px" BorderStyle="None">
                                <FooterStyle forecolor="#330099" backcolor="#FFFFCC"></FooterStyle>
                                <HeaderStyle font-bold="True" forecolor="#FFFFCC" backcolor="#990000"></HeaderStyle>
                                <PagerStyle horizontalalign="Center" forecolor="#330099" backcolor="#FFFFCC"></PagerStyle>
                                <SelectedItemStyle font-bold="True" forecolor="#663399" backcolor="#FFCC66"></SelectedItemStyle>
                                <ItemStyle forecolor="#330099" backcolor="White"></ItemStyle>
                            </asp:DataGrid>
                        </td>
                        <td>
                            <asp:DataGrid id="DataGrid2" runat="server" ShowHeader="False" CellPadding="4" BackColor="White" BorderColor="#3366CC" BorderWidth="1px" BorderStyle="None">
                                <FooterStyle forecolor="#003399" backcolor="#99CCCC"></FooterStyle>
                                <HeaderStyle font-bold="True" forecolor="#CCCCFF" backcolor="#003399"></HeaderStyle>
                                <PagerStyle horizontalalign="Left" forecolor="#003399" backcolor="#99CCCC" mode="NumericPages"></PagerStyle>
                                <SelectedItemStyle font-bold="True" forecolor="#CCFF99" backcolor="#009999"></SelectedItemStyle>
                                <ItemStyle forecolor="#003399" backcolor="White"></ItemStyle>
                            </asp:DataGrid>
                        </td>
                        <td>
                            <asp:DataGrid id="DataGrid3" runat="server" ShowHeader="False" CellPadding="3" BackColor="#DEBA84" BorderColor="#DEBA84" BorderWidth="1px" BorderStyle="None" CellSpacing="2">
                                <FooterStyle forecolor="#8C4510" backcolor="#F7DFB5"></FooterStyle>
                                <HeaderStyle font-bold="True" forecolor="White" backcolor="#A55129"></HeaderStyle>
                                <PagerStyle horizontalalign="Center" forecolor="#8C4510" mode="NumericPages"></PagerStyle>
                                <SelectedItemStyle font-bold="True" forecolor="White" backcolor="#738A9C"></SelectedItemStyle>
                                <ItemStyle forecolor="#8C4510" backcolor="#FFF7E7"></ItemStyle>
                            </asp:DataGrid>
                        </td>
                        <td>
                            <asp:DataGrid id="DataGrid4" runat="server" ShowHeader="False" CellPadding="3" BackColor="White" BorderColor="#E7E7FF" BorderWidth="1px" BorderStyle="None" GridLines="Horizontal">
                                <FooterStyle forecolor="#4A3C8C" backcolor="#B5C7DE"></FooterStyle>
                                <HeaderStyle font-bold="True" forecolor="#F7F7F7" backcolor="#4A3C8C"></HeaderStyle>
                                <PagerStyle horizontalalign="Right" forecolor="#4A3C8C" backcolor="#E7E7FF" mode="NumericPages"></PagerStyle>
                                <SelectedItemStyle font-bold="True" forecolor="#F7F7F7" backcolor="#738A9C"></SelectedItemStyle>
                                <AlternatingItemStyle backcolor="#F7F7F7"></AlternatingItemStyle>
                                <ItemStyle forecolor="#4A3C8C" backcolor="#E7E7FF"></ItemStyle>
                            </asp:DataGrid>
                        </td>
                    </tr>
                </tbody>
            </table>
        </p>
        <asp:Button id="Button1" runat="server" Text="Envoyer"></asp:Button>
    </form>
</body>
</html>

En mode conception, nous n'avons fixé que des propriétés de mise en forme. C'est dans le code de contrôle que nous associons des données aux quatre composants :

<%@ Page Language="VB" %>
<%@ import Namespace="system.data" %>
<script runat="server">

    ' les données globales
        dim textes1() as string={"un","deux","trois","quatre"}
        dim textes2() as string={"one","two","three","for"}
        dim valeurs() as string={"1","2","3","4"}
        dim myDataListe as new ArrayList
        dim myDataTable as new DataTable("table1")
        dim myDataSet as new DataSet

        ' procédure exécutée au chargement de la page
        Sub page_Load(sender As Object, e As EventArgs)
            if not IsPostBack then
              ' on crée les sources  des données qu'on va lier aux composants
              createDataSources
              ' liaison à un tableau [Array]
              bindToArray
              ' liaison à une liste [ArrayList]
              bindToArrayList
              ' liaison à une table [DataTable]
              bindToDataTable
              ' liaison à un groupe de données [DataSet]
              bindToDataSet
            end if
        End Sub

        sub createDataSources
          ' crée les sources de données qui seront liées aux composants
          ' arraylist
          dim i as integer
          for i=0 to textes1.length-1
            myDataListe.add(textes1(i))
          next
          ' datatable
          ' on définit ses deux colonnes
          myDataTable.Columns.Add("id",Type.GetType("System.Int32"))
          myDataTable.Columns.Add("texte1",Type.GetType("System.String"))
          myDataTable.Columns.Add("texte2",Type.GetType("System.String"))
          ' on remplit la table
          dim ligne as DataRow
          for i=0 to textes1.length-1
            ligne=myDataTable.NewRow
            ligne("id")=valeurs(i)
            ligne("texte1")=textes1(i)
            ligne("texte2")=textes2(i)
            myDataTable.Rows.Add(ligne)
          next
          ' dataset - une seule table
          myDataSet.Tables.Add(myDataTable)
        end sub

        ' liaison tableau
        sub bindToArray
          with DataGrid1
            .DataSource=textes1
            .DataBind
          end with
        end sub

        ' liaison arraylist
        sub bindToArrayList
          with DataGrid2
            .DataSource=myDataListe
            .DataBind
          end with
        end sub

        ' liaison datatable
        sub bindToDataTable
          with DataGrid3
            .DataSource=myDataTable
            .DataBind
          end with
        end sub

        ' liaison dataset
        sub bindToDataSet
          with DataGrid4
            .DataSource=myDataSet
            .DataBind
          end with
        end sub

</script>
<html>
...
</html>

Le code est assez semblable à celui de l'exemple précédent, aussi ne le commenterons-nous pas particulièrement. Remarquons cependant comment la liaison de données est faite. Pour chacun des quatre contrôle, la séquence suivante suffit :

          with [DataGrid]
            .DataSource=[source de données]
            .DataBind
          end with

où [source de données] est de type [Array] ou [ArrayList] ou [DataTable] ou [DataSet]. On voit donc qu'on a là, un outil puissant d'affichage de données en tableaux :

  • la mise en forme est faite avec [WebMatrix] ou tout autre IDE au moment de la conception
  • la liaison de données est faite dans le code. Avec [WebMatrix], elle peut être faite au moment de la conception si la source de données est une base SQL server ou une base ACCESS. Dans ce cas, on placera un composant de type [SqlDataSource] ou [AccessDataSource] sur la feuille. Ce composant peut être relié au moment de la conception à la source physique des données, une base SQL Server ou une base ACCESS selon les cas. Si on affecte à la propriété[DataSource] d'un composant [DataGrid] un objet [SqlDataSource] ou [AccessDataSource] relié à une source physique, alors on verra apparaître, en mode conception, les données réelles dans le composant [DataGrid].

8.5. ViewState des composants listes de données

Nous nous proposons ici de mettre en lumière le mécanisme du [VIEWSATE] pour les composants listes de données. On peut hésiter entre deux méthodes pour maintenir l'état d'un composant liste de données entre deux requêtes du client :

  • mettre son attribut [VIEWSTATE] à vrai
  • mettre son attribut [VIEWSTATE] à faux et mémoriser sa source de données dans la session afin d'être capable de relier dle composant à cette source lors de la prochaine requête.

L'exemple suivant illustre certains aspects du mécanisme du [VIEWSTATE] des conteneurs de données. A la première requête du client, l'application présente la vue suivante :

Image

nom

type

propriétés

rôle

1


DataGrid1

DataGrid

EnableViewState=true

affiche une source de données S

2


DataGrid2

DataGrid

EnableViewState=true

affiche la même source S que [DataGrid1]

3


Button1

Button

EnableViewState=false

bouton [submit]

Le composant [DataGrid1] est maintenu par le mécanisme du [VIEWSTATE]. On cherche à savoir si ce mécanisme qui régénère l'affichage de [DataGrid1] à chaque requête, régénère également sa source de données. Pour cela, celle-ci est liée au composant [DataGrid2]. La génération de celui-ci a chaque requête est faite par une liaison explicite à la source de de données de [DataGrid1]. Il a son attribut [EnableViewState] à [vrai] également.

Le code de présentation [main.aspx] de l'application est le suivant :


<%@ page src="main.aspx.vb" inherits="main" autoeventwireup="false" %>
<HTML>
   <HEAD>
       <title></title>
   </HEAD>
   <body>
       <form runat="server">
           <table>
               <tr>
                   <td align="center">DataGrid 1</td>
                   <td align="center">
                       DataGrid 2</td>
               </tr>
               <tr>
                   <td>
        <asp:DataGrid id="DataGrid1" runat="server" ...>
                           <SelectedItemStyle ...></SelectedItemStyle>
....
                       </asp:DataGrid></td>
                   <td>
        <asp:DataGrid id="Datagrid2" runat="server" ...>
....
                       </asp:DataGrid></td>
               </tr>
           </table>
           <asp:Button id="Button1" runat="server" Text="Envoyer"></asp:Button>
       </form>
   </body>
</HTML>

Le contrôleur [main.aspx.vb] est lui le suivant :


Imports System.Data

Public Class main
   Inherits System.Web.UI.Page

   Protected WithEvents DataGrid1 As System.Web.UI.WebControls.DataGrid
   Protected WithEvents Datagrid2 As System.Web.UI.WebControls.DataGrid
   Protected WithEvents Button1 As System.Web.UI.WebControls.Button

   Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
       ' à la 1ère requête la source de données est définie et liée au 1er datagrid
       If Not IsPostBack Then
           'définir la source de données
           With DataGrid1
               .DataSource = createDataSource()
               .DataBind()
           End With
       End If
       ' à chaque requête, on lie datagrid2 avec la source du 1er datagrid
       With Datagrid2
           .DataSource = DataGrid1.DataSource
           .DataBind()
       End With
   End Sub

   Private Function createDataSource() As DataTable
       ' on initialise la source de données
       Dim thèmes As New DataTable
       ' colonnes
       With thèmes.Columns
           .Add("id", GetType(System.Int32))
           .Add("thème", GetType(System.String))
           .Add("description", GetType(System.String))
       End With
       ' colonne id sera clé primaire
       thèmes.Constraints.Add("cléprimaire", thèmes.Columns("id"), True)
       ' lignes
       Dim ligne As DataRow
       For i As Integer = 0 To 4
           ligne = thèmes.NewRow
           ligne.Item("id") = i.ToString
           ligne.Item("thème") = "thème" + i.ToString
           ligne.Item("description") = "description du thème " + i.ToString
           thèmes.Rows.Add(ligne)
       Next
       Return thèmes
   End Function

   Private Sub InitializeComponent()

   End Sub
End Class

La méthode [createDataSource] crée une source S de type [DataTable]. Nous ne nous attarderons pas sur son code qui n'est pas l'objet de l'exemple. C'est la façon utilisée pour construire les deux composants [DataGrid] qui nous intéressent :

  • le composant [DataGrid1] est lié une fois à la table S, lors de la première requête. Il ne l'est plus ensuite.
  • le composant [DataGrid2] est lié à la source [DataGrid1.DataSource] à chaque nouvelle requête.

Lors de la première requête; nous obtenons la vue suivante :

Image

Assez logiquement, les deux composants affichent la source de données à laquelle ils ont été liés. Nous utilisons le bouton [Envoyer] pour provoquer un [PostBack] vers le serveur. La vue obtenue est alors la suivante :

Image

Nous constatons que le composant [DataGrid1] a conservé sa valeur mais pas le composant [DataGrid2]. Explications :

  • avant même que la procédure [Page_Load] ne démarre, les objets [DataGrid1] et [DataGrid2] ont récupéré la valeur qu'ils avaient lors de la requête précédente, à cause du mécanisme du [viewstate]. Ils ont en effet, tous les deux, leur propriété [EnableViewState] à [vrai].
  • la procédure [Page_Load] s'exécute. Comme on a affaire à une opération [PostBack], le composant [DataGrid1] n'est pas modifié par [Page_Load] (cf code). Aussi garde-t-il la valeur récupérée grâce au [viewstate]. C'est ce que montre l'écran ci-dessus.
  • le composant [DataGrid2] est lui relié [DataBind] à la source de données [DataGrid1.DataSource]. Il est donc reconstruit et la valeur qu'il venait de récupérer grâce au [viewstate] est perdue. Il y aurait donc eu intérêt ici à ce qu'il ait sa propriété [EnableViewState] à [faux] afin d'éviter la gestion inutile de son état. L'écran ci-dessus montre que [DataGrid2] a été lié à une source vide. Cette source étant [DataGrid1.DataSource], on en déduit que si le mécanisme du [viewstate] restaure bien l'affichage du composant [DataGrid1], il ne restaure pas pour autant ses propriétés telles que [DataSource].

Que conclure de cet exemple ? Il faut éviter de mettre la propriété [EnableViewState] d'un conteneur de données à [vrai] si celui-ci doit être lié (DataBind) à une source de données à chaque requête. Il y a cependant certains cas, où même dans ce cas de figure, la propriété [EnableViewState] du conteneur doit être maintenue à [vrai], sinon des événements que l'on voudrait gérer ne sont pas déclenchés. Nous aurons l'occasion d'en rencontrer un exemple.

Fréquemment, la source de données d'un conteneur de données évolue au fil des requêtes. Il faut donc, à chaque requête, relier le conteneur à la source de données. Il est fréquent que celle-ci soit mise en session afin que les requêtes y aient accès. Notre second exemple montre ce mécanisme. L'application ne fournit qu'une seule vue :

Image

nom

type

propriétés

rôle

1


DataGrid1

DataGrid

EnableViewState=true

affiche une source de données S

2


DataGrid2

DataGrid

EnableViewState=false

affiche la même source S que [DataGrid1]

3


Button1

Button

EnableViewState=false

bouton [submit]

4


lblInfo1
lblInfo2
lblInfo3

Label

EnableViewState=false

textes d'information

Le composant [DataGrid1] est lié aux données uniquement lors de la première requête. Il gardera sa valeur au fil des requêtes grâce au mécanisme du [viewstate]. Le composant [DataGrid2] est lié à chaque requête à une source de données à qui on ajoute un élément à chaque nouvelle requête. Il faut donc relier (DataBind) le composant [DataGrid2] à chaque requête. On a donc mis son attribut [EnableViewState] à [faux] comme il a été conseillé précédemment. Ainsi lors de la seconde requête (usage du bouton [Envoyer]), on a la réponse suivante :

Image

Le composant [DataGrid1] a gardé sa valeur initiale. Le composant [DataGrid2] a un élément de plus. Les trois informations [1,2,2] représentent le n° de requête. On voit que l'une des informations est erronée. Nous essaierons de comprendre pourquoi.

Le code de présentation [main.aspx] de l'application est le suivant :


<%@ Page src="main.aspx.vb" inherits="main" autoeventwireup="false" Language="vb" %>
<HTML>
   <HEAD>
       <title></title>
   </HEAD>
   <body>
       <form runat="server">
           <table>
               <tr>
                   <td align="center" bgColor="#ccffcc">DataGrid 1</td>
                   <td align="center" bgColor="#ffff99">DataGrid 2</td>
               </tr>
               <tr>
                   <td vAlign="top">
                       <asp:DataGrid id="DataGrid1" runat="server" ...>
...
                       </asp:DataGrid>
                   </td>
                   <td vAlign="top">
                       <asp:DataGrid id="Datagrid2" runat="server" ... EnableViewState="False">
....
                       </asp:DataGrid>
                   </td>
               </tr>
           </table>
           <P>Numéro de requête&nbsp;:
               <asp:Label id="lblInfo1" runat="server" EnableViewState="False"></asp:Label>,
               <asp:Label id="lblInfo2" runat="server" EnableViewState="False"></asp:Label>,
               <asp:Label id="lblInfo3" runat="server" EnableViewState="False"></asp:Label></P>
           <P>
               <asp:Button id="Button1" runat="server" Text="Envoyer" EnableViewState="False"></asp:Button></P>
       </form>
   </body>
</HTML>

Le code du contrôleur [main.aspx.vb] est le suivant :


Imports System.Data
Imports System

Public Class main
   Inherits System.Web.UI.Page

   Protected WithEvents DataGrid1 As System.Web.UI.WebControls.DataGrid
   Protected WithEvents Datagrid2 As System.Web.UI.WebControls.DataGrid
   Protected WithEvents Button1 As System.Web.UI.WebControls.Button
   Protected WithEvents lblInfo1 As System.Web.UI.WebControls.Label
   Protected WithEvents lblInfo2 As System.Web.UI.WebControls.Label
   Protected WithEvents lblInfo3 As System.Web.UI.WebControls.Label

   Dim dtThèmes As DataTable
   Dim numRequête1 As Integer
   Dim numRequête2 As Integer
   Dim numRequête3 As New entier

   Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
       ' à la 1ère requête la source de données est définie et liée au 1er datagrid
       If Not IsPostBack Then
           'définir la source de données
           dtThèmes = createDataSource()
           With DataGrid1
               .DataSource = dtThèmes
               .DataBind()
           End With
           ' mémoriser des informations dans la session
           Session("source") = dtThèmes
           numRequête1 = 0 : Session("numRequête1") = numRequête1
           numRequête2 = 0 : Session("numRequête2") = numRequête2
           numRequête3.valeur = 0 : Session("numRequête3") = numRequête3
       End If
       ' à chaque requête, on ajoute un nouveau thème
       dtThèmes = CType(Session("source"), DataTable)
       Dim nbThèmes = dtThèmes.Rows.Count
       Dim ligne As DataRow = dtThèmes.NewRow
       With ligne
           .Item("id") = nbThèmes.ToString
           .Item("thème") = "thème" + nbThèmes.ToString
           .Item("description") = "description du thème " + nbThèmes.ToString
       End With
       dtThèmes.Rows.Add(ligne)
       'lie datagrid2 avec la source de données
       With Datagrid2
           .DataSource = dtThèmes
           .DataBind()
       End With
       ' infos nbre de requêtes
       numRequête1 = CType(Session("numRequête1"), Integer)
       numRequête1 += 1
       lblInfo1.Text = numRequête1.ToString
       numRequête2 = CType(Session("numRequête2"), Integer)
       numRequête2 += 1
       lblInfo2.Text = numRequête2.ToString
       numRequête3 = CType(Session("numRequête3"), entier)
       numRequête3.valeur += 1
       lblInfo3.Text = numRequête3.valeur.ToString
       ' on mémorise qqs infos dans la session
       Session("numRequête2") = numRequête2
   End Sub

   Private Function createDataSource() As DataTable
....
   End Function
End Class

Public Class entier
   Private _valeur As Integer
   Public Property valeur() As Integer
       Get
           Return _valeur
       End Get
       Set(ByVal Value As Integer)
           _valeur = Value
       End Set
   End Property
End Class

Nous n'avons pas reproduit le code de la méthode [createDataSource]. C'est le même que dans l'application précédente au détail près qu'on ne met que trois lignes dans la source. Intéressons-nous tout d'abord à la gestion de la source de données et des deux conteneurs :


   Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
       ' à la 1ère requête la source de données est définie et liée au 1er datagrid
       If Not IsPostBack Then
           'définir la source de données
           dtThèmes = createDataSource()
           With DataGrid1
               .DataSource = dtThèmes
               .DataBind()
           End With
           ' mémoriser des informations dans la session
           Session("source") = dtThèmes
...
       End If
       ' à chaque requête, on ajoute un nouveau thème
       dtThèmes = CType(Session("source"), DataTable)
       Dim nbThèmes = dtThèmes.Rows.Count
       Dim ligne As DataRow = dtThèmes.NewRow
       With ligne
           .Item("id") = nbThèmes.ToString
           .Item("thème") = "thème" + nbThèmes.ToString
           .Item("description") = "description du thème " + nbThèmes.ToString
       End With
       dtThèmes.Rows.Add(ligne)
       'lie datagrid2 avec la source de données
       With Datagrid2
           .DataSource = dtThèmes
           .DataBind()
       End With
...
   End Sub

Le composant [DataGrid1] n'est lié à la source S de données que lors de la première requête (not IsPostBack). Celle-ci est alors placée dans la session. Elle ne le sera plus par la suite. La source de données S mise en session est récupérée à chaque requête et se voit ajouter une nouvelle ligne. Le composant [DataGrid2] est lié explicitement, à chaque requête, à la source S. C'est pourquoi son contenu augmente d'une ligne à chaque requête. On constate qu'après avoir modifié le contenu de la source S, on ne remet pas explicitement celle-ci en session par une opération :

            Session("source") = S

Pourquoi ? Lorsqu'une requête démarre, la session a en son sein un objet Session("source") de type [DataTable] qui est la source de données telle qu'elle était lors de la dernière requête. Appelons S l'objet Session("source"). Lorsque nous écrivons :


       dtThèmes = CType(Session("source"), DataTable)

[dtThèmes] et [S] sont deux références sur un même objet [DataTable]. Ainsi lorsque le code de [Page_Load] ajoute un élément à la table référencée par [dtThèmes], il l'ajoute du même coup à la table référencée par [S]. A la fin d'exécution de la page, tous les objets présents dans la session vont être sauvegardés et donc l'objet Session("source"), c.a.d. S, c.a.d. [dtThèmes]. C'est donc bien le nouveau contenu de la source de données qui est sauvegardée. Il n'y a pas eu besoin d'écrire :

            Session("source") = dtThèmes

pour faire cette sauvegarde, car Session("source") est déjà égal à [dtThèmes]. Ceci n'est plus vrai lorsque les données mises en session ne sont pas des objets, telles les structures [Integer, Float, ...]. C'est ce que montre la gestion des compteurs de requêtes :


   Dim numRequête1 As Integer
   Dim numRequête2 As Integer
   Dim numRequête3 As New entier

   Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
       ' à la 1ère requête la source de données est définie et liée au 1er datagrid
....
           ' mémoriser des informations dans la session
           Session("source") = dtThèmes
           numRequête1 = 0 : Session("numRequête1") = numRequête1
           numRequête2 = 0 : Session("numRequête2") = numRequête2
           numRequête3.valeur = 0 : Session("numRequête3") = numRequête3
       End If
       ' à chaque requête, on ajoute un nouveau thème
....
       ' infos nbre de requêtes
       numRequête1 = CType(Session("numRequête1"), Integer)
       numRequête1 += 1
       lblInfo1.Text = numRequête1.ToString
       numRequête2 = CType(Session("numRequête2"), Integer)
       numRequête2 += 1
       lblInfo2.Text = numRequête2.ToString
       numRequête3 = CType(Session("numRequête3"), entier)
       numRequête3.valeur += 1
       lblInfo3.Text = numRequête3.valeur.ToString
       ' on mémorise qqs infos dans la session
       Session("numRequête2") = numRequête2
   End Sub

   Private Function createDataSource() As DataTable
....
   End Function

   Private Sub InitializeComponent()

   End Sub
End Class

Public Class entier
   Private _valeur As Integer
   Public Property valeur() As Integer
       Get
           Return _valeur
       End Get
       Set(ByVal Value As Integer)
           _valeur = Value
       End Set
   End Property

On mémorise les compteurs de requêtes dans trois éléments :

  • numRequête1 et numRequête2 de type [Integer] - [Integer] n'est pas une classe mais une structure
  • numRequête3 de type [entier] - [entier] est une classe définie pour l'occasion

Lorsqu'on écrit :


       numRequête1 = CType(Session("numRequête1"), Integer)
..
       numRequête2 = CType(Session("numRequête2"), Integer)
..
       numRequête3 = CType(Session("numRequête3"), entier)
..
  • la structure [Session("numRequête1")] est recopiée dans [numRequête1]. Ainsi lorsque l'élément [numRequête1] est modifié, l'élément [Session("numRequête1")] lui, ne l'est pas
  • il en est de même pour [Session("numRequête2")] et [numRequête2]
  • ls éléments [Session("numRequête3")] et [numRequête3] sont eux deux références à un même objet de type [entier]. L'objet référencé peut être modifié indifféremment par l'une ou l'autre référence.

Ce ceci, on déduit qu'il est inutile d'écrire :

        Session("numRequête3") = numRequête3

pour mémoriser la nouvelle valeur de [numRequête3] dans la session. En revanche, il faudrait écrire :

        Session("numRequête1") = numRequête1
        Session("numRequête2") = numRequête2

pour mémoriser les nouvelles valeurs des structures [numRequête1] et [numRequête2]. Nous ne le faisons que pour [numRequête2], ce qui explique que sur la copie d'écran obtenue à l'issue de la seconde requête, le compteur [numRequête1] est erroné.

On retiendra donc qu'une fois mise en session, une donnée n' a pas à y être remise de façon répétée si elle est représentée par un objet. Dans les autres cas, il faut le faire si elle a été modifiée.

8.6. Affichage d'une liste de données à l'aide d'un DataGrid paginé et trié

Le composant [DataGrid] permet d'afficher le contenu d'un [DataSet]. Pour l'instant, nous avons toujours construit nos [DataSet] "à la main". Cette fois, nous utilisons un [DataSet] issu d'une base de données. Nous construisons l'application MVC suivante :

Image

Les trois vues seront incorporées dans le code de présentation du contrôleur [main.aspx] sous la forme de conteneurs. Aussi cette application a-t-elle une unique page [main.aspx].

8.6.1. Les classes métier

La classe [produits] donne accès à la base ACCESS suivante :

Image

Le code de la classe [produits] est le suivant :


Imports System
Imports System.Data
Imports System.Data.OleDb
Imports System.Xml

Namespace st.istia.univangers.fr

   Public Class produits
       Private chaineConnexionOLEDB As String

       Public Sub New(ByVal chaineConnexionOLEDB As String)
           ' on mémorise la chaîne de connexion
           Me.chaineConnexionOLEDB = chaineConnexionOLEDB
       End Sub

       Public Function getDataSet(ByVal commande As String) As DataSet
           ' on crée un objet DataAdapter pour lire les données de la source OLEDB
           Dim adaptateur As New OleDbDataAdapter(commande, chaineConnexionOLEDB)
           ' on crée une image en mémoire du résultat du select
           Dim contenu As New DataSet
           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 rend le résultat
           Return contenu
       End Function
   End Class
End Namespace

La base ACCESS sera gérée via un pilote OLEDB. On donne au constructeur de la classe [produits], la chaîne de connexion identifiant et le pilote OLEDB et la base que l'on doit gérer. La classe [produits] a une méthode [getDataSet] qui rend un [DataSet] obtenu par exécution d'une requête SQL [select] dont le texte est passé en paramètre. Au cour de la méthode, plusieurs opérations ont lieu : création de la connexion à la base, exécution du [select], fermeture de la connexion. Tout ceci peut générer une exception qui est ici gérée. Un programme de test pourrait être le suivant :

Option Explicit On 
Option Strict On

' espaces de noms
Imports System
Imports System.Data
Imports Microsoft.VisualBasic

Namespace st.istia.univangers.fr

    ' pg de test
    Module testproduits
        Sub Main(ByVal arguments() As String)
            ' affiche le contenu d'une table de produits
            ' la table est dans une base ACCESS dont le pg reçoit le nom de fichier
            Const syntaxe1 As String = "pg bdACCESS"

            ' 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 prépare la chaîne de connexion
            Dim chaineConnexion As String = "Provider=Microsoft.Jet.OLEDB.4.0; Ole DB Services=-4; Data Source=" + arguments(0)

            ' création d'un objet produits
            Dim objProduits As produits = New produits(chaineConnexion)

            ' on récupère la table des produits dans un dataset
            Dim contenu As DataSet
            Try
                contenu = objProduits.getDataSet("select id,nom,prix from liste")
            Catch ex As Exception
                Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
                Environment.Exit(2)
            End Try

            ' on affiche son contenu
            Dim lignes As DataRowCollection = contenu.Tables(0).Rows
            For i As Integer = 0 To lignes.Count - 1
                ' ligne i de la table
                Console.Out.WriteLine(lignes(i).Item("id").ToString + "," + lignes(i).Item("nom").ToString + _
                "," + lignes(i).Item("prix").ToString)
            Next
        End Sub
    End Module
End Namespace

La compilation des différents éléments de cette application se fait de la façon suivante :

dos>vbc /t:library /r:system.dll /r:system.data.dll /r:system.xml.dll produits.vb

dos>vbc /r:produits.dll /r:system.data.dll /r:system.xml.dll /r:system.dll testproduits.vb

dos>dir
06/05/2004  16:52              118 784 produits.mdb
07/05/2004  11:07                  902 produits.vb
07/04/2004  07:01                1 532 testproduits.vb
07/05/2004  14:21                3 584 produits.dll
07/05/2004  14:22                4 608 testproduits.exe

dos>testproduits produits.mdb
1,produit1,10
2,produit2,20
3,produit3,30

8.6.2. Les vues

Maintenant que nous avons la classe d'accès aux données, nous écrivons les contrôleurs et vues de cette application web. Voyons tout d'abord son fonctionnement. La première vue est la suivante :

Image

nom

type

propriétés

rôle

1


txtSelect

TextBox

EnableViewState=true

champ de saisie de la requête select

2


RequiredFieldValidator1

RequiredFieldValidator

EnableViewState=false

vérifie la présence de 1

3


txtPages

TextBox

EnableViewState=true

champ de saisie - indique le nombre de lignes de données à afficher par page de résultats

4


RequiredFieldValidator2

RequiredFieldValidator

EnableViewState=false

vérifie la présence de 3

5


RangeValidator1

RangeValidator

EnableViewState=false

vérifie que (3) est dans l'intervalle [1,30]

6


btnExécuter

Button

EnableViewState=false

bouton [submit]

Nous appellerons cette vue, la vue [formulaire]. Elle permet à l'utilisateur de faire exécuter une requête SQL select sur la base [produits.mdb]. L'exécution de la requête donne naissance à une nouvelle vue :

Image

nom

type

propriétés

rôle

1


lblSelect

Label

EnableViewState=false

champ d'information

2


rdCroissant
rdDécroissant

RadioButton

EnableViewState=false

GroupName=rdTri

permet de choisir un sens de tri

3


DataGrid1

DataGrid

EnableViewState=true

AllowPaging=true

AllowSorting=true

table d'affichage du résultat du select

4


lnkRésultats

LinkButton

EnableViewState=false

lien-bouton [submit]

Nous appellerons cette vue, la vue [résultats]. C'est elle qui contient le [DataGrid] qui va afficher le résultat de la commande SQL select. L'utilisateur peut se tromper dans sa requête. Certaines erreurs lui seront signalées dans la vue [erreurs] grâce aux contrôles de validation.

Image

D'autres erreurs lui seront signalées par la vue [erreurs] :

Image

La vue [erreurs] est affichée :

Image

nom

type

propriétés

rôle

1


erreursHTML

variable

 

code HTML nécessaire à l'affichage des erreurs

3


lnkForm2

LinkButton

EnableViewState=false

lien-bouton [submit]

Les trois vues de l'application sont trois conteneurs (panel) différents au sein de la même page [main.aspx]. Son code de présentation est le suivant :


<%@ Page src="main.aspx.vb" inherits="main" autoeventwireup="false" Language="vb" %>
<HTML>
   <HEAD>
   </HEAD>
   <body>
       <P>Liaison de données avec un DataGrid</P>
       <HR width="100%" SIZE="1">
       <form runat="server">
           <asp:panel id="vueFormulaire" runat="server">
               <P>Commande [select] à exécuter sur la table LISTE</P>
               <P>&nbsp;Exemple : select id, nom, prix from LISTE
               </P>
               <P>
                   <asp:TextBox id="txtSelect" runat="server" Columns="60"></asp:TextBox></P>
               <P>
                   <asp:RequiredFieldValidator id="RequiredFieldValidator1" runat="server" EnableViewState="False" EnableClientScript="False"
        ErrorMessage="Indiquez la requête [select] à exécuter" ControlToValidate="txtSelect" Display="Dynamic"></asp:RequiredFieldValidator></P>
               <P>Nombre de lignes par page :
                   <asp:TextBox id="txtPages" runat="server" Columns="3"></asp:TextBox></P>
               <P>
                   <asp:RequiredFieldValidator id="RequiredFieldValidator2" runat="server" EnableViewState="False" EnableClientScript="False"
        ErrorMessage="Indiquez le nombre de lignes par page désirées" ControlToValidate="txtPages" Display="Dynamic"></asp:RequiredFieldValidator>
                   <asp:RangeValidator id="RangeValidator1" runat="server" EnableViewState="False" EnableClientScript="False"
        ErrorMessage="Vous devez indiquer un nombre entre 1 et 30" ControlToValidate="txtPages" Display="Dynamic"
        Type="Integer" MinimumValue="1" MaximumValue="30"></asp:RangeValidator></P>
               <P>
                   <asp:Button id="btnExécuter" runat="server" EnableViewState="False" Text="Exécuter"></asp:Button></P>
           </asp:panel>
           <asp:Panel id="vueRésultats" runat="server">
               <P>Résultats de la requête
                   <asp:Label id="lblSelect" runat="server"></asp:Label></P>
               <P>Tri
                   <asp:RadioButton id="rdCroissant" runat="server" Text="croissant" Checked="True" GroupName="rdTri"></asp:RadioButton>
                   <asp:RadioButton id="rdDécroissant" runat="server" Text="décroissant" GroupName="rdTri"></asp:RadioButton></P>
               <P>
                   <asp:DataGrid id="DataGrid1" runat="server" BorderColor="#CC9966" BorderStyle="None" BorderWidth="1px"
        BackColor="White" CellPadding="4" AllowPaging="True" PageSize="4" AllowSorting="True">
                       <SelectedItemStyle Font-Bold="True" ForeColor="#663399" BackColor="#FFCC66"></SelectedItemStyle>
                       <ItemStyle ForeColor="#330099" BackColor="White"></ItemStyle>
                       <HeaderStyle Font-Bold="True" ForeColor="#FFFFCC" BackColor="#990000"></HeaderStyle>
                       <FooterStyle ForeColor="#330099" BackColor="#FFFFCC"></FooterStyle>
                       <PagerStyle NextPageText="Suivant" PrevPageText="Pr&#233;c&#233;dent" HorizontalAlign="Center"
         ForeColor="#330099" BackColor="#FFFFCC"></PagerStyle>
                   </asp:DataGrid></P>
               <P>
                   <asp:LinkButton id="lnkRésultats" runat="server" EnableViewState="False">Retour au formulaire</asp:LinkButton></P>
           </asp:Panel>
           <asp:Panel id="vueErreurs" runat="server">
               <P>Les erreurs suivantes se sont produites :</P>
               <% =erreursHTML %>
               <P>
                   <asp:LinkButton id="lnkErreurs" runat="server" EnableViewState="False">Retour vers le formulaire</asp:LinkButton></P>
           </asp:Panel>
       </form>
   </body>
</HTML>

8.6.3. Configuration du DataGrid

Attardons-nous sur la pagination du composant [DataGrid], option que nous rencontrons pour la première fois. Dans le code ci-dessus, cette pagination est contrôlée par les attributs suivants :


                   <asp:DataGrid id="DataGrid1" runat="server" PageSize="4" AllowPaging="True" ...>
...
                       <PagerStyle NextPageText="Suivant" PrevPageText="Pr&#233;c&#233;dent" ...></PagerStyle>
                   </asp:DataGrid></P>

AllowPaging="true"

autorise la pagination


PageSize="4"

quatre lignes de données par page


NextPageText="Suivant"

Le texte du lien pour aller à la page suivante de la source de données


PrevPageText="Précédent"

Le texte du lien pour aller à la page précédente de la source de données

Ces informations peuvent être saisies directement dans les attributs de la balise <asp:datagrid>. On peut également s'aider de [WebMatrix]. Dans la fenêtre de propriétés du [DataGrid], on suit le lien [Générateur de propriétés] :

Image

On obtient l'assistant suivant :

Image

On prend l'option [Pagination] :

Image

Nous retrouvons ci-dessus, les valeurs des attributs de pagination du [DataGrid] du code de présentation.

Par ailleurs, nous autorisons le tri des données sur l'une des colonnes du [DataGrid]. Il y a différentes façons de faire cela. L'une d'elles est de définir la propriété [AllowSorting=true] dans la fenêtre de propriétés du [DataGrid]. On peut aussi le générateur de propriétés. Quelque soit la méthode utilisée, cela se traduit par la présence de l'attribut [AllowSorting=true] dans la balise <asp:DataGrid> du composant :


                   <asp:DataGrid id="DataGrid1" runat="server" ... AllowPaging="True" PageSize="4" AllowSorting="True">

8.6.4. Les contrôleurs

Le contrôleur [global.asax, global.asax.vb] est le suivant :

[global.asax]

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

[global.asax.vb]


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 produits
       Dim objProduits As produits
       Try
           objProduits = New produits(ConfigurationSettings.AppSettings("OLEDBStringConnection"))
           ' on met l'objet dans l'application
           Application("objProduits") = objProduits
           ' 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

Lorsque l'application démarre (Application_Start), nous construisons un objet [produits] et le mettons dans l'application afin qu'il soit disponible pour toutes les requêtes de tous les clients. S'il y a exception lors de cette construction, elle est notée dans l'application. La procédure [Application_Start] ne s'exécutera qu'une fois. Ensuite le contrôleur [global.asax] n'interviendra plus. C'est le contrôleur [main.aspx.vb] qui fera alors le travail :


Imports System.Collections
Imports Microsoft.VisualBasic
Imports System.Data
Imports st.istia.univangers.fr
Imports System
Imports System.Xml

Public Class main
   Inherits System.Web.UI.Page

   ' composants page
   Protected WithEvents txtSelect As System.Web.UI.WebControls.TextBox
   Protected WithEvents RequiredFieldValidator1 As System.Web.UI.WebControls.RequiredFieldValidator
   Protected WithEvents txtPages As System.Web.UI.WebControls.TextBox
   Protected WithEvents RequiredFieldValidator2 As System.Web.UI.WebControls.RequiredFieldValidator
   Protected WithEvents RangeValidator1 As System.Web.UI.WebControls.RangeValidator
   Protected WithEvents btnExécuter As System.Web.UI.WebControls.Button
   Protected WithEvents vueFormulaire As System.Web.UI.WebControls.Panel
   Protected WithEvents lblSelect As System.Web.UI.WebControls.Label
   Protected WithEvents DataGrid1 As System.Web.UI.WebControls.DataGrid
   Protected WithEvents lnkRésultats As System.Web.UI.WebControls.LinkButton
   Protected WithEvents vueRésultats As System.Web.UI.WebControls.Panel
   Protected WithEvents lnkErreurs As System.Web.UI.WebControls.LinkButton
   Protected WithEvents vueErreurs As System.Web.UI.WebControls.Panel
   Protected WithEvents rdCroissant As System.Web.UI.WebControls.RadioButton
   Protected WithEvents rdDécroissant As System.Web.UI.WebControls.RadioButton
   ' données page
   Protected erreursHTML As String

   Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
       ' on regarde si l'application est en erreur
       If CType(Application("erreur"), Boolean) Then
           ' l'application ne s'est pas initialisée correctement
           Dim erreurs As New ArrayList
           erreurs.Add("Application momentanément indisponible (" + CType(Application("message"), String) + ")")
           afficheErreurs(erreurs, False)
           Exit Sub
       End If
       '1ère requête
       If Not IsPostBack Then
           ' on affiche le formulaire vide
           afficheFormulaire()
       End If
   End Sub

   Private Sub afficheErreurs(ByVal erreurs As ArrayList, ByVal afficheLien As Boolean)
       ' affiche la vue erreurs
       erreursHTML = ""
       For i As Integer = 0 To erreurs.Count - 1
           erreursHTML += "<li>" + erreurs(i).ToString + "</li>" + ControlChars.CrLf
       Next
       lnkErreurs.Visible = afficheLien
       ' on affiche la vue [erreurs]
       vueErreurs.Visible = True
       vueFormulaire.Visible = False
       vueRésultats.Visible = False
   End Sub

   Private Sub afficheFormulaire()
       ' on affiche la vue [formulaire]
       vueFormulaire.Visible = True
       vueErreurs.Visible = False
       vueRésultats.Visible = False
   End Sub

   Private Sub afficheRésultats(ByVal sqlTexte As String, ByVal données As DataView)
       ' on initialise les contrôles
       lblSelect.Text = sqlTexte
       With DataGrid1
           .DataSource = données
           .PageSize = CType(txtPages.Text, Integer)
           .CurrentPageIndex = 0
           .DataBind()
       End With
       ' on affiche la vue [résultats]
       vueRésultats.Visible = True
       vueFormulaire.Visible = False
       vueErreurs.Visible = False
   End Sub

   Private Sub btnExécuter_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnExécuter.Click
       ' page valide ?
       If Not Page.IsValid Then
           afficheFormulaire()
           Exit Sub
       End If

       ' exécution de la requête SELECT client
       Dim données As DataView
       Try
           données = CType(Application("objProduits"), produits).getDataSet(txtSelect.Text.Trim).Tables(0).DefaultView
       Catch ex As Exception
           Dim erreurs As New ArrayList
           erreurs.Add("erreur d'accès à la base de données (" + ex.Message + ")")
           afficheErreurs(erreurs, True)
           Exit Sub
       End Try
       ' tout va bien - on affiche les résultats
       afficheRésultats(txtSelect.Text.Trim, données)
       ' on met les données dans la session
       Session("données") = données
   End Sub

   Private Sub retourFormulaire(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkErreurs.Click, lnkRésultats.Click
       ' jeu de vues
       vueErreurs.Visible = False
       vueFormulaire.Visible = True
       vueRésultats.Visible = False
   End Sub

   Private Sub DataGrid1_PageIndexChanged(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridPageChangedEventArgs) Handles DataGrid1.PageIndexChanged
       ' chgt de page
       With DataGrid1
           .CurrentPageIndex = e.NewPageIndex
           .DataSource = CType(Session("données"), DataView)
           .DataBind()
       End With
   End Sub

   Private Sub DataGrid1_SortCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridSortCommandEventArgs) Handles DataGrid1.SortCommand
       ' on trie le dataview
       Dim données As DataView = CType(Session("données"), DataView)
       données.Sort = e.SortExpression + " " + CType(IIf(rdCroissant.Checked, "asc", "desc"), String)
       ' on l'affiche
       With DataGrid1
           .CurrentPageIndex = 0
           .DataSource = données
           .DataBind()
       End With
   End Sub
End Class

Au chargement de la page [Page_Load], nous regardons tout d'abord si l'application a pu s'initialiser correctement. Si ce n'est pas le cas, on affiche la vue [erreurs] sans lien de retour vers le formulaire car ce lien est alors inutile. En effet, seule la vue [erreurs] peut s'afficher si l'application n'a pas pu s'initialiser correctement. Sinon, on affiche la vue [formulaire] si on a affaire à la première requête du client. Pour le reste, nous laissons le lecteur le soin de comprendre le code. Nous nous attardons seulement sur trois procédures, la procédure [btnExécuter_Click] qui s'exécute lorsque l'utilisateur a demandé l'exécution de la requête SQL saisie dans la vue [formulaire], la procédure [DataGrid1_PageIndexChanged] qui est exécutée lorsque l'utilisateur utilise les liens [Suivant] et [Précédent] du [DataGrid] et la procédure [DataGrid1_SortCommand] qui est exécutée lorsque l'utilisateur clique sur le titre d'une colonne pour trier les données selon l'ordre de celle-ci. Le sens de l'ordre, croissant ou décroissant est fixé par les deux boutons radio de tri.

Dans la procédure [btnExécuter_Click], on commence donc par vérifier si la page est valide ou non. Lorsque la procédure [btnExécuter_Click] s'exécute, les vérifications liées aux différents contrôles de validation de la page ont été faites. Pour chaque contrôle de validation, deux attributs ont été positionnés :


IsValid

à vrai si la donnée vérifiée est valide, à faux sionon


ErrorMessage

le message d'erreur si la donnée vérifiée s'est avérée invalide

Pour la page elle-même, un attribut [IsValid] a été positionné. Il est à vrai uniquement si tous les contrôles de validation ont leur attribut [IsValid] à vrai. Si ce n'est pas le cas, il faut afficher la vue [formulaire]. Celle-ci contient les contrôles de validation qui afficheront leur attribut [errorMessage]. Si la page est valide , on utilise l'objet de type [produits] créé par [Application_Start] afin d'obtenir le [DataSet] correspondant à l'exécution de la requête SQL select. On transforme celui-ci en objet [DataView] :


       Dim données As DataView
       Try
           données = CType(Application("objProduits"), produits).getDataSet(txtSelect.Text.Trim).Tables(0).DefaultView
...

On aurait pu travailler simplement avec le [DataSet] et écrire :


       Dim données As DataSet
       Try
           données = CType(Application("objProduits"), produits).getDataSet(txtSelect.Text.Trim)
...

Un objet [DataSet] est à la base un ensemble de tables liées par des relations. Dans notre application particulière, le [DataSet] obtenu de la classe [produits] ne contient qu'une table, celle issue du résultat de l'instruction [select]. Une table peut être triée alors qu'un [DataSet] ne le peut pas, or on est intéressé par trier les données obtenues. Pour travailler avec la table résultat du [select], on aurait pu écrire :


       Dim données As DataTable
       Try
           données = CType(Application("objProduits"), produits).getDataSet(txtSelect.Text.Trim).Tables(0)
...

L'objet [DataTable], bien que réprésentant une table de base de données n'a pas de méthode de tri. Il faut pour cela avoir une vue sur la table. Une vue est un objet de type [DataView]. On peut avoir différentes vues sur une même table à l'aide de filtres. Une table a une vue par défaut qui est celle où aucun filtre n'est défini. Elle représente donc la totalité de la table. Cette vue par défaut est obtenue par [DataTable.DefaultView]. On peut trier une vue à l'aide de sa propriété [sort] sur laquelle nous reviendrons.

Si l'obtention du [DataSet] de la classe [produits] se passe bien, la vue [résultats] est affichée sinon c'est la vue [erreurs]. L'affichage de la vue [résultats] se fait par la procédure [afficheRésultats] à qui on passe deux paramètres :

  • le texte à placer dans le label [lblSelect]
  • le [DataView] à lier à [DataGrid1]

Cet exemple nous montre la grande souplesse du composant [DataGrid]. Il sait reconnaître la structure du [DataView] auquel on le lie et s'adapter à celle-ci. Enfin, la procédure [btnExécuter_Click] mémorise le [DataView] qu'elle vient d'obtenir dans la session de l'utilisateur afin d'en disposer lorsque celui-ci va demander d'autres pages du même [DataView].

La procédure [DataGrid1_PageIndexChanged] est exécutée lorsque l'utilisateur utilise les liens [Suivant] et [Précédent] du [DataGrid]. Elle reçoit deux paramètres :


   Private Sub DataGrid1_PageIndexChanged(ByVal source As Object, ByVal e As System.Web.UI.WebControls.) Handles DataGrid1.PageIndexChanged

source

l'objet origine de l'événement - ici, l'un des liens [Suivant] ou [Précédent]


e

des informations sur l'événement. L'attribut e.NewPageIndex est le n° de page à afficher pour répondre à la demande du client

Le code complet du gestionnaire d'événement est le suivant :


   Private Sub DataGrid1_PageIndexChanged(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridPageChangedEventArgs) Handles DataGrid1.PageIndexChanged
       ' chgt de page
       With DataGrid1
           .CurrentPageIndex = e.NewPageIndex
           .DataSource = CType(Session("données"), DataView)
           .DataBind()
       End With
   End Sub

Le composant [DataGrid] a un attribut [CurrentPageIndex] qui indique le n° de la page qu'il affiche ou va afficher. On donne à cet attribut la valeur [NewPageIndex] du paramètre [e]. Le [DataGrid] est ensuite associé au [DataView] qui avait été mémorisé dans la session, par la procédure [btnExécuter_Click].

On peut se demander si le [DataGrid] a besoin de l'attribut [EnableViewState=true] puisque son contenu est calculé par le code à chaque nouvelle exécution de la page. On pouvait penser que non. Or, si le [DataGrid] a l'attribut [EnableViewState=false], on constate que l'événement [DataGrid1.PageIndexChanged] n'est jamais déclenché. C'est pour cela qu'on a laissé [EnableViewState=true]. On sait que cela entraîne que le contenu du [DataGrid] va être mis dans le champ caché [__VIEWSTATE] de la page. Cela peut entraîner une surcharge importante de la page si le [DataGrid] est important. Si ce fait est gênant, on peut alors gérer la pagination soi-même sans utliliser la pagination automatique du [DataGrid].

La procédure [DataGrid1_SortCommand] est exécutée lorsque l'utilisateur clique sur le titre d'une des colonnes affichées par le [DataGrid] pour demander le tri des données dans l'ordre de cette colonne. Elle reçoit deux paramètres :


   Private Sub DataGrid1_SortCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridSortCommandEventArgs) Handles DataGrid1.SortCommand

source

l'objet origine de l'événement - ici, l'un des liens [Suivant] ou [Précédent]


e

des informations sur l'événement. L'attribut [e.SortExpression] est le nom de la colonne cliquée pour le tri

Le code complet du gestionnaire d'événement est le suivant :


   Private Sub DataGrid1_SortCommand(ByVal source As Object, ByVal e As System.Web.UI.WebControls.DataGridSortCommandEventArgs) Handles DataGrid1.SortCommand
       ' on trie le dataview
       Dim données As DataView = CType(Session("données"), DataView)
       données.Sort = e.SortExpression + " " + CType(IIf(rdCroissant.Checked, "asc", "desc"), String)
       ' on l'affiche
       With DataGrid1
           .CurrentPageIndex = 0
           .DataSource = données
           .DataBind()
       End With
   End Sub

On récupère le [DataView] visualisé par le [DataGrid] dans la session courante. C'est la procédure [btnExécuter_Click] qui l'y avait mis. Le composant [DataView] a une propriété [Sort] à qui on attribue l'expression de tri. Celle-ci obéit à la syntaxe [select ... order by expr1, expr2, ...] où chaque [expri] peut être suivi du mot clé [asc] pour un tri croissant ou [desc] pour un tri décroissant. L'expression [order by] utilisée ici, est [order by colonne asc/desc]. La propriété [e.SortExpression] nous donne le nom de la colonne du [DataGrid] qui a été cliquée pour le tri. La chaîne [asc/desc] est fixée d'après les valeurs des boutons radio du groupe [rdTri]. Une fois fixée l'expression de tri du [DataView], le [DataGrid] est associé à celui-ci. On positionne le [DataGrid] sur sa première page.

8.7. Composant DataList et liaison de données

Nous nous intéressons maintenant au composant [DataList]. Il offre plus de possibilités de mise en forme que le [DataGrid] mais est moins souple. Ainsi, il ne sait pas s'adapter automatiquement à la source de données à laquelle il est lié. Il faut faire cette adaptation par code si celle-ci est souhaitée. Si la structure de la source de données est connue d'avance, alors ce composant offre des possibilités de mise en forme qui peuvent le faire préférer au [DataGrid].

8.7.1. Application

Afin d'illustrer l'utilisation du [DataList], nous construisons une application MVC analogue à la précédente :

Image

Les trois vues seront incorporées dans le code de présentation du contrôleur [main.aspx] sous la forme de conteneurs. Aussi cette application a-t-elle une unique page [main.aspx].

8.7.2. Les classes métier

La classe [produits] est la même que précédemment.

8.7.3. Les vues

Lorsque l'utilisateur fait sa première requête à l'application, il obtient la vue [résultats1] suivante :

Image

nom

type

propriétés

rôle

1


RadioButton1
RadioButton2

RadioButton

EnableViewState=false

permet de choisir un style de [DataList] parmi deux

2


btnChanger

Button

EnableViewState=false

bouton [submit]

3


DataList1

DataList

EnableViewState=true

champ d'affichage de la liste de données

Si l'utilisateur choisit le style n° 2, il obtient la vue [résultats2] suivante :

Image

nom

type

propriétés

rôle

1


DataList2

DataList

EnableViewState=true

champ d'affichage de la liste de données

La vue [erreurs] signale un problème d'accès à la source de données :

Image

nom

type

propriétés

rôle

1


erreursHTML

variable

 

code HTML nécessaire à l'affichage des erreurs

Les trois vues de l'application sont trois conteneurs (panel) différents au sein de la même page [main.aspx]. Son code de présentation est le suivant :


<%@ Page src="main.aspx.vb" inherits="main" autoeventwireup="false" Language="vb" %>
<HTML>
   <HEAD>
   </HEAD>
   <body>
       <P>Liaison de données avec un DataList</P>
       <HR width="100%" SIZE="1">
       <form runat="server" ID="Form1">
           <asp:Panel Runat="server" ID="bandeau">
               <P>Choisissez votre style :
                   <asp:RadioButton id="RadioButton1" runat="server" EnableViewState="False" Text="1" GroupName="rdstyle"
        Checked="True"></asp:RadioButton>
                   <asp:RadioButton id="RadioButton2" runat="server" EnableViewState="False" Text="2" GroupName="rdstyle"></asp:RadioButton>
                   <asp:Button id="btnChanger" runat="server" EnableViewState="False" Text="Changer"></asp:Button></P>
               <HR width="100%" SIZE="1">
           </asp:Panel>
           <asp:Panel id="vueRésultats1" runat="server">
               <P>
                   <asp:DataList id="DataList1" runat="server" BorderColor="Tan" BorderWidth="1px" BackColor="LightGoldenrodYellow"
        CellPadding="2" RepeatDirection="Horizontal" RepeatColumns="4" ForeColor="Black">
                       <SelectedItemStyle ForeColor="GhostWhite" BackColor="DarkSlateBlue"></SelectedItemStyle>
                       <HeaderTemplate>
                           Contenu de la table [liste] de la base [produits]
                       </HeaderTemplate>
                       <AlternatingItemStyle BackColor="PaleGoldenrod"></AlternatingItemStyle>
                       <ItemTemplate>
                           nom :
                           <%# Container.DataItem("nom") %>
                           <br>
                           prix :
                           <%# databinder.eval(Container.DataItem,"prix","{0:C}") %>
                           <br>
                       </ItemTemplate>
                       <FooterStyle BackColor="Tan"></FooterStyle>
                       <HeaderStyle Font-Bold="True" BackColor="Tan"></HeaderStyle>
                   </asp:DataList></P>
           </asp:Panel>
           <asp:Panel id="vueRésultats2" runat="server">
               <P>
                   <asp:DataList id="DataList2" runat="server">
                       <HeaderTemplate>
                           Contenu de la table [liste] de la base [produits]
                           <HR width="100%" SIZE="1">
                       </HeaderTemplate>
                       <AlternatingItemStyle BackColor="Teal"></AlternatingItemStyle>
                       <SeparatorStyle BackColor="LightSkyBlue"></SeparatorStyle>
                       <ItemStyle BackColor="#C0C000"></ItemStyle>
                       <ItemTemplate>
                           nom :
                           <%# Container.DataItem("nom") %>
                           , prix :
                           <%# databinder.eval(Container.DataItem,"prix","{0:C}") %>
                           <BR>
                       </ItemTemplate>
                       <SeparatorTemplate>
                           <HR width="100%" SIZE="1">
                       </SeparatorTemplate>
                       <HeaderStyle BackColor="#C0C0FF"></HeaderStyle>
                   </asp:DataList></P>
           </asp:Panel>
           <asp:Panel id="vueErreurs" runat="server">
               <P>Les erreurs suivantes se sont produites :</P>
               <%= erreursHTML %>
           </asp:Panel>
       </form>
   </body>
</HTML>

8.7.4. Configuration des composants [DataList]

Intéressons-nous aux différents attributs d'un composant [DataList]. Ils sont très nombreux et nous n'en présentons qu'une faible partie. On peut définir jusqu'à sept modèles d'affichage à l'intérieur d'un [DataList] :


HeaderTemplate

modèle de l'entête du [DataList]


ItemTemplate

modèle des lignes affichant les éléments de la liste de données associée. Seul ce modèle est obligatoire.


AlternatingItemTemplate

pour différentier visuellement les éléments successifs affichés, on peut utiliser deux modèles : ItemTemplate pour l'élément n, AlternatingItemTemplate pour l'élément n+1


SelectedItemTemplate

modèle de l'élément sélectionné dans le [DataList]


SeparatorTemplate

modèle du séparateur entre deux éléments du [DataList]


EditItemTemplate

un [DataList] rend possible la modification des valeurs qu'il affiche. [EditItemTemplate] est le modèle d'un élément du [DataList] pour lequel on est en mode "édition"


FooterTemplate

modèle du pied de page du [DataList]

Le composant [DataList1] a été construit avec [WebMatrix]. Dans sa fenêtre de propriétés on a sélectionné le lien [Mise en forme automatique] :

Image Image

Ci-dessus, le schéma [Couleur 5] va générer un [DataList] avec des styles pour les modèles suivants : HeaderTemplate (1), ItemTemplate (2, 6), AlternatingTemplate (3, 5), SelectedItemTemplate (4), FooterTemplate (7). Le code généré est le suivant :


                   <asp:DataList id="DataList1" runat="server" BorderColor="Tan" BorderWidth="1px" BackColor="LightGoldenrodYellow"
        CellPadding="2" ForeColor="Black">
                       <SelectedItemStyle ForeColor="GhostWhite" BackColor="DarkSlateBlue"></SelectedItemStyle>
                       <AlternatingItemStyle BackColor="PaleGoldenrod"></AlternatingItemStyle>
                       <FooterStyle BackColor="Tan"></FooterStyle>
                       <HeaderStyle Font-Bold="True" BackColor="Tan"></HeaderStyle>
                   </asp:DataList></P>

Aucun modèle n'est défini. C'est à nous de le faire. Nous définissons les modèles suivants :


HeaderTemplate

                       <HeaderTemplate>
                           Contenu de la table [liste] de la base [produits]
                       </HeaderTemplate>

ItemTemplate

                       <ItemTemplate>
                           nom :
                           <%# Container.DataItem("nom") %>
                           <br>
                           prix :
                           <%# DataBinder.Eval(Container.DataItem,"prix","{0:C}") %>
                           <br>
                       </ItemTemplate>

Rappelons que le modèle [ItemTemplate] est celui de l'affichage des éléments de la source de données liée au [DataList]. Cette source de données est un ensemble de lignes de données, chacune comportant une ou plusieurs valeurs. La ligne courante de la source de données est représentée par l'objet [Container.DataItem]. Une telle ligne a des colonnes. [Container.DataItem("col1")] est la valeur de la colonne "col1" de la ligne courante. Pour inclure cette valeur dans le code de présentation, on écrit <%# Container.DataItem("col") %>. Parfois, on veut présenter un élément de la ligne courante sous un format spécial. Ici, on veut afficher la colonne "prix" de la ligne courante en euros. On utilise alors la fonction [DataBinder.Eval] qui adamet trois paramètres :

  • la ligne courante [Container.DataItem]
  • le nom de la colonne à formater
  • la chaîne de formatage sous la forme {0:format} où [format] est l'un des formats acceptés par la méthode [string.format].

Ainsi le code <%# DataBinder.Eval(Container.DataItem,"prix",{0:C}) %> va faire afficher la colonne [prix] de la ligne courante sous forme monétaire (format C=Currency).

Nous aurons donc un [DataList] qui va ressembler à ceci :

Image

Ci-dessus, les données ont été placées à raison de quatre données par ligne. Cela est obtenu avec les attributs suivants de [DataList] :


RepeatDirection

Horizontal


RepeatColumns

nombre de colonnes désirées

Au final, le code de [DataList1] est celui qui a été présenté dans le code de présentation un peu plus haut. Nous laissons au lecteur le soin d'étudier le code de présentation de [DataList2]. Comme pour le composant [DataGrid], la plupart des propriétés de [DataList] peuvent être fixées à l'aide d'un assistant de [WebMatrix]. On utilise pour cela, le lien [Générateur de propriétés] de la fenêtre de propriétés du [DataList] :

Image Image

8.7.5. Les contrôleurs

Le contrôleur [global.asax, global.asax.vb] est le suivant :

[global.asax]

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

[global.asax.vb]


Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr
Imports System.Configuration
Imports System.Data
Imports System.Collections

Public Class Global
   Inherits System.Web.HttpApplication

   Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
       ' on crée un objet produits
       Try
           Dim données As DataSet = New produits(ConfigurationSettings.AppSettings("OLEDBStringConnection")).getDataSet("select * from LISTE")
           ' on met l'objet dans l'application
           Application("données") = données
           ' 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

Lorsque l'application démarre (Application_Start), nous construisons un [dataset] à partir de la classe métier [produits] et le mettons dans l'application afin qu'il soit disponible pour toutes les requêtes de tous les clients. S'il y a exception lors de cette construction, elle est notée dans l'application. La procédure [Application_Start] ne s'exécutera qu'une fois. Ensuite le contrôleur [global.asax] n'interviendra plus. C'est le contrôleur [main.aspx.vb] qui fera alors le travail :


Imports System.Collections
Imports Microsoft.VisualBasic
Imports System.Data
Imports st.istia.univangers.fr
Imports System
Imports System.Xml

Public Class main
   Inherits System.Web.UI.Page

   ' composants page
   Protected WithEvents vueErreurs As System.Web.UI.WebControls.Panel
   Protected WithEvents DataList1 As System.Web.UI.WebControls.DataList
   Protected WithEvents DataList2 As System.Web.UI.WebControls.DataList
   Protected WithEvents vueRésultats1 As System.Web.UI.WebControls.Panel
   Protected WithEvents vueRésultats2 As System.Web.UI.WebControls.Panel
   Protected WithEvents RadioButton1 As System.Web.UI.WebControls.RadioButton
   Protected WithEvents RadioButton2 As System.Web.UI.WebControls.RadioButton
   Protected WithEvents btnChanger As System.Web.UI.WebControls.Button
   Protected WithEvents bandeau As System.Web.UI.WebControls.Panel
   ' données page
   Protected erreursHTML As String

   Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
       ' on regarde si l'application est en erreur
       If CType(Application("erreur"), Boolean) Then
           ' l'application ne s'est pas initialisée correctement
           Dim erreurs As New ArrayList
           erreurs.Add("Application momentanément indisponible (" + CType(Application("message"), String) + ")")
           afficheErreurs(erreurs, False)
           Exit Sub
       End If
       '1ère requête
       If Not IsPostBack Then
           ' on initialise les contrôles
           With DataList1
               .DataSource = CType(Application("données"), DataSet)
               .DataBind()
           End With
           With DataList2
               .DataSource = CType(Application("données"), DataSet)
               .DataBind()
           End With
           ' on affiche le formulaire vide
           afficheRésultats(True, False)
       End If
   End Sub

   Private Sub afficheErreurs(ByVal erreurs As ArrayList, ByVal afficheLien As Boolean)
       ' affiche la vue erreurs
       erreursHTML = ""
       For i As Integer = 0 To erreurs.Count - 1
           erreursHTML += "<li>" + erreurs(i).ToString + "</li>" + ControlChars.CrLf
       Next
       ' on affiche la vue [erreurs]
       vueErreurs.Visible = True
       vueRésultats1.Visible = False
       vueRésultats2.Visible = False
       bandeau.Visible = False
   End Sub

   Private Sub afficheRésultats(ByVal visible1 As Boolean, ByVal visible2 As Boolean)
       ' on affiche la vue [résultats]
       vueRésultats1.Visible = visible1
       vueRésultats2.Visible = visible2
       vueErreurs.Visible = False
   End Sub

   Private Sub btnChanger_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnChanger.Click
       ' on change le style
       afficheRésultats(RadioButton1.Checked, RadioButton2.Checked)
   End Sub
End Class

8.8. Composant Repeater et liaison de données

Le composant [Repeater] permet de répéter le code HTML de chacun des éléments d'une liste de données. Supposons qu'on veuille afficher une liste d'erreurs sous la forme suivante :

Image

Nous avons déjà rencontré ce problème et nous l'avons résolu en mettant dans le code de présentation une variable sous la forme <ul><% =erreursHTML %></ul>, la valeur de erreursHTML étant calculée par le contrôleur. Cette valeur contient du code HTML, celle d'une liste. Cela présente l'inconvénient que si on veut modifier la présentation de cette liste HTML on est obligé d'aller dans la partie contrôleur, ce qui va à l'encontre la séparation contrôleur/présentation. Le composant [Repeater] nous apporte une solution. Comme pour le [DataList], on peut définir les modèles <HeaderTemplate> pour l'entête, <ItemTemplate> pour l'élément courant de la liste de données et <FooterTemplate> pour la fin des données. Ici, nous pourrions avoir la définition suivante pour le composant [Repeater] :

            <asp:Repeater id="Repeater1" runat="server" EnableViewState="False">
                <HeaderTemplate>
                    Les erreurs suivantes se sont produites :
                    <ul>
                </HeaderTemplate>
                <ItemTemplate>
                    <li>
                        <%# Container.DataItem %>
                    </li>
                </ItemTemplate>
                <FooterTemplate>
                    </ul>
                </FooterTemplate>
            </asp:Repeater>

On rappelle que [Container.DataItem] représente une ligne de données si la source de données a plusieurs colonnes. Elle représente une donnée si la source n'a qu'une colonne. Ce sera le cas ici. Pour exemple, nous construisons l'application suivante :

Image

Le code de présentation de la page est le suivant :

<html>
<head>
</head>
<body>
    <form runat="server">
        <p>
            Liaison de données avec un composant [Repeater]
        </p>
        <hr />
        <p>
            <asp:Repeater id="Repeater1" runat="server" EnableViewState="False">
                <ItemTemplate>
                    <li>
                        <%# Container.DataItem %>
                    </li>
                </ItemTemplate>
                <HeaderTemplate>
                    Les erreurs suivantes se sont produites :
                    <ul>
                </HeaderTemplate>
                <FooterTemplate>
                    </ul>
                </FooterTemplate>
            </asp:Repeater>
        </p>
    </form>
</body>
</html>

Le code de contrôle est le suivant :

<%@ Page Language="VB" %>
<script runat="server">

    ' procédure exécutée au chargement de la page
    Sub page_Load(sender As Object, e As EventArgs)
        if not IsPostBack then
          ' on crée une source de données
          with Repeater1
              .DataSource=createDataSource
              .DataBind
          end with
        end if
    End Sub

    function createDataSource as ArrayList
      ' crée un arraylist
      dim erreurs as new ArrayList
      dim i as integer
      for i=0 to 5
        erreurs.add("erreur-"+i.ToString)
      next
      return erreurs
    end function

</script>
<html>
...
</html>

A la première requête du client, nous associons un objet [ArrayList] au composant [Repeater] supposé représenter une liste d'erreurs.

8.9. Application

Nous reprenons ici, une application déjà traitée avec des composants serveur. L'application permet de faire des simulations de calculs d'impôt. Elle s'appuie sur une classe [impot] qu'on ne rappellera pas ici. Cette classe a besoin de données qu'elle trouve dans une source de données OLEDB. Pour l'exemple, ce sera une source ACCESS. Nous amenons les nouveautés suivantes dans cette nouvelle version :

  • l'usage de composants de validation pour vérifier la validité des données
  • l'usage de composants serveur liés à des sources de données pour l'affichage des résultats

8.9.1. La structure MVC de l'application

La structure MVC de l'application est la suivante :

Image

Les trois vues seront incorporées dans le code de présentation du contrôleur [main.aspx] sous la forme de conteneurs. Aussi cette application a-t-elle une unique page [main.aspx].

8.9.2. Les vues de l'application

La vue [formulaire] est le formulaire de saisie des informations permettant le calcul de l'impôt d'un utilisateur :

Image

L'utilisateur remplit le formulaire :

Image

Il utilise le bouton [Envoyer] pour demander le calcul de son impôt. Il obtient la vue [simulations] suivante :

Image

Il retourne au formulaire par le lien ci-dessus. Il le retrouve dans l'état où il l'a saisi. Il peut faire des erreurs de saisie :

Image

Celles-ci lui sont signalées sur la vue [formulaire] :

Image

L'utilise corrige alors ses erreurs. Il peut faire de nouvelles simulations :

Image

Il obtient alors la vue [simulations] avec une simulation de plus :

Image

Enfin, si la source de données n'est pas disponible, cela est signalé à l'utilisateur dans la vue [erreurs] :

Image

8.9.2.1. Le code de présentation

Rappelons que la page [main.aspx] rassemble toutes les vues. C'et un unique formulaire avec trois conteneurs :

  • [panelform] pour la vue [formulaire]
  • [panelerreurs] pour la vue [erreurs]
  • [panelsimulations] pour la vue [simulations]

Nous détaillons maintenant les composants de ces trois conteneurs. Le conteneur [panelform] a la représentation visuelle suivante :

Image

nom

type

propriétés

rôle


0

panelform

Panel

 

vue formulaire


1

rdOui
rdNon

RadioButton

GroupName=rdmarie

boutons radio


2

cvMarie

CustomValidator

ErrorMessage=Vous n'avez pas indiqué votre état marital

EnableClientScript=false

vérifie que le programme client a bien envoyé le statut marital attendu


3

txtEnfants

TextBox

 

nombre d'enfants


4

rfvEnfants

RequiredFieldValidator

ErrorMessage=Indiquez le nombre d'enfants

ControlToValidate=txtEnfants

vérifie que le champ [txtEnfants] n'est pas vide


5

rvEnfants

RangeValidator

ErrorMessage=Tapez un nombre entre 1 et 30

ControlToValidate=txtEnfants

vérifie que le champ [txtEnfants] est dans l'intervalle [1,30]


6

txtSalaire

TextBox

EnableViewState=true

salaire annuel


7

rfvSalaire

RequiredFieldValidator

ControlToValidate=txtSalaire

ErrorMessage=Indiquez le montant de votre salaire

vérifie que le champ [txtSalaire] n'est pas vide


8

revSalaire

RegularExpressionValidator

ControlToValidate=txtSalaire

ErrorMessage=Salaire invalide

RegularExpression=\s*\d+\s*

vérifie que le champ [txtSalaire] est une suite de chiffes


9

btnCalculer

Button

CausesValidation=true

bouton [submit] du formulaire - lance le calcul de l'impôt


10

btnEffacer

Button

CausesValidation=false

bouton [submit] du formulaire - vide le formulaire

On pourra s'étonner du contrôle [cvMarié] chargé de vérifier que l'utilisateur a bien coché l'un des deux boutons radio. Il ne peut en effet en être autrement s'il utilise bien le formulaire envoyé par le serveur. Comme rien ne peut l'assurer, on est obligés de vérifier tous les paramètres postés. On notera également l'attribut [CausesValidation=false] du bouton [btnEffacer]. En effet, lorsque l'utilisateur utilise ce bouton on ne doit pas vérifier les données postées puisqu'elles vont être ignorées.

Tous les composants du conteneur ont la propiété [EnableViewState=false] sauf [panelForm, txtEnfants, txtSalaire, rdOui, rdNon]. Le conteneur [panelerreurs] a la reprsésentation visuelle suivante :

Image

nom

type

propriétés

rôle


0

panelerreurs

Panel

EnableViewState=false

vue erreurs


1

rptErreurs

Repeater

EnableViewState=false

affiche une liste d'erreurs

Le composant [rptErreurs] est défini de la façon suivante :


               <asp:Repeater id="rptErreurs" runat="server" EnableViewState="False">
                   <ItemTemplate>
                       <li>
                           <%# Container.Dataitem%>
                       </li>
                   </ItemTemplate>
                   <HeaderTemplate>
                       Les erreurs suivantes se sont produites
                       <ul>
                   </HeaderTemplate>
                   <FooterTemplate>
                       </ul>
                   </FooterTemplate>
               </asp:Repeater>

La liste de données associée au composant [rptErreurs] sera un objet [ArrayList] contenant une liste de messages d'erreurs. Ainsi ci-dessus, <%# Container.Dataitem%> désigne le message d'erreur courant. Le conteneur [panelsimulations] a la représentation visuelle suivante :

Image

nom

type

propriétés

rôle


0

panelsimulations

Panel

EnableViewState=false

vue simulations


2

lnkForm2

LinkButton

EnableViewState=false

lien vers le formulaire


1

dgSimulations

DataGrid

EnableViewState=false

chargé d'afficher les simulations placées dans un objet [DataTable]

Le composant [dgSimulations] est configuré de la façon suivante sous [Webmatrix]. Dans la fenêtre de propriétés de [dgSimulations], nous sélectionnons le lien [Mise en forme automatique] et sélectionnons le schéma [Couleur 3] :

Image Image

Puis, toujours dans la fenêtre de propriétés de [dlgSimulations], nous sélectionnons le lien [Générateur de propriétés]. Nous demandons l'affichage de l'en-tête des colonnes :

Image

Nous sélectionnons l'option [Colonnes] pour définir les quatre colonnes du [DataGrid] :

Image

Nous décochons la phrase [Créer des colonnes automatiquement ...]. Nous allons définir nous-mêmes les quatre colonnes du [DataGrid]. Celui-ci sera associé à un objet [DataTable] à quatre colonnes nommées respectivement "marié", "enfants", "salaire", "impôt". Pour créer une colonne dans le [DataGrid], nous sélectionnons l'option [Colonnes connexe] et utilisons le bouton de création comme indiqué ci-dessus. Une colonne est créée et nous pouvons définir ses caractéristiques. La première colonne du tableau des simulations contiendra la colonne "marié" de l'objet [DataTable] qui sera associé au [DataGrid]. Cette première colonne du [DataGrid] est définie comme suit dans l'assistant :

Image


Texte de l'en-tête

titre de la colonne, ici "Marié"


Champ de données

nom de la colonne de la source de données qui sera visualisée par cette colonne du [DataGrid]. Ici, c'est la colonne "marié" du [DataTable].

La deuxième colonne est définie comme suit :


Texte de l'en-tête

Enfants


Champ de données

enfants

La troisème colonne est définie comme suit :


Texte de l'en-tête

Salaire annuel


Champ de données

salaire


Expression de mise en forme

{0:C} - affichage monétaire pour obtenir le signe euro

La quatrième colonne est définie comme suit :


Texte de l'en-tête

Montant de l'impôt


Champ de données

impôt


Expression de mise en forme

{0:C} - affichage monétaire pour obtenir le signe euro

Nous mettons le code de de présentation et le code de contrôle dans deux fichiers séparés. Le premier sera dans [main.aspx] et le second dans [main.aspx.vb]. Le code de [main.aspx] est le suivant :


<%@ page src="main.aspx.vb" inherits="main" AutoEventWireUp="false" %>
<HTML>
   <HEAD>
       <title>Calculer votre impôt</title>
   </HEAD>
   <body>
       <P>Calculer votre impôt</P>
       <HR width="100%" SIZE="1">
       <FORM id="Form1" runat="server">

           <asp:panel id="panelform" Runat="server">
               <TABLE id="Table1" cellSpacing="1" cellPadding="1" border="0">
                   <TR>
                       <TD height="19">Etes-vous marié(e)</TD>
                       <TD height="19">
                           <asp:RadioButton id="rdOui" runat="server" GroupName="rdMarie"></asp:RadioButton>Oui
                           <asp:RadioButton id="rdNon" runat="server" GroupName="rdMarie" Checked="True"></asp:RadioButton>Non
                           <asp:CustomValidator id="cvMarie" runat="server" ErrorMessage="Vous n'avez pas indiqué votre état marital"
          Display="Dynamic" EnableClientScript="False" Visible="False" EnableViewState="False"></asp:CustomValidator></TD>
                   </TR>
                   <TR>
                       <TD>Nombre d'enfants</TD>
                       <TD>
                           <asp:TextBox id="txtEnfants" runat="server" Columns="3" MaxLength="3"></asp:TextBox>
                           <asp:RequiredFieldValidator id="rfvEnfants" runat="server" ErrorMessage="Indiquez le nombre d'enfants" Display="Dynamic"
          ControlToValidate="txtEnfants" EnableViewState="False"></asp:RequiredFieldValidator>
                           <asp:RangeValidator id="rvEnfants" runat="server" ErrorMessage="Tapez un nombre entre 1 et 30" Display="Dynamic"
          ControlToValidate="txtEnfants" Type="Integer" MaximumValue="30" MinimumValue="0" EnableViewState="False"></asp:RangeValidator></TD>
                   </TR>
                   <TR>
                       <TD>Salaire annuel (euro)</TD>
                       <TD>
                           <asp:TextBox id="txtSalaire" runat="server" Columns="10" MaxLength="10"></asp:TextBox>
                           <asp:RequiredFieldValidator id="rfvSalaire" runat="server" ErrorMessage="Indiquez le montant de votre salaire"
          Display="Dynamic" ControlToValidate="txtSalaire" EnableViewState="False"></asp:RequiredFieldValidator>
                           <asp:RegularExpressionValidator id="revSalaire" runat="server" ErrorMessage="Salaire invalide" Display="Dynamic"
          ControlToValidate="txtSalaire" ValidationExpression="\s*\d+\s*" EnableViewState="False"></asp:RegularExpressionValidator></TD>
                   </TR>
               </TABLE>
               <P>
                   <asp:Button id="btnCalculer" runat="server" Text="Calculer"></asp:Button>
                   <asp:Button id="btnEffacer" runat="server" Text="Effacer" CausesValidation="False"></asp:Button></P>
           </asp:panel>

      <asp:panel id="panelerreurs" runat="server" EnableViewState="False">
               <asp:Repeater id="rptErreurs" runat="server" EnableViewState="False">
                   <ItemTemplate>
                       <li>
                           <%# Container.Dataitem%>
                       </li>
                   </ItemTemplate>
                   <HeaderTemplate>
                       Les erreurs suivantes se sont produites
                       <ul>
                   </HeaderTemplate>
                   <FooterTemplate>
                       </ul>
                   </FooterTemplate>
               </asp:Repeater>
           </asp:panel>
 
     <asp:panel id="panelsimulations" runat="server" EnableViewState="False">
               <P>
                   <asp:DataGrid id="dgSimulations" runat="server" BorderColor="#DEBA84" BorderStyle="None" CellSpacing="2"
        BorderWidth="1px" BackColor="#DEBA84" CellPadding="3" AutoGenerateColumns="False" EnableViewState="False">
                       <SelectedItemStyle Font-Bold="True" ForeColor="White" BackColor="#738A9C"></SelectedItemStyle>
                       <ItemStyle ForeColor="#8C4510" BackColor="#FFF7E7"></ItemStyle>
                       <HeaderStyle Font-Bold="True" ForeColor="White" BackColor="#A55129"></HeaderStyle>
                       <FooterStyle ForeColor="#8C4510" BackColor="#F7DFB5"></FooterStyle>
                       <Columns>
                           <asp:BoundColumn DataField="mari&#233;" HeaderText="Mari&#233;"></asp:BoundColumn>
                           <asp:BoundColumn DataField="enfants" HeaderText="Enfants"></asp:BoundColumn>
                           <asp:BoundColumn DataField="salaire" HeaderText="Salaire annuel" DataFormatString="{0:C}"></asp:BoundColumn>
                           <asp:BoundColumn DataField="imp&#244;t" HeaderText="Montant de l'imp&#244;t" DataFormatString="{0:C}"></asp:BoundColumn>
                       </Columns>
                       <PagerStyle HorizontalAlign="Center" ForeColor="#8C4510" Mode="NumericPages"></PagerStyle>
                   </asp:DataGrid></P>
               <P>
                   <asp:LinkButton id="lnkForm2" runat="server" EnableViewState="False">Retour au formulaire</asp:LinkButton></P>
           </asp:panel>

    </FORM>
   </body>
</HTML>

8.9.3. Le code de contrôle de l'application

Le code de contrôle de l'application se trouve réparti dans les fichiers [global.asax.vb] et [main.aspx.vb]. Le fichier [global.asax] est défini comme suit :

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

Le fichier [global.asax.vb] est le suivant :


Imports System
Imports System.Web
Imports System.Web.SessionState
Imports st.istia.univangers.fr
Imports System.Configuration
Imports System.Collections
Imports System.Data

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

   Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
       ' début de session - on crée une liste de simulations vides
       Dim simulations As New DataTable("simulations")
       simulations.Columns.Add("marié", Type.GetType("System.String"))
       simulations.Columns.Add("enfants", Type.GetType("System.String"))
       simulations.Columns.Add("salaire", Type.GetType("System.Int32"))
       simulations.Columns.Add("impôt", Type.GetType("System.Int32"))
       Session.Item("simulations") = simulations
   End Sub
End Class

Lorsque l'application démarre (1ère requête faite à l'application), la procédure [Application_Start] est exécutée. Elle cherche à créer un objet de type [impot] prenant ses données dans une source OLEDB. Le lecteur est invité à lire le chapitre 5 où cette classe a été définie, s'il l'a oubliée. La construction de l'objet [impot] peut échouer si la source de données n'est pas disponible. Dans ce cas, l'erreur est mémorisée dans l'application afin que toutes les requêtes ultérieures sachent que celle-ci n'a pu s'initialiser correctement. Si la construction se passe bien, l'objet [impot] créé est lui aussi mémorisé dans l'application. Il sera utilisé par toutes les requêtes de calcul d'impôt. Lorsqu'un client fait sa première requête, une session est créée pour lui par la procédure [Application_Start]. Cette session est destinée à mémoriser les différentes simulations de calcul d'impôt qu'il va faire. Celles-ci seront mémorisées dans un objet [DataTable] associé à la clé de session "simulations". Lorsque la session démarre, cette clé est associée à un objet [DataTable] vide mais dont la structure a été définie. Les informations nécessaires à l'application sont placées dans son fichier de configuration [wenConfig] :


<?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\devel\aspnet\poly\webforms2\vs\impots6\impots.mdb" />
   </appSettings>
</configuration>

La clé [chaineConnexion] désigne la chaîne de connexion à la source OLEDB. L'autre partie du code de contrôle se trouve dans [main.aspx.vb] :


Imports System.Collections
Imports Microsoft.VisualBasic
Imports st.istia.univangers.fr
Imports System
Imports System.Web.UI.Control
Imports System.Data

Public Class main
   Inherits System.Web.UI.Page

   Protected WithEvents rdOui As System.Web.UI.WebControls.RadioButton
   Protected WithEvents rdNon As System.Web.UI.WebControls.RadioButton
   Protected WithEvents txtEnfants As System.Web.UI.WebControls.TextBox
   Protected WithEvents txtSalaire As System.Web.UI.WebControls.TextBox
   Protected WithEvents btnCalculer As System.Web.UI.WebControls.Button
   Protected WithEvents btnEffacer As System.Web.UI.WebControls.Button
   Protected WithEvents panelform As System.Web.UI.WebControls.Panel
   Protected WithEvents lnkForm2 As System.Web.UI.WebControls.LinkButton
   Protected WithEvents panelerreurs As System.Web.UI.WebControls.Panel
   Protected WithEvents rfvEnfants As System.Web.UI.WebControls.RequiredFieldValidator
   Protected WithEvents rvEnfants As System.Web.UI.WebControls.RangeValidator
   Protected WithEvents rfvSalaire As System.Web.UI.WebControls.RequiredFieldValidator
   Protected WithEvents rptErreurs As System.Web.UI.WebControls.Repeater
   Protected WithEvents dgSimulations As System.Web.UI.WebControls.DataGrid
   Protected WithEvents revSalaire As System.Web.UI.WebControls.RegularExpressionValidator
   Protected WithEvents cvMarie As System.Web.UI.WebControls.CustomValidator
   Protected WithEvents panelsimulations As System.Web.UI.WebControls.Panel

   ' variables locales

   Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
...
   End Sub

   Private Sub afficheErreurs(ByRef erreurs As ArrayList)
...
   End Sub

   Private Sub afficheFormulaire()
...
   End Sub

   Private Sub afficheSimulations(ByRef simulations As DataTable, ByRef lien As String)
...
   End Sub

   Private Sub btnCalculer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCalculer.Click
....
   End Sub

   Private Sub lnkForm1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
...
   End Sub

   Private Sub lnkForm2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm2.Click
...
   End Sub

   Private Sub btnEffacer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnEffacer.Click
...
   End Sub

   Private Sub razForm()
...
   End Sub

   Private Sub cvMarie_ServerValidate(ByVal source As System.Object, ByVal args As System.Web.UI.WebControls.ServerValidateEventArgs)
...
   End Sub
End Class

Le premier événement traité par le code est [Page_Load] :


   Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
       ' tout d'abord, on regarde dans quel état est l'application
       If CType(Application("erreur"), Boolean) Then
           ' l'application n'a pas pu s'initialiser
           ' on affiche la vue erreurs
           Dim erreurs As New ArrayList
           erreurs.Add("Application momentanément indisponible (" + CType(Application("message"), String) + ")")
           afficheErreurs(erreurs)
           Exit Sub
       End If
       ' pas d'erreurs - à la 1ère requête, on présente le formulaire
       If Not IsPostBack Then afficheFormulaire()
   End Sub

Avant de commencer à traiter la requête, nous nous assurons que l'application a pu s'initialiser correctement. Si ce n'est pas le cas, on affiche la vue [erreurs] avec la procédure [afficheErreurs]. Si c'est la première requête (IsPostBack=false), nous faisons afficher la vue [formulaire] avec [afficheFormulaire].

La procédure affichant la vue [erreurs] est la suivante :


   Private Sub afficheErreurs(ByRef erreurs As ArrayList)
       ' construit la liste des erreurs
       With rptErreurs
           .DataSource = erreurs
           .DataBind()
       End With
       ' affichage des conteneurs 
       panelerreurs.Visible = True
       panelform.Visible = False
       panelsimulations.Visible = False
       Exit Sub
   End Sub

La procédure reçoit comme paramètre une liste de messages d'erreurs dans [erreurs] de type [ArrayList]. Nous nous contentons de lier cette source de données au composant [rptErreurs] chargé de l'afficher.

La procédure affichant la vue [formulaire] est la suivante :


   Private Sub afficheFormulaire()
       ' affiche le formulaire
       panelform.Visible = True
       ' les autres conteneurs sont cachés
       panelerreurs.Visible = False
       panelsimulations.Visible = False
   End Sub

Cette procédure se contente de rendre visible le conteneur [panelform]. Les composants sont affichés avec leur valeur postée ou précédente (VIEWSTATE).

Lorsque l'utilisateur clique sur le bouton [Calculer] de la vue [formulaire], un POST vers [main.aspx] est fait. La procédure [Page_Load] est exécutée puis la procédure [btnCalculer_Click] :


   Private Sub btnCalculer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCalculer.Click
       ' on vérifie la validité des données saisies, s'il y a des erreurs, on le signale
       If Not Page.IsValid Then
           afficheFormulaire()
           Exit Sub
       End If
       ' pas d'erreurs - on calcule l'impôt
       Dim impot As Long = CType(Application("objImpot"), impot).calculer( _
       rdOui.Checked, CType(txtEnfants.Text, Integer), CType(txtSalaire.Text, Long))
       ' on rajoute le résultat aux simulations existantes
       Dim simulations As DataTable = CType(Session.Item("simulations"), DataTable)
       Dim simulation As DataRow = simulations.NewRow
       simulation("marié") = CType(IIf(rdOui.Checked, "oui", "non"), String)
       simulation("enfants") = txtEnfants.Text.Trim
       simulation("salaire") = CType(txtSalaire.Text.Trim, Long)
       simulation("impôt") = impot
       simulations.Rows.Add(simulation)
       ' on met les simulations dans la session
       Session.Item("simulations") = simulations
       ' on affiche la page des simulations
       afficheSimulations(simulations, "Retour au formulaire")
   End Sub

La procédure commence par vérifier la validité de la page. Rappelons ici que lorsque la procédure [btnCalculer_Click] s'exécute, les contrôles de validation ont fait leur travail et l'attribut [IsValid] de la page a été positionné. Si la page n'est pas valide, alors la vue [formulaire] est affichée de nouveau et la procédure est terminée. Les composants de validation de la vue [formulaire] ayant leur attribut [IsValid] à [false] afficheront leur attribut [ErrorMessage]. Si la page est valide, alors le montant de l'impôt est calculé grâce à l'objet de type [impot] qui avait été stocké dans l'application au démarrage de celle-ci. Cette nouvelle simulation est ajoutée à la liste des simulations déjà faites et stockée dans la session.

Enfin la vue [simulations] est affichée par la procédure [afficheSimulations] suivante :


   Private Sub afficheSimulations(ByRef simulations As DataTable, ByRef lien As String)
       ' on lie le datagrid à la source simulations
       With dgSimulations
           .DataSource = simulations
           .DataBind()
       End With
       ' lien
       lnkForm2.Text = lien
       ' les vues
       panelsimulations.Visible = True
       panelerreurs.Visible = False
       panelform.Visible = False
   End Sub

La procédure a deux paramètres :

  • une liste de simulations dans [simulations] de type [DataTable]
  • un texte de lien dans [lien]

La source de données [simulations] est liée au composant chargé de l'afficher. Le texte du lien est lui placé dans la propriété [Text] de l'objet [LinkButton] de la vue.

Lorsque l'utilisateur clique sur le bouton [Effacer] de la vue [formulaire], c'est la procédure [btnEffacer_click] qui s'exécute (toujours après [Page_Load]) :


   Private Sub btnEffacer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnEffacer.Click
       ' affiche le formulaire vide
       razForm()
       afficheFormulaire()
   End Sub

   Private Sub razForm()
       ' vide le formulaire
       rdOui.Checked = False
       rdNon.Checked = True
       txtEnfants.Text = ""
       txtSalaire.Text = ""
   End Sub

Le code ci-dessus est suffisamment simple pour ne pas avoir à être commenté. Il nous reste à gérer le clic sur les liens des vues [erreurs] et [simulations] :


   Private Sub lnkForm1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm1.Click
       ' affiche le formulaire
       afficheFormulaire()
   End Sub

   Private Sub lnkForm2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lnkForm2.Click
       ' affiche le formulaire
       afficheFormulaire()
   End Sub

Les deux procédures se contentent de faire afficher la vue [formulaire]. Nous savons que les champs de celle-ci vont obtenir une valeur qui est soit la valeur postée pour eux soit leur valeur précédente. Comme ici, le POST du client n'envoie aucune valeur pour les champs du formulaire, ceux-ci vont retrouver leur valeur précédente. Le formulaire est donc affiché avec les valeurs saisies par l'utilisateur.

8.9.4. Tests

Tous les fichiers nécessaires à l'application sont placés dans un dossier <application-path> :

Image

Le dossier [bin] contient la dll contenant les classes [impot], [impotsData], [impotsOLEDB] nécessaires à l'application :

Image

Le lecteur pourra, s'il le souhaite, relire le chapitre 5 où est expliquée la façon de créer le fichier [impot.dll] ci-dessus. Ceci fait, le serveur Cassini est lancé avec les paramètres (<application-path>,/impots6). On demande l'url [http://impots6/main.aspx] avec un navigateur :

Image

Si on renomme le fichier ACCESS [impots.mdb] en [impots1.mdb], on aura la page suivante :

Image

8.9.5. Conclusion

Nous avons une application MVC n'utilisant que des composants serveurs. L'usage de ceux-ci a permis de séparer complètement la partie présentation de l'application de sa partie contrôle. Cela n'avait pas encore été possible jusqu'à maintenant où le code de présentation contenait des variables calculées par le code de contrôle et ayant une valeur contenant du code HTML. Maintenant ce n'est plus le cas.