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 :

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 :

ASP.NET offre des composants appelés composants de validation qui permettent de vérifier les cas suivants :
|
Composant |
Rôle |
|
vérifie qu'un champ est non vide |
|
vérifie deux valeurs entre-elles |
|
vérifie q'une valeur est entre deux bornes |
|
vérifie qu'un champ vérifie une expression régulière |
|
permet au développeur de donner ses propres règles de validation - ce composant pourrait remplacer tous les autres |
|
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 :
|
|
n° |
nom |
type |
propriétés |
rôle |
|
1 |
|
TextBox |
EnableViewState=true |
champ de saisie |
|
2 |
|
RequiredFieldValidator |
EnableViewState=false EnableClientScript=true ErrorMessage=Le champ [nom] est obligatoire |
composant de validation |
|
3 |
|
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 :
|
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é. |
|
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. |
|
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] :

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" />
</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 :

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 :

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

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 :

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 :

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 :

|
n° |
nom |
type |
propriétés |
rôle |
|
1 |
|
DropDownList |
EnableViewState=true |
liste déroulante |
|
2 |
|
DropDownList |
EnableViewState=true |
liste déroulante |
|
3 |
|
CompareValidator |
EnableViewState=false EnableClientScript=true ErrorMessage=Choix 1 invalide ValeurToCompare=? Operator=NotEqual Type=string |
composant de validation |
|
4 |
|
CompareValidator |
EnableViewState=false EnableClientScript=true ErrorMessage=Choix 2 invalide ControlToCompare=? Operator=NotEqual Type=string |
composant de validation |
|
5 |
|
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 :
|
champ dont le contenu doit être contrôlé par le composant |
|
booléen - à vrai indique que le contenu du champ précédent doit être contrôlé également côté client |
|
le message d'erreur que le composant doit afficher en cas d'erreur détectée |
|
valeur à laquelle doit être comparée la valeur du champ [ControlToValidate] |
|
composant à la valeur duquel doit être comparée la valeur du champ [ControlToValidate] |
|
opérateur de comparaison entre les deux valeurs |
|
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 :

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 :

|
n° |
nom |
type |
propriétés |
rôle |
|
|
DropDownList |
EnableViewState=true |
liste déroulante |
|
|
CompareValidator |
EnableViewState=false EnableClientScript=true ErrorMessage=Diplôme invalide Operator=NotEqual ValueToCompare=? Type=String |
composant de validation du contrôle [1] |
|
|
TextBox |
EnableViewState=false |
champ de saisie |
|
|
CustomValidator |
EnableViewState=false EnableClientScript=true ErrorMessage=Précision diplôme invalide ClientValidationFunction=chkAutreDiplome |
composant de validation du champ [3] |
|
|
TextBox |
EnableViewState=false |
champ de saisie |
|
|
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] |
|
|
RequiredFieldValidator |
EnableViewState=false EnableClientScript=true ErrorMessage=Année diplôme requise ControlToValidate=txtAnDiplome |
composant de validation du champ [5] |
|
|
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 :
|
champ dont la valeur doit être contrôlée par le composant |
|
booléen - à vrai indique que le contenu du champ précédent doit être contrôlé également côté client |
|
le message d'erreur que le composant doit afficher en cas d'erreur détectée |
|
valeur min de la valeur du champ à contrôler |
|
valeur max de la valeur du champ à contrôler |
|
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 :
|
champ dont la valeur doit être contrôlée par le composant |
|
booléen - à vrai indique que le contenu du champ précédent doit être contrôlé également côté client |
|
le message d'erreur que le composant doit afficher en cas d'erreur détectée |
|
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îtrise">Maî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] :

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 :

|
n° |
nom |
type |
propriétés |
rôle |
|
|
TextBox |
EnableViewState=true |
champ de saisie |
|
|
RequiredFieldValidator |
ControlToValidate=txtMel Display=Dynamic |
composant de validation du contrôle [1] |
|
|
RegularExpressionValidator |
ControlToValidate=txtMel Display=Dynamic |
composant de validation du contrôle [1] |
|
|
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 :
|
champ dont la valeur doit être contrôlée par le composant |
|
booléen - à vrai indique que le contenu du champ précédent doit être contrôlé également côté client |
|
le message d'erreur que le composant doit afficher en cas d'erreur détectée |
|
l'expression régulière à laquelle le contenu de [ControlToValidate] va être comparé |
|
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 :

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

|
n° |
nom |
type |
propriétés |
rôle |
|
1 |
|
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 |
|
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 :

|
n° |
nom |
type |
propriétés |
rôle |
|
1 |
|
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 :

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

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 :

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

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 :
|
<option value="V">T</option> |
|
<input type="checkbox" value="V">T |
|
<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 :
|
une source de données [Array], [ArrayList], [DataTable], [DataSet], [HashTable], ... |
|
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. |
|
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] |
|
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 :
|
[ListControl].DataSource=A les champs [Text] et [Value] des éléments de [ListControl] auront pour valeurs les éléments de A |
|
[ListControl].DataSource=AL les champs [Text] et [Value] des éléments de [ListControl] auront pour valeurs les éléments de AL |
|
[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 |
|
[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" |
|
[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 :
|
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 |
|
Array |
|
ArrayList |
|
DataTable |
|
DataSet |
|
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 :
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 :
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 :
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] :
|
une source de données [Array], [ArrayList], [DataTable], [DataSet], ... |
|
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 :

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

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 :

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

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

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 :
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 :
|
|
n° |
nom |
type |
propriétés |
rôle |
|
1 |
|
DataGrid |
EnableViewState=true |
affiche une source de données S |
|
2 |
|
DataGrid |
EnableViewState=true |
affiche la même source S que [DataGrid1] |
|
3 |
|
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 :

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 :

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 :
|
|
n° |
nom |
type |
propriétés |
rôle |
|
1 |
|
DataGrid |
EnableViewState=true |
affiche une source de données S |
|
2 |
|
DataGrid |
EnableViewState=false |
affiche la même source S que [DataGrid1] |
|
3 |
|
Button |
EnableViewState=false |
bouton [submit] |
|
4 |
|
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 :

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 :
<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 :
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 :
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 :
pour mémoriser la nouvelle valeur de [numRequête3] dans la session. En revanche, il faudrait écrire :
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 :
|
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 :

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 :
|
|
n° |
nom |
type |
propriétés |
rôle |
|
1 |
|
TextBox |
EnableViewState=true |
champ de saisie de la requête select |
|
2 |
|
RequiredFieldValidator |
EnableViewState=false |
vérifie la présence de 1 |
|
3 |
|
TextBox |
EnableViewState=true |
champ de saisie - indique le nombre de lignes de données à afficher par page de résultats |
|
4 |
|
RequiredFieldValidator |
EnableViewState=false |
vérifie la présence de 3 |
|
5 |
|
RangeValidator |
EnableViewState=false |
vérifie que (3) est dans l'intervalle [1,30] |
|
6 |
|
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 :
|
|
n° |
nom |
type |
propriétés |
rôle |
|
1 |
|
Label |
EnableViewState=false |
champ d'information |
|
2 |
|
RadioButton |
EnableViewState=false GroupName=rdTri |
permet de choisir un sens de tri |
|
3 |
|
DataGrid |
EnableViewState=true AllowPaging=true AllowSorting=true |
table d'affichage du résultat du select |
|
4 |
|
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.

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

La vue [erreurs] est affichée :
|
|
n° |
nom |
type |
propriétés |
rôle |
|
1 |
|
variable |
code HTML nécessaire à l'affichage des erreurs |
|
|
3 |
|
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> 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écé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écédent" ...></PagerStyle>
</asp:DataGrid></P>
|
autorise la pagination |
|
quatre lignes de données par page |
|
Le texte du lien pour aller à la page suivante de la source de données |
|
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] :

On obtient l'assistant suivant :

On prend l'option [Pagination] :

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]
[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 :
|
à vrai si la donnée vérifiée est valide, à faux sionon |
|
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
|
l'objet origine de l'événement - ici, l'un des liens [Suivant] ou [Précédent] |
|
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
|
l'objet origine de l'événement - ici, l'un des liens [Suivant] ou [Précédent] |
|
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 :
|
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 :
|
|
n° |
nom |
type |
propriétés |
rôle |
|
1 |
|
RadioButton |
EnableViewState=false |
permet de choisir un style de [DataList] parmi deux |
|
2 |
|
Button |
EnableViewState=false |
bouton [submit] |
|
3 |
|
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 :
|
|
n° |
nom |
type |
propriétés |
rôle |
|
1 |
|
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 :
|
|
n° |
nom |
type |
propriétés |
rôle |
|
1 |
|
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] :
|
modèle de l'entête du [DataList] |
|
modèle des lignes affichant les éléments de la liste de données associée. Seul ce modèle est obligatoire. |
|
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 |
|
modèle de l'élément sélectionné dans le [DataList] |
|
modèle du séparateur entre deux éléments du [DataList] |
|
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" |
|
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] :
|
|
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 :
|
|
|
|
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 :

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] :
|
Horizontal |
|
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] :
|
|
8.7.5. Les contrôleurs
Le contrôleur [global.asax, global.asax.vb] est le suivant :
[global.asax]
[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 :

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 :

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

L'utilisateur remplit le formulaire :

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

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 :

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

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

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

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

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 :
|
|
n° |
nom |
type |
propriétés |
rôle |
|
|
Panel |
vue formulaire |
|
|
|
RadioButton |
GroupName=rdmarie |
boutons radio |
|
|
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 |
|
|
TextBox |
nombre d'enfants |
|
|
|
RequiredFieldValidator |
ErrorMessage=Indiquez le nombre d'enfants ControlToValidate=txtEnfants |
vérifie que le champ [txtEnfants] n'est pas vide |
|
|
RangeValidator |
ErrorMessage=Tapez un nombre entre 1 et 30 ControlToValidate=txtEnfants |
vérifie que le champ [txtEnfants] est dans l'intervalle [1,30] |
|
|
TextBox |
EnableViewState=true |
salaire annuel |
|
|
RequiredFieldValidator |
ControlToValidate=txtSalaire ErrorMessage=Indiquez le montant de votre salaire |
vérifie que le champ [txtSalaire] n'est pas vide |
|
|
RegularExpressionValidator |
ControlToValidate=txtSalaire ErrorMessage=Salaire invalide RegularExpression=\s*\d+\s* |
vérifie que le champ [txtSalaire] est une suite de chiffes |
|
|
Button |
CausesValidation=true |
bouton [submit] du formulaire - lance le calcul de l'impôt |
|
|
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 :
|
|
n° |
nom |
type |
propriétés |
rôle |
|
|
Panel |
EnableViewState=false |
vue erreurs |
|
|
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 :
|
|
n° |
nom |
type |
propriétés |
rôle |
|
|
Panel |
EnableViewState=false |
vue simulations |
|
|
LinkButton |
EnableViewState=false |
lien vers le formulaire |
|
|
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] :
|
|
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 :

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

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 :

|
titre de la colonne, ici "Marié" |
|
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 :
|
Enfants |
|
enfants |
La troisème colonne est définie comme suit :
|
Salaire annuel |
|
salaire |
|
{0:C} - affichage monétaire pour obtenir le signe euro |
La quatrième colonne est définie comme suit :
|
Montant de l'impôt |
|
impôt |
|
{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é" HeaderText="Marié"></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ôt" HeaderText="Montant de l'impô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 :
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> :
|
Le dossier [bin] contient la dll contenant les classes [impot], [impotsData], [impotsOLEDB] nécessaires à l'application :
|
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 :

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

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.