Suite de la série de billets sur les nouveautés d'ASP.NET MVC 3 écrits conjointement avec Philippe Viallate, MVP et responsable de la rubrique .NET sur Developpez.com. Vous pouvez consulter la première partie : [ASP.NET MVC] Nouveautés MVC 3 Part 1 - Améliorations dans Visual Studio 2010
La version 2 du Framework MVC, avait déjà vu l'apparition d'un ensemble de fonctionnalités assez puissantes de la validation coté client. La version 3 continue à améliorer ce mécanisme, en le rendant encore plus puissant et souple.
Gestion des DataAnnotations
Le modèle de validation ne prenait pas en compte, du temps de la version 2, toutes les nouveautés apportées par l'espace de noms DataAnnotations. En effet, par un souci de compatibilité, la version 2 du Framework MVC était compilée en mode de compatibilité .Net 3.5, ce qui l'empêchait de profiter de toutes les nouveautés de cet espace de nom. La version 3 étant compilée avec une dépendance sur le Framework .NET 4.0, il est désormais possible d'utiliser l'intégralité des DataAnnotations. Les nouveaux attributs disponibles sont les suivants :- AssociationAttribute : Représente un attribut utilisé pour spécifier qu'une propriété d'entité participe à une association.
- ConcurrencyCheckAttribute : Spécifie le type de données de la colonne utilisée pour les contrôles d'accès concurrentiel optimiste.
- CustomValidationAttribute : Spécifie une méthode de validation personnalisée à appeler au moment de l'exécution.
- DisplayAttribute : Fournit un attribut à usage général qui vous permet de spécifier les chaînes localisables pour les types et membres de classes partielles d'entité.
- EditableAttribute : Indique si un champ de données est modifiable.
- EnumDataTypeAttribute : Permet le mappage d'une énumération .NET Framework à une colonne de données.
- FilterUIHintAttribute : Représente un attribut utilisé pour spécifier le comportement de filtrage pour une colonne.
- KeyAttribute : Dénote une ou plusieurs propriétés qui identifient une entité de manière unique.
- TimestampAttribute : Spécifie le type de données de la colonne en tant que version de colonne.
- ValidationContext : Décrit le contexte dans lequel un contrôle de validation est exécuté.
- ValidationResult : Représente un conteneur pour les résultats d'une demande de validation.
- Validator : Définit un helper qui peut être utilisée pour valider des objets, des propriétés et des méthodes lorsqu'elle est incluse dans leurs attributs ValidationAttribute associés.
[Display(Name="PrenomName", Description="PrenomDesc", ResourceType=typeof(ModelResources))]Ce code va automatiquement aller chercher, dans ModelResources , le champ PrenomName qui correspond à la culture courante, et l'utiliser pour générer le label de nos champs en relation avec le prénom.
public string Prenom { get; set; }
Nouveaux attributs de validation
Deux nouveaux attributs ont fait leur apparition, de façon à faciliter certains scénarios de validation courants.CompareAttribute
L'idée derrière CompareAttribute est très simple. Cet attribut permet de vérifier qu'une propriété à la même valeur qu'une des autres propriétés du modèle en cours de validation. Les scénarios d'application vont être la confirmation d'une adresse ail ou d'un mot de passe. Par exemple, si, sur un formulaire d'inscription, on veut que l'utilisateur confirme son adresse e-mail, il nous suffira de produire le code suivant :[Required]L'attribut CompareAttribute étant supporté par la validation coté client, on verra le message apparaître directement lors de la saisie, sans avoir besoin d'un aller-retour avec le serveur.
[DataType(DataType.EmailAddress)]
public string Email { get; set; }
[Required]
[Display(Name = "Confirmez votre adresse e-mail")]
[Compare("Email", ErrorMessage = "Les deux adresses e-mail sont différentes")]
[DataType(DataType.EmailAddress)]
public string Emailconfirmation { get; set; }
RemoteAttribute
Autant la validation coté client avec MVC 2 était simple à mettre en place, autant dès qu'il fallait vérifier une donnée coté serveur...on se retrouvait à devoir mettre les mains dans le cambouis. Avec MVC V3, valider une donnée coté serveur devient d'une simplicité étonnante. En effet, il suffit de décorer la propriété à valider avec l'attribut RemoteAttribute, et de le configurer pour que la validation coté client se fasse avec un appel à une de nos actions. La configuration en elle-même est simplissime, il nous suffit de renseigner, au niveau de l'attribut :- L'action à appeler
- Le contrôleur exposant l'action
- Eventuellement, un message d'erreur, que ce soit sous la forme d'un texte, ou d'une ressource
[Required]Et d'ajouter, dans mon fichier UserController, une action renvoyant un résultat au format JSON:
[Remote("CheckLogin", "User", ErrorMessageResourceType = typeof(ModelResources), ErrorMessageResourceName = "DoublonLogin")]
public string Login {get ; set ;}
public ActionResult CheckLogin(string login)Désormais, lorsque l'utilisateur quitte le champ texte Login, l'appel à l'action CheckLogin est effectué, et un message apparait si le compte utilisateur existe déjà.
{
var manager = new UserManager();
return Json(!manager.ContainsLogin(login), JsonRequestBehavior.AllowGet);
}
Contrôle plus fin de la validation de requêtes via AllowHTML
Que ce soit avec Web Forms ou MVC, le comportement par défaut d'ASP.NET, lorsque l'on ajoute des balises html dans des champs, est de rejeter l'entrée utilisateur avec le message « A potentially dangerous Request.Form value was detected from the client ». En effet, le Framework va valider les requêtes utilisateur, de façon à empêcher les attaques de style XSS (Cross-site scripting) en n'acceptant pas de balises, qui pourraient contenir un script malicieux. Lorsque l'on veut permettre à un utilisateur de rentrer des tags HTML (pour utiliser un éditeur HTML, par exemple), la seule solution avec MVC V2 était de désactiver l'attribut ValidateInput au niveau de l'action. Supposons que nous voulions laissions à un utilisateur la possibilité d'ajouter un commentaire avec un éditeur HTML, il nous faudra donc produire le code suivant :[HttpPost]L'inconvénient de cette méthode étant qu'elle désactive la validation sur l'ensemble du modèle, et pas uniquement sur les propriétés dans lesquelles on veut lui laisser stocker du code HTML. Dans notre cas, l'utilisateur va donc pouvoir, à la main, ajouter des balises html dans *tous* les champs de notre vue. Avec MVC 3, il est désormais possible de désactiver la validation de requêtes au niveau d'une propriété, dans le modèle. On pourra donc, au niveau de notre objet CommentViewModel, faire ceci :
[ValidateInput(false)]
public ActionResult Addcomment(CommentViewModel comment){
// sauvegarde de notre nouveau commentaire
}
public class CommentViewModel{Le second point positif étant évidemment que cette modification est faite au niveau du modèle, et plus de la vue.
public string Titre {get ; set ;}
[AllowHTML]
public string Body {get ; set ;}
}
IValidatableObject
L'interface IValidatableObject permet de gérer des scenarios de validations plus complexes. Implémenter cette interface va nous permettre de rajouter, une fois que toutes les propriétés de l'objet ont été validées, une validation logique de l'ensemble de l'objet. Si on repart de notre exemple précédent de validation du login de l'utilisateur. Autant la validation coté client va très bien fonctionner si l'utilisateur a activé javascript. Autant il va quand même falloir vérifier coté serveur que les données sont effectivement valides. Pour cela, on va simplement modifier notre classe Utilisateur pour lui faire implémenter IValidatableObject, et implémenter la méthode Validate :public class User : IValidatableObjectIl nous suffira ensuite, dans le contrôleur, de tester que le modèle est valide pour récupérer l'erreur sur le login :
{
[Required]
[Remote("CheckLogin", "User", ErrorMessageResourceType = typeof(ModelResources), ErrorMessageResourceName = "DoublonLogin")))]
public string Login {get ; set ;}
public IEnumerable<ValidationResult> Validate(ValidationContext context)
{
var manager = new UserManager();
if (manager.ContainsLogin(login))
yield return new ValidationResult("Ce login existe déjà");
}
}
public ActionResult Create (User nouvelUtilisateur)
{
if (!ModelState.IsValid)
return View();
// reste de la fonction
}
IClientValidatable
Pour avoir une validation à la fois côté client et côté serveur, il existe désormais un mécanisme supplémentaire, qui peut être utilisé pour ajouter de nouveaux attributs de validation, dans la veine de CompareAttribute. Supposons que l'on veuille implémenter une validation personnalisée coté client et serveur. Notre cas métier étant, par exemple, que l'on veuille vérifier que le mot de passe d'un utilisateur est suffisamment complexe (qu'il fait au moins 8 caractères, contient des caractères en minuscule, en majuscule et non alpha). On va, de plus, vouloir bénéficier du support de la validation par Javascript discret (unobtrusive javascript) par JQuery. On va commencer par définir un nouvel attribut de validation, qui va implémenter IClientValidatable.public class LoginAttribute : RegularExpressionAttribute, IClientValidatableCe que fait ce code est assez simple. Comme on va utiliser une expression régulière, on va étendre RegularExpressionAttribute. On va ensuite, dans GetClientValidationRule, définir que la validation du login va utiliser le type de validation « login ». Ce type n'est actuellement pas défini dans les règles de JQuery. Il nous suffit de l'ajouter de la façon suivante :
{
public LoginAttribute() : base("^.*(?=.{8,})(?=.*[a-z])(?=.*[A-Z])(?=.*[\d\W]).*$ "){}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
yield return new ModelClientValidationRule
{
ErrorMessage = this.ErrorMessage,
ValidationType = "login"
};
}}
jQuery.validator.unobtrusive.adapters.add("login", function (rule) {A noter, ceci ne fonctionnera, évidemment, que si le javascript est bien en mode discret.
var message = rule.Message;
return function (value, context) {
var loginPattern = ^.*(?=.{8,})(?=.*[a-z])(?=.*[A-Z])(?=.*[\d\W]).*$ ;
return loginPattern.test(value);
};
});