Extreme ASP.NET

Validation de modèle & métadonnées dans ASP.NET MVC 2

K. Scott Allen

Télécharger l'exemple de code

L'une des nouvelles fonctionnalités ajoutées à la version ASP.NET MVC 2 est la capacité à valider l'entrée utilisateur à la fois sur le serveur et sur le client. Il vous suffit de fournir à l'infrastructure certaines informations concernant les données à valider, et celle-ci effectue tout le travail à votre place.

Cette fonctionnalité constitue un atout formidable pour ceux d'entre nous qui écrivaient du code de validation personnalisé et des classeurs de modèles personnalisés pour effectuer une simple validation de modèle avec ASP.NET MVC 1.0. Nous allons examiner dans cet article la prise en charge de validation intégrée dans ASP.NET MVC 2.

Avant de discuter des nouvelles fonctionnalités, je souhaiterais toutefois revisiter l'ancienne méthodologie. Les fonctionnalités de validation dans ASP.NET WebForms m'ont bien servi pendant de nombreuses années. J'estime utile de les passer en revue afin de mieux comprendre ce que procure une infrastructure de validation idéale.

Contrôle de la validation

Si vous avez déjà utilisé ASP.NET WebForms, vous savez qu'il est relativement facile d'ajouter une logique de validation à un WebForm. On exprime les règles de validation à l'aide de contrôles. Par exemple, si l'on souhaite s'assurer que l'utilisateur entre du texte dans un contrôle TextBox, il suffit d'ajouter un contrôle RequiredFieldValidator pointant sur le TextBox, comme ceci ;

<form id="form1" runat="server">
  <asp:TextBox runat="server" ID="_userName" />
  <asp:RequiredFieldValidator runat="server" ControlToValidate="_userName"
                               ErrorMessage="Please enter a username" />
  <asp:Button runat="server" ID="_submit" Text="Submit" />
</form>

Le contrôle RequiredFieldValidator encapsule la logique côté client et côté serveur afin de s'assurer que l'utilisateur fournit un nom d'utilisateur. Pour fournir une validation côté client, le contrôle émet un script JavaScript dans le navigateur du client, et ce script vérifie que l'utilisateur satisfait toutes les règles de validation avant de renvoyer le formulaire au serveur.

Ces contrôles de validation de WebForm offrent des fonctionnalités très puissantes :

  • Vous pouvez exprimer des règles de validation pour une page de manière déclarative à un emplacement unique.
  • La validation sur le client épargne un aller-retour jusqu'au serveur si l'utilisateur ne satisfait pas aux règles de validation.
  • La validation sur le serveur empêche tout utilisateur malveillant d'outrepasser le script client.
  • Les logiques de validation de serveur et de client restent synchronisées sans soulever de problème de maintenance.

Mais dans ASP.NET MVC, vous ne pouvez pas utiliser ces contrôles de validation et demeurer fidèle à l'esprit du modèle de conception MVC. Fort heureusement, la version 2 de l'infrastructure procure une solution encore plus avantageuse.

Contrôles et modèles

On peut considérer un contrôle WebForm, tel qu'un contrôle TextBox, comme un conteneur simple pour des données utilisateur. Il est possible de remplir le contrôle avec une valeur initiale et d'afficher cette valeur à l'utilisateur, et également d'extraire toute valeur entrée ou modifiée par l'utilisateur en inspectant le contrôle après la publication (postback). Lors de l'utilisation du modèle de conception MVC, le M (modèle) assume ce même rôle de conteneur de données. On remplit un modèle avec des informations qu'il faut remettre à un utilisateur, et ce modèle rapporte les valeurs mises à jour dans notre application. Ainsi, le modèle est l'emplacement idéal pour exprimer des règles de validation et des contraintes.

Voici un exemple tout simple. Si vous créez une application ASP.NET MVC 2, l'un des contrôleurs inclus dans le projet se nomme AccountController. Il est responsable de la gestion des nouvelles demandes d'enregistrement d'utilisateur, ainsi que des demandes d'ouverture de session et de modification de mot de passe. Chacune de ces actions utilise un objet modèle dédié. Ces modèles se trouvent dans le fichier AccountModels.cs dans le dossier Models. Par exemple, la classe RegisterModel, sans règles de validation, ressemble à ceci :

public class RegisterModel
{
  public string UserName { get; set; }
  public string Email { get; set; }
  public string Password { get; set; }
  public string ConfirmPassword { get; set; }
}

L'action Register du contrôle AccountController prend une instance de cette classe RegisterModel comme paramètre :

[HttpPost]
public ActionResult Register(RegisterModel model)
{
    // ...
}

Si le modèle est valide, l'action Register transfère les informations de modèle à un service qui peut créer un nouvel utilisateur.

Le modèle RegisterModel est un excellent exemple de modèle spécifique à la vue, ou modèle de vue. Il ne s'agit pas d'un modèle conçu pour fonctionner avec une table de base de données, un appel de service Web ou un objet métier spécifique, mais plutôt avec une vue spécifique (la vue Register.aspx, dont une partie est illustrée à la Figure 1). Chaque propriété sur le modèle mappe à un contrôle d'entrée dans la vue. Je vous encourage vivement à utiliser des modèles de vue, car ils simplifient de nombreux scénarios de développement MVC, notamment la validation.


Figure 1 Informations d'enregistrement

Modèles et métadonnées

Lorsqu'un utilisateur entre des informations sur un compte dans la vue Register, l'infrastructure MVC vérifie qu'il spécifie un UserName et un Email. Elle s'assure également que les chaînes Password et ConfirmPassword correspondent, et que le mot de passe comporte au moins six caractères. Comment fait-elle donc ? Tout simplement en inspectant et en agissant sur les métadonnées attachées à la classe RegisterModel. La Figure 2 montre la classe RegisterModel avec ses attributs de validation.

Figure 2 Classe RegisterModel avec attributs de validation

[PropertiesMustMatch("Password", "ConfirmPassword", 
  ErrorMessage = "The password and confirmation password do not match.")]
public class RegisterModel
{
  [Required]        
  public string UserName { get; set; }

  [Required]
  public string Email { get; set; }

  [Required]
  [ValidatePasswordLength]
  public string Password { get; set; }

  [Required]
  public string ConfirmPassword { get; set; }
}

Lorsque l'utilisateur soumet la vue Register, le classeur de modèles par défaut dans ASP.NET MVC tente de créer une nouvelle instance de la classe RegisterModel à passer comme paramètre de l'action Register du contrôle AccountController. Le modèle de classeurs extrait les informations de la demande actuelle afin de remplir l'objet RegisterModel. Par exemple, il peut trouver automatiquement la valeur POST d'un contrôle d'entrée HTML nommé UserName et utiliser cette valeur pour remplir la propriété UserName de RegisterModel. Ce comportement est présent dans ASP.NET MVC depuis la version 1 ; il ne s'agira donc pas d'une nouveauté si vous avez déjà utilisé l'infrastructure.

Ce qui est nouveau dans la version 2, c'est que le classeur de modèles par défaut demande également à un fournisseur de métadonnées si des métadonnées sont disponibles pour l'objet RegisterModel. Ce processus génère finalement un objet dérivé ModelMetaData dont le rôle consiste à décrire non seulement les règles de validation associées au modèle, mais également les informations relatives à l'affichage du modèle dans une vue. Brad Wilson, membre de l'équipe ASP.NET, propose une série d'articles concernant l'influence que peuvent avoir ces métadonnées sur l'affichage d'un modèle par le biais de modèles. Le premier de ces articles est accessible à l'adresse bradwilson.typepad.com/blog/2009/10/aspnet-mvc-2-templates-part-1-introduction.html.

Une fois que le classeur de modèles dispose d'un objet ModelMetaData associé au modèle, il peut utiliser les métadonnées de validation qui s'y trouvent pour valider l'objet de modèle. Par défaut, ASP.NET MVC utilise des métadonnées d'attributs d'annotation de données telles que [Required]. ASP.NET MVC étant bien entendu enfichable et extensible, si vous souhaitez concevoir une source différente pour les métadonnées de modèle, vous pouvez implémenter votre propre fournisseur de métadonnées. Ben Scheirman fournit quelques informations très utiles à ce sujet dans un article intitulé « Customizing ASP.NET MVC 2—Metadata and Validation », accessible à l'adresse dotnetslackers.com/articles/aspnet/customizing-asp-net-mvc-2-metadata-and-validation.aspx.

Annotations de données

Soit dit en passant, vous pouvez créer vos propres attributs de validation, comme nous le verrons plus loin, mais [Required] est l'un des quelques attributs de validation standard qui résident dans l'assembly System.ComponentModel.DataAnnotations. La Figure 3 présente une liste complète des attributs de validation de l'assembly d'annotations.

Figure 3 Attributs de validation de l'assembly d'annotations

Attribut Description
StringLength Indique la longueur maximale de la chaîne autorisée dans le champ de données.
Required Indique qu'un champ de données est obligatoire.
RegularExpression Indique qu'une valeur de champ de données doit correspondre à l'expression régulière spécifiée.
Range Indique les contraintes de plage numérique pour la valeur d'un champ de données.
DataType Indique le nom d'un type supplémentaire à associer à un champ de données (l'une des valeurs énumérées DataType, telles que EmailAddress, Url ou Password).

L'usage de ces attributs d'annotation de données tend à se banaliser dans le Microsoft .NET Framework. Vous pouvez non seulement les utiliser dans une application ASP.NET MVC, mais en plus ils sont reconnus par les services ASP.NET Dynamic Data, Silverlight et Silverlight RIA.

Affichage des validations

Avec les métadonnées de validation en place, les erreurs apparaissent automatiquement dans une vue lorsque l'utilisateur entre des données incorrectes. La Figure 4 illustre la vue Register lorsque l'utilisateur clique sur Register sans fournir d'information.


Figure 4 Échec de validation

L'affichage de la Figure 4 a été créé à l'aide de quelques-unes des assistances HTML dans ASP.NET MVC 2, notamment l'assistance ValidationMessageFor. ValidationMessageFor contrôle le placement d'un message de validation lorsque la validation échoue pour un champ de données particulier. La Figure 5 présente un extrait de Register.aspx illustrant comment utiliser les assistances ValidationMessageFor et ValidationSummary.

Figure 5 Comment utiliser les nouvelles assistances HTML

<% using (Html.BeginForm()) { %>
    <%= Html.ValidationSummary(true, "Account creation was unsuccessful. " +
    "Please correct the errors and try again.") %>
    <div>
        <fieldset>
            <legend>Account Information</legend>
            
            <div class="editor-label">
                <%= Html.LabelFor(m => m.UserName) %>
            </div>
            <div class="editor-field">
                <%= Html.TextBoxFor(m => m.UserName) %>
                <%= Html.ValidationMessageFor(m => m.UserName) %>
            </div>

Validations personnalisées

Les attributs de validation sur la classe RegisterModel ne sont pas tous des attributs de l'assembly d'annotation de données de Microsoft. [PropertiesMustMatch] et [ValidatePasswordLength] sont des attributs personnalisés que vous trouverez définis dans le fichier AccountModel.cs qui contient la classe RegisterModel. Nul besoin de se soucier des classes de métadonnées ou fournisseurs de métadonnées personnalisés si vous souhaitez simplement fournir une règle de validation personnalisée. Il vous suffit de dériver à partir de la classe abstraite ValidationAttribute et de fournir une implémentation pour la méthode IsValid. L'implémentation de l'attribut ValidatePasswordLength est illustrée à la Figure 6.

Figure 6 Implémentation de l'attribut ValidatePasswordLength

[AttributeUsage(AttributeTargets.Field | 
                AttributeTargets.Property, 
                AllowMultiple = false, 
                Inherited = true)]
public sealed class ValidatePasswordLengthAttribute 
    : ValidationAttribute
{
    private const string _defaultErrorMessage = 
        "’{0}’ must be at least {1} characters long.";

    private readonly int _minCharacters = 
        Membership.Provider.MinRequiredPasswordLength;

    public ValidatePasswordLengthAttribute()
        : base(_defaultErrorMessage)
    {
    }

    public override string FormatErrorMessage(string name)
    {
        return String.Format(CultureInfo.CurrentUICulture, 
            ErrorMessageString,
            name, _minCharacters);
    }

    public override bool IsValid(object value)
    {
        string valueAsString = value as string;
        return (valueAsString != null && 
            valueAsString.Length >= _minCharacters);
    }
}

L'autre attribut, PropertiesMustMatch, est un excellent exemple d'attribut de validation que vous pouvez appliquer au niveau de la classe pour effectuer des validations inter-porpriétés.

Validation sur le client

Toute la validation RegisterModel que nous avons examinée jusqu'à maintenant a lieu sur le serveur. Fort heureusement, il est facile d'activer la validation également sur le client. J'essaie dans la mesure du possible d'utiliser la validation sur le client car cela permet de fournir à l'utilisateur un retour d'information rapide tout en soulageant le serveur d'une partie du travail. La logique côté serveur doit toutefois demeurer en place, pour le cas où un utilisateur disposerait d'un navigateur dans lequel les scripts seraient désactivés (ou tenteraient intentionnellement d'envoyer des données incorrectes au serveur).

L'activation de la validation sur le client est un processus en deux étapes. L'étape 1 consiste à s'assurer que la vue inclut les scripts de validation appropriés. Tous les scripts dont vous avez besoin résident dans le dossier Scripts d'une nouvelle application MVC. Le script MicrosoftAjax.js est le cœur des bibliothèques Microsoft AJAX et le premier que vous devrez inclure. Le second script est MicrosoftMvcValidation.js. J'ajoute généralement un ContentPlaceHolder à la page maître de mon application MVC pour contenir les scripts, comme illustré ci-dessous :

<head runat="server">
    <title><asp:ContentPlaceHolder ID="TitleContent" runat=
"server" /></title>
    <link href="../../Content/Site.css" rel="stylesheet" type=
"text/css" />

    <asp:ContentPlaceHolder ID="Scripts" runat="server">
       
    </asp:ContentPlaceHolder>
    
</head>

Une vue peut alors inclure les scripts dont elle a besoin à l'aide d'un contrôle Content. Le code ci-dessous permet de s'assurer que les scripts de validation sont présents :

<asp:Content ContentPlaceHolderID="Scripts" runat="server">
    <script src="../../Scripts/MicrosoftAjax.js" 
            type="text/javascript"></script>
    <script src="../../Scripts/MicrosoftMvcValidation.js" 
            type="text/javascript"></script>
</asp:Content>

La deuxième étape requise pour utiliser la validation côté client consiste à appeler la méthode d'assistance HTML EnableClientValidation dans la vue où la prise en charge de la validation est nécessaire. Assurez-vous d'appeler cette méthode avant d'utiliser l'assistance HTML BeginForm, comme illustré ci-dessous :

<%
       Html.EnableClientValidation(); 
       using (Html.BeginForm())
        {
     %>
     
     <!-- the rest of the form ... -->
     
     <% } %>

Notez que la logique de validation côté client fonctionne uniquement avec les attributs de validation intégrés. Pour la vue Register, cela signifie que la validation côté client s'assurera que les champs obligatoires sont présents, mais ne saura pas comment valider la longueur du mot de passe ni confirmer que les deux mots de passe correspondent. Heureusement, il est facile d'ajouter une logique de validation JavaScript personnalisée enfichable dans l'infrastructure de validation JavaScript d'ASP.NET MVC. Vous trouverez davantage de détails à ce sujet dans le message de blog de Phil Haack intitulé « ASP.NET MVC 2 Custom Validation », accessible à l'adresse haacked.com/archive/2009/11/19/aspnetmvc2-custom-validation.aspx.

En guise de synthèse, on ne peut que reconnaître que la prise en charge intégrée des scénarios de validation courants est un ajout extrêmement utile dans ASP.NET MVC 2. Les règles de validation sont non seulement très faciles à ajouter grâce à des attributs sur un objet de modèle, mais les fonctionnalités de validation elles-mêmes sont flexibles et simples à étendre. Tirez vite profit de ces fonctionnalités pour gagner du temps et des lignes de code dans votre prochaine application ASP.NET MVC.

K. Scott Allen est membre du personnel technique de Pluralsight et fondateur d'OdeToCode. Vous pouvez le joindre à l'adresse scott@OdeToCode.com, lire son blog à l'adresse odetocode.com/blogs/scott ou le suivre sur Twitter à l'adresse twitter.com/OdeToCode.

Je remercie l'expert technique suivant d'avoir relu cet article : Brad Wilson