ASP.NET MVC 2 : Validation basée sur le modèle

2010-08-06 #mvc

Ceci est la traduction du billet "ASP.NET MVC 2: Model Validation" de Scott Guthrie.

Ceci est le deuxième billet d'une série consacrée à la prochaine sortie de ASP.NET MVC 2. Ce billet aborde les améliorations apportées à la validation dans la future version de ASP.NET MVC 2.

La validation dans ASP.NET MVC 2

Valider les saisies utilisateurs et appliquer des règles métiers est un besoin élémentaire dans la plupart des applications web. ASP.NET MVC 2 propose un tas de nouvelles fonctionnalités qui simplifient de façon significative la validation de la saisie utilisateur et l'application de règles de validation sur les objets Model ou ViewModel. Ces fonctionnalités sont conçues de façon à ce que la logique de validation soit toujours appliquée côté serveur et qu'en option elle puisse aussi être applicable côté client via du code Javascript. L'infrastructure et les fonctionnalités de validation d'ASP.NET MVC 2 sont conçues avec deux objectifs :

  • Les développeurs peuvent facilement employer les DataAnnotations pour gérer la validation. Basées sur de simples déclarations, les DataAnnotations donnent la possibilité d'ajouter des règles de validation aux objets et aux propriétés avec un minimum de code.
  • S'ils le souhaitent, les développeurs peuvent intégrer leur propre système de validation ou employer un framework de validation existant, tel que Castle Validator ou EntLib Validation Library. Les fonctionnalités de validation de ASP.NET MVC 2 sont conçues pour faciliter l'incorporation de tout type d'architecture de validation - tout en conservant les avantages de la nouvelle infrastructure de validation de ASP.NET MVC 2 (comprenant la validation côté client, la validation du binding avec le modèle, etc...)

Grâce à tout cela, la validation est vraiment très simple dans la majorité des cas, tout en restant suffisamment flexible pour les scénarios d'utilisation les plus évolués.

Utiliser les DataAnnotations pour la validation en ASP.NET MVC 2

Pour apprendre à tirer parti du support des DataAnnotations pour valider les données, nous allons étudier un exemple de scénario CRUD tout simple. Et nous allons plus précisément développer un formulaire "Create" qui permettra à l'utilisateur de saisir les coordonnées de ses amis :

Notre objectif est de vérifier que les informations saisies sont valides avant de les enregistrer dans la base de données et si ce n'est pas le cas afficher les messages d'erreurs correspondant :

Nous souhaitons que cette validation s'effectue à la fois côté serveur et côté client (via du Javascript). Et nous voulons que notre code respecte l'approche DRY ("Don't Repeat Yourself") - ce qui implique que nous devons appliquer les règles de validation en un seul endroit et qu'elles soient respectées par les contrôleurs, les actions et les vues.

Je vais utiliser VS 2010 et ASP.NET MVC 2 pour mettre en oeuvre le scénario décrit ci-dessus. Mais vous pouvez tout aussi bien employer VS 2008 et ASP.NET MVC 2 pour arriver au même résultat.

Etape 1: Implémenter FriendsController (sans validation pour

commencer)

Nous commencerons par ajouter une classe "Person" toute simple à notre nouveau projet ASP.NET MVC 2 :

Celle-ci contient 4 propriétés, implémentées en utilisant les propriétés automatiques de C# (un truc que VB gère aussi dans VS 2010 - Youhou !).

Nous allons ensuite ajouter une classe contrôleur "FriendsController" à notre projet qui exposera deux méthodes d'action "Create". La première action est appelée lorsque une requête HTTP-GET est faite sur l'URL /Friends/Create. Cela affichera un formulaire vide pour y saisir les informations d'une personnes. La seconde action est appelée lorsque une requête HTTP-POST est postée vers l'URL /Friends/Create. Elle fait correspondre les zones de saisie du formulaire avec un objet Person, contrôle qu'il n'y a pas d'erreur de correspondance, et si c'est correct va éventuellement l'enregistrer dans la base de données (nous verrons cet aspect du travail plus loin dans ce tutoriel). Si les données envoyées par le formulaire sont incorrectes, la méthode d'action ré-affiche le formulaire de saisi avec les messages d'erreur :

Une fois que nous avons programmé notre contrôleur, nous pouvons faire un clic-droit à l'intérieur d'une de ces méthodes d'action et sélectionner la commande "Add View" de Visual Studio - ce qui affiche la boite de dialogue "Add View". Nous demandons à générer une vue de type "Create" basée sur un objet Person :

Visual Studio va alors générer automatiquement un fichier Create.aspx dans le répertoire \Views\Friends de notre projet. Vous pouvez pouvoir voir ci-dessous comment il tire parti des nouveaux helpers HTML fortement typés gérés par ASP.NET MVC 2 (ce qui offre un contrôle à la compilation et une meilleure intellisense).

Et maintenant, lorsque nous lançons l'application et appelons l'URL /Friends/Create nous arrivons sur un formulaire vide dans lequel nous pouvons effectuer notre saisie :

Cependant, étant donné que nous n'avons pas encore implémenté l'aspect validation, rien ne nous empêche de saisir des informations erronées dans ce formulaire et de les publier vers le serveur.

Etape 2: Activer la Validation avec les DataAnnotations

Nous allons maintenant mettre à jour notre application pour qu'elle respecte quelques règles de validation de base. Nous implémenterons ces règles sur notre objet "Person" - et pas dans le contrôleur "FriendsController" ou la vue "Create". L'avantage d'implémenter ces règles au niveau de l'objet "Person" nous assure que ces règles seront suivies dans tous les cas où notre application utilisera l'objet "Person" (pour gérer l'action "Edit" par exemple). Cela nous aide à éviter de répéter nos règles de validation plusieurs fois dans notre application et donc à respecter la philosophie DRY.

ASP.NET MVC 2 permet aux développeurs d'ajouter facilement des attributs de validation sous forme de déclaration aux différentes classes Model ou ViewModel et ensuite que ces règles de validation soient automatiquement appliquées chaque fois que l'application fera du binding de données pour ces classes. Pour mettre cela en application, nous allons modifier notre classe Person pour y définir quelques attributs de validation. Pour cela, nous commençons par faire un "using" de l'espace de noms "System.ComponentModel.DataAnnotations" dans l'en-tête du fichier source - puis préfixer les propriétés de l'objet Person avec les attributs de validation [Required], [StringLength], [Range], et [RegularExpression] (qui sont tous disponible via cet espace de nom) :

Remarque : Nous avons défini les messages d'erreurs sous forme de chaines. Il est aussi possible de les définir dans des fichiers de ressources puis de les adapter en fonction de la langue des visiteurs. Vous avez plus d'informations sur la façon de localiser les messages d'erreur dans le billet "Localizing ASP.NET MVC Validation".

Maintenant que nous avons ajoutés des attributs de validation à notre classe Person, nous pouvons relancer l'application et regarder ce qui se passe quand nous saisissons des données incorrectes puis que nous les envoyons au serveur :

Vous pouvez constater que notre application offre désormais une gestion des erreurs tout à fait satisfaisante. Les zones de saisies avec des données incorrectes sont surlignées en rouge et les messages d'erreur de validation que nous avons définis sont portés à la connaissance de l'utilisateur. D'autre part, le formulaire a conservé le contenu des éléments saisis - si bien que l'utilisateur n'a qu'à les corriger et pas à les saisir à nouveau.

Vous vous demandez sans doute comment ça marche ?

Pour bien comprendre ce comportement, regardons de plus près la méthode d'action Create qui gère la requête POST envoyée par le formulaire :

Quand notre formulaire HTML est envoyé vers le serveur, la méthode ci-dessus est appelée. Étant donnée que cette méthode d'action attend un objet "Person" en paramètre, ASP.NET MVC va créer un objet "Person" et automatiquement initialiser ses propriétés à partir des informations du formulaire. Au cours de cette phase, il va aussi contrôler si les attributs de validation DataAnnotation sont valides pour cet objet Person. Si tout est correct, alors la propriété ModelState.IsValid renverra vrai et dans ce cas nous sauvegarderons (bientôt) cette personne dans la base de données puis nous ferons une redirection vers la page d'accueil.

S'il y a la moindre erreur de validation pour notre objet "Person", la méthode d'action va ré-afficher le formulaire avec les données saisies. C'est ce dont se charge la dernière ligne de code dans l'exemple ci-dessus.

Les messages d'erreurs sont alors affichés dans la vue grâce au fait que notre formulaire Create contient des méthodes helper <%= Html.ValidationMessageFor() %> en face de chaque helper <%= Html.TextBoxFor() %>. Ces helpers vont afficher le message d'erreur correspondant pour chaque propriété incorrecte qui aura été passée à à vue :

Le plus sympa avec cette approche, c'est qu'elle est carrément simple à mettre en œuvre et qu'elle nous permet très facilement d'ajouter ou de modifier des règles de validation à notre classe Person sans avoir à mettre à jour le moindre code dans nos vues ou nos contrôleurs. Cette capacité de définir les règles de validation à un seul endroit pour qu'elles soient prises en compte et respectées partout ailleurs nous permet de faire évoluer notre application et ses règles extrêmement rapidement avec un minimum de travail et en suivant au mieux la philosophie DRY.

Etape 3: Activer la validation côté client

Actuellement, la validation pour notre application ne s'exécute que côté serveur, ce qui signifie que nos utilisateurs devront valider le formulaire pour l'envoyer vers le serveur afin de savoir si leur saisie est correcte.

Un des aspects intéressant de l'architecture de validation de ASP.NET MVC 2 c'est qu'elle gère à la fois le côté serveur et le côté client. Tout ce que nous avons à faire pour que cela fonctionne, c'est de référencer deux Javascripts dans notre vue et d'ajouter une ligne de code :

Une fois que nous avons ajouté ces trois lignes, ASP.NET MVC 2 va utiliser les méta-données de validation que nous avons ajouté à notre classe Person et s'occupera pour nous de tous les traitements de validation en Javascript côté client. Cela signifie que les utilisateurs auront un retour immédiat sur les erreurs de validation dès qu'ils quitteront une zone de saisie.

Pour voir ce que donne cette prise en charge Javascript côté client dans le cas de notre application, nous n'avons qu'à relancer l'application et remplir les trois premières zones de saisie avec des valeurs correctes avant d'essayer de cliquer sur le bouton "Create". Comme vous pouvez le voir ci-dessous, nous obtenons aussitôt un message d'erreur sans que le formulaire ait besoin d'être envoyé vers le serveur :

Si nous saisissons alors n'importe quoi d'incorrect, le message d'erreur passe immédiatement de "Email Required" à "Not a valid email" (tout deux correspondant aux messages d'erreurs que nous avons définis quand nous avons ajouté les règles de validation à la classe Person) :

Le message d'erreur disparait dès que nous saisissons une adresse email valide et le fond de la zone de saisie revient à sa couleur normale :

Ce qu'il y a de bien, c'est que nous n'avons pas eu à écrire nous même le moindre morceau de JavaScript pour que cela fonctionne. Et notre code pour la validation reste fidèle au principe DRY - nous pouvons définir les règles à un seul endroit et elles s'appliquent dans toute l'application - aussi bien côté serveur que côté client.

Ce qu'il faut savoir, c'est que pour des raisons de sécurité, les règles de validations seront toujours appliquées côté serveur, même dans le cas où vous les avez déjà activées côté client. De cette façon, vous être protégé des pirates qui pourraient essayer de contourner les règles de validation côté client pour tromper votre serveur.

Avec ASP.NET MVC 2, le support de la validation côté client en JavaScript peut fonctionner avec n'importe quel framework ou système de validation que vous pouvez utiliser. Cela ne nécessite pas d'avoir choisi l'approche DataAnnotation - l'ensemble du système fonctionne de façon totalement indépendante des DataAnnotations et peut aussi bien être employée avec Castle Validator, EntLib Validation Block ou tout autre méthode de validation personnalisée que vous pouvez décider de mettre en place.

Si vous ne souhaitez pas utiliser nos fichiers JavaScript côté client, vous pouvez également les remplacer par un plugin de validation en jQuery et employer cette librairie à la place. Le téléchargement de ASP.NET MVC Futures intègrera la possibilité d'utiliser une validation jQuery en complément de la validation ASP.NET MVC 2 côté serveur.

Etape 4: Créer un attribut de validation personnalisé

L'espace de nom System.ComponentModel.DataAnnotations du framework .NET contient un certain nombre d'attributs de validation tous prêts que vous pouvez utiliser. Nous en avons déjà vu 4 jusqu'à présent : [Required], [StringLength], [Required], et [RegularExpression].

Vous avez aussi la possibilité de définir et d'employer vos propres attributs de validations personnalisés. Vous pouvez développer des attributs spécialisés à partir de zéro en utilisant la classe de base ValidationAttribute disponible dans l'espace de noms System.ComponentModel.DataAnnotations. Une autre solution consiste à hériter d'un des attributs de validation déjà existant et à simplement étendre certaine de ses fonctionnalités.

Par exemple, pour arriver à nettoyer un peu le code de la classe Person, nous pourrions créer un nouvel attribut de validation [Email] qui utiliserait l'expression régulière servant à vérifier les adresses email. Pour cela, il nous suffit d'hériter de la classe d'attribut RegularExpression et de simplement appeler le constructeur de l'objet de base avec la bonne expression régulière :

Nous pouvons alors mettre à jour la classe Person pour qu'elle utilise notre nouvel attribut [Email] au lieu de l'expression régulière que nous utilisions jusqu'ici - ce qui rend notre code beaucoup plus clair et concis :

Lors de la création d'attributs de validation personnalisés, vous pouvez spécifier une logique de validation qui s'exécute à la fois sur le serveur et sur le client via JavaScript.

Etape 5: Sauvegarder dans la base de données

Nous allons maintenant développer les traitements nécessaires pour enregistrer nos amis dans la base de données :

Pour l'instant, nous travaillons seulement avec une bonne vieille classe C# (aussi connue sous le nom de classe "POCO" pour "Plain Old CLR (ou C#) Object"). Nous aurions pu écrire un morceau de code supplémentaire pour assurer la persistance en faisant correspondre les éléments de cette classe à la base de données. Les solutions de mapping objet-relationnel (ORM) telles que NHibernate gèrent désormais très bien ce genre de mapping POCO / PI (Persistence Ignorant). la nouvelle version de ADO.NET Entity Framework (EF) qui va être livrée avec .NET 4 supportera elle aussi le mapping POCO / PI et comme NHibernate, elle permettra également de spécifier le mapping pour la persistance directement au niveau du code (sans qu'il soit besoin de passer par des fichiers de configuration ou des concepteurs visuels).

Si notre objet Person avait été mappé à la base de données d'une de ces façons, nous n'aurions rien eu d'autre à faire, ni au niveau de la classe Person, ni au niveau des règles de validation : tout aurait continué à fonctionner parfaitement.

Mais qu'est-ce qui se passe dans le cas où nous utilisons un outil

graphique pour effectuer notre mapping objet-relationnel ?

De nos jours, la plupart des développeurs qui utilisent Visual Studio n'écrivent pas eux même le code dont ils ont besoin pour le mapping et la persistance. Ils font confiance pour cela au concepteurs graphiques intégrés de Visual Studio qui s'en chargent pour eux.

Une question qui revient souvent quand on utilise les DataAnnotations (ou tout autre forme de validation basée sur des attributs) est : "Comment faire pour les appliquer lorsque le modèle d'objet avec lequel on travaille est créé / mis à jour via un concepteur visuel ?". Par exemple, que se passerait-il si au lieu d'avoir une classe Person de type POCO comme celle que nous utilisons depuis le début, nous avions mis au point cette classe Person avec un outil graphique tel que le concepteur LINQ to SQL ou celui de ADO.NET EF ?

La copie d'écran ci-dessus montre une classe Person crée à l'aide du concepteur ADO.NET EF dans VS 2010. La fenêtre du haut défini la classe Person et la fenêtre du bas montre l'éditeur de mapping qui sert à assurer la correspondance entre ses propriétés et une table "People" dans la base de données. Quand on clique sur "Save", le concepteur génère automatiquement une classe Person à votre place dans le projet. C'est très bien, sauf qu'à chaque fois que vous changez quelque chose et re-cliquez sur "Save", il va re-générer cette classe. Et ainsi tous les attributs de validation que vous auriez pu y définir seraient perdu.

Une méthode qui s'offre à nous pour ajouter des méta-données sous forme d'attribut (tels que les attributs de validations) à une classe qui est générée et maintenue par un concepteur de Visual Studio est d'employer la technique de la "buddy class". Cela consiste à créer une autre classe avec nos attributs de validation et autres méta-données puis à la relier à la classe qui a été généré par Visual Studio en appliquant un attribut "MetadataType" à une troisième classe partielle qui sera combinée avec la classe générée lors de la compilation. Prenons l'exemple des règles de validation que nous avons utilisées jusqu'à maintenant. Si nous voulons les appliquer à une classe Person qui provient des concepteurs LINQ to SQL ou ADO.NET EF, nous n'avons qu'à placer ce code de validation dans une classe à part nommée "Person_Validation" puis à faire le lien avec la classe "Person" générée par VS en utilisant le code ci-dessous :

L'approche ci-dessus n'est sans doute pas aussi élégante qu'une approche purement POCO mais présente l'avantage de fonctionner plutôt bien avec n'importe quel code généré par un outil ou un concepteur de Visual Studio.

Dernière étape - Enregistrer en base de données

Notre dernière étape - que nous utilisions une classe "Person" de type POCO ou générée automatiquement - sera de sauvegarder nos amis convenables dans la base de données.

Faire cela demande juste de remplacer le "Todo" laissé dans la classe FriendsController par 3 lignes de code qui enregistrent notre nouvel ami dans la base de données. Le code ci-dessous présente la totalité de la classe FriendsController dans le cas où on passe par ADO.NET EF pour gérer la persistance des données.

Et maintenant, quand nous allons sur l'URL /Friends/Create nous pouvons facilement ajouter des personnes à la base de données de nos amis :

La validation de toutes ces données est appliquée aussi bien par le navigateur client que par le serveur. Nous pouvons donc facilement ajouter, modifier ou supprimer des règles de validation à un seul endroit et avoir la certitude qu'elles seront prises en compte par tous les contrôleurs et toutes les vues de notre application.

Conclusion

ASP.NET MVC 2 simplifie énormément la gestion de la validation dans les applications web. Il favorise une approche DRY avec une validation qui s'appuie sur le modèle ce qui permet de s'assurer que les règles de validation seront appliquées de façon identique dans toute l'application. Avec la prise en charge des DataAnnotations par ASP.NET MVC 2, la mise en oeuvre de la plupart des scénarios classiques de validation devient un véritable jeu d'enfant. Et les possibilités d'extension au niveau de l'infrastructure de validation de ASP.NET MVC 2 vous permettent de gérer un grande variété de scénarios de validation plus poussés - et d'intégrer n'importe quel framework ou système de validation existant ou personnalisé.