ASP.NET 2.0 - Ergonomie et validation des formulaires : Best Practices
Par Laurent Duveau , posté le 10/01/2006
Profil : Développeur | Niveau : Intermédiaire (200)
1 Présentation
Aujourd'hui force est de constater que lors de la réalisation d'une application ASP.NET, on est toujours amené à gérer des données, qu'elles proviennent de SQL Server ou autre.
ASP.NET 2.0 introduit de nombreux contrôles de source de données (SqlDataSource, ObjectDataSource, XmlDataSource, .) et de présentation (GridView, FormView, DetailsView,.) rendant le databinding plus simple que jamais. Il est de plus en plus facile de permettre aux utilisateurs de manipuler nos données par le biais de formulaires web.
Je propose dans cet article de passer en revue l'ensemble des bonnes pratiques à mettre en oeuvre dans la réalisation de vos formulaires, en termes d'ergonomie et de validation des données.
Cette partie est trop souvent négligée à mon sens, c'est pourtant un des aspects les plus « ressentis » par l'utilisateur final, avec l'interface graphique. Celle qui fera que les utilisateurs ne vont pas fuir dès l'apparition de vos pages, mais au contraire qu'ils seront intéressés et suffisamment assistés pour la saisie des données.
Car la conception de formulaires ne se résume pas à glisser de la Toolbox Visual Studio quelques champs de saisie et un bouton. Selon moi, réaliser un formulaire complet, ergonomique, avec aides à la saisie, indications et validations peut demander facilement deux fois plus de temps de travail (je ne compte pas là le traitement des données).
Cet article ne traite pas en détail de l'aspect graphique ni de la cohérence des interfaces qui vont pourtant de paire avec une bonne ergonomie (on va effleurer ces concepts avec l'évocation des nouveaux contrôles Wizard et MultiView ainsi que les fichiers skin) ni des traitements liés aux saisies de données et le stockage en base.
Cet article a été réalisé sur les versions suivantes :
§ Framework .NET version 2.0.50727
§ Visual Studio 2005 version 8.0.50727.42 (RTM.050727-4200)
2 Ergonomie des formulaires
Voici mes recommandations à suivre pour proposer une ergonomie agréable, simple et efficace dans vos formulaires web.
2.1 Utilisez les nouveaux contrôles Wizard et MultiView
Ces contrôles introduits par ASP.NET 2.0 permettent d'obtenir une présentation claire et cohérente lors des traitements multi-écrans.
Sans rentrer dans le détail, chacun a son utilisation privilégiée :
§ Le Wizard est adapté dès lors que vous avez une logique de navigation à base de boutons du type : étape suivante, étape précédente, etc...
ex: choix des éléments / modification ou suppression / récapitulatif et confirmation; (à noter que ce "cheminement" dans les écrans peut être linéaire ou non). Ce contrôle Wizard pourrait faire l'objet d'un article à lui tout seul, à vous d'en découvrir les nombreuses possibilités mais à coup sûr il va jouer un rôle essentiel dans vos prochains développements de formulaires.
§ Le MultiView quant à lui intervient dès lors que l'on n'a plus cette notion d'enchaînements entre les écrans, mais uniquement un ensemble de vues, une seule étant active à la fois. Ex: Dans un écran de gestion des utilisateurs, avec une vue Création, une vue Modification, une vue Rôles associés, etc. dès qu'il n'y a pas un ordre particulier à suivre par l'utilisateur.
Pour vous familiariser avec ces 2 contrôles, utilisez le guide de référence du site ASP.NET : http://beta.asp.net/QUICKSTART/aspnet/doc/ctrlref/standard/default.aspx
2.2 Position de la fenêtre après un postback
Utilisez au maximum la directive de page MaintainScrollPositionOnPostback (booléen) afin de maintenir la position de la fenêtre du navigateur lors des postback. Par défaut à False, elle est pourtant la bienvenue la plupart du temps et peut être généralisée par le Web.config pour l'appliquer à toutes les pages :
dans la section .
(attention le Web.config est sensible à la casse)
2.3 Gestion du focus
Pensez à placer le focus, ou à le replacer après une action. C'est un petit plus pour l'utilisateur, malheureusement souvent oublié. En ASP.NET 2.0 il est désormais possible de gérer cet aspect en code serveur, via la nouvelle API de focus.
Exemple pour placer le focus initial sur le champ Textbox1 :
1: Page.Form.DefaultFocus = TextBox1.UniqueID;
Pour replacer le focus sur le champ TextBox2 :
1: this.TextBox2.Focus();
chaque contrôle de saisie ASP.NET 2.0 dispose de cette méthode focus(). Pour les contrôles composites, ex le contrôle Login, c'est la première TextBox qui reçoit le focus.
La même chose par la méthode SetFocus() :
1: Page.SetFocus(this.TextBox2);
Dans le cas d'un formulaire multizones, vous pouvez aussi définir le bouton par défaut pour chaque groupe :
1: Panel1.DefaultButton = ButtonGrp1.UniqueID;2: Panel2.DefaultButton = ButtonGrp2.UniqueID;3: ...
Les validateurs ont aussi une propriété pour placer automatiquement le focus sur le champ de saisie associé en cas d'erreur. Il suffit de passer la propriété SetFocusOnError à True (par défaut elle est à False).
2.4 SmartNavigation
Pour ceux qui utilisaient la propriété de page SmartNavigation, sachez que celle-ci est obsolète. Cette propriété n'était supportée qu'à partir d'Internet Explorer 5.5 pour améliorer la navigation en maintenant la position de la fenêtre entre les requêtes, de se souvenir des focus et de ne garder que la dernière version d'une page dans l'historique du navigateur.
Il faudra désormais lui préférer les deux éléments abordés plus haut : MaintainScrollPositionOnPostback et SetFocus() qui sont plus souples à utiliser et surtout qui s'adaptent à chaque type de navigateur.
2.5 Messages d'alerte
Ajoutez des messages JavaScript d'avertissement ou d'information à vos boutons d'action. Non pas que c'était impossible avant, mais ici Microsoft nous facilite encore plus le travail en ajoutant la propriété OnClientClick aux boutons (Button, LinkButton, ImageButton) qui exécute du code dans le navigateur, ex :
1: "Button1" runat="server" Text="Supprimer" OnClientClick="javascript:return confirm('Êtes-vous sûr de vouloir supprimer ce produit ?')" OnClick="Supprimer" />
Ici la méthode serveur Supprimer() ne sera appelée que si l'utilisateur clique sur Ok (le bouton Cancel annule le postback), pratique !
2.6 Ordre de tabulation
La plupart des utilisateurs se servent de la touche TAB pour naviguer d'un contrôle à l'autre dans un formulaire. Par défaut l'ordre de tabulation dans les champs est l'ordre dans lequel ils apparaissent dans la page, depuis le coin haut gauche jusqu'en bas à droite.
Vous pouvez contrôler cet ordre grâce à la propriété TabIndex des contrôles de saisie. C'est un entier (short) dont l'ordre séquentiel est respecté par les navigateurs ; depuis un contrôle avec TabIndex à 4, le focus ira sur le contrôle avec TabIndex à 5 (ou supérieur).
2.7 Raccourci clavier
Toujours concernant la navigation au clavier, il est simple d'ajouter un accès direct à un contrôle avec la propriété AccessKey. En combinaison avec la touche ALT elle offre un raccourci vers un champ, concrètement cela peut être utile pour accéder à une fonction spéciale présente sur toutes vos pages, une zone de recherche avec ALT+4 par exemple. Attention certaines touches sont réservées au navigateur, ex : ALT+F pour ouvrir le menu Fichier sous Internet Explorer.
Ainsi il est déconseillé d'utiliser des lettres pour les AccessKey, voir l'article suivant:
http://blog.alsacreations.com/2005/09/27/191-accesskey-le-grand-echec-de-laccessibilite-du-web
2.8 Taille des champs de saisie
Cela paraît évident mais pourtant parfois oublié ! Il faut limiter la taille des champs de saisie en nombre de caractères selon la colonne correspondante en base de donnée, pour du varchar(255). Par exemple, l'utilisateur ne doit pas pouvoir saisir plus de 255 caractères. La présentation doit aussi être cohérente avec le type de saisie, ne laissez pas 20 caractères à saisir pour un code postal !!!
Pour cela il y a 2 propriétés à définir dans le contrôle TextBox:
Columns : le nombre de caractères affichés.
MaxLength : le nombre de caractères maximal autorisés (la taille de la colonne en base).
Pour les raisons évoquées plus haut, il est parfois aberrant de voir la largeur des TextBox fixée en pixels !!!
2.9 Premier élément dans les listes
Sauf cas spécial, toute DropDownList devrait comporter un premier élément du type Choisir, Indifférent ou encore Tous selon les cas, et associé à un validateur (reportez vous au chapitre suivant « Validation des formulaires » pour la technique à employer).
Servez vous de la propriété AppendDataBoundItems à True (False par défaut) pour que ces éléments statiques ne soient pas écrasés lors du databinding avec votre source de données.
2.10 Aides à la saisie
Un bon formulaire doit porter des indications destinées à l'utilisateur. Signalez les champs obligatoires par une astérix et mettre la légende « * Champs obligatoires ». Mettre une courte phrase explicative devant une saisie qui n'est pas évidente. Les Labels et TextBox ont une propriété ToolTip pour indiquer une aide lors du passage de la souris sur le champ.
Il s'agit aussi d'informations sur le format attendu, par exemple précisez « JJ/MM/AAAA » pour une date. Aucune aide n'est superflue et cela limitera les hésitations des utilisateurs.
2.11 Cohérence des interfaces
Une bonne ergonomie passe aussi par une interface soignée et efficace. Veillez à ce que vos formulaires soient clairs, bien présentés, les zones bien délimitées, et l'ensemble doit être cohérent sur toutes les pages du site.
Concernant la délimitation des zones, je ne peux que rappeler la balise HTML à laquelle on associe un titre. Ce conteneur permet de marquer visuellement des groupes de contrôles dans vos pages. Vous pouvez obtenir le même résultat avec le contrôle serveur Panel et sa propriété GroupingText.
Voir ci-dessous un petit exemple de formulaire simple et efficace, qui résume bien les différents points, vus jusqu'ici.
Exemple issu de l'excellent tutoriel de Fred Cavazza « Des formulaires plus simples »
http://www.fredcavazza.net/doc/tutoriels/formulaire/SVF_intro.htm
Concernant l'harmonisation des interfaces, ASP.NET 2.0 propose le concept de fichiers skin. Vous pouvez complètement décrire comment s'afficheront vos contrôles serveur dans un fichier externe. Cela vous permet de dissocier totalement le contenu de la mise en forme dans vos pages.
Les fichiers Skin se placent dans le répertoire spécial App_Themes.
Dans mon exemple, je vais créer un fichier Default.skin dans le répertoire App_Themes/Bewise. Pour l'associer à une page vous pouvez utiliser la directive Theme, ou bien le généraliser à l'ensemble des pages du site dans la section du Web.config :
1: "Bewise">
Dans le fichier Default.skin, je décris la mise en forme des mes contrôles en leur associant des classes css (la feuille de style css est disponible avec le code de cet article) :
1: <asp:Wizard runat="server" CssClass="wizard">2: <NavigationStyle HorizontalAlign="Center" CssClass="wizardNavigation" />3: <HeaderStyle CssClass="wizardHeader" />4: <StepNavigationTemplate>5: <asp:Image runat="server" ImageUrl="~/images/fleche_gauche.gif" />6: <asp:LinkButton ID="StepPreviousButton" runat="server" CausesValidation="False" CommandName="MovePrevious" CssClass="liens">Etape Précédenteasp:LinkButton> |7: <asp:LinkButton ID="StepNextButton" runat="server" CommandName="MoveNext" CssClass="liens">Etape Suivanteasp:LinkButton>8: <asp:Image runat="server" ImageUrl="~/images/fleche_droite.gif" />9: StepNavigationTemplate>10: asp:Wizard>11:12: <asp:RequiredFieldValidator runat="server" CssClass="ErrorMsg" ForeColor="" />13: <asp:CustomValidator runat="server" CssClass="ErrorMsg" ForeColor="" />14:15: <asp:GridView runat="server" CssClass="gridview" >16: <FooterStyle CssClass="gvFooter" />17: <RowStyle CssClass="gvRow" />18: <SelectedRowStyle CssClass="gvSelectedRow" />19: <PagerStyle CssClass="gvPager" />20: <HeaderStyle CssClass="gvHeader" />21: <AlternatingRowStyle CssClass="gvAlternateRow" />22: <EmptyDataRowStyle CssClass="gvEmptyRow" />23: asp:GridView>
Voir page suivante l'interface obtenue dans une page.
Cet écran ne prétend pas afficher une interface idéale ni le bon goût des couleurs mais permet d'illustrer l'utilisation des fichiers skin.
Dans cet exemple j'ai utilisé un Wizard, une DataGrid, un fieldset et quelques champs de saisie et validateurs, tout ceci sans aucune mise en forme, ni même en précisant de classe css lors de la conception de la page ! En effet, j'ai simplement glissé-déposé les contrôles de la Toolbox de Visual Studio, et le fichier skin à fait le reste, en s'appuyant sur une feuille de style css. On comprend toute l'ampleur du concept quand une équipe de nombreux développeurs travaillent sur le même projet, où lorsque de nouveaux développeurs rejoignent un projet en cours, la connaissance de la charte graphique n'est plus indispensable, chacun peut réaliser ses formulaires sans se soucier de la mise en forme. Cela va dans le sens de la cohérence des interfaces graphiques de votre application.
3 Validation des formulaires
Avant de commencer je fais une petite parenthèse pour signaler avec plaisir qu'ASP.NET 2.0 simplifie nettement l'infrastructure de validation et les soucis parfois liés.
En effet ASP.NET 2.0 n'utilise plus le répertoire virtuel aspnet_client et les problèmes de versions des librairies JavaScript qui pouvaient survenir lors du déploiement de vos applications sur un nouveau serveur (différentes versions du Framework, aspnet_regiis -c, .).
Désormais tous les scripts de validation sont intégrés dans les assemblies ASP.NET, grâce au nouveau concept de WebResource. Pour en savoir plus sur les WebResources, je vous invite à consulter l'article du MSDN (en anglais) à cette adresse : http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnvs05/html/webresource.asp
3.1 Les validateurs
Inutile de rappeler ici l'intérêt de placer des validateurs dans tous vos formulaires, ni de détailler les contrôles de validation ASP.NET. Cependant j'aimerais attirer l'attention sur 2 cas d'utilisation méconnus.
1) Le CompareValidtor n'est pas limité à la comparaison de deux contrôles ! Il offre aussi la vérification de type (date, nombre, monnaie) sans avoir recours à un RegularExpressionValidator et la recherche de la bonne chaîne de validation. Pour ceci il faut utiliser les propriétés suivantes :
§ laisser ControlToCompare vide
§ donner le contrôle à valider dans ControlToValidate.
§ Placer Operator à DataTypeCheck
§ Préciser le Type à verifier : String, Integer, Double, Date ou Currency
Ainsi rien de plus simple pour vérifier qu'un champ ne contient que du texte par exemple ! De plus, il faut savoir que les validateurs s'adaptent automatiquement pour utiliser la culture en cours, on comprend vite l'intérêt pour les dates.
2) La validation de DropDownList ou ListBox est possible avec un RequiredFieldValidator ! Le cas le plus courant est de vérifier que l'élément sélectionné n'est pas le premier. Prenons une DropDownList dont le premier élément a pour Text/Value : Choisir, il suffit de rajouter un RequiredFieldValidator avec la propriété InitialValue à Choisir. Plus d'excuses pour ne pas l'utiliser désormais ! Notez que l'on peut également vérifier qu'un élément donné n'est pas sélectionné à l'aide d'un CompareValidator.
3.2 Activer la validation
Tous les contrôles de formulaires de type submit (Button, LinkButton, ImageButton, .) appellent automatiquement la méthode Page.Validate() sur l'évènement OnClick par le biais de la propriété CauseValidation. Nous n'avons donc pas à nous en préoccuper, même si nous verrons plus tard des cas où il est utile de mettre CauseValidation à false.
La validation étant activée par défaut, il nous reste qu'à la rendre effective dans nos méthodes d'évènements afin que notre code ne s'exécute que si le formulaire est valide :
1: protected void Button1_Click(object sender, EventArgs e)2: {3: if (Page.IsValid)4: {5: ....6: }7: }
Il est indispensable de faire cette vérification dans votre code, car même si vous avez placé tous vos validateurs dans un formulaire, si l'utilisateur désactive JavaScript dans son navigateur, votre code s'exécutera quel que soit le résultat de la validation serveur.
La règle à retenir est de toujours doubler les validations de saisies côté client (ergonomie pour l'utilisateur) par une vérification côté serveur (sécurité et intégrité des données). Ce sur quoi ASP.NET nous mâche le travail car tous les validateurs le font automatiquement par défaut. Il ne vous reste QUE la liberté de vous assurer du résultat de cette validation avec Page.IsValid.
Pour les autres contrôles qui font des postback sans appel automatique de Page.Validate (onglets de navigation par exemple) il suffit de la rajouter à votre méthode d'évènement :
1: protected void TabButton1_Click(object sender, EventArgs e)2: {3: Page.Validate();4: if (Page.IsValid)5: {6: ....7: }8: }
A l'inverse, pensez à désactiver la validation là où elle n'est pas nécessaire, voir bloquante; je pense aux boutons Annuler ou Retour par exemple. Il suffit pour cela de placer la propriété CauseValidation des boutons à false.
3.3 Validation par zone
Là aussi ASP.NET 2.0 nous facilite grandement la tâche ! En effet j'aborde ici le scénario où l'on a sur une même page des contrôles de validation regroupés en zones distinctes avec chacune son bouton de validation.
Une solution en ASP.NET 1.x était de désactiver la validation automatique de vos boutons (un autre cas où CauseValidation est à false), puis d'appeler côté serveur la méthode Validate() des contrôles correspondant à une zone donnée, et finalement de tester les propriétés IsValid :
1: protected void Button1_Click(object sender, EventArgs e)2: {3: RequiredFieldValidator1.Validate();4: RequiredFieldValidator2.Validate();5: if (RequiredFieldValidator1.IsValid && RequiredFieldValidator2.IsValid)6: {7: ....8: }9: }
Une autre méthode était de jouer avec la propriété Enable des validateurs pour garder la validation côté client, une solution qui a aussi son lot de désagréments mais je ne rentrerai pas dans le détail ici.
La nouveauté bien sympathique apportée par ASP.NET 2.0 est la gestion de groupes de validation. Pour les utiliser c'est très simple, il vous suffit d'attribuer le même nom à la propriété ValidationGroup des validateurs et boutons d'une même zone. C'est tout et c'est bien pratique !! On se retrouve avec la notion de formulaires indépendants au sein d'une même page avec chacun sa validation client/serveur associée.
Notez qu'il est bien sûr possible de gérer ses groupes en codebehind afin d'affecter dynamiquement un contrôle à tel ou tel groupe, et même de valider un groupes entier avec une surcharge de la méthode Page.Validate(groupe).
Enfin c'est avec la même technique que l'on peut avoir des boutons qui ne valident pas les mêmes contrôles au sein d'une zone donnée.
Il suffit de placer plusieurs validateurs sur certaines TextBox (avec la propriété Display à Dynamic pour laisser l'espace libre quand un validateur n'est pas affiché), et d'affecter à la propriété ValidationGroup :
« groupe123 » pour les validateurs de 1, 2, 3 et le bouton
« groupe234 » pour les validateurs de 2, 3, 4 et l'autre bouton !
Après un peu de pratique, vous verrez vite la puissance alliée à la simplicité des groupes de validation !
3.4 Validation conditionnelle
Je vais aborder ici le cas où l'action des validateurs dépend des données saisies dans le formulaire. Un petit exemple très connu est la liste avec un choix autre/précisez :
On veut rendre la saisie de la TextBox « précisez » obligatoire si l'utilisateur choisit « Autre ».
La mauvaise solution serait de vouloir utiliser un RequiredFieldValidator que l'on rend passif côté client (EnableClientScript à False) et de gérer le cas en codebehind :
1: protected void btnValider_Click(object sender, EventArgs e)2: {3: RequiredFieldValidator1.Enabled = false;4: if (rbAutre.Checked)5: RequiredFieldValidator1.Enabled = true;6:7: Page.Validate();8:9: if (Page.IsValid)10: {11: ....12: }13: }
Où rbAutre est l'ID du contrôle RadioButton « Autre » et RequiredFieldValidator1 un validateur que l'on a associé à la TextBox « précisez ». Notez que dans ce cas il faut relancer la validation avec Page.validate() pour prendre en compte le changement d'état du validateur (Enabled ou pas) survenu après le click sur le bouton « Valider ».
On obtient bien un message de validation uniquement pour le cas « Autre » mais seulement après un postback.
Une bien meilleure solution est d'utiliser un CustomValidator. Cette méthode est un peu moins facile à mettre en oeuvre car on va coder la logique de validation serveur (obligatoire) et cliente (en général avec JavaScript, attention VbScript n'est supporté que par Internet Explorer.) afin d'avoir une validation immédiate dans le navigateur (à condition que JavaScript soit activé bien sûr). Pensez aussi à mettre sa propriété ValidateEmptyText à True sinon dans notre cas le validateur restera inactif !
Voici le code correspondant à cet exemple :
Code serveur (ici en C#) :
1: protected void CustomValidator1_ServerValidate(object source, ServerValidateEventArgs args)2: {3: if (rbAutre.Checked && args.Value.Trim().Length == 0)4: args.IsValid = false;5: else6: args.IsValid = true;7: }
Code client (JavaScript) :
1:
Cette fois on obtient le message de validation mais sans postback !
3.5 Validation des requêtes dangereuses
Peut être avez-vous déjà rencontré ce message d'erreur :
A potentially dangerous Request.Form value was detected from the client (TextBox1="taux
Par défaut, ASP.NET interdit la saisie de certains caractères dans les formulaires. Il s'agit d'une liste de valeurs potentiellement dangereuses utilisées lors des attaques du type cross-site scripting ou SQL injection par exemple. Cette initiative part d'une bonne intention pour sécuriser rapidement les saisies de formulaire, mais reste insuffisante et dans la pratique les utilisateurs seront vite bloqués ; par exemple la saisie légitime de « taux
1:
Il est possible de la désactiver pour toutes vos pages dans le Web.config :
1: "false"
4 Conclusion
Nous avons vu dans cet article un ensemble de bonnes pratiques à respecter pour la réalisation de bons formulaires. Et vous avez pu constater que les recommandations sont nombreuses et se classent dans deux domaines : l'ergonomie et la validation.
Une bonne ergonomie est essentielle pour l'apprentissage et la bonne compréhension de l'utilisation.
La validation est indispensable pour protéger l'intégrité de vos données. De plus ce point est lié au précédent dans le sens où un formulaire intuitif limitera les risques d'erreurs de saisies.
Heureusement, grâce à ASP.NET 2.0 ce travail n'a jamais été autant facilité ! Les contrôles serveur ont été améliorés dans leur gestion du code client JavaScript. L'interface des formulaires est obtenue sans effort grâce aux fichiers skin. La validation se fait quasiment par des glisser-déposer et un peu de configuration, et de plus le code client est désormais automatiquement compatible avec tous les navigateurs (Firefox, Opéra, .) qui supportent le JavaScript 1.2. En ASP.NET 1.1 la validation côté client ne fonctionnait que sous Internet Explorer dû à l'utilisation de JavaScript non standard (l'objet document.all par exemple).
La conception d'un formulaire devrait toujours se faire avec votre modèle de données sous les yeux ! Les colonnes NON NULL en base de données sont à saisir dans un champ associé à un RequiredFieldValidator, le nombre de caractères acceptés dans les TextBox adaptés à la capacité du champ dans la table.
Enfin pour terminer, bien qu'ASP.NET 2.0 permette de construire facilement des formulaires sophistiqués, ma dernière recommandation est un constat : les meilleurs formulaires sont encore les plus simples.
Merci à Cyril DURAND pour sa relecture.



