Suite à mes précédents tests sur les performances des bindings WCF, je me suis rappelé la possibilité d'activer GZip pour certains bindings.
Avant tout, revenons rapidement sur ce qu'est un binding WCF.
Les bindings sont les éléments clés de WCF, il s'agit d'un composant permettant de spécifier comment les messages sont transférés entre le client et le serveur, c'est ici qu'est spécifié le mode de transport, l'encodage, la sécurité, etc ... Un binding est donc composé de plusieurs éléments qu'on appelle BindingElement
Pour qu'un binding soit fonctionnel, il faut au minimum spécifier un BindingElement de type Transport et un de type Encoder. Par défaut WCF fournit 3 encoder différents : Text, Binary et Mtom.
Bien sur on peut créer nos propres BindingElement. C'est ce qu'a fait Microsoft dans les exemples du SDK windows, en effet dans le dossier %SDKPath%\Samples\WCFSamples.zip\TechnologySamples\Extensibility\MessageEncoder\Compression se trouve un projet nommé GZipEncoder. Ce projet fournit un BindingElement de type Encoder qui permet de GZipper les messages. Vous pouvez télécharger le windows SDK ici ou ici pour télécharger seulement les exemples WCF (très intéressant !)
Pour utiliser ce BindingElement, il faut d'abord l'enregistrer dans le fichier de config. Puis créer un CustomBinding dans lequel on place les BindingElements souhaités.
<extensions>
<bindingElementExtensions>
<add name="GZipMessageEncoding" type="Microsoft.ServiceModel.Samples.GZipMessageEncodingElement, GZipEncoder" />
</bindingElementExtensions>
</extensions>
<binding name="customTcpBinaryGzip">
<GZipMessageEncoding />
<tcpTransport />
</binding>
Le GZipEncoder ne fait qu'encapsuler un Encoder existant, il est possible de spécifier quel encoder sous-jacent sera utilisé. Par défaut il s'agit du BinaryMessageEncoding mais on peut également spécifier le TextMessageEncoding.
<binding name="customNetTcpTextGZip">
<GZipMessageEncoding innerMessageEncoding="textMessageEncoding" />
<tcpTransport />
</binding>
Un article très complet est consacré à ce projet sur msdn : Custom Message Encoder: Compression Encoder
Nieau perf ?
J'ai procédé exactement au même test que précédemment, j'ai rajouté différentes méthodes, les méthodes en GetDS* retournent non pas une List<Person> mais un DataSet qui contient les mêmes informations.
Voici les résultats :
Une fois de plus, pas facile de commenter ces chiffres. Voici quelques conclusions :
- Compression Gzip Vs non GZip :
- Petits objets : La méthode Add net fait que transférer 3 entiers, on voit pour cette méthode qu'activer le GZip pour si peu de données n'est pas utile, cela ralentit et fait grossir les échanges par rapport à la version non Gzippé
- Moyens et Gros objets :
- La compression GZip fait généralement baisser la consommation réseau. A noter que le NetTcpNoSecu est plus efficace (conso réseau équivalente mais temps divisé par 2) que la version GZip lorsque l'on transfère une liste générique.
- Text+Gzip Vs Binary+Gzip
- La compression GZip réduit la consommation réseau du BinaryEncoder et TextEncoder au même niveau. Pour la durée, le Binary+GZip est à chaque fois plus rapide, mais la différence est faible et dépend du type d'objet transféré.
- D'une manière général, l'utilisation GZip est un peu plus longue que son équivalent non GZip, si vous avez un réseau rapide est des petits ou moyens objets, mettre en place du GZip n'améliorera pas forcément vos performances, au contraire !
- DataSet Vs List générique :
- Si l'on n' utilise pas le GZip, le DataSet est plus couteux, aussi bien en temps qu'en consommation réseau.
- Si l'on utilise le GZip, le DataSet reste plus couteux en temps (+50%) mais n'a aucune incidence au niveau consommation réseau.
Bien sur, on pourrait tirer pleins de conclusions hâtives à partir de ces données !
Il ne faut pas oublier que ces données ont été obtenus dans un contexte précis : un seul client non multi-threadé, pas de sécurité. Ces données permettent juste de données la couleur on ne peut pas généraliser. En effectuant mes tests (environ 3h pour tout jouer), je voyais nettement la différence entre les bindings via la consommation CPU du serveur, donnée qui n'est pas incluse ici.
D'autres commentaires sur ces données ?
J'aimerais faire des tests pour mesurer la conso CPU du serveur, connaissez-vous une solution me permettant de calculer le nombre d'unité CPU utilisé ? Je n'ai réussit à récupérer que la moyenne de consommation du CPU ce que je trouve pas très fiable, d'autres idées ?
J'avais déjà parlé il y a quelques temps des paramètres optionnels de VB.net : C# et paramètre optional. Je me suis récemment rendu compte d'une autre particularité.
Soit une assembly écrit en VB.net possédant une seule méthode :
Public Class Class1
Shared Sub Test(Optional ByVal i As Integer = 123)
Console.WriteLine(i)
End Sub
End Class
Puis une deuxième assembly utilisant cette assembly :
Module Module1
Sub Main()
ClassLibrary1.Class1.Test()
End Sub
End Module
Sans grande surprise, si on exécute la deuxième assembly, "123" va s'afficher sur la console.
Par contre si vous modifiez la valeur de l'argument optionnel de la première assembly, puis copiez l'assembly compilé dans le dossier de la 2ème assembly, alors votre application affichera toujours "123" et non la nouvelle valeur !
Mais si vous recompilez la deuxième assembly en faisant référence à votre première assembly alors vous aurez le résultat attendu.
Pourquoi ?
Les paramètres optionnels ne sont que des attributs ajoutés aux paramètres de la méthode. Si l'on appel ces méthodes en omettant des paramètres c'est lors de la compilation qu'ils seront rajoutés.
Les paramètres optionnels font partis des rares fonctionnalités que j'aurais envie de voir en C#. Cependant, on vient de voir que l'implémentation de VB.net pose quelques soucis, de plus cet implémentation n'est pas CLS compliant.
Existe-t-il une façon de rendre les paramètres optionnels CLS compliant ? Alors que je lisais Framework Ddesign Guidelines, un des auteurs explique que C# (et VB.net) pourrait implémenter cette fonctionnalité comme un sucre syntaxique. C'est à dire qu'au lieu de rajouter des attributs sur les arguments, le compilateur génère n méthodes en fonction des différents paramètres optionels.
Par exemple ce code :
public class HopeThings
{
public void MyMethod(int a, optional int b = 0) { }
}
serait compilé ainsi :
public class HopeThingsCompiled
{
public void MyMethod(int a) { this.MyMethod(a, 0); }
public void MyMethod(int a, int b) { }
}
Bien sur cela pose quelques soucis puisque l'on ne pourra plus choisir les paramètres que l'on veut renseigner grâce à l'opérateur := de VB.net
Module Module1
Sub Main()
MyMethod(b:=3)
End Sub
Public Sub MyMethod(Optional ByVal a As Integer = 0, Optional ByVal b As Integer = 0)
End Sub
End Module
Il y a surement d'autres cas actuellement possible en VB.net qui deviendront impossible avec cette astuce.
Qu'en pensez-vous ?
[Update] d'autres résultats disponible : WCF - GZip à la rescousse des performances ?
WCF est une couche de .net 3 permettant la communication entre 2 machines. Son principal intérêt est de pouvoir changer le moyen de communication (le binding) sans modifier de code, juste en modifiant le fichier de configuration. C'est à dire que si vous décidez plus tard d'utiliser du netTcp au lieu de Http, vous ne devez modifier que le fichier de configuration.
Souvent, nous n'avons pas de contraintes particulières au niveau du choix du binding, netTcp, basicHttp, wsHttp, etc ... En effet, il n'est pas rare de contrôler l'environnement du client et du serveur.
J'ai cherché à connaitre les différentes caractéristiques au niveau des performances de plusieurs bindings. Pour cela mon scénario est le suivant : je créé un client, je l'ouvre, j'appelle ma méthode et je ferme le client, il n'y a pas de notion de session, de transaction, etc ...
Pour cela j'ai créé un service avec 3 méthodes. Chaque méthode me permet de voir le comportement du binding lorsque l'on transfère des petits, moyens ou gros objets.
[ServiceContract]
public interface IMyService
{
[OperationContract]
int Add(int a, int b);
[OperationContract]
List<Person> Get20Persons();
[OperationContract]
List<Person> Get10000Persons();
}
Au niveau du client, j'appelle chaque méthode 100 fois pour chaque binding puis je regarde le temps passé par appel ainsi que la consommation réseau via le compteur ".net CLR networking".
J'ai testé 9 bindings, avec ou sans sécurité, en netTcp, http, etc ... Voici leurs configurations :
<bindings>
<basicHttpBinding>
<binding name="basicHttp" maxBufferSize="2147483647" maxReceivedMessageSize="2147483647" />
</basicHttpBinding>
<customBinding>
<binding name="customNetTcpText">
<textMessageEncoding />
<tcpTransport />
</binding>
<binding name="customHttpBinary">
<binaryMessageEncoding />
<httpTransport />
</binding>
</customBinding>
<netTcpBinding>
<binding name="netTcpNoSecu" maxBufferSize="2147483647" maxReceivedMessageSize="2147483647">
<security mode="None" />
</binding>
<binding name="netTcpMessageSecu" maxBufferSize="2147483647" maxReceivedMessageSize="2147483647">
<security mode="Message" />
</binding>
<binding name="netTcpNoSecuStreamed" transferMode="Streamed"
maxBufferSize="2147483647" maxReceivedMessageSize="2147483647" />
<binding name="netTcp" maxBufferSize="2147483647" maxReceivedMessageSize="2147483647" />
</netTcpBinding>
<wsHttpBinding>
<binding name="wsHttpNoSecu" maxReceivedMessageSize="2147483647">
<security mode="None" />
</binding>
<binding name="wsHttpTransportSecu" maxReceivedMessageSize="2147483647">
<security mode="Transport">
<transport proxyCredentialType="Windows" />
</security>
</binding>
<binding name="wsHttp" maxReceivedMessageSize="2147483647" />
</wsHttpBinding>
</bindings>
Enfin, j'ai réalisé mes tests entre 2 machines distinctes :
- server : Win Server 2008 x86 - 1.5 GB ram - machine virtuelle hébergée via Hyper-V (core 2 Duo - 4GB ram)
- client : Win Server 2008 x64 - 4 GB ram - core 2 Quad
- connexion 100 Mbits/s entre les 2 machines
Chaque test est réalisé 3 fois, il y a donc 3 * 100 appels pour chaque méthode et chaque binding, les tests ne sont pas multi-threadés, c'est à dire que le serveur n'exécute qu'une seule méthode à la fois.
J'ai également testé en utilisant différentes configurations pour le mode de concurrence et d'instanciation du service.
Voici les résultats obtenus avec la création d'une instance du service par appel (valeur par défaut). Dans notre cas, puisque l'on ouvre et ferme le client pour chaque appel le mode PerSession est équivalent au mode PerCall.
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class MyService : IMyService
{
// ...
}
Voici les résultats obtenus en ne créant qu'une seule instance du service :
[ServiceBehavior(
InstanceContextMode=InstanceContextMode.Single,
ConcurrencyMode=ConcurrencyMode.Multiple)]
public class MyService : IMyService
{
// ...
}
Au niveau de la différence entre les modes d'instanciation voici les résultats qui compare les 2 modes :
Les résultats sont obtenus côté client.
- La colonne duration correspond au temps en milliseconde pour effectuer 1 appel, c'est à dire à partir de l'instanciation du client jusqu'a la fermeture de celui-ci
- La colonne output correspond au nombre d'octets envoyés par le client au serveur pour 1 appel
- La colonne input correspond au nombre d'octets téléchargés par le client du serveur pour 1 appel
Il n'est pas facile de tirer des conclusions de ces résultats, chaque binding possède ses avantages et défauts, de plus il faut bien garder en tête le scénario : pas de session/transaction et les résultats sont obtenus avec un seul client non multi-threadé !
- Pour des petits et moyens objets :
- Le plus rapide : binding personalisé en utilisant du http et du binaire
- Le plus économique (network IO) : netTcp sans sécurité
- Pour des gros objets :
- Le plus rapide et le plus économique : netTcp sans sécurité
- Sécurité :
- Pour des petits objets, la sécurité prend beaucoup de place, la différence est négligeable pour de gros objets.
- les bindings sans sécurité sont plus rapides et économiques que leurs équivalents avec sécurité
- la sécurité en mode Message (netTcpMessageSecu et wsHttp) rallonge considérablement la durée des appels.
- je n'explique pas pourquoi le netTcpMessageSecu télécharge à chaque fois 5ko de données, j'ai vérifié les données sont bien présentes, je n'ai pas regardé en détail, ce qu'il se passe avec WireShark.
- Différence entre InstanceContextMode.Single et PerSession
- Globalement la différence est négligeable, on peut sans trop de risque dire que ce paramètre n'influe pas sur les performances.
Je suis assez étonné par les performances de HTTP par rapport à netTcp, avant de faire ce test, j'étais persuadé que tcp serait plus rapide. Je ne sais pas expliquer ce comportement, l'idée que j'ai est que les binding Http utilise le driver http.sys qui a été optimisé pour IIS, une autre idée / explication ?
Attention, de ne pas tirer des conclusions hâtives ! Les résultats exposés ici sont pour un scénario précis, si vous avez un scénario différent, je vous invite vivement à faire (puis partager) vos tests.
Je vais essayer de compléter mes tests, en rajoutant du multithreading et en analysant la conso CPU du serveur.
Avez-vous des questions particulières par rapport à ces résultats ? Voyez-vous un autre binding à tester ?
Vous pouvez retrouver le projet de test ainsi que les résultats au format excel dans le zip associé à ce billet.
Cherchant à connaitre le trafic réseau que consomme une application .net utilisant un service WCF, j'ai cherché un moyen d'obtenir facilement ces informations. Je suis donc allé voir du coté des compteur de performances et j'ai trouvé le compteur .NET CLR Networking, qui me permet de connaitre le nombre d'octets envoyés et reçues.
Ce compteur semble parfait. Malheureusement, après plusieurs essais, pas moyen de le faire fonctionner. J'ai d'abord pensé à un bug lié à ma configuration (server 2008 x64), même problème avec une autre configuration (server 2003 x86).
Après quelques recherches je suis tombé par hasard sur un bug de connect : Feedback .net CLR Networking Performance Counter.
Pour des raisons de perfs (je suppose) il faut spécifier à .net de renseigner les compteurs de performances, pour cela il faut rajouter cette configuration au niveau du fichier de config :
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.net>
<settings>
<performanceCounters enabled="true" />
</settings>
</system.net>
</configuration>
Grace à cette configuration, les compteurs de performances seront renseignés.

Alors que je suis en train de préparer des exemples d'application WCF pour une formation. Je me suis retrouvé à devoir créer de nombreux projets auxquels je rajoutais à chaque fois les mêmes lignes.
Visual Studio permet très facilement de créer des modèles de projet ou Project Template en anglais.
Qu'est-ce qu'un modèle de projet ?
Si vous avez déjà utilisé Visual Studio, vous avez forcément déjà utilisé un Modèle. Comme son nom l'indique, un modèle de projet ou de solution permet de créer une solution "vide" afin de commencer votre nouveau projet. Visual Studio contient de nombreux templates et sont accessible via le menu Fichier > Nouveau :
Si vous créez souvent des projets de test et rajouter à chaque fois les mêmes lignes, Visual Studio vous permet d'exporter le modèle via le menu "Fichier > Exporter le modèle". Une fois cette opération terminé vous trouverez votre modèle dans la liste des projets :
Cette solution est sympathique, mais dans mon cas, je dois créer plusieurs projets dans la même solution. En effet un des "Best practices" de WCF consiste à bien séparer le contrat du service. Je me retrouve donc avec une solution contenant 4 projets :
Heureusement, il est possible de créer des modèles regroupant plusieurs projets. Pour cela j'ai suivi la documentation disponible ici : How to: Create Starter Kits. La création de tel template prend peu de temps. Au bout de 5 minutes, j'avais créer mon zip.
Je peux désormais créer des exemples d'application WCF en quelques clics. J'ai attaché en pièce jointe, le modèle que j'utilise. Il faut placer le zip dans le dossier "C:\Users\Cyril\Documents\Visual Studio 2008\Templates\ProjectTemplates".
Je profite de ce message, pour vous dire que si vous cherchez des formations sur WCF, ASP.net ou JavaScript, contactez moi.
Trop souvent, les entrées utilisateurs ne sont pas vérifiées. Dans le meilleur des cas, le thread exécutant la requête s’arrête à cause d’une exception lors de son traitement dans la couche métier ou dans la couche base de données, mais dans certains cas cela peut avoir des incidences graves : corruption de données, ou pire encore : injection SQL.
Dans cet article, nous allons voir les bonnes pratiques afin de valider la saisie utilisateur dans une application ASP.net.
Les contrôles de validation
Les contrôles de validation fournis avec ASP.net valident la saisie utilisateur à la fois coté client ET coté serveur.
Pourquoi valider les données deux fois ? Coté client et serveur ?
La validation coté client s’effectuera forcément via JavaScript, or il est très simple de désactiver JavaScript ou alors d’envoyer ses propres requêtes HTTP, par conséquent il est nécessaire de toujours effectuer une vérification coté serveur.
La classe BaseValidator
La classe BaseValidator est le contrôle de base dont héritent tous les autres contrôles de validation, cette classe hérite de Label elle possède donc toutes ses propriétés mais aussi quelques autres propriétés intéressantes afin de configurer les différents contrôles de validation :
-
-
Text : Message d’erreur du contrôle, il s’agit de la propriété par défaut
-
ErrorMessage : Message d’erreur affiché dans le
ValidationSummary -
Display : Indique comment le message d’erreur est affiché, les valeurs possibles sont :
-
None : Le message d’erreur n’est jamais affiché.
-
Static : Le contrôle réserve la place au message d’erreur mais n’est affiché que lorsque le contrôle est invalide ; dans ce cas l’affichage ou non du message d’erreur se fait grâce à la propriété CSS visibility.
-
Dynamic : Le contrôle ne réserve pas de place dans la page lorsque le contrôle est valide ; l’affichage du message d’erreur ce fait via la propriété CSS display.
-
-
SetFocusOnError : Indique qu’il faut positionner le curseur sur le contrôle à valider si celui-ci est invalide.
-
IsValid : Indique si l’état du contrôle est valide ou non
La plupart du temps on renseigne la propriété Text avec un astérisque et la propriété ErrorMessage avec le détail du problème, ainsi nous avons un astérisque à coté des contrôles invalide et un résumé des erreurs dans le ValidationSummary que nous verrons plus tard.
La propriété ValidationGroup est très utile, elle permet d’avoir des zones de formulaire au sein d’une même page, cette propriété est également disponible sur les boutons, liste déroulante, ou tout autre contrôle effectuant un postback, cela permet ainsi de valider le formulaire coté client avant de faire un postback.
D’une manière générale chaque contrôle de validation doit avoir sa propriété ValidationGroup renseignée.
La validation s’effectue de façon automatique, tout d’abord coté client via JavaScript puis côté serveur. Lorsque vous utilisez des ValidationGroup, il vous sera alors nécessaire de vérifier si votre groupe de validation est valide avant de rentrer dans le code critique ! Si la page n’est pas valide, alors les contrôles invalides afficheront leur message d’erreur.
protected void btnSubmit_Click(object sender, EventArgs e)
{
Page.Validate("MyValidationGroup");
if (Page.IsValid)
{
// critical code goes here
}
}
Si vous voulez qu’un contrôle précis ne valide pas le formulaire, ce qui est généralement le cas pour les boutons « annuler », il faut alors renseigner la propriété CausesValidation du contrôle faisant le postback (Button, ImageButton, …) à false.
Il existe 5 contrôles de validation fournis par ASP.net
RequiredFieldValidator
Le RequiredFieldValidator permet de vérifier qu’une valeur ait bien été saisie. Voici un exemple d’utilisation :
<asp:label runat="server" AssociatedControlID="tbFirstName">FirstName :</asp:label>
<asp:textbox id="tbFirstName" runat="server"/>
<asp:RequiredFieldValidator runat="server" ControlToValidate="tbFirstName" Display="dynamic"
ErrorMessage="You must enter a first name" Text="*" ValidationGroup="MyValidationGroup" />
A noter que l’on peut aussi renseigner la propriété Text de tous les contrôles de validation directement à l’intérieur de ceux-ci :
<asp:label runat="server" AssociatedControlID="tbFirstName">FirstName :</asp:label>
<asp:textbox id="tbFirstName" runat="server"/>
<asp:RequiredFieldValidator runat="server" ControlToValidate="tbFirstName"
ErrorMessage="You must enter a first name" Display="dynamic"
ValidationGroup="MyValidationGroup" >*</asp:RequiredFieldValidator>
Ce contrôle possède également une propriété DefaultValue qui lui permet de vérifier que la valeur saisie est différente d’une valeur par défaut. Cela est très utile dans le cas d’une liste déroulante, en effet on renseigne souvent la première valeur avec « Sélectionnez un élément » ou autre.
<asp:DropDownList ID="ddlCountry" runat="server" AppendDataBoundItems="true">
<asp:ListItem Value="-1">Sélectionnez un élément</asp:ListItem>
</asp:DropDownList>
<asp:RequiredFieldValidator runat="server" ControlToValidate="ddlCountry" InitialValue="-1"
Text="Choisissez un élément" Display="Dynamic" ValidationGroup="MyValidationGroup"/>
CompareValidator et RangeValidator
Ces deux contrôles sont assez proches et ont d’ailleurs un parent commun BaseCompareValidator. Ils permettent de vérifier la validité d’une entrée par rapport à une autre valeur.
Le CompareValidator vérifie l’entrée en fonction d’une autre valeur, pour cela il dispose de différentes propriétés :
-
-
Equal, NotEqual, GreaterThan, GreaterThanEqual, LessThan, LessThanEqual
-
DataTypeCheck : Permet de vérifier que la valeur est du type voulu. s’utilise en coordination avec la propriété Type.
-
Type : String, Integer, Double, Date, Currency
-
ValueToCompare,
ControlToCompare : indique avec quelle valeur le contrôle doit comparer la valeur du contrôle renseigné par la propriété
ControlToValidate
Le RangeValidator permet de vérifier qu’une valeur est comprise entre deux autres, voici les propriétés du contrôle :
-
Type : String, Integer, Double, Date, Currency
-
Toutes les comparaisons s’effectuent avec la culture du thread en cours, si vous avez un problème pour valider une date dans un autre format, pensez à vérifier la culture du thread ! Cette culture se spécifie dans le web.config ou dans la directive de page.
<globalization culture="fr-FR" uiCulture="fr-FR">
Vous pouvez aussi mettre auto comme valeur, dans ce cas là, la culture sera suivant la configuration du navigateur.
Voici quelques exemples d’utilisation du CompareValidator et RangeValidator.
Validation d'un nombre entier :
<asp:label runat="server" AssociatedControlID="tbInteger" Text="Nombre entier" />
<asp:TextBox runat="server" ID="tbInteger" />
<asp:CompareValidator runat="server" ControlToValidate="tbInteger"
Type="Integer" Operator="DataTypeCheck" Text="*" />
Vérifie que le nombre saisit est compris entre int.MinValue et int.MaxValue.
Validation d'un nombre à virgule :
<asp:Label runat="server" AssociatedControlID="tbDouble" Text="Nombre à virgule" />
<asp:TextBox ID="tbDouble" runat="server" />
<asp:CompareValidator runat="server" ControlToValidate="tbDouble" Text="*"
ValidationGroup="MyValidationGroup" Operator="DataTypeCheck" Type="Double" />
Vérifie que le nombre est compris entre Double.MinValue et Double.MaxValue. La virgule est dépendante de la culture du thread en cours.
Validation d'une date :
<asp:Label runat="server" AssociatedControlID="tbDate" Text="Date" />
<asp:TextBox ID="tbDate" runat="server" />
<asp:CompareValidator runat="server" ControlToValidate="tbDate" Text="*"
ValidationGroup="MyValidationGroup" Operator="DataTypeCheck" Type="date" />
Là encore le format de la date sera en fonction de la culture en cours. Afin de rendre la saisie beaucoup plus agréable pour l'utilisateur, il est possible d’utiliser le contrôle Calendar des Ajax toolkits,
Validation d'une valeur par rapport à une autre valeur fixe :
Comme son nom l'indique, le CompareValidator peut aussi vérifier qu'une valeur est inférieure, égale ou supérieure à une autre ...
<asp:Label runat="server" AssociatedControlID="tbCompare" Text="Comparaison" />
<asp:TextBox ID="tbCompare" runat="server" />
<asp:CompareValidator runat="server" ControlToValidate="tbCompare" Text="*" Type="Integer"
ValueToCompare="5" Operator="LessThan" ValidationGroup="MyValidationGroup" />
Les opérateurs disponibles sont LessThan, LessThanEqual, NotEqual, Equal, GreaterThan, GreaterThanEqual. Vous pouvez faire la même chose avec le type Date, Double, String. Dans le cas d'une comparaison avec un String ce sera la méthode String.Compare qui sera utilisé, pour la plupart des cultures, c'est l'ordre alphabétique qui est pris en compte.
Validation d'une valeur via un intervalle de valeur fixe :
On utilise cette fois-ci le RangeValidator :
<asp:Label runat="server" AssociatedControlID="tbCompare2" Text="Comparaison 2" />
<asp:TextBox ID="tbCompare2" runat="server" />
<asp:RangeValidator runat="server" ControlToValidate="tbCompare2" Type="Date" Text="*"
ValidationGroup="MyValidationGroup" MinimumValue="01/01/2007" MaximumValue="31/12/2007" />
Là encore on peut tester les dates, les entiers, les nombres à virgules et les strings, les comparaisons se feront suivant la culture courante.
Validation d'une valeur par rapport à une autre valeur définit par l'utilisateur
Lorsque vous utilisez un CompareValidator avec la propriété ValueToCompare vous pouvez la remplacer par la propriété ControlToCompare afin de laisser le choix à l'utilisateur sur quel valeur il doit comparer. C'est ce qu'on utilise lorsqu'on veut vérifier que 2 mots de passe sont identiques.
<asp:TextBox ID="tbCompare" runat="server" />
<asp:TextBox ID="tbCompare2" runat="server" />
<asp:CompareValidator runat="server" ControlToValidate="tbCompare" Text="*" Type="String"
ValidationGroup="MyValidationGroup" ControlToCompare="tbCompare2" Operator="LessThan" />
RegularExpressionValidator
Le RegularExpressionValidator permet de vérifier que la valeur d’un contrôle est conforme avec une expression régulière.
<asp:TextBox ID="tbMail" runat="server" />
<asp:RegularExpressionValidator runat="server" ControlToValidate="tbMail"
Display="Dynamic" ValidationGroup="newUser"
ErrorMessage="Email invalide veuillez vérifier la saisie de votre e-mail"
ValidationExpression="^[a-zA-Z0-9-+.'_]+@\w+([-.]\w+)*\.\w+([-.]\w+)*$" />
CustomValidator
Le CustomValidator est à utiliser lorsqu’aucun autre contrôle n’est utilisable. Grâce à lui vous pouvez écrire votre code de validation à la fois côté client et/ou côté serveur. Pour cela le CustomValidator possède une propriété et un événement :
-
ServerValidate : événement se déclenchant lorsque la page valide le contrôle coté serveur, le retour de la validation se fait via la propriété
IsValid de l’
EventArgs
-
ClientValidationFunction : nom de la fonction
JavaScript à exécuter lors de la validation cliente. La méthode
JavaScript doit prendre 2 arguments, le premier correspond à l’élément HTML représentant le contrôle de validation, le second argument possède une propriété
IsValid permettant de renseigner l’état de validation.
Voici un exemple de l’utilisation du CustomValidator afin de valider que l’utilisateur ait bien coché une checkbox
<script type="text/javascript">
var validateAcceptCondition = function(source, args){
args.IsValid = $get('<%=cbAcceptCondition.ClientID%>').checked;
}
</script>
<asp:CustomValidator ID="cvAcceptCondition" runat="server"
ClientValidationFunction="validateAcceptCondition"
Text="Vous devez accepter les conditions" ValidationGroup="Condition" />
<asp:CheckBox ID="cbAcceptCondition" runat="server" ValidationGroup="Condition"
Text="En cochant cette case, j'accepte les conditions ci-dessus" />
// Coté serveur
protected void cvAcceptCondition_ServerValidate(object source, ServerValidateEventArgs args)
{
args.IsValid = cbAcceptCondition.Checked;
}
Contrôle de validation personnalisé
Si vous utilisez souvent le même CustomValidator, je ne peux que vous conseiller de créer un contrôle de validation personnalisé qui hérite de BaseValidator ou de CustomValidator, ou encore de IValidator, en effet cela vous permettra de mutualiser le code de vérification.
Voici le même exemple que le précédent mais écrit sous la forme d’un contrôle de validation personnalisé.
public class CheckBoxValidator : BaseValidator
{
private String ClientValidationFunction
{
get
{
this.EnsureID();
return String.Format("cbValidate{0}", this.ClientID);
}
}
private ICheckBoxControl CheckBoxToValidate
{
get
{
if (String.IsNullOrEmpty(this.ControlToValidate))
throw new ArgumentNullException("ControlToValidate");
ICheckBoxControl cb = this.NamingContainer
.FindControl(this.ControlToValidate) as ICheckBoxControl;
if (cb == null)
throw new ArgumentException("ControlToValidate",
String.Format("Control {0} not found", this.ControlToValidate));
return cb;
}
}
protected override bool EvaluateIsValid()
{
return this.CheckBoxToValidate.Checked;
}
protected override void OnPreRender(EventArgs e)
{
String script = String.Format(@"var {0}=function(source,args){{args.IsValid={1}.checked}}",
this.ClientValidationFunction, ((Control)this.CheckBoxToValidate).ClientID);
ScriptManager.RegisterClientScriptBlock(this, typeof(CheckBoxValidator),
this.ClientValidationFunction, script, true);
base.OnPreRender(e);
}
}
Ainsi on pourra vérifier que l’utilisateur a coché une checkbox en n’utilisant que ce code :
<cs:CheckBoxValidator runat="server" ControlToValidate="cbAcceptCondition"
Text="Vous devez accepter les conditions" ValidationGroup="Condition" />
<asp:CheckBox ID="cbAcceptCondition" runat="server"
Text="En cochant cette case, j'accepte les conditions ci-dessus"
ValidationGroup="Condition" />
On voit ici que nous n’avons plus aucune règle métier dans la partie aspx, tout est mutualisé au sein du contrôle personnalisé.
Le contrôle ValidationSummary
Contrairement aux autres contrôles présentés jusqu’ici, le ValidationSummary n’est pas un contrôle qui permet de valider les données, ce contrôle permet d’afficher au même endroit les propriétés ErrorMessage des contrôles de validation non valide.
Voici ses principales propriétés :
-
DisplayMode : indique comment les erreurs sont affichées, les valeurs possibles sont :
-
List : utilise une liste séparée par des retours à la ligne
-
BulletList : utilise une liste (ul/li) pour afficher les erreurs
-
SingleParagraph : affiche les erreurs les unes à la suite des autres
-