Validation de modèle dans ASP.NET Core MVC et Razor Pages

Cet article explique comment valider l’entrée utilisateur dans ASP.NET Core MVC ou l’application Razor Pages.

Affichez ou téléchargez un exemple de code (procédure de téléchargement).

État du modèle

L’état du modèle représente les erreurs qui proviennent de deux sous-systèmes : liaison de modèle et validation de modèle. Les erreurs qui proviennent de la liaison de modèle sont généralement des erreurs de conversion de données. Par exemple, un « x » est entré dans un champ de nombres entiers. La validation du modèle se produit après la liaison de modèle et signale des erreurs lorsque les données ne sont pas conformes aux règles d’entreprise. Par exemple, un 0 est entré dans un champ qui attend une évaluation comprise entre 1 et 5.

La liaison et la validation de modèle se produisent avant l’exécution d’une action de contrôleur ou d’une méthode de gestionnaire Razor Pages. Pour les applications web, il incombe à l’application d’inspecter ModelState.IsValid et de réagir de façon appropriée. Les applications web affichent généralement de nouveau la page avec un message d’erreur, comme illustré dans l’exemple Razor Pages suivant :

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    _context.Movies.Add(Movie);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

Pour ASP.NET Core MVC avec des contrôleurs et des vues, l’exemple suivant montre comment vérifier ModelState.IsValid dans une action de contrôleur :

public async Task<IActionResult> Create(Movie movie)
{
    if (!ModelState.IsValid)
    {
        return View(movie);
    }

    _context.Movies.Add(movie);
    await _context.SaveChangesAsync();

    return RedirectToAction(nameof(Index));
}

Les contrôleurs d’API web ne sont pas obligés de vérifier s’ils ont l’attribut ModelState.IsValid[ApiController]. Dans ce cas, une réponse HTTP 400 automatique contenant les détails du problème est retournée si l’état du modèle n’est pas valide. Pour plus d’informations, consultez Réponses HTTP 400 automatiques.

Réexécuter la validation

La validation est automatique, mais vous souhaiterez peut-être la répéter manuellement. Par exemple, vous pourriez calculer une valeur pour une propriété, et souhaiter réexécuter la validation après avoir affecté la valeur calculée comme valeur de la propriété. Pour réexécuter la validation, appelez ModelStateDictionary.ClearValidationState pour effacer la validation spécifique au modèle en cours de validation, suivi de TryValidateModel:

public async Task<IActionResult> OnPostTryValidateAsync()
{
    var modifiedReleaseDate = DateTime.Now.Date;
    Movie.ReleaseDate = modifiedReleaseDate;

    ModelState.ClearValidationState(nameof(Movie));
    if (!TryValidateModel(Movie, nameof(Movie)))
    {
        return Page();
    }

    _context.Movies.Add(Movie);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

Attributs de validation

Les attributs de validation vous permettent de spécifier des règles de validation pour des propriétés de modèle. L’exemple suivant tiré de l’exemple d’application montre une classe de modèle annotée avec des attributs de validation. L’attribut [ClassicMovie] est un attribut de validation personnalisé et les autres sont prédéfinis. [ClassicMovieWithClientValidator] n’apparaît pas, cela montre une autre façon d’implémenter un attribut personnalisé.

public class Movie
{
    public int Id { get; set; }

    [Required]
    [StringLength(100)]
    public string Title { get; set; } = null!;

    [ClassicMovie(1960)]
    [DataType(DataType.Date)]
    [Display(Name = "Release Date")]
    public DateTime ReleaseDate { get; set; }

    [Required]
    [StringLength(1000)]
    public string Description { get; set; } = null!;

    [Range(0, 999.99)]
    public decimal Price { get; set; }

    public Genre Genre { get; set; }

    public bool Preorder { get; set; }
}

Attributs prédéfinis

Voici certains des attributs de validation prédéfinis :

  • [ValidateNever] : indique qu’une propriété ou un paramètre doit être exclu de la validation.
  • [CreditCard] : vérifie que la propriété a un format de carte de crédit. Nécessite des méthodes supplémentaires de validation jQuery.
  • [Compare]: vérifie que deux propriétés d’un modèle correspondent.
  • [EmailAddress]: vérifie que la propriété a un format d’e-mail.
  • [Phone]: vérifie que la propriété a un format de numéro de téléphone.
  • [Range]: vérifie que la valeur de propriété est comprise dans une plage spécifiée.
  • [RegularExpression]: vérifie que la valeur de propriété correspond à une expression régulière spécifiée.
  • [Required]: valide que le champ n’est pas Null. Pour plus d’informations sur le comportement de cet attribut, consultez Attribut [Remote][Required].
  • [StringLength]: vérifie qu’une valeur de propriété de chaîne ne dépasse pas une limite de longueur spécifiée.
  • [Url]: vérifie que la propriété a un format d’URL.
  • [Remote]: valide l’entrée sur le client en appelant une méthode d’action sur le serveur. Pour plus d’informations sur le comportement de cet attribut, consultez [Remote]Attribut.

Vous trouverez la liste complète des attributs de validation dans l’System.ComponentModel.DataAnnotations espace de noms.

Messages d’erreur

Les attributs de validation vous permettent de spécifier le message d’erreur à afficher pour l’entrée non valide. Par exemple :

[StringLength(8, ErrorMessage = "Name length can't be more than 8.")]

En interne, les attributs appellent String.Format avec un espace réservé pour le nom de champ et parfois d’autres espaces réservés. Par exemple :

[StringLength(8, ErrorMessage = "{0} length must be between {2} and {1}.", MinimumLength = 6)]

Appliqué à une propriété Name, le message d’erreur créé par le code précédent serait « Name length must be between 6 and 8 ».

Pour savoir quels paramètres sont passés à String.Format pour le message d’erreur d’un attribut particulier, consultez le code source de DataAnnotations.

Utilisez JSON sur les noms de propriété pour les erreurs de validation

Par défaut, lorsqu’une erreur de validation se produit, la validation du modèle génère un ModelStateDictionary avec le nom de propriété comme clé d’erreur. Certaines applications, comme les applications monopages, tirent parti de l’utilisation de noms de propriétés JSON pour les erreurs de validation générées à partir des API web. Le code suivant configure la validation pour utiliser SystemTextJsonValidationMetadataProvider afin d’utiliser les noms de propriété JSON :

using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers(options =>
{
    options.ModelMetadataDetailsProviders.Add(new SystemTextJsonValidationMetadataProvider());
});

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Le code suivant configure la validation pour utiliser NewtonsoftJsonValidationMetadataProvider afin d’utiliser les noms de propriété JSON lors de l’utilisation de Json.NET :

using Microsoft.AspNetCore.Mvc.NewtonsoftJson;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers(options =>
{
    options.ModelMetadataDetailsProviders.Add(new NewtonsoftJsonValidationMetadataProvider());
}).AddNewtonsoftJson();

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Pour obtenir un exemple de stratégie d’utilisation de camel-casing, consultez Program.cs sur GitHub.

Types de référence non Null et attribut [Required]

Par défaut, le système de validation traite les propriétés ou paramètres n’acceptant pas les valeurs Null comme s’ils avaient un attribut [Required(AllowEmptyStrings = true)]. En activant Nullable les contextes, MVC commence implicitement à valider les propriétés ou paramètres non null comme s’ils avaient l’attribut [Required(AllowEmptyStrings = true)] . Prenez le code suivant :

public class Person
{
    public string Name { get; set; }
}

Si l’application a été générée avec <Nullable>enable</Nullable>, une valeur manquante pour Name dans une publication JS ON ou un formulaire entraîne une erreur de validation. Cela peut sembler contradictoire puisque l’attribut [Required(AllowEmptyStrings = true)] est implicite, mais ce comportement est attendu, car chaînes vides sont converties en null par défaut. Utilisez un type de référence Null pour autoriser la spécification de valeurs Null ou manquantes pour la propriété Name :

public class Person
{
    public string? Name { get; set; }
}

Ce comportement peut être désactivé en configurant SuppressImplicitRequiredAttributeForNonNullableReferenceTypes dans Program.cs:

builder.Services.AddControllers(
    options => options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true);

Validation de [Required] sur le serveur

Sur le serveur, une valeur obligatoire est considérée comme manquante si la propriété est Null. Un champ n’acceptant pas les valeurs Null est toujours valide, et le message d’erreur de l’attribut [Required] n’est jamais affiché.

Toutefois, la liaison de modèle pour une propriété n’acceptant pas les valeurs Null peut échouer, entraînant l’affichage d’un message d’erreur tel que The value '' is invalid. Pour spécifier un message d’erreur personnalisé pour la validation côté serveur des types n’acceptant pas les valeurs Null, vous disposez des options suivantes :

  • Rendre le champ Nullable (par exemple, decimal? au lieu de decimal). Les types valeur Null<T> sont traités comme des types Null standard.

  • Spécifier le message d’erreur par défaut devant être utilisé par la liaison de modèle, comme indiqué dans l’exemple suivant :

    builder.Services.AddRazorPages()
        .AddMvcOptions(options =>
        {
            options.MaxModelValidationErrors = 50;
            options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
                _ => "The field is required.");
        });
    
    builder.Services.AddSingleton
        <IValidationAttributeAdapterProvider, CustomValidationAttributeAdapterProvider>();
    

    Pour plus d’informations sur les erreurs de liaison de modèle pour lesquelles vous pouvez définir des messages par défaut, consultez DefaultModelBindingMessageProvider.

Validation de [Required] sur le client

Les chaînes et les types n’acceptant pas les valeurs Null sont gérés différemment sur le client et sur le serveur. Sur le client :

  • Une valeur est considérée comme présente uniquement si une entrée est tapée pour celle-ci. Par conséquent, la validation côté client gère les types n’acceptant pas les valeurs Null de la même façon que les types Nullable
  • Un espace blanc dans un champ de chaîne est considéré comme une entrée valide par la méthode required jQuery Validate. La validation côté serveur considère qu’un champ de chaîne obligatoire est non valide si seul un espace blanc est entré.

Comme indiqué précédemment, les types n’acceptant pas les valeurs Null sont traités comme s’ils avaient un attribut [Required(AllowEmptyStrings = true)]. Cela signifie que vous bénéficiez d’une validation côté client même si vous n’appliquez pas l’attribut [Required(AllowEmptyStrings = true)]. En revanche, si vous n’utilisez pas l’attribut, vous recevez un message d’erreur par défaut. Pour spécifier un message d’erreur personnalisé, utilisez l’attribut.

Attribut [Remote]

L’attribut [Remote] implémente la validation côté client qui nécessite l’appel d’une méthode sur le serveur pour déterminer si le champ d’entrée est valide. Par exemple, l’application peut avoir besoin de vérifier si un nom d’utilisateur est déjà en cours d’utilisation.

Pour implémenter la validation à distance

  1. Créez une méthode d’action devant être appelée par JavaScript. La méthode de validation jQuery remote attend une réponse JS ON :

    • true signifie que les données d’entrée sont valides.
    • false, undefined ou null signifie que l’entrée n’est pas valide. Affichez le message d’erreur par défaut.
    • Toute autre chaîne signifie que l’entrée n’est pas valide. Affichez la chaîne en tant que message d’erreur personnalisé.

    Voici un exemple de méthode d’action qui retourne un message d’erreur personnalisé :

    [AcceptVerbs("GET", "POST")]
    public IActionResult VerifyEmail(string email)
    {
        if (!_userService.VerifyEmail(email))
        {
            return Json($"Email {email} is already in use.");
        }
    
        return Json(true);
    }
    
  2. Dans la classe de modèle, annotez la propriété avec un attribut [Remote] qui pointe vers la méthode d’action de validation, comme indiqué dans l’exemple suivant :

    [Remote(action: "VerifyEmail", controller: "Users")]
    public string Email { get; set; } = null!;
    

La validation côté serveur doit également être implémentée pour les clients qui ont désactivé JavaScript.

Champs supplémentaires

La propriété AdditionalFields de l’attribut [Remote] vous permet de valider des combinaisons de champs relativement à des données présentes sur le serveur. Par exemple, si le modèle User a des propriétés FirstName et LastName, vous pouvez vérifier qu’aucun utilisateur existant n’a déjà cette paire de noms. L'exemple suivant montre comment utiliser AdditionalFields :

[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(LastName))]
[Display(Name = "First Name")]
public string FirstName { get; set; } = null!;

[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(FirstName))]
[Display(Name = "Last Name")]
public string LastName { get; set; } = null!;

AdditionalFields peut être défini explicitement sur les chaînes «Prénom» et «Nom», mais utiliser le nom de l’opérateur simplifie la refactorisation ultérieure. La méthode d’action pour cette validation doit accepter à la fois les arguments de firstName et lastName :

[AcceptVerbs("GET", "POST")]
public IActionResult VerifyName(string firstName, string lastName)
{
    if (!_userService.VerifyName(firstName, lastName))
    {
        return Json($"A user named {firstName} {lastName} already exists.");
    }

    return Json(true);
}

Quand l’utilisateur entre un nom ou un prénom, JavaScript effectue un appel à distance pour vérifier si cette paire de noms est déjà utilisée.

Pour valider deux champs supplémentaires ou plus, spécifiez-les sous la forme d’une liste délimitée par des virgules. Par exemple, pour ajouter une propriété MiddleName au modèle, définissez l’attribut [Remote] comme indiqué dans l’exemple suivant :

[Remote(action: "VerifyName", controller: "Users",
    AdditionalFields = nameof(FirstName) + "," + nameof(LastName))]
public string MiddleName { get; set; }

AdditionalFields, comme tous les arguments d’attribut, doit être une expression constante. Vous ne devez donc pas utiliser une chaîne interpolée ou appeler Join pour initialiser AdditionalFields.

Alternatives aux attributs prédéfinis

Si vous avez besoin d’une validation non fournie par les attributs prédéfinis, vous pouvez :

Attributs personnalisés

Pour les scénarios non gérés par les attributs de validation prédéfinis, vous pouvez créer des attributs de validation personnalisés. Créez une classe qui hérite de ValidationAttribute et substituez la méthode IsValid.

La méthode IsValid accepte un objet nommé value, qui est l’entrée à valider. Une surcharge accepte également un objet ValidationContext, qui fournit des informations supplémentaires telles que l’instance de modèle créée par la liaison de modèle.

L’exemple suivant vérifie que la date de sortie d’un film appartenant au genre Classic n’est pas ultérieure à une année spécifiée. L’attribut [ClassicMovie]:

  • Est exécuté uniquement sur le serveur.
  • Pour les films classiques, valide la date de publication :
public class ClassicMovieAttribute : ValidationAttribute
{
    public ClassicMovieAttribute(int year)
        => Year = year;

    public int Year { get; }

    public string GetErrorMessage() =>
        $"Classic movies must have a release year no later than {Year}.";

    protected override ValidationResult? IsValid(
        object? value, ValidationContext validationContext)
    {
        var movie = (Movie)validationContext.ObjectInstance;
        var releaseYear = ((DateTime)value!).Year;

        if (movie.Genre == Genre.Classic && releaseYear > Year)
        {
            return new ValidationResult(GetErrorMessage());
        }

        return ValidationResult.Success;
    }
}

La variable movie de l’exemple précédent représente un objet Movie qui contient les données de l’envoi du formulaire. Quand la validation échoue, un ValidationResult avec un message d’erreur est retourné.

IValidatableObject

L’exemple précédent fonctionne uniquement avec les types Movie. Une autre option de validation au niveau de la classe consiste à implémenter IValidatableObject dans la classe de modèle, comme indiqué dans l’exemple suivant :

public class ValidatableMovie : IValidatableObject
{
    private const int _classicYear = 1960;

    public int Id { get; set; }

    [Required]
    [StringLength(100)]
    public string Title { get; set; } = null!;

    [DataType(DataType.Date)]
    [Display(Name = "Release Date")]
    public DateTime ReleaseDate { get; set; }

    [Required]
    [StringLength(1000)]
    public string Description { get; set; } = null!;

    [Range(0, 999.99)]
    public decimal Price { get; set; }

    public Genre Genre { get; set; }

    public bool Preorder { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (Genre == Genre.Classic && ReleaseDate.Year > _classicYear)
        {
            yield return new ValidationResult(
                $"Classic movies must have a release year no later than {_classicYear}.",
                new[] { nameof(ReleaseDate) });
        }
    }
}

Validation personnalisée

Le code suivant montre comment ajouter une erreur de modèle après avoir examiné le modèle :

if (Contact.Name == Contact.ShortName)
{
    ModelState.AddModelError("Contact.ShortName", 
                             "Short name can't be the same as Name.");
}

Le code suivant implémente le test de validation dans un contrôleur :

if (contact.Name == contact.ShortName)
{
    ModelState.AddModelError(nameof(contact.ShortName),
                             "Short name can't be the same as Name.");
}

Le code suivant vérifie que le numéro de téléphone et l’adresse e-mail sont uniques :

public async Task<IActionResult> OnPostAsync()
{
    // Attach Validation Error Message to the Model on validation failure.          

    if (Contact.Name == Contact.ShortName)
    {
        ModelState.AddModelError("Contact.ShortName", 
                                 "Short name can't be the same as Name.");
    }

    if (_context.Contact.Any(i => i.PhoneNumber == Contact.PhoneNumber))
    {
        ModelState.AddModelError("Contact.PhoneNumber",
                                  "The Phone number is already in use.");
    }
    if (_context.Contact.Any(i => i.Email == Contact.Email))
    {
        ModelState.AddModelError("Contact.Email", "The Email is already in use.");
    }

    if (!ModelState.IsValid || _context.Contact == null || Contact == null)
    {
        // if model is invalid, return the page with the model state errors.
        return Page();
    }
    _context.Contact.Add(Contact);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

Le code suivant implémente le test de validation dans un contrôleur :

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Id,Name,ShortName,Email,PhoneNumber")] Contact contact)
{
    // Attach Validation Error Message to the Model on validation failure.
    if (contact.Name == contact.ShortName)
    {
        ModelState.AddModelError(nameof(contact.ShortName),
                                 "Short name can't be the same as Name.");
    }

    if (_context.Contact.Any(i => i.PhoneNumber == contact.PhoneNumber))
    {
        ModelState.AddModelError(nameof(contact.PhoneNumber),
                                  "The Phone number is already in use.");
    }
    if (_context.Contact.Any(i => i.Email == contact.Email))
    {
        ModelState.AddModelError(nameof(contact.Email), "The Email is already in use.");
    }

    if (ModelState.IsValid)
    {
        _context.Add(contact);
        await _context.SaveChangesAsync();
        return RedirectToAction(nameof(Index));
    }
    return View(contact);
}

La vérification d’un numéro de téléphone ou d’un e-mail unique est généralement également effectuée avec la validation à distance.

Validationresult

Considérez la classe ValidateNameAttribute personnalisée suivante :

public class ValidateNameAttribute : ValidationAttribute
{
    public ValidateNameAttribute()
    {
        const string defaultErrorMessage = "Error with Name";
        ErrorMessage ??= defaultErrorMessage;
    }

    protected override ValidationResult? IsValid(object? value,
                                         ValidationContext validationContext)
    {
        if (value == null || string.IsNullOrWhiteSpace(value.ToString()))
        {
            return new ValidationResult("Name is required.");
        }

        if (value.ToString()!.ToLower().Contains("zz"))
        {

            return new ValidationResult(
                        FormatErrorMessage(validationContext.DisplayName));
        }

        return ValidationResult.Success;
    }
}

Dans le code suivant, l’attribut [ValidateName] personnalisé est appliqué :

public class Contact
{
    public Guid Id { get; set; }

    [ValidateName(ErrorMessage = "Name must not contain `zz`")] 
    public string? Name { get; set; }
    public string? Email { get; set; }
    public string? PhoneNumber { get; set; }
}

Lorsque le modèle contient zz, un nouveau ValidationResult est retourné.

Validation du nœud de niveau supérieur

Les nœuds de niveau supérieur incluent les éléments suivants :

  • Paramètres d’action
  • Propriétés du contrôleur
  • Paramètres du gestionnaire de page
  • Propriétés du modèle de page

Les nœuds de niveau supérieur liés au modèle sont validés en plus de la validation des propriétés du modèle. Dans l’exemple suivant tiré de l’exemple d’application, la méthodeVerifyPhone utilise RegularExpressionAttribute pour valider le paramètre d’action phone :

[AcceptVerbs("GET", "POST")]
public IActionResult VerifyPhone(
    [RegularExpression(@"^\d{3}-\d{3}-\d{4}$")] string phone)
{
    if (!ModelState.IsValid)
    {
        return Json($"Phone {phone} has an invalid format. Format: ###-###-####");
    }

    return Json(true);
}

Les nœuds de niveau supérieur peuvent utiliser BindRequiredAttribute avec des attributs de validation. Dans l’exemple suivant de l’exemple d’application, la méthodeCheckAge spécifie que le paramètre age doit être lié à partir de la chaîne de requête au moment de l’envoi du formulaire :

[HttpPost]
public IActionResult CheckAge([BindRequired, FromQuery] int age)
{

Dans la page de vérification de l’âge (CheckAge.cshtml), il y a deux formulaires. Le premier formulaire envoie une valeur Age égale à 99 en tant que chaîne de requête : https://localhost:5001/Users/CheckAge?Age=99.

Quand un paramètre age au format approprié est envoyé à partir de la chaîne de requête, le formulaire est validé.

Le second formulaire de la page de vérification de l’âge envoie la valeur Age dans le corps de la requête, ce qui entraîne un échec de la validation. L’échec de la liaison est dû au fait que le paramètre age doit provenir d’une chaîne de requête.

Quantité maximale d’erreurs

La validation s’arrête quand le nombre maximal d’erreurs est atteint (200 par défaut). Vous pouvez configurer ce nombre avec le code suivant dans Program.cs :

builder.Services.AddRazorPages()
    .AddMvcOptions(options =>
    {
        options.MaxModelValidationErrors = 50;
        options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
            _ => "The field is required.");
    });

builder.Services.AddSingleton
    <IValidationAttributeAdapterProvider, CustomValidationAttributeAdapterProvider>();

Récursivité maximale

ValidationVisitor parcourt le graphe d’objet du modèle en cours de validation. Pour les modèles très profonds ou infiniment récursifs, la validation peut entraîner un dépassement de la capacité de la pile. MvcOptions.MaxValidationDepth fournit un moyen d’interrompre la validation de manière anticipée si la récursivité du visiteur dépasse une profondeur configurée. La valeur par défaut de MvcOptions.MaxValidationDepth est 32.

Court-circuit automatique

La validation est automatiquement court-circuitée (ignorée) si le graphe du modèle ne nécessite pas de validation. Les objets pour lesquels le runtime ignore la validation comprennent les collections de primitives (telles que byte[], string[], Dictionary<string, string>) et les graphes d’objets complexes qui n’ont pas de validateur.

Validation côté client

La validation côté client empêche l’envoi jusqu’à ce que le formulaire soit valide. Le bouton Submit exécute le code JavaScript qui envoie le formulaire ou qui affiche des messages d’erreur.

La validation côté client permet d’éviter un aller-retour inutile vers le serveur quand il existe des erreurs d’entrée sur un formulaire. Les références de script suivantes dans _Layout.cshtml et _ValidationScriptsPartial.cshtmlvalident du côté du client:

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.3/jquery.validate.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-unobtrusive/3.2.12/jquery.validate.unobtrusive.js"></script>

Le script jQuery Unobtrusive Validation est une bibliothèque frontale personnalisée de Microsoft qui s’appuie sur le plug-in bien connu jQuery Validate. Sans jQuery Unobtrusive Validation, vous devriez coder la même logique de validation à deux endroits : une fois dans les attributs de validation côté serveur sur les propriétés du modèle, puis à nouveau dans les scripts côté client. Au lieu de cela, les Tag Helpers et les helpers HTML utilisent les attributs de validation et les métadonnées de type des propriétés du modèle afin de restituer les attributs data- HTML 5 pour les éléments de formulaire nécessitant une validation. La validation non obstrustice jQuery analyse les attributs data- et transmet la logique à jQuery Validate, en « copiant » la logique de validation côté serveur vers le client. Vous pouvez afficher les erreurs de validation sur le client en utilisant des Tag Helpers, comme indiqué ici :

<div class="form-group">
    <label asp-for="Movie.ReleaseDate" class="control-label"></label>
    <input asp-for="Movie.ReleaseDate" class="form-control" />
    <span asp-validation-for="Movie.ReleaseDate" class="text-danger"></span>
</div>

Les assistants de balisage précédents restituent le code HTML suivant :

<div class="form-group">
    <label class="control-label" for="Movie_ReleaseDate">Release Date</label>
    <input class="form-control" type="date" data-val="true"
        data-val-required="The Release Date field is required."
        id="Movie_ReleaseDate" name="Movie.ReleaseDate" value="">
    <span class="text-danger field-validation-valid"
        data-valmsg-for="Movie.ReleaseDate" data-valmsg-replace="true"></span>
</div>

Notez que les attributs data- dans la sortie HTML correspondent aux attributs de validation pour la propriété Movie.ReleaseDate. L’attribut data-val-required contient un message d’erreur à afficher si l’utilisateur ne renseigne pas le champ correspondant à la date de sortie. La validation non obstructive jQuery transmet cette valeur à la méthode de validation jQuery requise, qui affiche alors ce message dans l’élément <span > qui l’accompagne.

La validation du type de données est basée sur le type .NET d’une propriété, sauf en cas de substitution par un attribut [DataType] . Les navigateurs ont leurs propres messages d’erreur par défaut, mais le package jQuery Validation Unobtrusive Validation peut remplacer ces messages. Les attributs [DataType]et les sous-classes comme [EmailAddress] permettent de spécifier le message d’erreur.

Validation non obstructive

Pour plus d’informations sur la validation non obstructive, consultez ce problème GitHub.

Ajouter une validation à des formulaires dynamiques

La validation jQuery non obstructive transmet la logique et les paramètres de validation à jQuery Validate lors du premier chargement de la page. Par conséquent, la validation ne fonctionne pas automatiquement sur les formulaires générés de manière dynamique. Pour activer la validation, vous devez faire en sorte que jQuery Validate analyse le formulaire dynamique immédiatement après l’avoir créé. Par exemple, le code suivant définit la validation côté client sur un formulaire ajouté par le biais d’AJAX.

$.get({
    url: "https://url/that/returns/a/form",
    dataType: "html",
    error: function(jqXHR, textStatus, errorThrown) {
        alert(textStatus + ": Couldn't add form. " + errorThrown);
    },
    success: function(newFormHTML) {
        var container = document.getElementById("form-container");
        container.insertAdjacentHTML("beforeend", newFormHTML);
        var forms = container.getElementsByTagName("form");
        var newForm = forms[forms.length - 1];
        $.validator.unobtrusive.parse(newForm);
    }
})

La méthode $.validator.unobtrusive.parse() accepte un sélecteur jQuery comme argument. Cette méthode indique à jQuery Unobtrusive Validation d’analyser les attributs data- des formulaires dans ce sélecteur. Les valeurs de ces attributs sont ensuite transmises au plug-in jQuery Validate.

Ajouter une validation à des contrôles dynamiques

La méthode $.validator.unobtrusive.parse() opère sur un formulaire entier, et non sur des contrôles individuels générés de manière dynamique tels que <input> et <select/>. Pour réanalyser le formulaire, supprimez les données de validation qui ont été ajoutées quand le formulaire a été analysé précédemment, comme illustré dans l’exemple suivant :

$.get({
    url: "https://url/that/returns/a/control",
    dataType: "html",
    error: function(jqXHR, textStatus, errorThrown) {
        alert(textStatus + ": Couldn't add control. " + errorThrown);
    },
    success: function(newInputHTML) {
        var form = document.getElementById("my-form");
        form.insertAdjacentHTML("beforeend", newInputHTML);
        $(form).removeData("validator")    // Added by jQuery Validation
               .removeData("unobtrusiveValidation");   // Added by jQuery Unobtrusive Validation
        $.validator.unobtrusive.parse(form);
    }
})

Validation personnalisée côté client

La validation personnalisée côté client s’effectue en générant des attributs HTML data- qui fonctionnent avec un adaptateur jQuery Validate personnalisé. L’exemple de code d’adaptateur suivant a été écrit pour les attributs [ClassicMovie] et [ClassicMovieWithClientValidator] qui ont été introduits plus haut dans cet article :

$.validator.addMethod('classicmovie', function (value, element, params) {
    var genre = $(params[0]).val(), year = params[1], date = new Date(value);

    // The Classic genre has a value of '0'.
    if (genre && genre.length > 0 && genre[0] === '0') {
        // The release date for a Classic is valid if it's no greater than the given year.
        return date.getUTCFullYear() <= year;
    }

    return true;
});

$.validator.unobtrusive.adapters.add('classicmovie', ['year'], function (options) {
    var element = $(options.form).find('select#Movie_Genre')[0];

    options.rules['classicmovie'] = [element, parseInt(options.params['year'])];
    options.messages['classicmovie'] = options.message;
});

Pour plus d’informations sur la façon d’écrire des adaptateurs, consultez la documentation de jQuery Validate.

L’utilisation d’un adaptateur pour un champ donné est déclenchée par des attributs data- qui :

  • Marquent le champ comme étant soumis à validation (data-val="true").
  • Identifient un nom de règle de validation et un texte de message d’erreur (par exemple, data-val-rulename="Error message.").
  • Fournissent les éventuels paramètres supplémentaires dont le validateur a besoin (par exemple, data-val-rulename-param1="value").

L’exemple suivant montre les attributs data- pour l’attribut de l’exemple d’application ClassicMovie :

<input class="form-control" type="date"
    data-val="true"
    data-val-classicmovie="Classic movies must have a release year no later than 1960."
    data-val-classicmovie-year="1960"
    data-val-required="The Release Date field is required."
    id="Movie_ReleaseDate" name="Movie.ReleaseDate" value="">

Comme mentionné plus haut, les Tag Helpers et les helpers HTML utilisent les informations des attributs de validation pour restituer les attributs data-. Il existe deux options pour l’écriture de code qui entraîne la création d’attributs HTML data- personnalisés :

  • Créez une classe qui dérive de AttributeAdapterBase<TAttribute> et une classe qui implémente IValidationAttributeAdapterProvider, et inscrivez votre attribut et son adaptateur dans l’injection de dépendances. Cette méthode respecte le principe de responsabilité unique, dans la mesure où le code de validation lié au serveur et celui lié au client se trouvent dans des classes distinctes. Il existe un autre avantage : l’adaptateur étant inscrit dans l’injection de dépendances, les autres services dans l’injection de dépendances lui sont accessibles si nécessaire.
  • Implémentez IClientModelValidator dans votre classe ValidationAttribute. Cette méthode peut être appropriée si l’attribut n’effectue aucune validation côté serveur et n’a besoin d’aucun service à partir de l’injection de dépendances.

AttributeAdapter pour la validation côté client

Cette méthode de rendu des attributs data- en HTML est utilisée par l’attribut ClassicMovie dans l’exemple d’application. Pour ajouter la validation côté client à l’aide de cette méthode

  1. Créez une classe d’adaptateurs d’attributs pour l’attribut de validation personnalisé. Dérivez la classe de AttributeAdapterBase<TAttribute>. Créez une méthode AddValidation qui ajoute des attributs data- à la sortie restituée, comme illustré dans cet exemple :

    public class ClassicMovieAttributeAdapter : AttributeAdapterBase<ClassicMovieAttribute>
    {
        public ClassicMovieAttributeAdapter(
            ClassicMovieAttribute attribute, IStringLocalizer? stringLocalizer)
            : base(attribute, stringLocalizer)
        {
    
        }
    
        public override void AddValidation(ClientModelValidationContext context)
        {
            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-classicmovie", GetErrorMessage(context));
    
            var year = Attribute.Year.ToString(CultureInfo.InvariantCulture);
            MergeAttribute(context.Attributes, "data-val-classicmovie-year", year);
        }
    
        public override string GetErrorMessage(ModelValidationContextBase validationContext)
            => Attribute.GetErrorMessage();
    }
    
  2. Créez une classe de fournisseurs d’adaptateurs qui implémente IValidationAttributeAdapterProvider. Dans la méthode GetAttributeAdapter, passez l’attribut personnalisé au constructeur de l’adaptateur, comme illustré dans cet exemple :

    public class CustomValidationAttributeAdapterProvider : IValidationAttributeAdapterProvider
    {
        private readonly IValidationAttributeAdapterProvider baseProvider =
            new ValidationAttributeAdapterProvider();
    
        public IAttributeAdapter? GetAttributeAdapter(
            ValidationAttribute attribute, IStringLocalizer? stringLocalizer)
        {
            if (attribute is ClassicMovieAttribute classicMovieAttribute)
            {
                return new ClassicMovieAttributeAdapter(classicMovieAttribute, stringLocalizer);
            }
    
            return baseProvider.GetAttributeAdapter(attribute, stringLocalizer);
        }
    }
    
  3. Inscrivez le fournisseur d’adaptateurs auprès de l’injection de dépendances dans Program.cs :

    builder.Services.AddRazorPages()
        .AddMvcOptions(options =>
        {
            options.MaxModelValidationErrors = 50;
            options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
                _ => "The field is required.");
        });
    
    builder.Services.AddSingleton
        <IValidationAttributeAdapterProvider, CustomValidationAttributeAdapterProvider>();
    

IClientModelValidator pour la validation côté client

Cette méthode de rendu des attributs data- en HTML est utilisée par l’attribut ClassicMovieWithClientValidator dans l’exemple d’application. Pour ajouter la validation côté client à l’aide de cette méthode

  • Dans l’attribut de validation personnalisé, implémentez l’interface IClientModelValidator et créez une méthode AddValidation. Dans la méthode AddValidation, ajoutez des attributs data- pour la validation, comme indiqué dans l’exemple suivant :

    public class ClassicMovieWithClientValidatorAttribute :
        ValidationAttribute, IClientModelValidator
    {
        public ClassicMovieWithClientValidatorAttribute(int year)
            => Year = year;
    
        public int Year { get; }
    
        public void AddValidation(ClientModelValidationContext context)
        {
            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-classicmovie", GetErrorMessage());
    
            var year = Year.ToString(CultureInfo.InvariantCulture);
            MergeAttribute(context.Attributes, "data-val-classicmovie-year", year);
        }
    
        public string GetErrorMessage() =>
            $"Classic movies must have a release year no later than {Year}.";
    
        protected override ValidationResult? IsValid(
            object? value, ValidationContext validationContext)
        {
            var movie = (Movie)validationContext.ObjectInstance;
            var releaseYear = ((DateTime)value!).Year;
    
            if (movie.Genre == Genre.Classic && releaseYear > Year)
            {
                return new ValidationResult(GetErrorMessage());
            }
    
            return ValidationResult.Success;
        }
    
        private static bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
        {
            if (attributes.ContainsKey(key))
            {
                return false;
            }
    
            attributes.Add(key, value);
            return true;
        }
    }
    

Désactiver la validation côté client

Le code suivant désactive la validation côté client dans Razor Pages :

builder.Services.AddRazorPages()
    .AddViewOptions(options =>
    {
        options.HtmlHelperOptions.ClientValidationEnabled = false;
    });

Autres options pour désactiver la validation côté client :

  • Commentez la référence à _ValidationScriptsPartial dans tous les fichiers .cshtml.
  • Supprimez le contenu du fichier Pages\Shared_ValidationScriptsPartial.cshtml .

L’approche précédente n’empêche pas la validation côté client de la bibliothèque de classes IdentityRazor ASP.NET Core. Pour plus d’informations, consultez Génération de modèles automatiqueIdentity dans les projets ASP.NET Core.

Détails du problème

Les détails du problème ne sont pas le seul format de réponse à décrire une erreur d’API HTTP. Toutefois, ils sont couramment utilisés pour signaler des erreurs pour les API HTTP.

Le service des détails du problème implémente l’interface IProblemDetailsService, qui prend en charge la création de détails de problème dans ASP.NET Core. La méthode d’extension AddProblemDetails sur IServiceCollection enregistre l’implémentation IProblemDetailsService par défaut.

Dans les applications ASP.NET Core, l’intergiciel suivant génère des réponses HTTP sur les détails de problème lorsque AddProblemDetails est appelé, sauf si l’en-tête HTTP de requêteAccept n’inclut pas l’un des types de contenu pris en charge par le IProblemDetailsWriter inscrit (par défaut : application/json) :

  • ExceptionHandlerMiddleware : génère une réponse sur les détails du problème lorsqu’un gestionnaire personnalisé n’est pas défini.
  • StatusCodePagesMiddleware : génère une réponse de détails de problème par défaut.
  • DeveloperExceptionPageMiddlewareHTTP : génère une réponse sur les détails du problème lors du développement lorsque l’en-tête HTTP de la requête Accept n’inclut pas text/html.

Ressources supplémentaires

Cet article explique comment valider l’entrée utilisateur dans ASP.NET Core MVC ou l’application Razor Pages.

Affichez ou téléchargez un exemple de code (procédure de téléchargement).

État du modèle

L’état du modèle représente les erreurs qui proviennent de deux sous-systèmes : liaison de modèle et validation de modèle. Les erreurs qui proviennent de la liaison de modèle sont généralement des erreurs de conversion de données. Par exemple, un « x » est entré dans un champ de nombres entiers. La validation du modèle se produit après la liaison de modèle et signale des erreurs lorsque les données ne sont pas conformes aux règles d’entreprise. Par exemple, un 0 est entré dans un champ qui attend une évaluation comprise entre 1 et 5.

La liaison et la validation de modèle se produisent avant l’exécution d’une action de contrôleur ou d’une méthode de gestionnaire Razor Pages. Pour les applications web, il incombe à l’application d’inspecter ModelState.IsValid et de réagir de façon appropriée. Les applications web affichent généralement de nouveau la page avec un message d’erreur, comme illustré dans l’exemple Razor Pages suivant :

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    _context.Movies.Add(Movie);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

Pour ASP.NET Core MVC avec des contrôleurs et des vues, l’exemple suivant montre comment vérifier ModelState.IsValid dans une action de contrôleur :

public async Task<IActionResult> Create(Movie movie)
{
    if (!ModelState.IsValid)
    {
        return View(movie);
    }

    _context.Movies.Add(movie);
    await _context.SaveChangesAsync();

    return RedirectToAction(nameof(Index));
}

Les contrôleurs d’API web ne sont pas obligés de vérifier s’ils ont l’attribut ModelState.IsValid[ApiController]. Dans ce cas, une réponse HTTP 400 automatique contenant les détails du problème est retournée si l’état du modèle n’est pas valide. Pour plus d’informations, consultez Réponses HTTP 400 automatiques.

Réexécuter la validation

La validation est automatique, mais vous souhaiterez peut-être la répéter manuellement. Par exemple, vous pourriez calculer une valeur pour une propriété, et souhaiter réexécuter la validation après avoir affecté la valeur calculée comme valeur de la propriété. Pour réexécuter la validation, appelez ModelStateDictionary.ClearValidationState pour effacer la validation spécifique au modèle en cours de validation, suivi de TryValidateModel:

public async Task<IActionResult> OnPostTryValidateAsync()
{
    var modifiedReleaseDate = DateTime.Now.Date;
    Movie.ReleaseDate = modifiedReleaseDate;

    ModelState.ClearValidationState(nameof(Movie));
    if (!TryValidateModel(Movie, nameof(Movie)))
    {
        return Page();
    }

    _context.Movies.Add(Movie);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

Attributs de validation

Les attributs de validation vous permettent de spécifier des règles de validation pour des propriétés de modèle. L’exemple suivant tiré de l’exemple d’application montre une classe de modèle annotée avec des attributs de validation. L’attribut [ClassicMovie] est un attribut de validation personnalisé et les autres sont prédéfinis. [ClassicMovieWithClientValidator] n’apparaît pas, cela montre une autre façon d’implémenter un attribut personnalisé.

public class Movie
{
    public int Id { get; set; }

    [Required]
    [StringLength(100)]
    public string Title { get; set; } = null!;

    [ClassicMovie(1960)]
    [DataType(DataType.Date)]
    [Display(Name = "Release Date")]
    public DateTime ReleaseDate { get; set; }

    [Required]
    [StringLength(1000)]
    public string Description { get; set; } = null!;

    [Range(0, 999.99)]
    public decimal Price { get; set; }

    public Genre Genre { get; set; }

    public bool Preorder { get; set; }
}

Attributs prédéfinis

Voici certains des attributs de validation prédéfinis :

  • [ValidateNever] : indique qu’une propriété ou un paramètre doit être exclu de la validation.
  • [CreditCard] : vérifie que la propriété a un format de carte de crédit. Nécessite des méthodes supplémentaires de validation jQuery.
  • [Compare]: vérifie que deux propriétés d’un modèle correspondent.
  • [EmailAddress]: vérifie que la propriété a un format d’e-mail.
  • [Phone]: vérifie que la propriété a un format de numéro de téléphone.
  • [Range]: vérifie que la valeur de propriété est comprise dans une plage spécifiée.
  • [RegularExpression]: vérifie que la valeur de propriété correspond à une expression régulière spécifiée.
  • [Required]: valide que le champ n’est pas Null. Pour plus d’informations sur le comportement de cet attribut, consultez Attribut [Remote][Required].
  • [StringLength]: vérifie qu’une valeur de propriété de chaîne ne dépasse pas une limite de longueur spécifiée.
  • [Url]: vérifie que la propriété a un format d’URL.
  • [Remote]: valide l’entrée sur le client en appelant une méthode d’action sur le serveur. Pour plus d’informations sur le comportement de cet attribut, consultez [Remote]Attribut.

Vous trouverez la liste complète des attributs de validation dans l’System.ComponentModel.DataAnnotations espace de noms.

Messages d’erreur

Les attributs de validation vous permettent de spécifier le message d’erreur à afficher pour l’entrée non valide. Par exemple :

[StringLength(8, ErrorMessage = "Name length can't be more than 8.")]

En interne, les attributs appellent String.Format avec un espace réservé pour le nom de champ et parfois d’autres espaces réservés. Par exemple :

[StringLength(8, ErrorMessage = "{0} length must be between {2} and {1}.", MinimumLength = 6)]

Appliqué à une propriété Name, le message d’erreur créé par le code précédent serait « Name length must be between 6 and 8 ».

Pour savoir quels paramètres sont passés à String.Format pour le message d’erreur d’un attribut particulier, consultez le code source de DataAnnotations.

Types de référence ne pouvant accepter la valeur Null et attribut [Required]

Par défaut, le système de validation traite les propriétés ou paramètres n’acceptant pas les valeurs Null comme s’ils avaient un attribut [Required(AllowEmptyStrings = true)]. En activant Nullable les contextes, MVC commence implicitement à valider les propriétés ou paramètres non Null des types non génériques ou de leurs paramètres comme s’ils avaient reçu l’attribut [Required(AllowEmptyStrings = true)]. Prenez le code suivant :

public class Person
{
    public string Name { get; set; }
}

Si l’application a été générée avec <Nullable>enable</Nullable>, une valeur manquante pour Name dans une publication JS ON ou un formulaire entraîne une erreur de validation. Utilisez un type de référence Null pour autoriser la spécification de valeurs Null ou manquantes pour la propriété Name :

public class Person
{
    public string? Name { get; set; }
}

Ce comportement peut être désactivé en configurant SuppressImplicitRequiredAttributeForNonNullableReferenceTypes dans Program.cs:

builder.Services.AddControllers(
    options => options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true);

Propriétés ne pouvant pas accepter la valeur Null sur les types génériques et l’attribut [Required]

Les propriétés ne pouvant pas accepter la valeur Null sur les types génériques doivent inclure l’attribut [Required] lorsque le type est requis. Dans le code suivant, TestRequired n’est pas obligatoire :

public class WeatherForecast<T>
{
    public string TestRequired { get; set; } = null!;
    public T? Inner { get; set; }
}

Dans le code suivant, TestRequired est explicitement marqué comme obligatoire :

using System.ComponentModel.DataAnnotations;

public class WeatherForecast<T>
{
    [Required]
    public string TestRequired { get; set; } = null!;
    public T? Inner { get; set; }
}

Validation de [Required] sur le serveur

Sur le serveur, une valeur obligatoire est considérée comme manquante si la propriété est Null. Un champ n’acceptant pas les valeurs Null est toujours valide, et le message d’erreur de l’attribut [Required] n’est jamais affiché.

Toutefois, la liaison de modèle pour une propriété n’acceptant pas les valeurs Null peut échouer, entraînant l’affichage d’un message d’erreur tel que The value '' is invalid. Pour spécifier un message d’erreur personnalisé pour la validation côté serveur des types n’acceptant pas les valeurs Null, vous disposez des options suivantes :

  • Rendre le champ Nullable (par exemple, decimal? au lieu de decimal). Les types valeur Null<T> sont traités comme des types Null standard.

  • Spécifier le message d’erreur par défaut devant être utilisé par la liaison de modèle, comme indiqué dans l’exemple suivant :

    builder.Services.AddRazorPages()
        .AddMvcOptions(options =>
        {
            options.MaxModelValidationErrors = 50;
            options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
                _ => "The field is required.");
        });
    
    builder.Services.AddSingleton
        <IValidationAttributeAdapterProvider, CustomValidationAttributeAdapterProvider>();
    

    Pour plus d’informations sur les erreurs de liaison de modèle pour lesquelles vous pouvez définir des messages par défaut, consultez DefaultModelBindingMessageProvider.

Validation de [Required] sur le client

Les chaînes et les types n’acceptant pas les valeurs Null sont gérés différemment sur le client et sur le serveur. Sur le client :

  • Une valeur est considérée comme présente uniquement si une entrée est tapée pour celle-ci. Par conséquent, la validation côté client gère les types n’acceptant pas les valeurs Null de la même façon que les types Nullable
  • Un espace blanc dans un champ de chaîne est considéré comme une entrée valide par la méthode required jQuery Validate. La validation côté serveur considère qu’un champ de chaîne obligatoire est non valide si seul un espace blanc est entré.

Comme indiqué précédemment, les types n’acceptant pas les valeurs Null sont traités comme s’ils avaient un attribut [Required(AllowEmptyStrings = true)]. Cela signifie que vous bénéficiez d’une validation côté client même si vous n’appliquez pas l’attribut [Required(AllowEmptyStrings = true)]. En revanche, si vous n’utilisez pas l’attribut, vous recevez un message d’erreur par défaut. Pour spécifier un message d’erreur personnalisé, utilisez l’attribut.

Attribut [Remote]

L’attribut [Remote] implémente la validation côté client qui nécessite l’appel d’une méthode sur le serveur pour déterminer si le champ d’entrée est valide. Par exemple, l’application peut avoir besoin de vérifier si un nom d’utilisateur est déjà en cours d’utilisation.

Pour implémenter la validation à distance

  1. Créez une méthode d’action devant être appelée par JavaScript. La méthode de validation jQuery remote attend une réponse JS ON :

    • true signifie que les données d’entrée sont valides.
    • false, undefined ou null signifie que l’entrée n’est pas valide. Affichez le message d’erreur par défaut.
    • Toute autre chaîne signifie que l’entrée n’est pas valide. Affichez la chaîne en tant que message d’erreur personnalisé.

    Voici un exemple de méthode d’action qui retourne un message d’erreur personnalisé :

    [AcceptVerbs("GET", "POST")]
    public IActionResult VerifyEmail(string email)
    {
        if (!_userService.VerifyEmail(email))
        {
            return Json($"Email {email} is already in use.");
        }
    
        return Json(true);
    }
    
  2. Dans la classe de modèle, annotez la propriété avec un attribut [Remote] qui pointe vers la méthode d’action de validation, comme indiqué dans l’exemple suivant :

    [Remote(action: "VerifyEmail", controller: "Users")]
    public string Email { get; set; } = null!;
    

Champs supplémentaires

La propriété AdditionalFields de l’attribut [Remote] vous permet de valider des combinaisons de champs relativement à des données présentes sur le serveur. Par exemple, si le modèle User a des propriétés FirstName et LastName, vous pouvez vérifier qu’aucun utilisateur existant n’a déjà cette paire de noms. L'exemple suivant montre comment utiliser AdditionalFields :

[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(LastName))]
[Display(Name = "First Name")]
public string FirstName { get; set; } = null!;

[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(FirstName))]
[Display(Name = "Last Name")]
public string LastName { get; set; } = null!;

AdditionalFields peut être défini explicitement sur les chaînes «Prénom» et «Nom», mais utiliser le nom de l’opérateur simplifie la refactorisation ultérieure. La méthode d’action pour cette validation doit accepter à la fois les arguments de firstName et lastName :

[AcceptVerbs("GET", "POST")]
public IActionResult VerifyName(string firstName, string lastName)
{
    if (!_userService.VerifyName(firstName, lastName))
    {
        return Json($"A user named {firstName} {lastName} already exists.");
    }

    return Json(true);
}

Quand l’utilisateur entre un nom ou un prénom, JavaScript effectue un appel à distance pour vérifier si cette paire de noms est déjà utilisée.

Pour valider deux champs supplémentaires ou plus, spécifiez-les sous la forme d’une liste délimitée par des virgules. Par exemple, pour ajouter une propriété MiddleName au modèle, définissez l’attribut [Remote] comme indiqué dans l’exemple suivant :

[Remote(action: "VerifyName", controller: "Users",
    AdditionalFields = nameof(FirstName) + "," + nameof(LastName))]
public string MiddleName { get; set; }

AdditionalFields, comme tous les arguments d’attribut, doit être une expression constante. Vous ne devez donc pas utiliser une chaîne interpolée ou appeler Join pour initialiser AdditionalFields.

Alternatives aux attributs prédéfinis

Si vous avez besoin d’une validation non fournie par les attributs prédéfinis, vous pouvez :

Attributs personnalisés

Pour les scénarios non gérés par les attributs de validation prédéfinis, vous pouvez créer des attributs de validation personnalisés. Créez une classe qui hérite de ValidationAttribute et substituez la méthode IsValid.

La méthode IsValid accepte un objet nommé value, qui est l’entrée à valider. Une surcharge accepte également un objet ValidationContext, qui fournit des informations supplémentaires telles que l’instance de modèle créée par la liaison de modèle.

L’exemple suivant vérifie que la date de sortie d’un film appartenant au genre Classic n’est pas ultérieure à une année spécifiée. L’attribut [ClassicMovie]:

  • Est exécuté uniquement sur le serveur.
  • Pour les films classiques, valide la date de publication :
public class ClassicMovieAttribute : ValidationAttribute
{
    public ClassicMovieAttribute(int year)
        => Year = year;

    public int Year { get; }

    public string GetErrorMessage() =>
        $"Classic movies must have a release year no later than {Year}.";

    protected override ValidationResult? IsValid(
        object? value, ValidationContext validationContext)
    {
        var movie = (Movie)validationContext.ObjectInstance;
        var releaseYear = ((DateTime)value!).Year;

        if (movie.Genre == Genre.Classic && releaseYear > Year)
        {
            return new ValidationResult(GetErrorMessage());
        }

        return ValidationResult.Success;
    }
}

La variable movie de l’exemple précédent représente un objet Movie qui contient les données de l’envoi du formulaire. Quand la validation échoue, un ValidationResult avec un message d’erreur est retourné.

IValidatableObject

L’exemple précédent fonctionne uniquement avec les types Movie. Une autre option de validation au niveau de la classe consiste à implémenter IValidatableObject dans la classe de modèle, comme indiqué dans l’exemple suivant :

public class ValidatableMovie : IValidatableObject
{
    private const int _classicYear = 1960;

    public int Id { get; set; }

    [Required]
    [StringLength(100)]
    public string Title { get; set; } = null!;

    [DataType(DataType.Date)]
    [Display(Name = "Release Date")]
    public DateTime ReleaseDate { get; set; }

    [Required]
    [StringLength(1000)]
    public string Description { get; set; } = null!;

    [Range(0, 999.99)]
    public decimal Price { get; set; }

    public Genre Genre { get; set; }

    public bool Preorder { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (Genre == Genre.Classic && ReleaseDate.Year > _classicYear)
        {
            yield return new ValidationResult(
                $"Classic movies must have a release year no later than {_classicYear}.",
                new[] { nameof(ReleaseDate) });
        }
    }
}

Validation du nœud de niveau supérieur

Les nœuds de niveau supérieur incluent les éléments suivants :

  • Paramètres d’action
  • Propriétés du contrôleur
  • Paramètres du gestionnaire de page
  • Propriétés du modèle de page

Les nœuds de niveau supérieur liés au modèle sont validés en plus de la validation des propriétés du modèle. Dans l’exemple suivant tiré de l’exemple d’application, la méthodeVerifyPhone utilise RegularExpressionAttribute pour valider le paramètre d’action phone :

[AcceptVerbs("GET", "POST")]
public IActionResult VerifyPhone(
    [RegularExpression(@"^\d{3}-\d{3}-\d{4}$")] string phone)
{
    if (!ModelState.IsValid)
    {
        return Json($"Phone {phone} has an invalid format. Format: ###-###-####");
    }

    return Json(true);
}

Les nœuds de niveau supérieur peuvent utiliser BindRequiredAttribute avec des attributs de validation. Dans l’exemple suivant de l’exemple d’application, la méthodeCheckAge spécifie que le paramètre age doit être lié à partir de la chaîne de requête au moment de l’envoi du formulaire :

[HttpPost]
public IActionResult CheckAge([BindRequired, FromQuery] int age)
{

Dans la page de vérification de l’âge (CheckAge.cshtml), il y a deux formulaires. Le premier formulaire envoie une valeur Age égale à 99 en tant que chaîne de requête : https://localhost:5001/Users/CheckAge?Age=99.

Quand un paramètre age au format approprié est envoyé à partir de la chaîne de requête, le formulaire est validé.

Le second formulaire de la page de vérification de l’âge envoie la valeur Age dans le corps de la requête, ce qui entraîne un échec de la validation. L’échec de la liaison est dû au fait que le paramètre age doit provenir d’une chaîne de requête.

Quantité maximale d’erreurs

La validation s’arrête quand le nombre maximal d’erreurs est atteint (200 par défaut). Vous pouvez configurer ce nombre avec le code suivant dans Program.cs :

builder.Services.AddRazorPages()
    .AddMvcOptions(options =>
    {
        options.MaxModelValidationErrors = 50;
        options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
            _ => "The field is required.");
    });

builder.Services.AddSingleton
    <IValidationAttributeAdapterProvider, CustomValidationAttributeAdapterProvider>();

Récursivité maximale

ValidationVisitor parcourt le graphe d’objet du modèle en cours de validation. Pour les modèles très profonds ou infiniment récursifs, la validation peut entraîner un dépassement de la capacité de la pile. MvcOptions.MaxValidationDepth fournit un moyen d’interrompre la validation de manière anticipée si la récursivité du visiteur dépasse une profondeur configurée. La valeur par défaut de MvcOptions.MaxValidationDepth est 32.

Court-circuit automatique

La validation est automatiquement court-circuitée (ignorée) si le graphe du modèle ne nécessite pas de validation. Les objets pour lesquels le runtime ignore la validation comprennent les collections de primitives (telles que byte[], string[], Dictionary<string, string>) et les graphes d’objets complexes qui n’ont pas de validateur.

Validation côté client

La validation côté client empêche l’envoi jusqu’à ce que le formulaire soit valide. Le bouton Submit exécute le code JavaScript qui envoie le formulaire ou qui affiche des messages d’erreur.

La validation côté client permet d’éviter un aller-retour inutile vers le serveur quand il existe des erreurs d’entrée sur un formulaire. Les références de script suivantes dans _Layout.cshtml et _ValidationScriptsPartial.cshtmlvalident du côté du client:

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.3/jquery.validate.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-unobtrusive/3.2.12/jquery.validate.unobtrusive.js"></script>

Le script jQuery Unobtrusive Validation est une bibliothèque frontale personnalisée de Microsoft qui s’appuie sur le plug-in bien connu jQuery Validate. Sans jQuery Unobtrusive Validation, vous devriez coder la même logique de validation à deux endroits : une fois dans les attributs de validation côté serveur sur les propriétés du modèle, puis à nouveau dans les scripts côté client. Au lieu de cela, les Tag Helpers et les helpers HTML utilisent les attributs de validation et les métadonnées de type des propriétés du modèle afin de restituer les attributs data- HTML 5 pour les éléments de formulaire nécessitant une validation. La validation non obstrustice jQuery analyse les attributs data- et transmet la logique à jQuery Validate, en « copiant » la logique de validation côté serveur vers le client. Vous pouvez afficher les erreurs de validation sur le client en utilisant des Tag Helpers, comme indiqué ici :

<div class="form-group">
    <label asp-for="Movie.ReleaseDate" class="control-label"></label>
    <input asp-for="Movie.ReleaseDate" class="form-control" />
    <span asp-validation-for="Movie.ReleaseDate" class="text-danger"></span>
</div>

Les assistants de balisage précédents restituent le code HTML suivant :

<div class="form-group">
    <label class="control-label" for="Movie_ReleaseDate">Release Date</label>
    <input class="form-control" type="date" data-val="true"
        data-val-required="The Release Date field is required."
        id="Movie_ReleaseDate" name="Movie.ReleaseDate" value="">
    <span class="text-danger field-validation-valid"
        data-valmsg-for="Movie.ReleaseDate" data-valmsg-replace="true"></span>
</div>

Notez que les attributs data- dans la sortie HTML correspondent aux attributs de validation pour la propriété Movie.ReleaseDate. L’attribut data-val-required contient un message d’erreur à afficher si l’utilisateur ne renseigne pas le champ correspondant à la date de sortie. La validation non obstructive jQuery transmet cette valeur à la méthode de validation jQuery requise, qui affiche alors ce message dans l’élément <span > qui l’accompagne.

La validation du type de données est basée sur le type .NET d’une propriété, sauf en cas de substitution par un attribut [DataType] . Les navigateurs ont leurs propres messages d’erreur par défaut, mais le package jQuery Validation Unobtrusive Validation peut remplacer ces messages. Les attributs [DataType]et les sous-classes comme [EmailAddress] permettent de spécifier le message d’erreur.

Validation non obstructive

Pour plus d’informations sur la validation non obstructive, consultez ce problème GitHub.

Ajouter une validation à des formulaires dynamiques

La validation jQuery non obstructive transmet la logique et les paramètres de validation à jQuery Validate lors du premier chargement de la page. Par conséquent, la validation ne fonctionne pas automatiquement sur les formulaires générés de manière dynamique. Pour activer la validation, vous devez faire en sorte que jQuery Validate analyse le formulaire dynamique immédiatement après l’avoir créé. Par exemple, le code suivant définit la validation côté client sur un formulaire ajouté par le biais d’AJAX.

$.get({
    url: "https://url/that/returns/a/form",
    dataType: "html",
    error: function(jqXHR, textStatus, errorThrown) {
        alert(textStatus + ": Couldn't add form. " + errorThrown);
    },
    success: function(newFormHTML) {
        var container = document.getElementById("form-container");
        container.insertAdjacentHTML("beforeend", newFormHTML);
        var forms = container.getElementsByTagName("form");
        var newForm = forms[forms.length - 1];
        $.validator.unobtrusive.parse(newForm);
    }
})

La méthode $.validator.unobtrusive.parse() accepte un sélecteur jQuery comme argument. Cette méthode indique à jQuery Unobtrusive Validation d’analyser les attributs data- des formulaires dans ce sélecteur. Les valeurs de ces attributs sont ensuite transmises au plug-in jQuery Validate.

Ajouter une validation à des contrôles dynamiques

La méthode $.validator.unobtrusive.parse() opère sur un formulaire entier, et non sur des contrôles individuels générés de manière dynamique tels que <input> et <select/>. Pour réanalyser le formulaire, supprimez les données de validation qui ont été ajoutées quand le formulaire a été analysé précédemment, comme illustré dans l’exemple suivant :

$.get({
    url: "https://url/that/returns/a/control",
    dataType: "html",
    error: function(jqXHR, textStatus, errorThrown) {
        alert(textStatus + ": Couldn't add control. " + errorThrown);
    },
    success: function(newInputHTML) {
        var form = document.getElementById("my-form");
        form.insertAdjacentHTML("beforeend", newInputHTML);
        $(form).removeData("validator")    // Added by jQuery Validation
               .removeData("unobtrusiveValidation");   // Added by jQuery Unobtrusive Validation
        $.validator.unobtrusive.parse(form);
    }
})

Validation personnalisée côté client

La validation personnalisée côté client s’effectue en générant des attributs HTML data- qui fonctionnent avec un adaptateur jQuery Validate personnalisé. L’exemple de code d’adaptateur suivant a été écrit pour les attributs [ClassicMovie] et [ClassicMovieWithClientValidator] qui ont été introduits plus haut dans cet article :

$.validator.addMethod('classicmovie', function (value, element, params) {
    var genre = $(params[0]).val(), year = params[1], date = new Date(value);

    // The Classic genre has a value of '0'.
    if (genre && genre.length > 0 && genre[0] === '0') {
        // The release date for a Classic is valid if it's no greater than the given year.
        return date.getUTCFullYear() <= year;
    }

    return true;
});

$.validator.unobtrusive.adapters.add('classicmovie', ['year'], function (options) {
    var element = $(options.form).find('select#Movie_Genre')[0];

    options.rules['classicmovie'] = [element, parseInt(options.params['year'])];
    options.messages['classicmovie'] = options.message;
});

Pour plus d’informations sur la façon d’écrire des adaptateurs, consultez la documentation de jQuery Validate.

L’utilisation d’un adaptateur pour un champ donné est déclenchée par des attributs data- qui :

  • Marquent le champ comme étant soumis à validation (data-val="true").
  • Identifient un nom de règle de validation et un texte de message d’erreur (par exemple, data-val-rulename="Error message.").
  • Fournissent les éventuels paramètres supplémentaires dont le validateur a besoin (par exemple, data-val-rulename-param1="value").

L’exemple suivant montre les attributs data- pour l’attribut de l’exemple d’application ClassicMovie :

<input class="form-control" type="date"
    data-val="true"
    data-val-classicmovie="Classic movies must have a release year no later than 1960."
    data-val-classicmovie-year="1960"
    data-val-required="The Release Date field is required."
    id="Movie_ReleaseDate" name="Movie.ReleaseDate" value="">

Comme mentionné plus haut, les Tag Helpers et les helpers HTML utilisent les informations des attributs de validation pour restituer les attributs data-. Il existe deux options pour l’écriture de code qui entraîne la création d’attributs HTML data- personnalisés :

  • Créez une classe qui dérive de AttributeAdapterBase<TAttribute> et une classe qui implémente IValidationAttributeAdapterProvider, et inscrivez votre attribut et son adaptateur dans l’injection de dépendances. Cette méthode respecte le principe de responsabilité unique, dans la mesure où le code de validation lié au serveur et celui lié au client se trouvent dans des classes distinctes. Il existe un autre avantage : l’adaptateur étant inscrit dans l’injection de dépendances, les autres services dans l’injection de dépendances lui sont accessibles si nécessaire.
  • Implémentez IClientModelValidator dans votre classe ValidationAttribute. Cette méthode peut être appropriée si l’attribut n’effectue aucune validation côté serveur et n’a besoin d’aucun service à partir de l’injection de dépendances.

AttributeAdapter pour la validation côté client

Cette méthode de rendu des attributs data- en HTML est utilisée par l’attribut ClassicMovie dans l’exemple d’application. Pour ajouter la validation côté client à l’aide de cette méthode

  1. Créez une classe d’adaptateurs d’attributs pour l’attribut de validation personnalisé. Dérivez la classe de AttributeAdapterBase<TAttribute>. Créez une méthode AddValidation qui ajoute des attributs data- à la sortie restituée, comme illustré dans cet exemple :

    public class ClassicMovieAttributeAdapter : AttributeAdapterBase<ClassicMovieAttribute>
    {
        public ClassicMovieAttributeAdapter(
            ClassicMovieAttribute attribute, IStringLocalizer? stringLocalizer)
            : base(attribute, stringLocalizer)
        {
    
        }
    
        public override void AddValidation(ClientModelValidationContext context)
        {
            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-classicmovie", GetErrorMessage(context));
    
            var year = Attribute.Year.ToString(CultureInfo.InvariantCulture);
            MergeAttribute(context.Attributes, "data-val-classicmovie-year", year);
        }
    
        public override string GetErrorMessage(ModelValidationContextBase validationContext)
            => Attribute.GetErrorMessage();
    }
    
  2. Créez une classe de fournisseurs d’adaptateurs qui implémente IValidationAttributeAdapterProvider. Dans la méthode GetAttributeAdapter, passez l’attribut personnalisé au constructeur de l’adaptateur, comme illustré dans cet exemple :

    public class CustomValidationAttributeAdapterProvider : IValidationAttributeAdapterProvider
    {
        private readonly IValidationAttributeAdapterProvider baseProvider =
            new ValidationAttributeAdapterProvider();
    
        public IAttributeAdapter? GetAttributeAdapter(
            ValidationAttribute attribute, IStringLocalizer? stringLocalizer)
        {
            if (attribute is ClassicMovieAttribute classicMovieAttribute)
            {
                return new ClassicMovieAttributeAdapter(classicMovieAttribute, stringLocalizer);
            }
    
            return baseProvider.GetAttributeAdapter(attribute, stringLocalizer);
        }
    }
    
  3. Inscrivez le fournisseur d’adaptateurs auprès de l’injection de dépendances dans Program.cs :

    builder.Services.AddRazorPages()
        .AddMvcOptions(options =>
        {
            options.MaxModelValidationErrors = 50;
            options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
                _ => "The field is required.");
        });
    
    builder.Services.AddSingleton
        <IValidationAttributeAdapterProvider, CustomValidationAttributeAdapterProvider>();
    

IClientModelValidator pour la validation côté client

Cette méthode de rendu des attributs data- en HTML est utilisée par l’attribut ClassicMovieWithClientValidator dans l’exemple d’application. Pour ajouter la validation côté client à l’aide de cette méthode

  • Dans l’attribut de validation personnalisé, implémentez l’interface IClientModelValidator et créez une méthode AddValidation. Dans la méthode AddValidation, ajoutez des attributs data- pour la validation, comme indiqué dans l’exemple suivant :

    public class ClassicMovieWithClientValidatorAttribute :
        ValidationAttribute, IClientModelValidator
    {
        public ClassicMovieWithClientValidatorAttribute(int year)
            => Year = year;
    
        public int Year { get; }
    
        public void AddValidation(ClientModelValidationContext context)
        {
            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-classicmovie", GetErrorMessage());
    
            var year = Year.ToString(CultureInfo.InvariantCulture);
            MergeAttribute(context.Attributes, "data-val-classicmovie-year", year);
        }
    
        public string GetErrorMessage() =>
            $"Classic movies must have a release year no later than {Year}.";
    
        protected override ValidationResult? IsValid(
            object? value, ValidationContext validationContext)
        {
            var movie = (Movie)validationContext.ObjectInstance;
            var releaseYear = ((DateTime)value!).Year;
    
            if (movie.Genre == Genre.Classic && releaseYear > Year)
            {
                return new ValidationResult(GetErrorMessage());
            }
    
            return ValidationResult.Success;
        }
    
        private static bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
        {
            if (attributes.ContainsKey(key))
            {
                return false;
            }
    
            attributes.Add(key, value);
            return true;
        }
    }
    

Désactiver la validation côté client

Le code suivant désactive la validation côté client dans Razor Pages :

builder.Services.AddRazorPages()
    .AddViewOptions(options =>
    {
        options.HtmlHelperOptions.ClientValidationEnabled = false;
    });

Autres options pour désactiver la validation côté client :

  • Commentez la référence à _ValidationScriptsPartial dans tous les fichiers .cshtml.
  • Supprimez le contenu du fichier Pages\Shared_ValidationScriptsPartial.cshtml .

L’approche précédente n’empêche pas la validation côté client de la bibliothèque de classes IdentityRazor ASP.NET Core. Pour plus d’informations, consultez Génération de modèles automatiqueIdentity dans les projets ASP.NET Core.

Ressources supplémentaires

Cet article explique comment valider l’entrée utilisateur dans ASP.NET Core MVC ou l’application Razor Pages.

Affichez ou téléchargez un exemple de code (procédure de téléchargement).

État du modèle

L’état du modèle représente les erreurs qui proviennent de deux sous-systèmes : liaison de modèle et validation de modèle. Les erreurs qui proviennent de la liaison de modèle sont généralement des erreurs de conversion de données. Par exemple, un « x » est entré dans un champ de nombres entiers. La validation du modèle se produit après la liaison de modèle et signale des erreurs lorsque les données ne sont pas conformes aux règles d’entreprise. Par exemple, un 0 est entré dans un champ qui attend une évaluation comprise entre 1 et 5.

La liaison et la validation de modèle se produisent avant l’exécution d’une action de contrôleur ou d’une méthode de gestionnaire Razor Pages. Pour les applications web, il incombe à l’application d’inspecter ModelState.IsValid et de réagir de façon appropriée. En règle générale, les applications web réaffichent la page avec un message d’erreur :

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    _context.Movies.Add(Movie);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

Les contrôleurs d’API web ne sont pas obligés de vérifier s’ils ont l’attribut ModelState.IsValid[ApiController]. Dans ce cas, une réponse HTTP 400 automatique contenant les détails du problème est retournée si l’état du modèle n’est pas valide. Pour plus d’informations, consultez Réponses HTTP 400 automatiques.

Réexécuter la validation

La validation est automatique, mais vous souhaiterez peut-être la répéter manuellement. Par exemple, vous pourriez calculer une valeur pour une propriété, et souhaiter réexécuter la validation après avoir affecté la valeur calculée comme valeur de la propriété. Pour réexécuter la validation, appelez ModelStateDictionary.ClearValidationState pour effacer la validation spécifique au modèle en cours de validation, suivi de TryValidateModel:

public async Task<IActionResult> OnPostTryValidateAsync()
{
    var modifiedReleaseDate = DateTime.Now.Date;
    Movie.ReleaseDate = modifiedReleaseDate;

    ModelState.ClearValidationState(nameof(Movie));
    if (!TryValidateModel(Movie, nameof(Movie)))
    {
        return Page();
    }

    _context.Movies.Add(Movie);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

Attributs de validation

Les attributs de validation vous permettent de spécifier des règles de validation pour des propriétés de modèle. L’exemple suivant tiré de l’exemple d’application montre une classe de modèle annotée avec des attributs de validation. L’attribut [ClassicMovie] est un attribut de validation personnalisé et les autres sont prédéfinis. [ClassicMovieWithClientValidator] n’apparaît pas, cela montre une autre façon d’implémenter un attribut personnalisé.

public class Movie
{
    public int Id { get; set; }

    [Required]
    [StringLength(100)]
    public string Title { get; set; }

    [ClassicMovie(1960)]
    [DataType(DataType.Date)]
    [Display(Name = "Release Date")]
    public DateTime ReleaseDate { get; set; }

    [Required]
    [StringLength(1000)]
    public string Description { get; set; }

    [Range(0, 999.99)]
    public decimal Price { get; set; }

    public Genre Genre { get; set; }

    public bool Preorder { get; set; }
}

Attributs prédéfinis

Voici certains des attributs de validation prédéfinis :

  • [ValidateNever] : indique qu’une propriété ou un paramètre doit être exclu de la validation.
  • [CreditCard] : vérifie que la propriété a un format de carte de crédit. Nécessite des méthodes supplémentaires de validation jQuery.
  • [Compare]: vérifie que deux propriétés d’un modèle correspondent.
  • [EmailAddress]: vérifie que la propriété a un format d’e-mail.
  • [Phone]: vérifie que la propriété a un format de numéro de téléphone.
  • [Range]: vérifie que la valeur de propriété est comprise dans une plage spécifiée.
  • [RegularExpression]: vérifie que la valeur de propriété correspond à une expression régulière spécifiée.
  • [Required]: valide que le champ n’est pas Null. Pour plus d’informations sur le comportement de cet attribut, consultez Attribut [Remote][Required].
  • [StringLength]: vérifie qu’une valeur de propriété de chaîne ne dépasse pas une limite de longueur spécifiée.
  • [Url]: vérifie que la propriété a un format d’URL.
  • [Remote]: valide l’entrée sur le client en appelant une méthode d’action sur le serveur. Pour plus d’informations sur le comportement de cet attribut, consultez [Remote]Attribut.

Vous trouverez la liste complète des attributs de validation dans l’System.ComponentModel.DataAnnotations espace de noms.

Messages d’erreur

Les attributs de validation vous permettent de spécifier le message d’erreur à afficher pour l’entrée non valide. Par exemple :

[StringLength(8, ErrorMessage = "Name length can't be more than 8.")]

En interne, les attributs appellent String.Format avec un espace réservé pour le nom de champ et parfois d’autres espaces réservés. Par exemple :

[StringLength(8, ErrorMessage = "{0} length must be between {2} and {1}.", MinimumLength = 6)]

Appliqué à une propriété Name, le message d’erreur créé par le code précédent serait « Name length must be between 6 and 8 ».

Pour savoir quels paramètres sont passés à String.Format pour le message d’erreur d’un attribut particulier, consultez le code source de DataAnnotations.

Types de référence non Null et attribut [Required]

Par défaut, le système de validation traite les propriétés ou paramètres n’acceptant pas les valeurs Null comme s’ils avaient un attribut [Required(AllowEmptyStrings = true)]. En activant Nullable les contextes, MVC commence implicitement à valider les propriétés ou paramètres non null comme s’ils avaient l’attribut [Required(AllowEmptyStrings = true)] . Prenez le code suivant :

public class Person
{
    public string Name { get; set; }
}

Si l’application a été générée avec <Nullable>enable</Nullable>, une valeur manquante pour Name dans une publication JS ON ou un formulaire entraîne une erreur de validation. Utilisez un type de référence Null pour autoriser la spécification de valeurs Null ou manquantes pour la propriété Name :

public class Person
{
    public string? Name { get; set; }
}

Ce comportement peut être désactivé en configurant SuppressImplicitRequiredAttributeForNonNullableReferenceTypes dans Startup.ConfigureServices:

services.AddControllers(options => options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true);

Validation de [Required] sur le serveur

Sur le serveur, une valeur obligatoire est considérée comme manquante si la propriété est Null. Un champ n’acceptant pas les valeurs Null est toujours valide, et le message d’erreur de l’attribut [Required] n’est jamais affiché.

Toutefois, la liaison de modèle pour une propriété n’acceptant pas les valeurs Null peut échouer, entraînant l’affichage d’un message d’erreur tel que The value '' is invalid. Pour spécifier un message d’erreur personnalisé pour la validation côté serveur des types n’acceptant pas les valeurs Null, vous disposez des options suivantes :

  • Rendre le champ Nullable (par exemple, decimal? au lieu de decimal). Les types valeur Null<T> sont traités comme des types Null standard.

  • Spécifier le message d’erreur par défaut devant être utilisé par la liaison de modèle, comme indiqué dans l’exemple suivant :

    services.AddRazorPages()
        .AddMvcOptions(options =>
        {
            options.MaxModelValidationErrors = 50;
            options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
                _ => "The field is required.");
        });
    
    services.AddSingleton<IValidationAttributeAdapterProvider,
        CustomValidationAttributeAdapterProvider>();
    

    Pour plus d’informations sur les erreurs de liaison de modèle pour lesquelles vous pouvez définir des messages par défaut, consultez DefaultModelBindingMessageProvider.

Validation de [Required] sur le client

Les chaînes et les types n’acceptant pas les valeurs Null sont gérés différemment sur le client et sur le serveur. Sur le client :

  • Une valeur est considérée comme présente uniquement si une entrée est tapée pour celle-ci. Par conséquent, la validation côté client gère les types n’acceptant pas les valeurs Null de la même façon que les types Nullable
  • Un espace blanc dans un champ de chaîne est considéré comme une entrée valide par la méthode required jQuery Validate. La validation côté serveur considère qu’un champ de chaîne obligatoire est non valide si seul un espace blanc est entré.

Comme indiqué précédemment, les types n’acceptant pas les valeurs Null sont traités comme s’ils avaient un attribut [Required(AllowEmptyStrings = true)]. Cela signifie que vous bénéficiez d’une validation côté client même si vous n’appliquez pas l’attribut [Required(AllowEmptyStrings = true)]. En revanche, si vous n’utilisez pas l’attribut, vous recevez un message d’erreur par défaut. Pour spécifier un message d’erreur personnalisé, utilisez l’attribut.

Attribut [Remote]

L’attribut [Remote] implémente la validation côté client qui nécessite l’appel d’une méthode sur le serveur pour déterminer si le champ d’entrée est valide. Par exemple, l’application peut avoir besoin de vérifier si un nom d’utilisateur est déjà en cours d’utilisation.

Pour implémenter la validation à distance

  1. Créez une méthode d’action devant être appelée par JavaScript. La méthode de validation jQuery remote attend une réponse JS ON :

    • true signifie que les données d’entrée sont valides.
    • false, undefined ou null signifie que l’entrée n’est pas valide. Affichez le message d’erreur par défaut.
    • Toute autre chaîne signifie que l’entrée n’est pas valide. Affichez la chaîne en tant que message d’erreur personnalisé.

    Voici un exemple de méthode d’action qui retourne un message d’erreur personnalisé :

    [AcceptVerbs("GET", "POST")]
    public IActionResult VerifyEmail(string email)
    {
        if (!_userService.VerifyEmail(email))
        {
            return Json($"Email {email} is already in use.");
        }
    
        return Json(true);
    }
    
  2. Dans la classe de modèle, annotez la propriété avec un attribut [Remote] qui pointe vers la méthode d’action de validation, comme indiqué dans l’exemple suivant :

    [Remote(action: "VerifyEmail", controller: "Users")]
    public string Email { get; set; }
    

Champs supplémentaires

La propriété AdditionalFields de l’attribut [Remote] vous permet de valider des combinaisons de champs relativement à des données présentes sur le serveur. Par exemple, si le modèle User a des propriétés FirstName et LastName, vous pouvez vérifier qu’aucun utilisateur existant n’a déjà cette paire de noms. L'exemple suivant montre comment utiliser AdditionalFields :

[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(LastName))]
[Display(Name = "First Name")]
public string FirstName { get; set; }

[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(FirstName))]
[Display(Name = "Last Name")]
public string LastName { get; set; }

AdditionalFields peut être défini explicitement sur les chaînes «Prénom» et «Nom», mais utiliser le nom de l’opérateur simplifie la refactorisation ultérieure. La méthode d’action pour cette validation doit accepter à la fois les arguments de firstName et lastName :

[AcceptVerbs("GET", "POST")]
public IActionResult VerifyName(string firstName, string lastName)
{
    if (!_userService.VerifyName(firstName, lastName))
    {
        return Json($"A user named {firstName} {lastName} already exists.");
    }

    return Json(true);
}

Quand l’utilisateur entre un nom ou un prénom, JavaScript effectue un appel à distance pour vérifier si cette paire de noms est déjà utilisée.

Pour valider deux champs supplémentaires ou plus, spécifiez-les sous la forme d’une liste délimitée par des virgules. Par exemple, pour ajouter une propriété MiddleName au modèle, définissez l’attribut [Remote] comme indiqué dans l’exemple suivant :

[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(FirstName) + "," + nameof(LastName))]
public string MiddleName { get; set; }

AdditionalFields, comme tous les arguments d’attribut, doit être une expression constante. Vous ne devez donc pas utiliser une chaîne interpolée ou appeler Join pour initialiser AdditionalFields.

Alternatives aux attributs prédéfinis

Si vous avez besoin d’une validation non fournie par les attributs prédéfinis, vous pouvez :

Attributs personnalisés

Pour les scénarios non gérés par les attributs de validation prédéfinis, vous pouvez créer des attributs de validation personnalisés. Créez une classe qui hérite de ValidationAttribute et substituez la méthode IsValid.

La méthode IsValid accepte un objet nommé value, qui est l’entrée à valider. Une surcharge accepte également un objet ValidationContext, qui fournit des informations supplémentaires telles que l’instance de modèle créée par la liaison de modèle.

L’exemple suivant vérifie que la date de sortie d’un film appartenant au genre Classic n’est pas ultérieure à une année spécifiée. L’attribut [ClassicMovie]:

  • Est exécuté uniquement sur le serveur.
  • Pour les films classiques, valide la date de publication :
public class ClassicMovieAttribute : ValidationAttribute
{
    public ClassicMovieAttribute(int year)
    {
        Year = year;
    }

    public int Year { get; }

    public string GetErrorMessage() =>
        $"Classic movies must have a release year no later than {Year}.";

    protected override ValidationResult IsValid(object value,
        ValidationContext validationContext)
    {
        var movie = (Movie)validationContext.ObjectInstance;
        var releaseYear = ((DateTime)value).Year;

        if (movie.Genre == Genre.Classic && releaseYear > Year)
        {
            return new ValidationResult(GetErrorMessage());
        }

        return ValidationResult.Success;
    }
}

La variable movie de l’exemple précédent représente un objet Movie qui contient les données de l’envoi du formulaire. Quand la validation échoue, un ValidationResult avec un message d’erreur est retourné.

IValidatableObject

L’exemple précédent fonctionne uniquement avec les types Movie. Une autre option de validation au niveau de la classe consiste à implémenter IValidatableObject dans la classe de modèle, comme indiqué dans l’exemple suivant :

public class ValidatableMovie : IValidatableObject
{
    private const int _classicYear = 1960;

    public int Id { get; set; }

    [Required]
    [StringLength(100)]
    public string Title { get; set; }

    [DataType(DataType.Date)]
    [Display(Name = "Release Date")]
    public DateTime ReleaseDate { get; set; }

    [Required]
    [StringLength(1000)]
    public string Description { get; set; }

    [Range(0, 999.99)]
    public decimal Price { get; set; }

    public Genre Genre { get; set; }

    public bool Preorder { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (Genre == Genre.Classic && ReleaseDate.Year > _classicYear)
        {
            yield return new ValidationResult(
                $"Classic movies must have a release year no later than {_classicYear}.",
                new[] { nameof(ReleaseDate) });
        }
    }
}

Validation du nœud de niveau supérieur

Les nœuds de niveau supérieur incluent les éléments suivants :

  • Paramètres d’action
  • Propriétés du contrôleur
  • Paramètres du gestionnaire de page
  • Propriétés du modèle de page

Les nœuds de niveau supérieur liés au modèle sont validés en plus de la validation des propriétés du modèle. Dans l’exemple suivant tiré de l’exemple d’application, la méthodeVerifyPhone utilise RegularExpressionAttribute pour valider le paramètre d’action phone :

[AcceptVerbs("GET", "POST")]
public IActionResult VerifyPhone(
    [RegularExpression(@"^\d{3}-\d{3}-\d{4}$")] string phone)
{
    if (!ModelState.IsValid)
    {
        return Json($"Phone {phone} has an invalid format. Format: ###-###-####");
    }

    return Json(true);
}

Les nœuds de niveau supérieur peuvent utiliser BindRequiredAttribute avec des attributs de validation. Dans l’exemple suivant de l’exemple d’application, la méthodeCheckAge spécifie que le paramètre age doit être lié à partir de la chaîne de requête au moment de l’envoi du formulaire :

[HttpPost]
public IActionResult CheckAge([BindRequired, FromQuery] int age)
{

Dans la page de vérification de l’âge (CheckAge.cshtml), il y a deux formulaires. Le premier formulaire envoie une valeur Age égale à 99 en tant que chaîne de requête : https://localhost:5001/Users/CheckAge?Age=99.

Quand un paramètre age au format approprié est envoyé à partir de la chaîne de requête, le formulaire est validé.

Le second formulaire de la page de vérification de l’âge envoie la valeur Age dans le corps de la requête, ce qui entraîne un échec de la validation. L’échec de la liaison est dû au fait que le paramètre age doit provenir d’une chaîne de requête.

Quantité maximale d’erreurs

La validation s’arrête quand le nombre maximal d’erreurs est atteint (200 par défaut). Vous pouvez configurer ce nombre avec le code suivant dans Startup.ConfigureServices :

services.AddRazorPages()
    .AddMvcOptions(options =>
    {
        options.MaxModelValidationErrors = 50;
        options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
            _ => "The field is required.");
    });

services.AddSingleton<IValidationAttributeAdapterProvider,
    CustomValidationAttributeAdapterProvider>();

Récursivité maximale

ValidationVisitor parcourt le graphe d’objet du modèle en cours de validation. Pour les modèles très profonds ou infiniment récursifs, la validation peut entraîner un dépassement de la capacité de la pile. MvcOptions.MaxValidationDepth fournit un moyen d’interrompre la validation de manière anticipée si la récursivité du visiteur dépasse une profondeur configurée. La valeur par défaut de MvcOptions.MaxValidationDepth est 32.

Court-circuit automatique

La validation est automatiquement court-circuitée (ignorée) si le graphe du modèle ne nécessite pas de validation. Les objets pour lesquels le runtime ignore la validation comprennent les collections de primitives (telles que byte[], string[], Dictionary<string, string>) et les graphes d’objets complexes qui n’ont pas de validateur.

Validation côté client

La validation côté client empêche l’envoi jusqu’à ce que le formulaire soit valide. Le bouton Submit exécute le code JavaScript qui envoie le formulaire ou qui affiche des messages d’erreur.

La validation côté client permet d’éviter un aller-retour inutile vers le serveur quand il existe des erreurs d’entrée sur un formulaire. Les références de script suivantes dans _Layout.cshtml et _ValidationScriptsPartial.cshtmlvalident du côté du client:

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.1/jquery.validate.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-unobtrusive/3.2.11/jquery.validate.unobtrusive.js"></script>

Le script jQuery Unobtrusive Validation est une bibliothèque frontale personnalisée de Microsoft qui s’appuie sur le plug-in bien connu jQuery Validate. Sans jQuery Unobtrusive Validation, vous devriez coder la même logique de validation à deux endroits : une fois dans les attributs de validation côté serveur sur les propriétés du modèle, puis à nouveau dans les scripts côté client. Au lieu de cela, les Tag Helpers et les helpers HTML utilisent les attributs de validation et les métadonnées de type des propriétés du modèle afin de restituer les attributs data- HTML 5 pour les éléments de formulaire nécessitant une validation. La validation non obstrustice jQuery analyse les attributs data- et transmet la logique à jQuery Validate, en « copiant » la logique de validation côté serveur vers le client. Vous pouvez afficher les erreurs de validation sur le client en utilisant des Tag Helpers, comme indiqué ici :

<div class="form-group">
    <label asp-for="Movie.ReleaseDate" class="control-label"></label>
    <input asp-for="Movie.ReleaseDate" class="form-control" />
    <span asp-validation-for="Movie.ReleaseDate" class="text-danger"></span>
</div>

Les assistants de balisage précédents restituent le code HTML suivant :

<div class="form-group">
    <label class="control-label" for="Movie_ReleaseDate">Release Date</label>
    <input class="form-control" type="date" data-val="true"
        data-val-required="The Release Date field is required."
        id="Movie_ReleaseDate" name="Movie.ReleaseDate" value="">
    <span class="text-danger field-validation-valid"
        data-valmsg-for="Movie.ReleaseDate" data-valmsg-replace="true"></span>
</div>

Notez que les attributs data- dans la sortie HTML correspondent aux attributs de validation pour la propriété Movie.ReleaseDate. L’attribut data-val-required contient un message d’erreur à afficher si l’utilisateur ne renseigne pas le champ correspondant à la date de sortie. La validation non obstructive jQuery transmet cette valeur à la méthode de validation jQuery requise, qui affiche alors ce message dans l’élément <span > qui l’accompagne.

La validation du type de données est basée sur le type .NET d’une propriété, sauf en cas de substitution par un attribut [DataType] . Les navigateurs ont leurs propres messages d’erreur par défaut, mais le package jQuery Validation Unobtrusive Validation peut remplacer ces messages. Les attributs [DataType]et les sous-classes comme [EmailAddress] permettent de spécifier le message d’erreur.

Validation non obstructive

Pour plus d’informations sur la validation non obstructive, consultez ce problème GitHub.

Ajouter une validation à des formulaires dynamiques

La validation jQuery non obstructive transmet la logique et les paramètres de validation à jQuery Validate lors du premier chargement de la page. Par conséquent, la validation ne fonctionne pas automatiquement sur les formulaires générés de manière dynamique. Pour activer la validation, vous devez faire en sorte que jQuery Validate analyse le formulaire dynamique immédiatement après l’avoir créé. Par exemple, le code suivant définit la validation côté client sur un formulaire ajouté par le biais d’AJAX.

$.get({
    url: "https://url/that/returns/a/form",
    dataType: "html",
    error: function(jqXHR, textStatus, errorThrown) {
        alert(textStatus + ": Couldn't add form. " + errorThrown);
    },
    success: function(newFormHTML) {
        var container = document.getElementById("form-container");
        container.insertAdjacentHTML("beforeend", newFormHTML);
        var forms = container.getElementsByTagName("form");
        var newForm = forms[forms.length - 1];
        $.validator.unobtrusive.parse(newForm);
    }
})

La méthode $.validator.unobtrusive.parse() accepte un sélecteur jQuery comme argument. Cette méthode indique à jQuery Unobtrusive Validation d’analyser les attributs data- des formulaires dans ce sélecteur. Les valeurs de ces attributs sont ensuite transmises au plug-in jQuery Validate.

Ajouter une validation à des contrôles dynamiques

La méthode $.validator.unobtrusive.parse() opère sur un formulaire entier, et non sur des contrôles individuels générés de manière dynamique tels que <input> et <select/>. Pour réanalyser le formulaire, supprimez les données de validation qui ont été ajoutées quand le formulaire a été analysé précédemment, comme illustré dans l’exemple suivant :

$.get({
    url: "https://url/that/returns/a/control",
    dataType: "html",
    error: function(jqXHR, textStatus, errorThrown) {
        alert(textStatus + ": Couldn't add control. " + errorThrown);
    },
    success: function(newInputHTML) {
        var form = document.getElementById("my-form");
        form.insertAdjacentHTML("beforeend", newInputHTML);
        $(form).removeData("validator")    // Added by jQuery Validation
               .removeData("unobtrusiveValidation");   // Added by jQuery Unobtrusive Validation
        $.validator.unobtrusive.parse(form);
    }
})

Validation personnalisée côté client

La validation personnalisée côté client s’effectue en générant des attributs HTML data- qui fonctionnent avec un adaptateur jQuery Validate personnalisé. L’exemple de code d’adaptateur suivant a été écrit pour les attributs [ClassicMovie] et [ClassicMovieWithClientValidator] qui ont été introduits plus haut dans cet article :

$.validator.addMethod('classicmovie', function (value, element, params) {
    var genre = $(params[0]).val(), year = params[1], date = new Date(value);

    // The Classic genre has a value of '0'.
    if (genre && genre.length > 0 && genre[0] === '0') {
        // The release date for a Classic is valid if it's no greater than the given year.
        return date.getUTCFullYear() <= year;
    }

    return true;
});

$.validator.unobtrusive.adapters.add('classicmovie', ['year'], function (options) {
    var element = $(options.form).find('select#Movie_Genre')[0];

    options.rules['classicmovie'] = [element, parseInt(options.params['year'])];
    options.messages['classicmovie'] = options.message;
});

Pour plus d’informations sur la façon d’écrire des adaptateurs, consultez la documentation de jQuery Validate.

L’utilisation d’un adaptateur pour un champ donné est déclenchée par des attributs data- qui :

  • Marquent le champ comme étant soumis à validation (data-val="true").
  • Identifient un nom de règle de validation et un texte de message d’erreur (par exemple, data-val-rulename="Error message.").
  • Fournissent les éventuels paramètres supplémentaires dont le validateur a besoin (par exemple, data-val-rulename-param1="value").

L’exemple suivant montre les attributs data- pour l’attribut de l’exemple d’application ClassicMovie :

<input class="form-control" type="date"
    data-val="true"
    data-val-classicmovie="Classic movies must have a release year no later than 1960."
    data-val-classicmovie-year="1960"
    data-val-required="The Release Date field is required."
    id="Movie_ReleaseDate" name="Movie.ReleaseDate" value="">

Comme mentionné plus haut, les Tag Helpers et les helpers HTML utilisent les informations des attributs de validation pour restituer les attributs data-. Il existe deux options pour l’écriture de code qui entraîne la création d’attributs HTML data- personnalisés :

  • Créez une classe qui dérive de AttributeAdapterBase<TAttribute> et une classe qui implémente IValidationAttributeAdapterProvider, et inscrivez votre attribut et son adaptateur dans l’injection de dépendances. Cette méthode respecte le principe de responsabilité unique, dans la mesure où le code de validation lié au serveur et celui lié au client se trouvent dans des classes distinctes. Il existe un autre avantage : l’adaptateur étant inscrit dans l’injection de dépendances, les autres services dans l’injection de dépendances lui sont accessibles si nécessaire.
  • Implémentez IClientModelValidator dans votre classe ValidationAttribute. Cette méthode peut être appropriée si l’attribut n’effectue aucune validation côté serveur et n’a besoin d’aucun service à partir de l’injection de dépendances.

AttributeAdapter pour la validation côté client

Cette méthode de rendu des attributs data- en HTML est utilisée par l’attribut ClassicMovie dans l’exemple d’application. Pour ajouter la validation côté client à l’aide de cette méthode

  1. Créez une classe d’adaptateurs d’attributs pour l’attribut de validation personnalisé. Dérivez la classe de AttributeAdapterBase<TAttribute>. Créez une méthode AddValidation qui ajoute des attributs data- à la sortie restituée, comme illustré dans cet exemple :

    public class ClassicMovieAttributeAdapter : AttributeAdapterBase<ClassicMovieAttribute>
    {
        public ClassicMovieAttributeAdapter(ClassicMovieAttribute attribute,
            IStringLocalizer stringLocalizer)
            : base(attribute, stringLocalizer)
        {
    
        }
    
        public override void AddValidation(ClientModelValidationContext context)
        {
            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-classicmovie", GetErrorMessage(context));
    
            var year = Attribute.Year.ToString(CultureInfo.InvariantCulture);
            MergeAttribute(context.Attributes, "data-val-classicmovie-year", year);
        }
    
        public override string GetErrorMessage(ModelValidationContextBase validationContext) =>
            Attribute.GetErrorMessage();
    }
    
  2. Créez une classe de fournisseurs d’adaptateurs qui implémente IValidationAttributeAdapterProvider. Dans la méthode GetAttributeAdapter, passez l’attribut personnalisé au constructeur de l’adaptateur, comme illustré dans cet exemple :

    public class CustomValidationAttributeAdapterProvider : IValidationAttributeAdapterProvider
    {
        private readonly IValidationAttributeAdapterProvider baseProvider =
            new ValidationAttributeAdapterProvider();
    
        public IAttributeAdapter GetAttributeAdapter(ValidationAttribute attribute,
            IStringLocalizer stringLocalizer)
        {
            if (attribute is ClassicMovieAttribute classicMovieAttribute)
            {
                return new ClassicMovieAttributeAdapter(classicMovieAttribute, stringLocalizer);
            }
    
            return baseProvider.GetAttributeAdapter(attribute, stringLocalizer);
        }
    }
    
  3. Inscrivez le fournisseur d’adaptateurs auprès de l’injection de dépendances dans Startup.ConfigureServices :

    services.AddRazorPages()
        .AddMvcOptions(options =>
        {
            options.MaxModelValidationErrors = 50;
            options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
                _ => "The field is required.");
        });
    
    services.AddSingleton<IValidationAttributeAdapterProvider,
        CustomValidationAttributeAdapterProvider>();
    

IClientModelValidator pour la validation côté client

Cette méthode de rendu des attributs data- en HTML est utilisée par l’attribut ClassicMovieWithClientValidator dans l’exemple d’application. Pour ajouter la validation côté client à l’aide de cette méthode

  • Dans l’attribut de validation personnalisé, implémentez l’interface IClientModelValidator et créez une méthode AddValidation. Dans la méthode AddValidation, ajoutez des attributs data- pour la validation, comme indiqué dans l’exemple suivant :

    public class ClassicMovieWithClientValidatorAttribute :
        ValidationAttribute, IClientModelValidator
    {
        public ClassicMovieWithClientValidatorAttribute(int year)
        {
            Year = year;
        }
    
        public int Year { get; }
    
        public void AddValidation(ClientModelValidationContext context)
        {
            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-classicmovie", GetErrorMessage());
    
            var year = Year.ToString(CultureInfo.InvariantCulture);
            MergeAttribute(context.Attributes, "data-val-classicmovie-year", year);
        }
    
        public string GetErrorMessage() =>
            $"Classic movies must have a release year no later than {Year}.";
    
        protected override ValidationResult IsValid(object value,
            ValidationContext validationContext)
        {
            var movie = (Movie)validationContext.ObjectInstance;
            var releaseYear = ((DateTime)value).Year;
    
            if (movie.Genre == Genre.Classic && releaseYear > Year)
            {
                return new ValidationResult(GetErrorMessage());
            }
    
            return ValidationResult.Success;
        }
    
        private bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
        {
            if (attributes.ContainsKey(key))
            {
                return false;
            }
    
            attributes.Add(key, value);
            return true;
        }
    }
    

Désactiver la validation côté client

Le code suivant désactive la validation côté client dans Razor Pages :

services.AddRazorPages()
    .AddViewOptions(options =>
    {
        options.HtmlHelperOptions.ClientValidationEnabled = false;
    });

Autres options pour désactiver la validation côté client :

  • Commentez la référence à _ValidationScriptsPartial dans tous les fichiers .cshtml.
  • Supprimez le contenu du fichier Pages\Shared_ValidationScriptsPartial.cshtml .

L’approche précédente n’empêche pas la validation côté client de la bibliothèque de classes IdentityRazor ASP.NET Core. Pour plus d’informations, consultez Génération de modèles automatiqueIdentity dans les projets ASP.NET Core.

Ressources supplémentaires