Tutoriel : Maitrisez les validators ASP.NET 2.0 en C#

Nico-pyright(c)

Cet article a pour but de présenter les principes et l'utilisation de la validation des saisies utilisateurs d'une page web à travers le framework de validation d'ASP.NET 2.0.

N'hésitez pas à commenter cet article ! Commentez ♪

- Titre : Tutoriel : Maitrisez les validators ASP.NET 2.0 en C#

- Auteur : Nico-pyright(c)

- Parution : 27 mai 2008

- Mise à jour : 27 mai 2008

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2007 Nico-pyright(c). Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts. Droits de diffusion permanents accordés à Developpez LLC.

Sommaire

- 1.Introduction

- 2.Le besoin de valider les informations saisies

- 3.Principe des validateurs d'ASP.NET

- 3.1.Classe de base

- 3.2.Afficher un message d'erreur

- 3.2.1.Propriété Text

- 3.2.2.Propriété ErrorMessage

- 3.2.3.Message personnalisé entre les balises

- 3.3.Les différents mode d'affichage d'une erreur

- 4.Comment valider ? Et que valider ?

- 4.1.Comment valider ?

- 4.2.Que valider ?

- 5.Les validateurs d'ASP.NET

- 5.1.RequiredFieldValidator

- 5.2.RangeValidator

- 5.3.CompareValidator

- 5.3.1.Comparer à une valeur

- 5.3.2.Comparer à un autre champ

- 5.3.3.Vérifier la validité d'un type

- 5.4.RegularExpressionValidator

- 5.5.CustomValidator

- 5.5.1.Côté serveur

- 5.5.2.Côté client

- 5.6.ValidationSummary

- 5.7.Combiner les validateurs

- 6.Un peu plus loin avec les validateurs

- 6.1.Les groupes de validations

- 6.2.Manipuler les validateurs pour afficher un message d'erreur personnalisé

- 6.2.1.Côté serveur

- 6.2.2.Côté client

- 6.2.3.Remarque

- 6.3.Afficher un MessageBox après un retour serveur

- 6.4.Désactiver la validation côté client

- 6.5.Vérifier coté Javascript que la validation est active

- 6.6.Utilisation avancée du CustomValidator

- 6.6.1.Passer des valeurs à la fonction javascript

- 6.6.2.Utiliser le CustomValidator pour valider un RadioButton

- 6.6.3.Valider deux contrôles avec le même validator / Changer le message d'erreur du validateur

- 6.6.4.1.Côté client

- 6.6.4.2.Côté serveur

- 6.6.4.3.Remarques

- 6.6.4.Utiliser un CustomValidator pour afficher des images en fonction du résultat de la validation

- 6.7.Réflexion sur le passage de paramètres

- 7.Créer son propre validator

- 8.Validator et ajax

- 8.1.Validator et UpdatePanel

- 8.2.Un validateur amélioré avec l'AJAX Control Toolkit

- 10.Conclusion

- Remerciements

- Contact

1.Introduction

Vous êtes un développeur ASP.NET utilisant le C# et vous souhaitez apprendre à utiliser les validateurs (ou validators) ou approfondir vos connaissances pour faire une utilisation avancée des validators et comprendre les mécanismes du framework de validation d'ASP.NET ?
Alors ce tutoriel est pour vous.

Vous apprendrez dans ce cours à utiliser les différents validators, à comprendre l'importance de la validation des saisies utilisateurs et à mettre à profit la richesse et les possibilités d'ASP.NET pour rendre la validation efficace.
Tout au long de ce tutoriel, j'ai utilisé le logiciel Visual Studio 2008 et j'ai créé des projets de type « Web Application ».

2.Le besoin de valider les informations saisies

La collecte d'informations via un formulaire est une des nombreuses taches que peut effectuer une page d'un site web.
Vous pouvez par exemple avoir besoin de saisir les coordonnées d'un client, des informations sur une commande, etc.

Les pages web disposent de toute une série de contrôles qui permettent d'obtenir des saisies de l'utilisateur. Champ de texte, radio bouton, case à cocher, liste déroulante, etc.

Mais qui dit « utilisateur » dit « risque d'erreur ».
Par exemple, la page web réclame un nombre pour indiquer un nombre de jours, et l'utilisateur se trompe en saisissant une chaine de caractères qui n'a rien à voir avec le nombre attendu.
Et encore, ceci correspond juste à une erreur… Que dire d'un utilisateur malveillant qui tente de pénétrer les secrets de vos pages web…

La solution : la validation des saisies utilisateurs.

Votre champ texte attend un nombre à trois chiffres et uniquement ceci ? Alors, notre tache va consister à vérifier la saisie de l'utilisateur et à l'informer s'il s'est trompé ou pas.

C'est ici qu'interviennent les validators. Ce sont des contrôles ASP.NET qui intègrent la logique permettant de vérifier la saisie des utilisateurs. Ils vont grandement nous simplifier la tâche pour vérifier qu'un champ obligatoire est correctement saisi ou qu'une valeur correspond bien à ce qu'on attend, etc.
Ils permettent de contrôler la saisie côté client si le navigateur le supporte, ce qui améliore l'interaction avec l'utilisateur.
Ils permettent également de valider les saisies du côté du serveur, ce qui est une étape OBLIGATOIRE. En effet, la validation côté client peut avoir été désactivée ou altérée par du code malveillant. La seule façon fiable de vérifier la validité d'une saisie est de la faire côté serveur.

Les validators nous facilitent grandement la tâche en disposant d'un moyen puissant de contrôler la saisie et d'afficher des messages d'erreurs pour avertir de la saisie incorrecte.

La validation des contrôles apparait à un moment précis du cycle de vie d'une page, entre la fin des événements d'initialisation par le code utilisateur et les événements des contrôles, comme vous pouvez le voir dans ce tutoriel.
Forcément, cela se passe après le chargement des valeurs du postback par la page… et avant les événements des contrôles. Cela permet de savoir au moment du clic sur un bouton si la page est correcte et dans ce cas, rediriger vers la bonne page.
Il est important de comprendre l'ordre d'enchainement des événements, si l'on a à faire des traitements en cas d'erreur, ou en cas de succès de validation.

3.Principe des validateurs d'ASP.NET

Les validators sont des contrôles ASP.NET au même titre qu'un TextBox. Leur particularité de validator se remarque au fait qu'ils implémentent IValidator (ou dérivent de BaseValidator)

3.1.Classe de base

Tous les validators d'ASP.NET héritent de la classe BaseValidator.
Cette classe contient le cœur de l'implémentation des validators. Elle contient beaucoup de propriétés, dont certaines particulièrement intéressantes :

- ControlToValidate : permet d'associer à un validateur le contrôle qu'il doit valider. Il est requis pour tous les validators, sauf pour le CustomValidator où il est optionnel. Nous verrons son utilisation plus loin dans le tutoriel ;

- Display : permet de définir la façon dont vont s'afficher les messages d'erreurs des validators. Nous verrons son utilisation plus loin dans le tutoriel ;

- EnableClientScript : permet d'indiquer si l'on souhaite utiliser une validation cliente ou non. (Vrai par défaut) ;

- ErrorMessage : permet d'indiquer le message d'erreur qui sera affiché. Cette propriété est utilisée notamment pour le contrôle ValidationSummary. Nous verrons son utilisation dans le paragraphe suivant ;

- SetFocusOnError : met le focus sur le champ indiqué par ControlToValidate en cas d'erreur ;

- Text : permet d'indiquer le message d'erreur qui sera affiché. Nous verrons son utilisation dans le paragraphe suivant ;

- ValidationGroup : permet de grouper les contrôles pour que ne soient validés que les contrôles qui font partie du même groupe. Cela permet de définir plusieurs zones de validation dans une page.

3.2.Afficher un message d'erreur

Prenons l'exemple du RequiredFieldValidator, nous y reviendrons en détail plus tard.
Ce validator, comme vous l'avez sans doute compris, sert à vérifier (grâce à ControlToValidate) que le textBox « LeTextBox » doit être saisi (Required).

<div>
<asp:TextBox runat="server" ID="LeTextBox" />
</div>
<asp:RequiredFieldValidator runat="server" ControlToValidate="LeTextBox" Text="Le champ doit être saisi" />

<asp:Button runat="server" Text="Valider" />

En voyant cette écriture, on devine qu'il va afficher l'erreur « Le champ doit être saisi » si le champ est vide.
Il existe plusieurs façons d'afficher un message d'erreur, nous allons les observer.

3.2.1.Propriété Text

Lorsqu'on utilise la propriété Text, le message s'affiche en rouge par défaut. Comme ci-dessous :

3.2.2.Propriété ErrorMessage

<asp:RequiredFieldValidator runat="server" ControlToValidate="LeTextBox" ErrorMessage="Le champ doit être saisi" />

L'utilisation de la propriété ErrorMessage semble produire le même effet que la propriété Text. Il y a cependant une différence, et nous le verrons plus tard, le fait de définir la propriété ErrorMessage permettra au contrôle ValidationSummary de réutiliser le message d'erreur.

3.2.3.Message personnalisé entre les balises

Une autre façon d'afficher un message est de le définir entre les balises de RequiredFieldValidator, ce qui permet de faire un peu ce qu'on veut. Mettre des couleurs, des images, etc.

<asp:RequiredFieldValidator runat="server" ControlToValidate="LeTextBox">
Le message doit être saisi <img src="erreur.jpg" alt="erreur de saisie" />
</asp:RequiredFieldValidator>

3.3.Les différents mode d'affichage d'une erreur

- Display=« None » : permet de ne pas afficher de message d'erreur. Le fait de ne pas afficher de message d'erreur peut être utile lorsqu'on utilise un ValidationSummary, comme on le verra plus tard.

- Display=« Dynamic » : affiche le message d'erreur « dynamiquement », le message apparaitra à l'emplacement du validator en décalant les autres contrôles de la page.

- Display=« Static » : affiche le message d'erreur « statiquement », c'est-à-dire que l'emplacement du message d'erreur est réservé. On voit un « trou » à l'emplacement où sera affiché le message s'il y a une erreur. On utilise cette option lorsqu'on a besoin que l'affichage ou l'absence du message ne casse pas la présentation, dans des cellules d'un tableau par exemple.

4.Comment valider ? Et que valider ?

4.1.Comment valider ?

La validation de champs de formulaire se fait lorsqu'on « POST » la page, typiquement une action de type form.submit(). C'est-à-dire qu'une validation est possible pour des contrôles particuliers.
C'est le cas des <asp:Button>, des <asp:LinkButton>, <asp:ImageButton> et des <asp:BulletedList>.
On les utilise ainsi :

<asp:Button runat="server" Text="Valider" />
<asp:LinkButton runat="server" Text="Valider" PostBackUrl="page2.aspx" />
<asp:ImageButton runat="server" ImageUrl="valider.jpg" PostBackUrl="page2.aspx" />
<asp:BulletedList CausesValidation="true" BulletStyle="Disc" DisplayMode="LinkButton" runat="server">
<asp:ListItem Value="Element 1"/>
<asp:ListItem Value="Element 2"/>
<asp:ListItem Value="Element 3"/>
</asp:BulletedList>

NB : on remarque pour le contrôle BulletedList qu'il y a une propriété CausesValidation=« true », en fait, elle vaut vraie par défaut pour tous les autres, c'est pour ça que je ne l'indique pas.

- Lorsque cette propriété vaut true (par défaut dans presque tous les cas), ASP.NET va lancer la validation de la page lorsque l'utilisateur cliquera sur ces composants.

- Lorsque cette propriété vaut false, ASP.NET ignorera la validation et la page sera postée normalement.

La validation de notre page se fait par défaut côté client (si le navigateur le supporte) et côté serveur. Lorsque la validation côté client n'est pas bonne, la page n'est pas postée. Le cycle de la page est donc interrompu.
La validation côté serveur est également automatique lorsque CausesValidation vaut true, la page est postée et le cycle de vie est classique.
Ce qui fait que lorsqu'un événement, par exemple l'événement Click du bouton, est levé, il faudra tester le résultat de la validation dans cet événement :

protected void ClickButton(object sender, EventArgs e)
{
if (Page.IsValid)
{
// la validation est bonne ...
}
}

4.2.Que valider ?

Les contrôles serveur que l'on peut valider sont :

- TextBox ;

- ListBox ;

- DropDownList ;

- RadioButtonList ;

- HtmlInputText ;

- HtmlTextArea ;

- HtmlSelect.

Certains contrôles ne peuvent pas être validés directement, c'est le cas par exemple du RadioButton ou de la CheckBox
Typiquement, on peut valider tous les contrôles qui prennent en charge l'attribut ValidationProperty.
Ainsi, on peut créer son contrôle pour qu'il soit sujet à la validation. Par exemple, ici je crée un contrôle qui affiche un <input type=« text »> :

[ValidationProperty("Valeur")]
public class MonControl : WebControl
{
private string _valeur;
public string Valeur
{
get { return _valeur; }
set { _valeur = value; }
}

protected override void Render(HtmlTextWriter writer)
{
writer.WriteBeginTag("input");
writer.WriteAttribute("id", ID);
writer.WriteAttribute("type", "text");
writer.WriteAttribute("value", _valeur);
writer.Write(HtmlTextWriter.SlashChar);
writer.Write(HtmlTextWriter.TagRightChar);
}
}

Ici je définis la propriété Valeur comme étant la propriété à tester pour la validation grâce à l'attribut ValidationProperty.
Je pourrais l'utiliser de cette façon :

<Exemple:MonControl runat="serveur" id="monControl" Valeur="Une valeur" />
<asp:RequiredFieldValidator runat="server" ControlToValidate="monControl" ErrorMessage="La valeur doit être saisi" />

NB : pour que l'attribut ValidationProperty fonctionne, il doit mapper l'attribut html value du contrôle (ici l'input type=« text »), c'est pour cela que le contrôle CheckBox ne peut pas être validé, il ne possède pas d'attribut value.
Pour qu'un contrôle complexe qui ne peut pas mapper d'attribut html value puisse être validé, on utilisera un CustomValidator, comme on le verra plus tard.
Plus d'infos sur la création de custom controls dans la FAQASP.NET.

5.Les validateurs d'ASP.NET

5.1.RequiredFieldValidator

Le RequiredFieldValidator, comme son nom le suggère, permet de tester si un champ est rempli ou non.

<asp:TextBox runat="server" ID="LeTextBox" />
<asp:RequiredFieldValidator runat="server" ControlToValidate="LeTextBox" Display="dynamic" ErrorMessage="Vous devez saisir la valeur" />
<asp:Button runat="server" Text="Valider" />

Ici le RequiredFieldValidator va vérifier que le contrôle leTextBox (précisé dans ControlToValidate) est bien saisi. Si ce n'est pas le cas, il affichera la valeur de ErrorMessage.

Il peut arriver des fois qu'un contrôle possède une valeur par défaut. La propriété InitialValue permet de valider le contrôle uniquement si la valeur du contrôle est différente de celle saisie dans InitialValue. On peut l'appliquer par exemple pour une DropDownList :

<asp:DropDownList id="DropDownList1" runat="server">
<asp:ListItem Selected="True">Sélectionnez une valeur</asp:ListItem>
<asp:ListItem>Valeur 1</asp:ListItem>
<asp:ListItem>Valeur 2</asp:ListItem>
<asp:ListItem>Valeur 3</asp:ListItem>
</asp:DropDownList>
<asp:RequiredFieldValidator Display="Dynamic" runat="server" ErrorMessage="N'oubliez pas de saisir une valeur"
ControlToValidate="DropDownList1" InitialValue="Selectionnez une valeur"/>

PS : n'oubliez pas de toujours tester la validation côté serveur, même lorsqu'il s'agit d'un simple RequiredFieldValidator.
En effet, une personne peut très bien avoir désactivé le javascript (ou provoqué un résultat similaire) et dans ce cas, la validation côté client va passer, il faut donc toujours tester la validation côté serveur, c'est le seul moyen fiable de contrôler les saisies.

Imaginons un bouton qui comporte un événement Click, et dans lequel on redirige vers une page web :

<asp:TextBox runat="server" ID="LeTextBox" />
<asp:RequiredFieldValidator runat="server" ControlToValidate="LeTextBox" Display="dynamic" ErrorMessage="Vous devez saisir la valeur" />
<asp:Button runat="server" Text="Valider" OnClick="ButtonClick" />

protected void ButtonClick(object sender, EventArgs e)
{
Response.Redirect("http://www.developpez.com");
}

Si le javascript est désactivé, peu importe qu'une valeur soit saisie ou non, on sera dans tous les cas redirigé vers la page de developpez.com.

La bonne façon de faire la redirection est :

protected void ButtonClick(object sender, EventArgs e)
{
if (Page.IsValid)
{
// la page est valide
Response.Redirect("http://www.developpez.com");
}
}

5.2.RangeValidator

Le RangeValidator vérifie si la valeur d'un contrôle d'entrée se trouve dans une plage de valeurs spécifiée.
On peut comparer des Integer, Double, Date, String et Currency. Pour préciser quel type on attend, on va utiliser la propriété Type. Les bornes à valider seront saisies grâce aux propriétés MinimumValue et MaximumValue.

<asp:TextBox runat="server" ID="LeTextBox" />
<asp:RangeValidator runat="server" ControlToValidate="LeTextBox" Type="currency" MinimumValue="100"
MaximumValue="200" Display="dynamic" ErrorMessage="Saisissez un montant entre 100 et 200 €" />

N. B. Si le contrôle est vide, la validation sera toujours bonne. Si cette valeur doit obligatoirement être saisie, on combinera le RangeValidator avec un RequiredFieldValidator comme on le verra plus tard.

5.3.CompareValidator

Le CompareValidator permet de comparer la valeur entrée par l'utilisateur avec une valeur ou avec la valeur d'un autre contrôle.
On peut également se servir de ce contrôle pour vérifier qu'une donnée saisie est d'un type particulier. (on utilisera l'opérateur DataTypeCheck).
On peut comparer des Integer, Double, Date, String et Currency. Pour préciser quel type on attend, on va utiliser la propriété Type.
Pour effectuer la comparaison, on utilisera un opérateur qui permettra de spécifier le type de l'opération.
On pourra effectuer une comparaison d'égalité (Equal), d'inégalité (NotEqual), de supériorité (GreaterThan), de supériorité ou d'égalité (GreaterThanEqual), d'infériorité (LessThan) et enfin d'infériorité ou d'égalité (LessThanEqual).

5.3.1.Comparer à une valeur

<asp:TextBox runat="server" ID="LeTextBox" />
<asp:CompareValidator runat="server" ControlToValidate="LeTextBox" Type="Integer" Operator="NotEqual" ValueToCompare="0"
ErrorMessage="L'entier saisi doit être différent de 0" />
<asp:TextBox runat="server" ID="LeTextBoxDate" />
<asp:CompareValidator ID="CompareValidator1" runat="server" ControlToValidate="LeTextBoxDate" Type="Date"
Operator="GreaterThan" ValueToCompare="01/01/2000" ErrorMessage="Vous devez être né après l'an 2000 pour bénéficier de cette promotion" />

N. B. Si la valeur du contrôle ne peut pas être convertie, la validation échouera.

5.3.2.Comparer à un autre champ

<asp:TextBox runat="server" ID="Email1" />
<asp:TextBox runat="server" ID="Email2" />
<asp:CompareValidator runat="server" ControlToValidate="Email1" Type="String" Operator="Equal"
ControlToCompare="Email2" ErrorMessage="Les emails saisis doivent être identiques !" />

Attention, dans la comparaison à un contrôle, si le type du ControlToCompare ne correspond pas à la valeur saisie, la validation peut passer.
En effet, l'exemple suivant qui compare deux dates :

<asp:TextBox runat="server" ID="Date1"/>
<asp:TextBox runat="serveur" ID="Date2"/>
<asp:CompareValidator runat="server" ControlToValidate="Date1" Type="Date" Operator="LessThanEqual"
ControlToCompare="Date2" ErrorMessage="La date doit être inférieure !" />

est malheureusement valide si je saisis les valeurs ci-dessous.

Il faudra effectuer également une validation sur le contrôle identifié par ControlToCompare.

5.3.3.Vérifier la validité d'un type

L'exemple ci-dessous permet de vérifier qu'on a bien saisi un entier.

<asp:TextBox runat="server" ID="LeTextBox" />
<asp:CompareValidator runat="server" ControlToValidate="LeTextBox" Type="Integer" Operator="DataTypeCheck"
ErrorMessage="Veuillez saisir un entier" Display="Dynamic" />

5.4.RegularExpressionValidator

Le RegularExpressionValidator permet de vérifier une entrée à partir d'une expression régulière.
Par exemple, pour vérifier un email :

<asp:TextBox runat="server" ID="LeTextBox" />
<asp:RegularExpressionValidator runat="server" ControlToValidate="LeTextBox" ErrorMessage="L'email saisi n'est pas correct" Display="dynamic"
ValidationExpression="^[_a-zA-Z0-9-]+(\.[_a-zA-Z0-9-]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.(([0-9]{1,3})|([a-zA-Z]{2,3})|(aero|coop|info|museum|name))$" />

Plus d'infos sur les expressions régulières : https://lgmorand.developpez.com/dotnet/regex/.
Un annuaire d'expressions régulières très utile : http://regexlib.com/.

5.5.CustomValidator

Le CustomValidator permet de créer sa propre fonction de validation, côté client et côté serveur.
On utilisera ServerValidate pour fournir sa propre méthode de validation côté serveur.
À noter que la propriété ControlToValidate n'est pas obligatoire, on pourra effectivement utiliser directement les contrôles de la page pour faire notre validation.
Pour faire une validation côté client, il faudra créer une fonction javascript dont le nom sera passé à la propriété ClientValidationFunction. On utilisera également les paramètres pour donner le résultat de la validation et connaitre la valeur de l'objet associé à ControlToValidate.
On pourra utiliser ValidateEmptyText pour indiquer si un texte vide devra être soumis à validation. Cela ne nous dispense pas de devoir tester si la valeur est vide ou non, cela signifie que lorsqu'un contrôle est vide, on passera dans la fonction de validation.

5.5.1.Côté serveur

Si la propriété ControlToValidate est définie, on pourra utiliser la propriété Value du paramètre ServerValidateEventArgs pour récupérer la valeur du contrôle.
On utilisera la propriété IsValid de l'objet ServerValidateEventArgs pour stocker le résultat de la validation, qui sera utilisée dans l'évaluation de la propriété Page.IsValid.
Le prototype de la fonction qui servira à la validation sera :

protected void NomFonction(object source, ServerValidateEventArgs args)
{
}

Ainsi, on pourra par exemple valider qu'un utilisateur existe de cette façon :

<asp:TextBox runat="server" ID="TextBoxLogin"/>
<asp:CustomValidator runat="server" ControlToValidate="TextBoxLogin" ErrorMessage="Cet utilisateur est inconnu"
ValidateEmptyText="true" OnServerValidate="ValiderLogin" Display="dynamic"/>

<asp:Button runat="server" Text="Valider" OnClick="ButtonClick" />

protected void ValiderLogin(object source, ServerValidateEventArgs args)
{
args.IsValid = UserExist(args.Value); // args.Value contient la valeur saisie
// dans le contrôle identifié par ControlToValidate
}

private bool UserExist(string value)
{
return value == "nico" || value == "test";
}

protected void ButtonClick(object sender, EventArgs e)
{
if (Page.IsValid)
{
// la page est valide, continuer
}
}

Notez que la propriété ValidateEmptyText m'empêche ici d'avoir une valeur vide, car ma méthode UserExist renvoi faux pour une valeur vide.

En général, lorsqu'on fait une validation qui a besoin de plusieurs contrôles, on n'utilisera pas la propriété ControlToValidate et on utilisera directement les objets représentant les contrôles.
On pourra également utiliser le CustomValidator pour valider des contrôles qui ne sont pas prévus pour, comme la checkbox.

<asp:CheckBox runat="server" ID="NewUser" Text="Etes vous déjà client chez nous ?" />
<asp:TextBox runat="server" ID="TextBoxLogin"/>
<asp:CustomValidator runat="server" ErrorMessage="Cet utilisateur est inconnu" ValidateEmptyText="true"
OnServerValidate="ValiderLogin" Display="dynamic"/>

protected void ValiderLogin(object source, ServerValidateEventArgs args)
{
if (!NewUser.Checked)
{
CreerNouvelUtilisateur(TextBoxLogin.Text);
args.IsValid = true;
}
else
args.IsValid = UserExist(TextBoxLogin.Text);
}

Dans cet exemple, si la case n'est pas cochée, alors la validation est bonne (et on crée l'utilisateur), si elle est cochée, le résultat de la validation dépend de l'existence de l'utilisateur.

5.5.2.Côté client

Comme vu au paragraphe précédent, le CustomValidator procède à la vérification côté serveur. On peut améliorer l'interactivité avec l'utilisateur en proposant également une validation côté client, ce qui permettra d'éviter éventuellement un aller-retour serveur inutile.

On va créer une méthode javascript, qui aura la signature suivante :

function NomMethode(sender, args)
{
}

et de la même façon que côté serveur, on utilisera les objets passés en paramètres pour récupérer les éventuelles valeurs passées par ControlToValidate ou pour récupérer la valeur du contrôle.

<script type="text/javascript">
function ValideNombrePair(sender, args)
{
if (args.Value == '')
args.IsValid = false;
else
args.IsValid = (args.Value % 2 == 0);
}
</script>

<asp:TextBox runat="server" ID="NombrePair"/>
<asp:CustomValidator runat="server" ControlToValidate="NombrePair" ErrorMessage="Le nombre doit être pair" ValidateEmptyText="true"
ClientValidationFunction="ValideNombrePair" OnServerValidate="ValiderNombrePair" Display="dynamic"/>
<asp:Button runat="server" Text="Valider" OnClick="ButtonClick" />

N'oubliez pas qu'il faut absolument faire une validation côté serveur, la validation côté client pouvant être désactivée.

protected void ButtonClick(object sender, EventArgs e)
{
if (Page.IsValid)
{
// la page est valide, continuer
}
}

protected void ValiderNombrePair(object source, ServerValidateEventArgs args)
{
int nombre;
if (int.TryParse(args.Value, out nombre))
{
args.IsValid = nombre%2 == 0;
return;
}
args.IsValid = false;
}

5.6.ValidationSummary

Le contrôle ValidationSummary n'est pas un validator comme les autres, il n'effectue pas de validation, mais propose de récapituler les différentes erreurs survenues lors de la validation de la page.
Il se base uniquement sur le contenu des propriétés ErrorMessage de chaque validator qui n'a pas passé la validation.
On a plusieurs possibilités pour avoir un récapitulatif des erreurs.
On peut dans un premier temps les voir dans un alert Javascript côté client, il faut utiliser la propriété ShowMessageBox et la mettre à true.

Nom : <asp:TextBox runat="server" ID="Nom" />
<asp:RequiredFieldValidator runat="server" ControlToValidate="Nom" ErrorMessage="Le nom doit être saisi" Display="none" />
<br />
Email : <asp:TextBox runat="server" ID="Email" />
<asp:RegularExpressionValidator runat="server" ControlToValidate="Email" ErrorMessage="L'email saisi n'est pas correct" Display="none"
ValidationExpression="^[_a-zA-Z0-9-]+(\.[_a-zA-Z0-9-]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.(([0-9]{1,3})|([a-zA-Z]{2,3})|(aero|coop|info|museum|name))$" />
<br />
<asp:ValidationSummary runat="server" ShowMessageBox="true" ShowSummary="false" />
<asp:Button runat="server" Text="Valider" />

Dans l'exemple précédent, j'ai mis la propriété Display à none pour chaque validator et j'ai mis la propriété ShowSummary à false du ValidationSummary. Ce qui a pour effet d'afficher uniquement une alert javascript.
Vous l'aurez compris, la propriété ShowSummary permet de dire si on veut voir un résumé dans la page html ou non (par défaut à true).
Les différentes propriétés ne sont pas exclusives, on peut combiner l'affichage d'une alert javascript et l'affichage de l'erreur dans la page.
Il existe différentes façons d'avoir notre résumé dans la page, il faut utiliser la propriété DisplayMode et ses différentes valeurs : SingleParagraph, List et BulletList.

<asp:ValidationSummary ID="ValidationSummary1" runat="server" DisplayMode="BulletList" />
<asp:ValidationSummary ID="ValidationSummary2" runat="server" DisplayMode="List" />
<asp:ValidationSummary ID="ValidationSummary3" runat="server" DisplayMode="SingleParagraph" />

Sur l'image ci-dessous, vous pouvez voir les trois types de résumés possibles :

Il est également possible de saisir un entête, grâce à la propriété HeaderText :

<asp:ValidationSummary runat="server" DisplayMode="BulletList" HeaderText="<b>Voici la liste des erreurs :</b>" />

5.7.Combiner les validateurs

Pour satisfaire aux contraintes de nos sites, nous avons en général plusieurs conditions qui font qu'un champ sera validé. Par exemple, un email obligatoire doit être saisi et valide.
On pourra combiner un RequiredFieldValidator et un RegularExpressionValidator. Comme ci-dessous :

Email : <asp:TextBox runat="server" ID="Email" />
<asp:RequiredFieldValidator ID="RequiredFieldValidator1" runat="server" ControlToValidate="Email"
ErrorMessage="N'oubliez pas de saisir votre email" Display="dynamic" />
<asp:RegularExpressionValidator runat="server" ControlToValidate="Email" ErrorMessage="L'email saisi n'est pas correct" Display="dynamic"
ValidationExpression="^[_a-zA-Z0-9-]+(\.[_a-zA-Z0-9-]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.(([0-9]{1,3})|([a-zA-Z]{2,3})|(aero|coop|info|museum|name))$" />

<asp:Button runat="server" Text="Valider" />

Il suffira de faire pointer à chaque fois la propriété ControlToValidate vers le TextBox Email.

6.Un peu plus loin avec les validateurs

Vous venez de voir les bases de la validation. L'utilisation des différents contrôles simplifie grandement l'approche de la validation.
On peut encore aller plus loin, comme nous allons le voir tout de suite.

6.1.Les groupes de validations

Il est assez courant d'avoir plusieurs boutons sur une page et seuls certains champs doivent être validés en fonction du bouton cliqué.
Imaginons une page qui contient une zone d'identification, avec un TextBox pour un login et un TextBox pour un mot de passe. Un bouton permet de valider ce login.
Sur cette même page, on a une zone qui permet de saisir un email pour s'inscrire à une newsletter, avec un TextBox et un bouton.
Il ne faut bien sûr pas valider les informations du login si on choisit de s'inscrire à la newsletter.
Pour ceci, on va indiquer les contrôles qui sont reliés à un bouton en les plaçant dans un même groupe, grâce à la propriété ValidationGroup.
Ainsi, je pourrais placer sur la même page mes différents champs, mes différents boutons de validation et mes différents validators.

Par exemple, les contrôles associés au login seront dans le groupe login (ValidationGroup=« login ») :

Login : <asp:TextBox runat="server" ID="Login" />
Mot de passe : <asp:TextBox runat="server" ID="Pwd" TextMode="Password" />
<asp:RequiredFieldValidator ID="RequiredFieldValidator1" runat="server" ControlToValidate="Login"
ErrorMessage="N'oubliez pas de saisir votre login" Display="dynamic" ValidationGroup="login" />
<asp:RequiredFieldValidator ID="RequiredFieldValidator2" runat="server" ControlToValidate="Pwd"
ErrorMessage="N'oubliez pas de saisir votre mot de passe" Display="dynamic" ValidationGroup="login" />

<asp:Button ID="Button1" runat="server" Text="Valider" OnClick="ButtonClick" ValidationGroup="login" />

Et les contrôles associés à la newsletter seront dans le groupe newsletter (ValidationGroup=« newsletter »).

<asp:TextBox runat="server" ID="email" />
<asp:RequiredFieldValidator ID="RequiredFieldValidator3" runat="server" ControlToValidate="Login"
ErrorMessage="N'oubliez pas de saisir votre login" Display="dynamic" ValidationGroup="newsletter" />
<asp:RegularExpressionValidator ID="RegularExpressionValidator1" runat="server" ControlToValidate="email"
ErrorMessage="L'email saisi n'est pas correct" Display="dynamic" ValidationGroup="newsletter"
ValidationExpression="^[_a-zA-Z0-9-]+(\.[_a-zA-Z0-9-]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.(([0-9]{1,3})|([a-zA-Z]{2,3})|(aero|coop|info|museum|name))$" />

<asp:Button ID="Button2" runat="server" Text="Valider" OnClick="ButtonClickNewsLetter" ValidationGroup="newsletter" />

Dans cet exemple, la validation ne s'effectue que sur le champ email lorsque je clique sur le bouton d'inscription à la newsletter.

6.2.Manipuler les validateurs pour afficher un message d'erreur personnalisé

Un problème assez classique de ces validators est qu'on a du mal à agir sur la présentation des messages d'erreurs, nonobstant les différentes options du ValidationSummary.
Nous allons voir comment on peut créer son propre message, en manipulant les validators.

6.2.1.Côté serveur

La propriété Page.Validators contient la liste de tous les validators de la page. Comme chaque validator hérite de BaseValidator, on peut itérer sur les validators avec une simple boucle foreach :

foreach (BaseValidator validator in Page.Validators)
{
}

et accéder à ses propriétés, notamment IsValid et ErrorMessage.

Pour créer un message d'erreur personnalisé, on pourra :

- mettre CausesValidation à false sur notre contrôle qui déclenche la validation (pour ne pas lancer de validation automatique) ;

- lancer la validation de la page à la main côté serveur ;

- itérer sur tous les validators et récupérer les messages d'erreurs ;

- mettre le display à none sur les validators ;

- et puis les reformater.

(Les deux premières étapes ne sont pas obligatoires, mais permettent un peu plus de souplesse.)
Ainsi, imaginons une validation d'un login + mot de passe :

Login : <asp:TextBox runat="server" ID="Login" />
Mot de passe : <asp:TextBox runat="server" ID="Pwd" TextMode="Password" />
<asp:RequiredFieldValidator ID="RequiredFieldValidator1" runat="server" ControlToValidate="Login"
ErrorMessage="N'oubliez pas de saisir votre login" Display="none" />
<asp:RequiredFieldValidator ID="RequiredFieldValidator2" runat="server" ControlToValidate="Pwd"
ErrorMessage="N'oubliez pas de saisir votre mot de passe" Display="none"/>
<asp:CustomValidator runat="server" OnServerValidate="ValidateLogin" ErrorMessage="Le login/pwd est incorrect" />

<asp:Button runat="server" Text="Valider" CausesValidation="false" OnClick="ButtonClick" />

J'ai deux textbox, deux RequiredFieldValidator qui vérifient qu'il y a bien une valeur saisie dans ces textbox et un CustomValidator qui va aller vérifier côté serveur que le login est bon.

protected void ValidateLogin(object source, ServerValidateEventArgs args)
{
args.IsValid = Login.Text == "test" && Pwd.Text == "test2";
}

Notez que le bouton a la propriété CausesValidation à false. On pourra donc dans la méthode ButtonClick, faire ceci :

Validate(); // lancer la validation
if (Page.IsValid)
{
// la page est valide, continuer
}
else
{
Controls.Add(new LiteralControl("<div>"));
int decalage = 20;
foreach (BaseValidator validator in Page.Validators)
{
if (!validator.IsValid)
{
validator.Display = ValidatorDisplay.None;

Controls.Add(new LiteralControl(string.Format("<div style=\"padding-left:{0}px\">", decalage)));
Label errorMessage = new Label();
errorMessage.ForeColor = Color.Red;
errorMessage.Text = validator.ErrorMessage;
Controls.Add(errorMessage);
Controls.Add(new LiteralControl("</div>"));
decalage += 10;
}
}
Controls.Add(new LiteralControl("</div>"));
}

La méthode Validate() est une méthode du framework .NET, qui va lancer la validation de tous les validators de la page, dont celle qui est rattachée au CustomBalidator (ValidateLogin). Elle va mettre à jour la propriété Page.IsValid avec le résultat de la validation.

Je lance donc la validation, si elle n'est pas valide, je crée à chaque fois un message d'erreur rouge dans un label, qui se décale vers la droite.

Pardonnez-moi mon manque d'imagination, c'est très moche, je sais :)

6.2.2.Côté client

La même chose peut se faire côté client, il faudra manipuler les objets javascript côté client. Voici comment produire le même résultat.
La propriété CausesValidation du bouton est toujours à false et j'ai rajouté la propriété OnClientClick, qui va appeler la méthode javascript checkForm() lorsqu'on cliquera sur le bouton.

Login : <asp:TextBox runat="server" ID="Login" />
Mot de passe : <asp:TextBox runat="server" ID="Pwd" TextMode="Password" />
<asp:RequiredFieldValidator ID="RequiredFieldValidator1" runat="server" ControlToValidate="Login"
ErrorMessage="N'oubliez pas de saisir votre login" Display="none" />
<asp:RequiredFieldValidator ID="RequiredFieldValidator2" runat="server" ControlToValidate="Pwd"
ErrorMessage="N'oubliez pas de saisir votre mot de passe" Display="none"/>
<asp:CustomValidator ID="CustomValidator1" runat="server" OnServerValidate="ValidateLogin"
ErrorMessage="Le login/pwd est incorrect" Display="None" />

<asp:Button ID="Button1" runat="server" Text="Valider" CausesValidation="false" OnClick="ButtonClick" OnClientClick="checkForm()" />

<div id="result" style="color:red"></div>

J'ai rajouté un DIV vide qui va contenir le résultat de ma validation.
Le code javascript créé des div à la volée, il est à noter que pour appeler la validation on utilise Page_ClientValidate. Les validators sont ensuite accessibles via le tableau Page_Validators.
On testera la propriété isvalid et on utilisera la propriété errormessage.

<script type="text/javascript">
function checkForm()
{
try
{
if (!Page_ClientValidate(''))
{
var i;
var div = document.getElementById('result');
if (div)
{
while (div.lastChild)
div.removeChild(div.lastChild);
for (i = 0; i < Page_Validators.length; i++)
{
if (!Page_Validators[i].isvalid)
{
var nouveauDiv = document.createElement("div");
nouveauDiv.style.paddingLeft = i*10 + 'px';
nouveauDiv.innerHTML = Page_Validators[i].errormessage;
div.appendChild(nouveauDiv);
}
}
}
return false;
}
}
catch (e) {}
}
</script>

N. B. Bien sûr, ça n'a aucun sens de faire la validation du login côté client, c'est pour cela que le CustomValidator n'a pas de méthode javascript de validation associée.

6.2.3.Remarque

Dans mes exemples, j'utilise à chaque fois des validators qui n'ont pas de ValidationGroup, pour simplifier les écritures.
On peut également utiliser les ValidationGroup, mais pour ça, il faudra passer le nom du groupe en paramètre aux fonctions de validations :
Côté serveur :

Validate("nom du groupe");

Coté client :

Page_ClientValidate("nom du groupe");

Pour itérer sur les validators côté serveur, on les récupérera de cette façon :

Page.GetValidators(ValidationGroup)

au lieu de

Page.Validators

6.3.Afficher un MessageBox après un retour serveur

Il peut arriver qu'on veuille attirer l'attention sur un élément particulier, par exemple, je valide une authentification côté serveur et si la validation ne passe pas, j'affiche une alerte javascript pour attirer l'attention.
On pourra le faire de cette façon :

<asp:TextBox runat="server" ID="Login"/>
<asp:TextBox runat="serveur" ID="Pwd"/>
<asp:CustomValidator runat="server" OnServerValidate="ValidateLogin" Display="none" />
<asp:Button runat="server" Text="Valider" OnClick="ButtonClick" />

protected void ValidateLogin(object source, ServerValidateEventArgs args)
{
args.IsValid = Login.Text == "test" && Pwd.Text == "test";
if (!args.IsValid)
ClientScript.RegisterStartupScript(GetType(), "login invalid", "alert('le login est invalide');", true);
}

protected void ButtonClick(object sender, EventArgs e)
{
if (Page.IsValid)
{
// la page est valide
}
}

L'astuce consiste à enregistrer notre script avec RegisterStartupScript lorsque la validation n'est pas bonne.

Notez que je n'ai pas voulu utiliser le ValidationSummary et sa propriété ShowMessageBox, même si dans ce cas précis cela pourrait revenir au même.
Dans cet exemple, si j'ai plusieurs validators et plusieurs erreurs, je veux qu'il y ait une alerte uniquement pour l'email.

6.4.Désactiver la validation côté client

Il peut être utile de désactiver la validation côté client, par exemple si on veut faire un bouton « Annuler », ou « Précédent ».
Plusieurs solutions s'offrent à nous, elles sont à utiliser en fonction des besoins.

On peut déjà mettre CauseValidation à false. Mais ceci désactivera également la validation côté serveur. Pour forcer la validation côté serveur, on utilisera Validate() comme on l'a vu précédemment, avant de tester Page.IsValid.

Il est également possible de positionner une variable javascript à false pour désactiver la validation côté client : il s'agit de Page_ValidationActive.
Par exemple, voici une case à cocher qui permet d'activer ou de désactiver la validation côté client.

<asp:CheckBox runat="server" onclick="Page_ValidationActive = !Page_ValidationActive;" Text="Désactiver la validation cliente" />

Sur l'événement javascript onclick, on change le statut de cette variable (NB : bien sûr, il faudra initialiser la variable en fonction de l'état initial de la case à cocher, par exemple après le postback si on veut conserver les valeurs d'un postback à l'autre).
Sur certains contrôles, comme le bouton, on ne peut pas accéder à cet événement javascript directement, il faudra passer par le code behind :

MonBouton.Attributes["onclick"] = "Page_ValidationActive = false;";

Une autre solution, si on doit désactiver un ou plusieurs validateurs, mais pas tous, est d'utiliser ValidatorEnable(val, enable).
Si enable vaut faux, le validator apparaitra comme toujours valide.

Pour déterminer l'objet représentant validator, on va se servir de document.getElementById et du clientId du validator. (Notez qu'on pourrait également utiliser la méthode ValidatorGetValue comme on le verra dans le chapitre 7).
Par exemple, on aura cette fonction javascript :

<script type="text/javascript">
function desactiverMonValidator(id)
{
var obj = document.getElementById(id);
if (obj)
ValidatorEnable(obj, false);
}
</script>

Qu'on pourra appeler de cette façon :

MonBouton.Attributes["onclick"] = string.Format("desactiverMonValidator('{0}');", RequiredFieldValidator1.ClientID);

Ainsi, lors du clic sur le bouton, on désactivera le validator RequiredFieldValidator1.

6.5.Vérifier coté Javascript que la validation est active

On a vu un peu plus haut comment parcourir les validators de la page en javascript, mais ceci présupposait qu'il y avait effectivement des validateurs sur la page.
Que faire pour savoir s'il y en a ou pas ? (dans notre cas, vu qu'on appelait la validation cliente, nous n'avions pas besoin de faire ce test).
On peut savoir s'il n'y a pas de validation côté client avec ce javascript :

// ne rien faire si la validation client n'est pas active
if (typeof(Page_Validators) == "undefined") return;

6.6.Utilisation avancée du CustomValidator

6.6.1.Passer des valeurs à la fonction javascript

Lorsqu'on utilise des CustomValidator, il est possible de faire passer des valeurs entre le code behind et la fonction javascript du CustomValidator.
Pour ce faire, on va utiliser la méthode RegisterExpandoAttribute de l'objet Page.ClientScript.

Page.ClientScript.RegisterExpandoAttribute(CustomValidator1.ClientID, "valeur", "5");

On utilise le ClientID du validator, on lui passe un nom (ici « valeur ») et la valeur à passer (ici « 5 »).
Et on pourra récupérer cette info côté client :

<asp:CustomValidator ID="CustomValidator1" ValidateEmptyText="true" ClientValidationFunction="Valider"
ControlToValidate="LeTextBox" runat="server" ErrorMessage="Le champ n'est pas valide" />

Avec le script suivant :

<script type="text/javascript">
function Valider(sender, args)
{
args.IsValid = (args.Value == sender.valeur)
}
</script>

On avait déjà vu que la valeur du contrôle à valider était accessible par args.Value.
Pour récupérer la valeur passée, on va utiliser l'objet sender, et comme on a passé « valeur », sender.valeur vaudra « 5 ».
Ainsi, cette fonction valide toute valeur égale à 5.
Notez qu'on passe une chaine de caractères, mais qu'elle sera bien sur convertible en javascript. Notez également que la méthode RegisterExpandoAttribute permet d'encoder la valeur grâce à un boolean supplémentaire, lorsque les caractères sont spéciaux.

6.6.2.Utiliser le CustomValidator pour valider un RadioButton

Vous vous rappelez, nous avons vu qu'on ne pouvait pas utiliser de validator pour le RadioButton. Et pour cause, dans un groupe, il y a toujours un RadioButton de sélectionné, les autres ne l'étant pas.
Sauf au chargement de la page. L'astuce en général consiste à en sélectionner un par défaut, comme ça, pas besoin de faire de validation.
Nous allons néanmoins voir comment valider ces choix, si aucun n'a été sélectionné par défaut :

<h4>Civilité :</h4>
<asp:RadioButton id="Radio1" Text="M." GroupName="RadioGroup1" runat="server" />
<asp:RadioButton id="Radio2" Text="Mme" GroupName="RadioGroup1" runat="server"/>
<asp:RadioButton id="Radio3" Text="Mlle" GroupName="RadioGroup1" runat="server"/>

<asp:button ID="Button1" text="Valider" runat="server"/>

<asp:CustomValidator ID="CustomValidator1" runat="server" ClientValidationFunction="ValiderRadio"
OnServerValidate="ValiderRadio" ErrorMessage="Veuillez saisir la civilité" Display="dynamic" />

On va se servir de RegisterExpandoAttribute comme on l'a vu dans le paragraphe précédent et passer à la fonction javascript cliente les ClientId des RadioButton.

Page.ClientScript.RegisterExpandoAttribute(CustomValidator1.ClientID, "rb1Id", Radio1.ClientID);
Page.ClientScript.RegisterExpandoAttribute(CustomValidator1.ClientID, "rb2Id", Radio2.ClientID);
Page.ClientScript.RegisterExpandoAttribute(CustomValidator1.ClientID, "rb3Id", Radio3.ClientID);

Ce qui fait que la fonction javascript cliente pourra ressembler à :

<script type="text/javascript">
function ValiderRadio(sender, args)
{
var radio1 = document.getElementById(sender.rb1Id);
var radio2 = document.getElementById(sender.rb2Id);
var radio3 = document.getElementById(sender.rb3Id);
if (radio1 && radio2 && radio3)
args.IsValid = radio1.checked || radio2.checked || radio3.checked;
else
args.IsValid = false;
}
</script>

Sans oublier la méthode côté serveur :

protected void ValiderRadio(object source, ServerValidateEventArgs args)
{
args.IsValid = Radio1.Checked || Radio2.Checked || Radio3.Checked;
}

Et voilà, grâce au CustomValidator nous pouvons valider un groupe de RadioButton.
N. B. Dans un cas comme celui-ci, vous auriez tout à fait intérêt à utiliser le RadioButtonList.

6.6.3.Valider deux contrôles avec le même validator / Changer le message d'erreur du validateur

Pouvoir passer des paramètres au validator va nous permettre de valider plusieurs contrôles avec le même validator, s'ils ont une relation de dépendance entre eux.
Par exemple, j'ai deux TextBox pour saisir le numéro de téléphone fixe et le numéro de téléphone mobile. Je veux qu'il soit obligatoire d'en saisir au moins un des deux.
On sent que potentiellement, il peut y avoir plusieurs messages d'erreurs (le numéro de téléphone est incorrect, veuillez saisir au moins un numéro de téléphone, etc.).

Voilà comment je pourrais faire :

<asp:TextBox runat="server" ID="TelFixe" MaxLength="10" />
<asp:TextBox runat="server" ID="TelMobile" MaxLength="10" />
<asp:CustomValidator ID="CustomValidator1" runat="server" ClientValidationFunction="ValiderTelephones"
OnServerValidate="ValiderTelephones" Display="dynamic">
<%#GetMessageErreur() %>
</asp:CustomValidator>

<asp:Button runat="server" Text="Valider" />

On remarque l'utilisation du scriptlet d'expressions liées (#) à l'intérieur de la balise du CustomValidator, ce qui nous permettra côté serveur de préciser le message d'erreur que l'on veut au moment de la validation. Nous y reviendrons un peu plus bas.

6.6.4.1.Côté client

Côté client, pour piloter le message d'erreur en javascript, on utilisera l'objet représentant le validator : sender. Notamment les propriétés sender.innerHTML et sender.errormessage.
Le champ sender.innerHTML permettra de renseigner le corps du validator, ce qui correspond à ce qui serait affiché par le scriplet d'expressions liés #, grâce à <%#GetMessageErreur() %> .
Il ne faut pas oublier de renseigner sender.errormessage afin qu'il puisse potentiellement être exploité par un ValidationSummary.

On va passer à la fonction les id des deux contrôles à valider et on va passer également les messages d'erreurs. Les messages d'erreurs pourraient être écrits directement dans la fonction javascript, mais j'en ai décidé arbitrairement autrement.
Notez l'utilisation du quatrième paramètre à true pour encoder la chaine.

Page.ClientScript.RegisterExpandoAttribute(CustomValidator1.ClientID, "telfixe", TelFixe.ClientID);
Page.ClientScript.RegisterExpandoAttribute(CustomValidator1.ClientID, "telmobile", TelMobile.ClientID);
Page.ClientScript.RegisterExpandoAttribute(CustomValidator1.ClientID, "fixeErreur",
"Le téléphone doit être saisi sous la forme 0101010101", true);
Page.ClientScript.RegisterExpandoAttribute(CustomValidator1.ClientID, "mobileErreur",
"Le téléphone doit être saisi sous la forme 0601010101", true);
Page.ClientScript.RegisterExpandoAttribute(CustomValidator1.ClientID, "unDesDeuxErreur",
"Veuillez saisir au moins un numéro de téléphone", true);

Ainsi, le contrôle des champs de téléphone pourra s'effectuer ainsi :

function ValiderTelephones(sender, args)
{
var fixeValue = document.getElementById(sender.telfixe).value;
var mobileValue = document.getElementById(sender.telmobile).value;
var reFixe = /^[0-9]{10}$/;
var reMobile = /^06[0-9]{8}$/;
var fixeOk = reFixe.test(fixeValue);
var fixeVide = fixeValue.length == 0;
var mobileOk = reMobile.test(mobileValue);
var mobileVide = mobileValue.length == 0;

args.IsValid = true;
if (fixeVide && mobileVide)
{
sender.innerHTML = sender.unDesDeuxErreur;
sender.errormessage = sender.unDesDeuxErreur;
args.IsValid = false;
}
if (!fixeVide && !fixeOk)
{
sender.innerHTML = sender.fixeErreur;
sender.errormessage = sender.fixeErreur;
args.IsValid = false;
}
if (!mobileVide && !mobileOk)
{
sender.innerHTML = sender.mobileErreur;
sender.errormessage = sender.mobileErreur;
args.IsValid = false;
}
}

Côté client, nous avons donc pu changer notre message d'erreur en fonction de l'évaluation de la méthode de validation.

6.6.4.2.Côté serveur

La validation côté serveur est très semblable, cependant il faut toujours la faire. (Je crois que je l'aurai assez répété tout au long de ce tutoriel) :

protected void ValiderTelephones(object source, ServerValidateEventArgs args)
{
_messageErreur = "";
bool isValid = true;

Regex regTelFixe = new Regex("^[0-9]{10}$");
Regex regTelMobile = new Regex("^06[0-9]{8}$");
bool fixeOk = regTelFixe.IsMatch(TelFixe.Text);
bool fixeVide = string.IsNullOrEmpty(TelFixe.Text);
bool mobileOk = regTelMobile.IsMatch(TelMobile.Text);
bool mobileVide = string.IsNullOrEmpty(TelMobile.Text);

if (fixeVide && mobileVide)
{
if (_messageErreur.Length != 0)
_messageErreur = _messageErreur + "<br/>";
_messageErreur += "Veuillez saisir au moins un numéro de téléphone";
isValid = false;
}
if (!fixeVide && !fixeOk)
{
if (_messageErreur.Length != 0)
_messageErreur = _messageErreur + "<br/>";
_messageErreur += "Le numéro de téléphone est incorrect";
isValid = false;
}
if (!mobileVide && !mobileOk)
{
if (_messageErreur.Length != 0)
_messageErreur = _messageErreur + "<br/>";
_messageErreur += "Le numéro de mobile est incorrect";
isValid = false;
}
if (!isValid)
CustomValidator1.DataBind();
args.IsValid = isValid;
}

private string _messageErreur;

protected string GetMessageErreur()
{
return _messageErreur;
}

À la fin de la méthode de validation, s'il y a une erreur, on appelle la méthode DataBind sur le CustomValidator, ainsi, la fonction GetMessageErreur sera évaluée et renverra la liste des erreurs. Ici, elle sera simplement formatée avec un <br/> entre chaque message.
Côté serveur, nous avons également pu changer notre message d'erreur en fonction de l'évaluation de la méthode de validation.

6.6.4.3.Remarques

Deux remarques à faire sur cette validation :

- évidemment, il serait plus intelligent de faire plusieurs validators, en combinant RequiredFieldValidator, RegularExpressionValidator et autres, plutôt que d'utiliser un seul CustomValidator, mais vous l'aurez compris… c'est pour l'exemple ;

- le problème de cette solution est que comme on ne précise pas de ControlToValidate la validation ne s'exécute que lors du clic sur le bouton, alors qu'en général, elle peut s'effectuer par exemple lors de la perte de focus du contrôle si ControlToValidate est renseigné.

La solution est d'enregistrer chaque contrôle à surveiller grâce à la fonction javascript ValidatorHookupControlID.
Comme cet enregistrement doit se faire après la création automatique de la méthode ValidatorOnSubmit, on va utiliser la méthode Page.ClientScript.RegisterStartupScript pour enregistrer notre script :

Page.ClientScript.RegisterStartupScript(GetType(), "hookup", string.Format("ValidatorHookupControlID('{0}',
document.all ? document.all['{1}'] : document.getElementById('{1}'));", TelFixe.ClientID, CustomValidator1.ClientID), true);
Page.ClientScript.RegisterStartupScript(GetType(), "hookup2", string.Format("ValidatorHookupControlID('{0}',
document.all ? document.all['{1}'] : document.getElementById('{1}'));", TelMobile.ClientID, CustomValidator1.ClientID), true);

N. B. Lorsque l'on utilise ValidatorHookupControlID, il ne faut absolument pas utiliser la propriété ControlToValidate.

6.6.4.Utiliser un CustomValidator pour afficher des images en fonction du résultat de la validation

C'est bien joli les messages d'erreurs, mais moi j'aimerai quelque chose d'un peu plus visuel, très synoptique, pour que mon utilisateur sache exactement ce qui va et ce qui ne va pas.
Lui indiquer par exemple par une icône que le champ est obligatoire, s'il est mal renseigné ou si tout va bien.

Quelque chose comme ça…

Pour indiquer que le champ est obligatoire

Pour indiquer que le champ n'est pas valide

Pour indiquer que le champ est correct

<div style="float:left">
<asp:TextBox runat="serveur" ID="Email" />
</div>
<div style="float:left">
<div id="image" runat="server"><asp:Image ID="Image1" runat="server" ImageUrl="warning.jpg" /></div>
<div id="imageKO" style="display:none;" runat="server"><asp:Image ID="Image2" runat="server" ImageUrl="erreur.jpg" /></div>
<div id="imageOK" style="display:none;" runat="server"><asp:Image ID="Image3" runat="server" ImageUrl="ok.jpg" /></div>
</div>
<div style="clear:both"></div>

<asp:CustomValidator ID="CustomValidator1" runat="server" ValidateEmptyText="true" ControlToValidate="Email" ErrorMessage="L'email n'est pas correct"
Display="Dynamic" OnServerValidate="ValiderEmail" ClientValidationFunction="ValiderEmail"/>

<asp:Button runat="server" Text="Valider" />

Je mets mes trois images dans des div et je cache les inutiles (avec display:none). Ce sont ces div que je vais manipuler pour donner le bon affichage, je vais donc les passer en paramètres à la fonction de validation cliente.

Page.ClientScript.RegisterExpandoAttribute(CustomValidator1.ClientID, "ImageDefaultDiv", image.ClientID);
Page.ClientScript.RegisterExpandoAttribute(CustomValidator1.ClientID, "ImageKODiv", imageKO.ClientID);
Page.ClientScript.RegisterExpandoAttribute(CustomValidator1.ClientID, "ImageOKDiv", imageOK.ClientID);

La fonction javascript mettra à jour la visibilité des images en fonction du résultat de la validation :

<script type="text/javascript">
function setDisplayDiv(id, visible)
{
var o;
if (document.getElementById)
o = document.getElementById(id).style;
else if (document.layers)
o = document.layers[id];
else if (document.all)
o = document.all[id].style;
if (o)
o.display = (visible ? 'block' : 'none');
}

function ValiderEmail(sender, args)
{
setDisplayDiv(sender.ImageDefaultDiv, false);
var re = /^[_a-zA-Z0-9-]+(\.[_a-zA-Z0-9-]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.(([0-9]{1,3})|([a-zA-Z]{2,3})|(aero|coop|info|museum|name))$/;
args.IsValid = re.test(args.Value);
setDisplayDiv(sender.ImageKODiv, !args.IsValid);
setDisplayDiv(sender.ImageOKDiv, args.IsValid);
}
</script>

Et voilà… Mais… N'avons-nous pas oublié quelque chose ?
La validation côté serveur bien sur !
Nous devons reproduire le même fonctionnement :

protected void ValiderEmail(object source, ServerValidateEventArgs args)
{
Regex reg = new Regex(
@"^[_a-zA-Z0-9-]+(\.[_a-zA-Z0-9-]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.(([0-9]{1,3})|([a-zA-Z]{2,3})|(aero|coop|info|museum|name))$");
args.IsValid = reg.IsMatch(args.Value);
image.Attributes["style"] = "display:none";
imageKO.Attributes["style"] = args.IsValid ? "display:none" : "display:block";
imageOK.Attributes["style"] = args.IsValid ? "display:block" : "display:none";
}

Outre les images qui, je le conçois, ne sont peut-être pas des plus belles, ça rend quand même pas mal non ? :)

6.7.Réflexion sur le passage de paramètres

Pourquoi utiliser RegisterExpandoAttribute plutôt que directement <%=… .ClientId%> ?
En effet, au lieu de combiner par exemple :

Page.ClientScript.RegisterExpandoAttribute(CustomValidator1.ClientID, "ImageDefaultDiv", image.ClientID);

et

function ValiderEmail(sender, args)
{
setDisplayDiv(sender.ImageDefaultDiv, false);
}

On pourrait faire directement :

function ValiderEmail(sender, args)
{
setDisplayDiv('<%=image.ClientID %>', false);
}

Oui, mais là, deux soucis…
Ceci rend la fonction ValiderEmail dépendante d'une image (ou autre), ce qui fait que si j'ai besoin de valider un autre email plus bas dans la page… je suis obligé de dupliquer la fonction.
Cela oblige également à écrire la méthode javascript directement dans la page, pour que le scriptlet d'expressions systématiques <%= expression %> soit évalué.
Donc, cela empêche de mettre les scripts dans un fichier .js indépendant, dont la gestion du cache serait gérée par le navigateur.

Conclusion : utiliser RegisterExpandoAttribute, c'est plus propre, plus générique, plus réutilisable, plus séduisant…

7.Créer son propre validator

Vous utilisez une validation particulière dans votre site à plusieurs endroits différents ? Et vous ne voulez pas dupliquer à l'infini votre code (et vous avez raison !) ?
La solution : créer son propre validator.

Toute classe qui implémente IValidator est éligible pour être un validator dans le framework de validation d'ASP.NET.
L'interface IValidator est définie comme ci-dessous :

public interface IValidator
{
string ErrorMessage { get; set; }
bool IsValid { get; set; }
void Validate();
}

En général, on n'aura pas besoin d'implémenter directement l'interface, car le framework.net dispose d'une classe abstraite BaseValidator qui sert de base pour les contrôles de validation.
BaseValidator dérive elle même de la classe Label, c'est pour cela que le rendu HTML est un span.
Elle implémente IValidator et fournit l'implémentation des fonctionnalités élémentaires d'un validator, comme son enregistrement dans la collection des validators de la page, l'accès aux méthodes javascript de validations, etc.
Elle expose la propriété ControlToValidate, la méthode GetControlValidationValue qui permet de récupérer la valeur du contrôle à valider.
Elle définit la méthode abstraite EvaluateIsValid qu'il faudra surcharger pour implémenter la logique de validation.
Elle gère le rendu du validator quand il n'est pas valide…

Bref, elle fait presque tout. Nous avons juste à l'adapter à nos besoins.

Afin d'illustrer la création d'un validator, nous allons gérer la validation d'un RIB bancaire.
Pour savoir comment valider un RIB, je vous renvoie vers l'article de Wikipédia : http://fr.wikipedia.org/wiki/Clé_RIB.
Pour résumer, un RIB est composé de quatre champs et doit suivre les règles suivantes :

- le code banque doit être composé de cinq chiffres ;

- le code guichet doit être composé de cinq chiffres ;

- le numéro de compte doit être composé de onze chiffres ou lettres ;

- la clé RIB doit être composée de deux chiffres ;

- la clé RIB doit correspondre à celle calculée à partir des trois autres champs.

Voici ce que va contenir notre page ASPX sans validation :

<style type="text/css">
.cell
{
float:left;
width:150px;
}
.width2
{
width:30px;
}
.width5
{
width:70px;
}
.width11
{
width:120px;
}
.clear
{
clear:both;
}
</style>

<form id="form1" runat="server">

<div>
<div class="cell">Code banque</div>
<div class="cell">Code guichet</div>
<div class="cell">Numéro de compte</div>
<div class="cell">Clé RIB</div>
<div class="clear"></div>
</div>
<div>
<div class="cell"><asp:TextBox runat="server" ID="CodeBanque" MaxLength="5" CssClass="width5" /></div>
<div class="cell"><asp:TextBox runat="server" ID="CodeGuichet" MaxLength="5" CssClass="width5"/></div>
<div class="cell"><asp:TextBox runat="server" ID="NumeroCompte" MaxLength="11" CssClass="width11"/></div>
<div class="cell"><asp:TextBox runat="server" ID="CleRib" MaxLength="2" CssClass="width2"/></div>
<div class="clear"></div>
</div>
<asp:button ID="Button1" text="Valider" runat="server"/>
</form>

Ce qui nous donne ça :

Créons donc maintenant notre validator. Comme je veux qu'il puisse fonctionner indépendamment, je vais créer un nouveau projet de type « Bibliothèques de contrôles web ».
Puis je vais créer une classe que je vais appeler RIBValidator.
Elle va donc dériver de BaseValidator :

namespace RibValidator
{
public class RIBValidator : BaseValidator
{
}
}

Mon but est que mon validator ressemble à ça :

<Exemple:RibValidator runat="server" ID="MonRibValidator" CodeBanqueID="CodeBanque" CodeGuichetID="CodeGuichet"
NumeroCompteID="NumeroCompte" CleRibID="CleRib" Display="Dynamic" />

Première chose à faire, créer les propriétés CodeBanqueID, CodeGuichetID, NumeroCompteID et CleRibID :

[Bindable(true), IDReferenceProperty(typeof(TextBox)), TypeConverter(typeof(TextBoxIDConverter)), Category("Controles"),
DefaultValue(""), Description("L'id du TextBox représentant le code banque")]
public string CodeBanqueID
{
get
{
string codeBanqueId = (string)ViewState["CodeBanqueID"];
return codeBanqueId ?? String.Empty;
}
set { ViewState["CodeBanqueID"] = value; }
}
[Bindable(true), IDReferenceProperty(typeof(TextBox)), TypeConverter(typeof(TextBoxIDConverter)), Category("Controles"),
DefaultValue(""), Description("L'id du TextBox représentant le code guichet")]
public string CodeGuichetID
{
get
{
string codeGuichetID = (string)ViewState["CodeGuichetID"];
return codeGuichetID ?? String.Empty;
}
set { ViewState["CodeGuichetID"] = value; }
}
[Bindable(true), IDReferenceProperty(typeof(TextBox)), TypeConverter(typeof(TextBoxIDConverter)), Category("Controles"),
DefaultValue(""), Description("L'id du TextBox représentant le numéro du compte")]
public string NumeroCompteID
{
get
{
string numeroCompteID = (string)ViewState["NumeroCompteID"];
return numeroCompteID ?? String.Empty;
}
set { ViewState["NumeroCompteID"] = value; }
}
[Bindable(true), IDReferenceProperty(typeof(TextBox)), TypeConverter(typeof(TextBoxIDConverter)), Category("Controles"),
DefaultValue(""), Description("L'id du TextBox représentant la clé RIB")]
public string CleRibID
{
get
{
string cleRibID = (string)ViewState["CleRibID"];
return cleRibID ?? String.Empty;
}
set { ViewState["CleRibID"] = value; }
}

Chaque propriété dispose d'un get et d'un set. On stocke la valeur dans le ViewState. Rien de sorcier.

Au-dessus de chaque propriété, on observe un certain nombre d'attributs, je vais passer rapidement sur les attributs Bindable, Category, DefaultValue et Description que vous connaissez sans doute déjà, sinon vous trouverez plus d'infos dans MSDN.
Ils permettent respectivement de spécifier que la propriété peut subir des opérations de liaisons de données, de la ranger dans une catégorie (dans le designer pour la fenêtre de propriétés), de lui spécifier une valeur par défaut et de décrire la propriété.
L'attribut IDReferenceProperty permet de dire que cette propriété acceptera uniquement des ID correspondant à des contrôles.
Ainsi, resharper pourra nous signaler une erreur par exemple si l'on met n'importe quoi :

La valeur est en rouge et resharper signale que la valeur ne peut pas être résolue.
Grâce à cet attribut, maintenant la propriété accepte des ID de contrôles.

Dans notre cas, on ne souhaite associer que des TextBox. On peut affiner cette restriction en utilisant l'attribut TypeConverter qui, associé à une classe que nous allons écrire, filtrera uniquement les TextBox.
Cette classe sera bien sur TextBoxIDConverter, dont le type est passé en paramètre à TypeConverter.

public class TextBoxIDConverter : ControlIDConverter
{
protected override bool FilterControl(Control control)
{
return control is TextBox;
}
}

Elle hérite de ControlIDConverter qui fournit un convertisseur de type récupérant une liste d'ID de contrôle dans le conteneur actuel.
En surchargeant la méthode FilterControl, on retournera vrai uniquement si le contrôle est un TextBox.
Ainsi, dans le designer, on aura une liste déroulante qui nous proposera que les textbox disponibles dans la page :

(note qui n'a rien à voir, il est dommage que resharper ne sache pas exploiter cet attribut)
Maintenant, je vais surcharger la propriété ControlToValidate, car je ne veux pas qu'elle puisse être utilisée, je lève donc une exception :

public new string ControlToValidate
{
get { return string.Empty; }
set { throw new NotSupportedException("Ne pas utiliser ControlToValidate"); }
}

Grâce au designer, on réduit grandement les risques de mauvaise utilisation du contrôle, mais il faut quand même vérifier que les données qu'on reçoit sont bien celles qu'on attend (au cas où l'utilisateur ferait n'importe quoi…).
C'est à ça que va servir la méthode ControlPropertiesValid :

protected override bool ControlPropertiesValid()
{
// vérifie que les ID sont renseignés
if (CodeBanqueID.Length == 0)
throw new HttpException(string.Format("L'id du code banque du validator '{0}' doit être renseigné.", ID));
if (CodeGuichetID.Length == 0)
throw new HttpException(string.Format("L'id du code guichet du validator '{0}' doit être renseigné.", ID));
if (NumeroCompteID.Length == 0)
throw new HttpException(string.Format("L'id du numéro de compte du validator '{0}' doit être renseigné.", ID));
if (CleRibID.Length == 0)
throw new HttpException(string.Format("L'id de la clé rib du validator '{0}' doit être renseigné.", ID));

// vérifie que sont associés des TextBox
if (!(FindControl(CodeBanqueID) is TextBox))
throw new HttpException(string.Format("Le contrôle code banque {0} doit être un TextBox", CodeBanqueID));
if (!(FindControl(CodeGuichetID) is TextBox))
throw new HttpException(string.Format("Le contrôle code guichet {0} doit être un TextBox", CodeGuichetID));
if (!(FindControl(NumeroCompteID) is TextBox))
throw new HttpException(string.Format("Le contrôle numéro de compte {0} doit être un TextBox", NumeroCompteID));
if (!(FindControl(CleRibID) is TextBox))
throw new HttpException(string.Format("Le contrôle clé rib {0} doit être un TextBox", CleRibID));

return true; // tout est bon
}

Je vais lever des exceptions si les propriétés nécessaires sont vides ou ne correspondent pas à des TextBox.

Maintenant, je vais gérer la validation en elle même, c'est à ça que va me servir la méthode EvaluateIsValid. C'est cette méthode qui va contenir toute la logique du contrôle :

protected override bool EvaluateIsValid()
{
StringBuilder sb = new StringBuilder();

string codeBanqueString = GetControlValidationValue(CodeBanqueID);
string codeGuichetString = GetControlValidationValue(CodeGuichetID);
string numeroCompteString = GetControlValidationValue(NumeroCompteID);
string cleRibString = GetControlValidationValue(CleRibID);

codeBanqueString = codeBanqueString.Trim();
codeGuichetString = codeGuichetString.Trim();
numeroCompteString = numeroCompteString.Trim();
cleRibString = cleRibString.Trim();

int codeBanque;
int codeGuichet;
int numeroCompte1 = 0;
int numeroCompte2 = 0;
int cleRib;
bool isValid = true;
if (!int.TryParse(codeBanqueString, out codeBanque) || codeBanqueString.Length != 5)
{
sb.Append("<br/>");
sb.Append("Le code banque doit être composé de 5 chiffres");
isValid = false;
}
if (!int.TryParse(codeGuichetString, out codeGuichet) || codeGuichetString.Length != 5)
{
sb.Append("<br/>");
sb.Append("Le code guichet doit être composé de 5 chiffres");
isValid = false;
}
if (numeroCompteString.Length != 11)
{
sb.Append("<br/>");
sb.Append("Le numéro de compte doit être composé de 11 caractères (chiffres ou lettres)");
isValid = false;
}
else
{
char[] lettres = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray();
char[] corresp = "12345678912345678923456789".ToCharArray();
numeroCompteString = numeroCompteString.ToUpper();
for (int i = 0; i < lettres.Length; i++)
numeroCompteString = numeroCompteString.Replace(lettres[i], corresp[i]);
if (!int.TryParse(numeroCompteString.Substring(0, 6), out numeroCompte1) ||
!int.TryParse(numeroCompteString.Substring(6, 5), out numeroCompte2))
{
sb.Append("<br/>");
sb.Append("Le numéro de compte doit être composé de 11 caractères (chiffres ou lettres)");
isValid = false;
}
}
if (!int.TryParse(cleRibString, out cleRib) || cleRibString.Length != 2)
{
sb.Append("<br/>");
sb.Append("La clé RIB doit être composée de 2 chiffres");
isValid = false;
}
if (isValid)
{
int cleCalcul = 97 - ((89 * codeBanque + 15 * codeGuichet + 76 * numeroCompte1 + 3 * numeroCompte2) % 97);
if (cleCalcul != cleRib)
{
sb.Append("<br/>");
sb.Append("La clé RIB ne valide pas les autres champs");
isValid = false;
}
}
if (!isValid)
ErrorMessage = sb.ToString();
return isValid;
}

La méthode GetControlValidationValue permet de récupérer la valeur associée à nos propriétés.
Je ne vais pas vous décrire tout en détail, mais grosso modo, voilà ce qu'il se passe :

- on récupère les valeurs et on vérifie qu'elles correspondent bien à ce qu'on attend ;

- si on détecte une erreur, on l'ajoute à une variable qui va contenir toutes les erreurs séparées par des retours à la ligne HTML <br/> ;

- on effectue la transformation des lettres en chiffres ;

- on calcule la clé de RIB ;

- et si ce n'est pas valide, on renseigne la propriété ErrorMessage avec notre message d'erreur personnalisé.

Le retour de la fonction indiquera si la validation est bonne ou non.

Pour que le contrôle s'insère dans le framework de validation client, il va falloir ajouter des attributs, cela se fait dans la méthode AddAttributesToRender :

protected override void AddAttributesToRender(HtmlTextWriter writer)
{
base.AddAttributesToRender(writer);
if (RenderUplevel)
{
Page.ClientScript.RegisterExpandoAttribute(ClientID, "evaluationfunction",
"RibValidatorEvaluateIsValid", false);
Page.ClientScript.RegisterExpandoAttribute(ClientID, "codebanqueid", CodeBanqueID, false);
Page.ClientScript.RegisterExpandoAttribute(ClientID, "codeguichetid", CodeGuichetID, false);
Page.ClientScript.RegisterExpandoAttribute(ClientID, "numerocompteid", NumeroCompteID, false);
Page.ClientScript.RegisterExpandoAttribute(ClientID, "cleribid", CleRibID, false);

Page.ClientScript.RegisterExpandoAttribute(ClientID, "banquemessage",
"Le code banque doit être composé de 5 chiffres", true);
Page.ClientScript.RegisterExpandoAttribute(ClientID, "guichetMessage",
"Le code guichet doit être composé de 5 chiffres", true);
Page.ClientScript.RegisterExpandoAttribute(ClientID, "numCompteMessage",
"Le numéro de compte doit être composé de 11 caractères (chiffres ou lettres)", true);
Page.ClientScript.RegisterExpandoAttribute(ClientID, "cleRibMessage",
"La clé RIB doit être composée de 2 chiffres", true);
Page.ClientScript.RegisterExpandoAttribute(ClientID, "cleRibMessageValid",
"La clé RIB ne valide pas les autres champs", true);
}
}

Nous avons besoin du nom de la méthode cliente qui va faire la validation, on utilise evaluationfunction à laquelle on associe la méthode javascript RibValidatorEvaluateIsValid que nous n'allons pas tarder à écrire.
Nous aurons besoin des ID des contrôles qui sont associés aux propriétés du validator.
Et des messages d'erreurs (que je ne veux pas gérer dans le javascript).

Voici maintenant la fonction de validation côté cliente. Je vais créer un nouveau fichier que je vais ajouter à la solution : RIBValidator.js
Il va contenir la fonction ci-dessous :
On remarque qu'en paramètre est passé le validateur, on pourra récupérer les valeurs passées grâce à RegisterExpandoAttribute comme d'habitude, en utilisant sender.codebanqueid par exemple.
On remarque également l'utilisation de la méthode ValidatorGetValue pour récupérer la valeur d'un contrôle. C'est une fonction qui fait partie du framework client de validation. Elle va remplacer les « document.getElementById » que nous utilisions plus haut.
J'utilise également ValidatorTrim pour enlever les espaces inutiles.

function RibValidatorEvaluateIsValid(sender)
{
var codeBanque = ValidatorTrim(ValidatorGetValue(sender.codebanqueid));
var codeGuichet = ValidatorTrim(ValidatorGetValue(sender.codeguichetid));
var numCompte = ValidatorTrim(ValidatorGetValue(sender.numerocompteid));
var cleRib = ValidatorTrim(ValidatorGetValue(sender.cleribid));

var isValid = true;
var messageErreur = "";

var re = /^[0-9]{5}$/;
if (!re.test(codeBanque))
{
messageErreur = messageErreur + '<br/>' + sender.banquemessage;
isValid = false;
}
if (!re.test(codeGuichet))
{
messageErreur = messageErreur + '<br/>' + sender.guichetMessage;
isValid = false;
}
re = /^[a-zA-Z0-9]{11}$/;
if (!re.test(numCompte))
{
messageErreur = messageErreur + '<br/>' + sender.numCompteMessage;
isValid = false;
}

var lettres = "abcdefghijklmnopqrstuvwxyz";
var corresp = [1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9,2,3,4,5,6,7,8,9];
var exp;
for (i = 0 ; i < lettres.length ; i++)
{
exp = new RegExp(lettres.substr(i,1), "gi");
numCompte = numCompte.replace(exp, corresp[i]);
}
var intNumCompte1 = 0;
var intNumCompte2 = 0;
try
{
numCompte1 = parseInt(numCompte.substr(0, 6));
numCompte2 = parseInt(numCompte.substr(6, 11));
}
catch (e)
{
messageErreur = messageErreur + '<br/>' + sender.numCompteMessage;
isValid = false;
}
re = /^[0-9]{2}$/;
if (!re.test(cleRib))
{
messageErreur = messageErreur + '<br/>' + sender.cleRibMessage;
isValid = false;
}

var intCodeBanque;
var intCodeGuichet;
var intCleRib;
intCodeBanque = parseInt(codeBanque);
intCodeGuichet = parseInt(codeGuichet);
intCleRib = parseInt(cleRib);
if (isValid)
{
var cleCalcul = 97 - ( (89 * intCodeBanque + 15 * intCodeGuichet + 76 * numCompte1 + 3 * numCompte2) % 97);
if (cleCalcul != intCleRib)
{
messageErreur = messageErreur + '<br/>' + sender.cleRibMessageValid;
isValid = false;
}
}
if (!isValid)
{
sender.innerHTML = messageErreur;
sender.errormessage = messageErreur;
}
return isValid;
}

La fonction effectue la même validation que côté serveur. Elle construit un message d'erreur. Si la fonction n'est pas valide, on affectera les propriétés innerHTML et errormessage du validator.
Il reste deux choses à faire :

- inclure le script JS ;

- définir les contrôles qui feront l'objet d'une surveillance côté client par le framework de validation.

Pour inclure le script JS, plusieurs solutions s'offrent à nous.

- Soit, on écrit directement le JS dans la page, mais cela pose le problème que la page va s'alourdir inutilement en contenant le code javascript.

- Soit on inclut le javascript en utilisant une balise du style :

<script type="text/javascript" scr="RIBValidator.js"></script>

avec éventuellement le chemin d'accès au fichier, mais cela veut dire qu'il faut se trimballer le fichier .js toujours au bon emplacement, ce qui pose des problèmes de réutilisabilité.

- Soit on inclut le javascript dans les ressources, comme ça, on aura une DLL qui représente un webcontrol, et celle-ci contiendra tout ce qu'il faut.

C'est bien sûr cette troisième solution que nous allons utiliser, le .js sera indépendant et pourra être mis en cache par le navigateur si besoin.

Tout d'abord, faites un clic droit sur le fichier javascript, affichez la fenêtre des propriétés et changez l'action de génération à ressource incorporée
(en anglais : Build Action => « Embedded Resource »)
Maintenant, à la compilation, le js sera incorporé à l'assembly.
Pour accéder désormais à notre ressource, on utilisera le HTTP Handler « WebResource.axd ».

La première chose à faire est de signaler à l'assembly cette ressource afin qu'on ait le droit d'y accéder. Pour ce faire, on va modifier le fichier AssemblyInfo.cs et rajouter la ligne :

[assembly: WebResource("RibValidator.RIBValidator.js", "text/javascript")]

La chaine à passer est de type Namespace.NomFichier.Extension (pour bien faire la différence, le « rib » du namespace est en minuscule alors que le nom du fichier est en majuscules).
Attention : bien vérifier que le namespace utilisé correspond à celui qu'il y a dans les propriétés du projet, onglet Application, Default namespace.
On lui indique également le type MIME du fichier.
Ainsi, on pourra obtenir l'url du fichier en utilisant :

Page.ClientScript.GetWebResourceUrl(GetType(), "RibValidator.RIBValidator.js")

Il suffira alors de le combiner à l'utilisation de la méthode Page.ClientScript.RegisterClientScriptInclude.

Pour la deuxième chose à faire, comme vu précédemment, on utilisera ValidatorHookupControlID. On l'insérera dans la page grâce à Page.ClientScript.RegisterStartupScript :

protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
if (RenderUplevel)
{
Page.ClientScript.RegisterClientScriptInclude("RibValidatorJavaScript",
Page.ClientScript.GetWebResourceUrl(GetType(), "RibValidator.RIBValidator.js"));
string codeBanqueClientId = FindControl(CodeBanqueID) == null ? string.Empty : FindControl(CodeBanqueID).ClientID;
string codeGuichetClientId = FindControl(CodeGuichetID) == null ? string.Empty : FindControl(CodeGuichetID).ClientID;
string numeroCompteClientId = FindControl(NumeroCompteID) == null ? string.Empty : FindControl(NumeroCompteID).ClientID;
string cleRibClientId = FindControl(CleRibID) == null ? string.Empty : FindControl(CleRibID).ClientID;

Page.ClientScript.RegisterStartupScript(GetType(), "hookup1", string.Format("
ValidatorHookupControlID('{0}', document.all ? document.all['{1}'] :
document.getElementById('{1}'));", codeBanqueClientId, ClientID), true);
Page.ClientScript.RegisterStartupScript(GetType(), "hookup2", string.Format("
ValidatorHookupControlID('{0}', document.all ? document.all['{1}'] :
document.getElementById('{1}'));", codeGuichetClientId, ClientID), true);
Page.ClientScript.RegisterStartupScript(GetType(), "hookup3", string.Format("
ValidatorHookupControlID('{0}', document.all ? document.all['{1}'] :
document.getElementById('{1}'));", numeroCompteClientId, ClientID), true);
Page.ClientScript.RegisterStartupScript(GetType(), "hookup4", string.Format("
ValidatorHookupControlID('{0}', document.all ? document.all['{1}'] :
document.getElementById('{1}'));", cleRibClientId, ClientID), true);
}
}

Tout ceci se passe dans la méthode OnPreRender.
Vous aurez noté qu'on fait un test sur la propriété RenderUplevel qui permet de déterminer si le navigateur dispose des fonctionnalités pour supporter la validation cliente.

N'oubliez pas, pour utiliser un contrôle personnalisé, on va le déclarer comme ceci dans la page :

<%@ Register TagPrefix="Exemple" Namespace="RibValidator" Assembly="RIBValidator" %>

comme expliqué dans la FAQ ASP.NET.
Vous n'aurez bien sûr pas oublié non plus de référencer le projet correspondant au contrôle ou directement son assembly…

Téléchargez le projet démo complet fait avec Visual Studio 2008 (42 Ko)

8.Validator et ajax

8.1.Validator et UpdatePanel

Dans certaines versions de Visual Studio 2005, on pouvait avoir des comportements étranges en combinant les validators et un UpdatePanel.
Par exemple, des messages d'erreurs qui ne s'affichaient pas : http://blogs.msdn.com/mattgi/archive/2007/01/23/asp-net-ajax-validators.aspx

Il fallait également s'assurer par exemple que lorsqu'on disposait un CompareValidator dans un UpdatePanel, le contrôle auquel il est associé soit également dans ce même UpdatePanel.
Plus d'infos dans MSDN.

Je ne constate plus ces erreurs avec Visual Studio 2008.

8.2.Un validateur amélioré avec l'AJAX Control Toolkit

L'Ajax Control Toolkit est un projet communautaire orchestré par Microsoft qui propose un certain nombre de nouveaux contrôles améliorés basés sur les extensions ASP.NET (System.Web.Extensions).
Ce toolkit dispose entre autres d'un contrôle qui va prendre en compte le rendu de l'erreur de notre validator. Il s'agit du contrôle ValidatorCalloutExtender.
Prenons par exemple cette simple validation d'un Textbox qui nécessite qu'une valeur soit saisie et qu'elle soit un chiffre pair :

<asp:TextBox runat="server" ID="LeTextBox" />

<asp:RequiredFieldValidator ID="RequiredFieldValidator1" runat="server" ErrorMessage="Merci de saisir une valeur"
ControlToValidate="LeTextBox" Display="Dynamic" />
<asp:CustomValidator runat="server" ID="CustomValidator1" ControlToValidate="LeTextBox" ClientValidationFunction="ValideNombrePair"
OnServerValidate="ValiderNombrePair" ErrorMessage="La valeur doit être un nombre pair" Display="Dynamic" />
<br/>
<asp:Button runat="server" Text="Valider" />

Avec les méthodes de validation qui vont bien, côté client et côté serveur.

<script type="text/javascript">
function ValideNombrePair(sender, args)
{
if (args.Value == '')
args.IsValid = false;
else
args.IsValid = (args.Value % 2 == 0);
}
</script>

protected void ValiderNombrePair(object source, ServerValidateEventArgs args)
{
int nombre;
if (int.TryParse(args.Value, out nombre))
{
args.IsValid = nombre % 2 == 0;
return;
}
args.IsValid = false;
}

Nous obtenons le résultat suivant :

Maintenant, utilisons le fameux ValidatorCalloutExtender.
Pour pouvoir utiliser l'Ajax Control Toolkit, si vous utilisez Visual Studio 2005, il vous faut dans un premier temps ajouter les extensions ASP.NET.
Si vous utilisez Visual Studio 2008, il vous suffit de cibler le framework 3.5.
Pour ajouter les extensions ASP.NET, il faut les installer : http://asp.net/ajax/downloads/
N'oubliez pas d'aller modifier votre web.config et d'ajouter :

<httpHandlers>
<add verb="GET,HEAD" path="ScriptResource.axd" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions,
Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" validate="false"/>
</httpHandlers>

(dans la section <system.web>)
sinon, vous aurez la fameuse erreur javascript : Sys is not defined.

Une fois votre application web disposant des extensions ASP.NET, il faut installer l'Ajax Control Toolkit (une simple assembly suffit).
Vous pouvez la télécharger ici ou simplement la récupérer dans mon projet de démo.
Reste plus qu'à modifier le web.config pour qu'ASP.NET reconnaisse les contrôles de l'Ajax Control Toolkit :

<add tagPrefix="ajaxToolkit" namespace="AjaxControlToolkit" assembly="AjaxControlToolkit"/>

(à disposer dans la section <system.web>, <pages>, <controls>)
Vous pouvez désormais utiliser ces nouveaux contrôles.
Modifiez vos validators pour avoir un display à none et ajoutez simplement un script manager au-dessus des autres contrôles :

<asp:ScriptManager runat="server" />

Et ajoutez les contrôles ValidatorCalloutExtender, un pour chaque validator :

<ajaxToolkit:ValidatorCalloutExtender ID="ValidatorCalloutExtender1" runat="server" TargetControlID="RequiredFieldValidator1" />
<ajaxToolkit:ValidatorCalloutExtender ID="ValidatorCalloutExtender2" runat="server" TargetControlID="CustomValidator1" />

Il suffit juste d'indiquer dans la propriété TargetControlID l'id du validator à surveiller.
Notre rendu sera désormais le suivant :

Après, on peut paramétrer ce contrôle pour changer les images par défaut grâce aux propriétés CloseImageUrl ou WarningIconImageUrl, changer la taille de la bulle grâce à la propriété Width, etc.

Téléchargez le projet de démo fait avec Visual Studio 2008 (362 Ko)

10.Conclusion

J'espère qu'à travers cet article vous avez pu améliorer vos connaissances dans la validation des saisies utilisateur, apprendre à utiliser au meilleur de ses capacités le framework de validation d'ASP.NET.
La validation est une étape cruciale du cycle de vie de la page ASP.NET, ne la négligez pas.

Remerciements

Je remercie l'équipe Dotnet pour ses relectures attentives du document.

Contact

Si vous constatez une erreur dans le tutoriel, dans le source, dans la programmation ou pour toutes informations, n'hésitez pas à me contacter par mail, ou par le forum.