Validation des formulaires Blazor ASP.NET Core
Remarque
Ceci n’est pas la dernière version de cet article. Pour la version actuelle, consultez la version .NET 8 de cet article.
Avertissement
Cette version d’ASP.NET Core n’est plus prise en charge. Pour plus d’informations, consultez la Stratégie de prise en charge de .NET et .NET Core. Pour la version actuelle, consultez la version .NET 8 de cet article.
Important
Ces informations portent sur la préversion du produit, qui est susceptible d’être en grande partie modifié avant sa commercialisation. Microsoft n’offre aucune garantie, expresse ou implicite, concernant les informations fournies ici.
Pour la version actuelle, consultez la version .NET 8 de cet article.
Cet article explique comment utiliser la validation dans les formulaires Blazor.
Validation de formulaire
Dans les scénarios de validation de formulaire de base, une instance EditForm peut utiliser les instances déclarées EditContext et ValidationMessageStore pour valider les champs de formulaire. Un gestionnaire pour l’événement OnValidationRequested du EditContext exécute la logique de validation personnalisée. Le résultat du gestionnaire met à jour l’instance ValidationMessageStore.
La validation de base du formulaire est utile dans les cas où le modèle du formulaire est défini dans le composant hébergeant le formulaire, soit en tant que membres directement sur le composant, soit dans une sous-classe. L’utilisation d’un composant validateur est recommandée lorsqu’une classe de modèle indépendante est utilisée sur plusieurs composants.
Dans les Blazor Web App, la validation côté client nécessite un circuit actif BlazorSignalR. La validation côté client n’est pas disponible pour les formulaires dans les composants qui ont adopté un rendu côté serveur statique (SSR statique). Les formulaires qui adoptent le SSR statique sont validés sur le serveur après l’envoi du formulaire.
Dans le composant suivant, la méthode du gestionnaire HandleValidationRequested
efface tous les messages de validation existants en appelant ValidationMessageStore.Clear avant de valider le formulaire.
Starship8.razor
:
@page "/starship-8"
@implements IDisposable
@inject ILogger<Starship8> Logger
<h2>Holodeck Configuration</h2>
<EditForm EditContext="editContext" OnValidSubmit="Submit" FormName="Starship8">
<div>
<label>
<InputCheckbox @bind-Value="Model!.Subsystem1" />
Safety Subsystem
</label>
</div>
<div>
<label>
<InputCheckbox @bind-Value="Model!.Subsystem2" />
Emergency Shutdown Subsystem
</label>
</div>
<div>
<ValidationMessage For="() => Model!.Options" />
</div>
<div>
<button type="submit">Update</button>
</div>
</EditForm>
@code {
private EditContext? editContext;
[SupplyParameterFromForm]
private Holodeck? Model { get; set; }
private ValidationMessageStore? messageStore;
protected override void OnInitialized()
{
Model ??= new();
editContext = new(Model);
editContext.OnValidationRequested += HandleValidationRequested;
messageStore = new(editContext);
}
private void HandleValidationRequested(object? sender,
ValidationRequestedEventArgs args)
{
messageStore?.Clear();
// Custom validation logic
if (!Model!.Options)
{
messageStore?.Add(() => Model.Options, "Select at least one.");
}
}
private void Submit() => Logger.LogInformation("Submit: Processing form");
public class Holodeck
{
public bool Subsystem1 { get; set; }
public bool Subsystem2 { get; set; }
public bool Options => Subsystem1 || Subsystem2;
}
public void Dispose()
{
if (editContext is not null)
{
editContext.OnValidationRequested -= HandleValidationRequested;
}
}
}
@page "/starship-8"
@implements IDisposable
@inject ILogger<Starship8> Logger
<h2>Holodeck Configuration</h2>
<EditForm EditContext="editContext" OnValidSubmit="Submit" FormName="Starship8">
<div>
<label>
<InputCheckbox @bind-Value="Model!.Subsystem1" />
Safety Subsystem
</label>
</div>
<div>
<label>
<InputCheckbox @bind-Value="Model!.Subsystem2" />
Emergency Shutdown Subsystem
</label>
</div>
<div>
<ValidationMessage For="() => Model!.Options" />
</div>
<div>
<button type="submit">Update</button>
</div>
</EditForm>
@code {
private EditContext? editContext;
[SupplyParameterFromForm]
private Holodeck? Model { get; set; }
private ValidationMessageStore? messageStore;
protected override void OnInitialized()
{
Model ??= new();
editContext = new(Model);
editContext.OnValidationRequested += HandleValidationRequested;
messageStore = new(editContext);
}
private void HandleValidationRequested(object? sender,
ValidationRequestedEventArgs args)
{
messageStore?.Clear();
// Custom validation logic
if (!Model!.Options)
{
messageStore?.Add(() => Model.Options, "Select at least one.");
}
}
private void Submit() => Logger.LogInformation("Submit: Processing form");
public class Holodeck
{
public bool Subsystem1 { get; set; }
public bool Subsystem2 { get; set; }
public bool Options => Subsystem1 || Subsystem2;
}
public void Dispose()
{
if (editContext is not null)
{
editContext.OnValidationRequested -= HandleValidationRequested;
}
}
}
@page "/starship-8"
@implements IDisposable
@inject ILogger<Starship8> Logger
<h2>Holodeck Configuration</h2>
<EditForm EditContext="editContext" OnValidSubmit="Submit">
<div>
<label>
<InputCheckbox @bind-Value="Model!.Subsystem1" />
Safety Subsystem
</label>
</div>
<div>
<label>
<InputCheckbox @bind-Value="Model!.Subsystem2" />
Emergency Shutdown Subsystem
</label>
</div>
<div>
<ValidationMessage For="() => Model!.Options" />
</div>
<div>
<button type="submit">Update</button>
</div>
</EditForm>
@code {
private EditContext? editContext;
public Holodeck? Model { get; set; }
private ValidationMessageStore? messageStore;
protected override void OnInitialized()
{
Model ??= new();
editContext = new(Model);
editContext.OnValidationRequested += HandleValidationRequested;
messageStore = new(editContext);
}
private void HandleValidationRequested(object? sender,
ValidationRequestedEventArgs args)
{
messageStore?.Clear();
// Custom validation logic
if (!Model!.Options)
{
messageStore?.Add(() => Model.Options, "Select at least one.");
}
}
private void Submit()
{
Logger.LogInformation("Submit called: Processing the form");
}
public class Holodeck
{
public bool Subsystem1 { get; set; }
public bool Subsystem2 { get; set; }
public bool Options => Subsystem1 || Subsystem2;
}
public void Dispose()
{
if (editContext is not null)
{
editContext.OnValidationRequested -= HandleValidationRequested;
}
}
}
Composant validateur d’annotations de données et validation personnalisée
Le composant DataAnnotationsValidator attache la validation des annotations de données à une cascade EditContext. L’activation de la validation des annotations de données nécessite le composant DataAnnotationsValidator. Pour utiliser un système de validation différent des annotations de données, utilisez une implémentation personnalisée au lieu du composant DataAnnotationsValidator. Les implémentations de l’infrastructure pour DataAnnotationsValidator sont disponibles pour inspection dans la source de référence :
Remarque
Les liens de documentation vers la source de référence .NET chargent généralement la branche par défaut du référentiel, qui représente le développement actuel pour la prochaine version de .NET. Pour sélectionner une balise pour une version spécifique, utilisez la liste déroulante Échanger les branches ou les balises. Pour plus d’informations, consultez Comment sélectionner une balise de version du code source ASP.NET Core (dotnet/AspNetCore.Docs #26205).
Blazor effectue deux types de validation :
- La validation du champ est effectuée lorsque l’utilisateur affiche des onglets hors d’un champ. Pendant la validation de champ, le composant DataAnnotationsValidator associe tous les résultats de validation signalés au champ.
- La validation du modèle est exécutée lorsque l’utilisateur envoie le formulaire. Pendant la validation du modèle, le composant DataAnnotationsValidator tente de déterminer le champ en fonction du nom de membre que le résultat de la validation indique. Les résultats de validation qui ne sont pas associés à un membre individuel sont associés au modèle plutôt qu’à un champ.
Composants validateurs
Les composants validateurs prennent en charge la validation des formulaires en gérant un ValidationMessageStore pour le EditContext d’un formulaire.
L’infrastructure Blazor fournit le composant DataAnnotationsValidator permettant d’attacher la prise en charge de la validation aux formulaires en fonction d’attributs de validation (annotations de données). Vous pouvez créer des composants validateurs personnalisés pour traiter les messages de validation de différents formulaires sur la même page ou le même formulaire à différentes étapes de traitement des formulaires (par exemple, la validation du client suivie de la validation du serveur). L’exemple de composant validateur indiqué dans cette section, CustomValidation
, est utilisé dans les sections suivantes de cet article :
- Validation de logique métier avec un composant validateur
- Validation du serveur avec un composant validateur
Parmi les validateurs intégrés d’annotation de données, seul l’attribut de validation [Remote]
n’est pas pris en charge dans Blazor.
Remarque
Dans de nombreux cas, les attributs de validation d’annotation de données personnalisées peuvent être utilisés au lieu des composants validateurs personnalisés. Les attributs personnalisés appliqués au modèle du formulaire s’activent avec l’utilisation du composant DataAnnotationsValidator. Lorsqu’ils sont utilisés avec la validation du serveur, tous les attributs personnalisés appliqués au modèle doivent être exécutables sur le serveur. Pour plus d'informations, consultez la section Attributs de validation personnalisés.
Créez un composant validateur à partir de ComponentBase :
- Le formulaire EditContext est un paramètre en cascade du composant.
- Lorsque le composant validateur est initialisé, un nouveau ValidationMessageStore est créé pour conserver une liste actuelle d’erreurs de formulaire.
- La banque de messages reçoit des erreurs lorsque le code du développeur dans le composant du formulaire appelle la méthode
DisplayErrors
. Les erreurs sont passées à la méthodeDisplayErrors
dans unDictionary<string, List<string>>
. Dans le dictionnaire, la clé est le nom du champ de formulaire qui contient une ou plusieurs erreurs. La valeur est la liste d’erreurs. - Les messages sont effacés lorsque l’un des éléments suivants s’est produit :
- La validation est demandée sur le EditContext lorsque l’événement OnValidationRequested est déclenché. Toutes les erreurs sont effacées.
- Un champ change dans le formulaire lorsque l’événement OnFieldChanged est déclenché. Seules les erreurs du champ sont effacées.
- La méthode
ClearErrors
est appelée par le code du développeur. Toutes les erreurs sont effacées.
Mettez à jour l’espace de noms dans la classe suivante pour une correspondance avec l’espace de noms de votre application.
CustomValidation.cs
:
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
namespace BlazorSample;
public class CustomValidation : ComponentBase
{
private ValidationMessageStore? messageStore;
[CascadingParameter]
private EditContext? CurrentEditContext { get; set; }
protected override void OnInitialized()
{
if (CurrentEditContext is null)
{
throw new InvalidOperationException(
$"{nameof(CustomValidation)} requires a cascading " +
$"parameter of type {nameof(EditContext)}. " +
$"For example, you can use {nameof(CustomValidation)} " +
$"inside an {nameof(EditForm)}.");
}
messageStore = new(CurrentEditContext);
CurrentEditContext.OnValidationRequested += (s, e) =>
messageStore?.Clear();
CurrentEditContext.OnFieldChanged += (s, e) =>
messageStore?.Clear(e.FieldIdentifier);
}
public void DisplayErrors(Dictionary<string, List<string>> errors)
{
if (CurrentEditContext is not null)
{
foreach (var err in errors)
{
messageStore?.Add(CurrentEditContext.Field(err.Key), err.Value);
}
CurrentEditContext.NotifyValidationStateChanged();
}
}
public void ClearErrors()
{
messageStore?.Clear();
CurrentEditContext?.NotifyValidationStateChanged();
}
}
Important
La spécification d’un espace de noms est requise lors de la dérivation de ComponentBase. L’échec de la spécification d’un espace de noms entraîne une erreur de build :
Tag helpers cannot target tag name '<global namespace>.{CLASS NAME}' because it contains a ' ' character.
L’espace réservé {CLASS NAME}
est le nom de la classe de composant. L’exemple de validateur personnalisé dans cette section spécifie l’exemple d’espace de noms BlazorSample
.
Remarque
Les expressions lambda anonymes sont des gestionnaires d’événements inscrits pour OnValidationRequested et OnFieldChanged dans l’exemple précédent. Il n’est pas nécessaire d’implémenter IDisposable et de désinscrire les délégués d’événement dans ce scénario. Pour plus d’informations, consultez le cycle de vie des composants Razor ASP.NET Core.
Validation de logique métier avec un composant validateur
Pour la validation de logique métier générale, utilisez un composant validateur qui reçoit des erreurs de formulaire dans un dictionnaire.
La validation de base est utile dans les cas où le modèle du formulaire est défini dans le composant hébergeant le formulaire, soit en tant que membres directement sur le composant, soit dans une sous-classe. L’utilisation d’un composant validateur est recommandée lorsqu’une classe de modèle indépendante est utilisée sur plusieurs composants.
Dans l’exemple suivant :
- Une version abrégée du formulaire
Starfleet Starship Database
(composantStarship3
) de la section Exemple de formulaire de l’article Composants d’entrée est utilisée, et accepte seulement la classification et la description du vaisseau spatial. La validation de l’annotation des données ne se déclenche pas lors de l’envoi du formulaire, car le composant DataAnnotationsValidator n’est pas inclus dans le formulaire. - Le composant
CustomValidation
de la section Composants du validateur de cet article est utilisé. - La validation nécessite une valeur pour la description du navire (
Description
) si l’utilisateur sélectionne la classification du navire «Defense
» (Classification
).
Lorsque les messages de validation sont définis dans le composant, ils sont ajoutés au validateur ValidationMessageStore et affichés dans le résumé de la validation du EditForm.
Starship9.razor
:
@page "/starship-9"
@inject ILogger<Starship9> Logger
<h1>Starfleet Starship Database</h1>
<h2>New Ship Entry Form</h2>
<EditForm Model="Model" OnValidSubmit="Submit" FormName="Starship9">
<CustomValidation @ref="customValidation" />
<ValidationSummary />
<div>
<label>
Primary Classification:
<InputSelect @bind-Value="Model!.Classification">
<option value="">
Select classification ...
</option>
<option checked="@(Model!.Classification == "Exploration")"
value="Exploration">
Exploration
</option>
<option checked="@(Model!.Classification == "Diplomacy")"
value="Diplomacy">
Diplomacy
</option>
<option checked="@(Model!.Classification == "Defense")"
value="Defense">
Defense
</option>
</InputSelect>
</label>
</div>
<div>
<label>
Description (optional):
<InputTextArea @bind-Value="Model!.Description" />
</label>
</div>
<div>
<button type="submit">Submit</button>
</div>
</EditForm>
@code {
private CustomValidation? customValidation;
[SupplyParameterFromForm]
private Starship? Model { get; set; }
protected override void OnInitialized() =>
Model ??= new() { ProductionDate = DateTime.UtcNow };
private void Submit()
{
customValidation?.ClearErrors();
var errors = new Dictionary<string, List<string>>();
if (Model!.Classification == "Defense" &&
string.IsNullOrEmpty(Model.Description))
{
errors.Add(nameof(Model.Description),
new() { "For a 'Defense' ship classification, " +
"'Description' is required." });
}
if (errors.Any())
{
customValidation?.DisplayErrors(errors);
}
else
{
Logger.LogInformation("Submit called: Processing the form");
}
}
}
@page "/starship-9"
@inject ILogger<Starship9> Logger
<h1>Starfleet Starship Database</h1>
<h2>New Ship Entry Form</h2>
<EditForm Model="Model" OnValidSubmit="Submit" FormName="Starship9">
<CustomValidation @ref="customValidation" />
<ValidationSummary />
<div>
<label>
Primary Classification:
<InputSelect @bind-Value="Model!.Classification">
<option value="">
Select classification ...
</option>
<option checked="@(Model!.Classification == "Exploration")"
value="Exploration">
Exploration
</option>
<option checked="@(Model!.Classification == "Diplomacy")"
value="Diplomacy">
Diplomacy
</option>
<option checked="@(Model!.Classification == "Defense")"
value="Defense">
Defense
</option>
</InputSelect>
</label>
</div>
<div>
<label>
Description (optional):
<InputTextArea @bind-Value="Model!.Description" />
</label>
</div>
<div>
<button type="submit">Submit</button>
</div>
</EditForm>
@code {
private CustomValidation? customValidation;
[SupplyParameterFromForm]
private Starship? Model { get; set; }
protected override void OnInitialized() =>
Model ??= new() { ProductionDate = DateTime.UtcNow };
private void Submit()
{
customValidation?.ClearErrors();
var errors = new Dictionary<string, List<string>>();
if (Model!.Classification == "Defense" &&
string.IsNullOrEmpty(Model.Description))
{
errors.Add(nameof(Model.Description),
new() { "For a 'Defense' ship classification, " +
"'Description' is required." });
}
if (errors.Any())
{
customValidation?.DisplayErrors(errors);
}
else
{
Logger.LogInformation("Submit called: Processing the form");
}
}
}
@page "/starship-9"
@inject ILogger<Starship9> Logger
<h1>Starfleet Starship Database</h1>
<h2>New Ship Entry Form</h2>
<EditForm Model="Model" OnValidSubmit="Submit">
<CustomValidation @ref="customValidation" />
<ValidationSummary />
<div>
<label>
Primary Classification:
<InputSelect @bind-Value="Model!.Classification">
<option value="">Select classification ...</option>
<option value="Exploration">Exploration</option>
<option value="Diplomacy">Diplomacy</option>
<option value="Defense">Defense</option>
</InputSelect>
</label>
</div>
<div>
<label>
Description (optional):
<InputTextArea @bind-Value="Model!.Description" />
</label>
</div>
<div>
<button type="submit">Submit</button>
</div>
</EditForm>
@code {
private CustomValidation? customValidation;
public Starship? Model { get; set; }
protected override void OnInitialized() =>
Model ??= new() { ProductionDate = DateTime.UtcNow };
private void Submit()
{
customValidation?.ClearErrors();
var errors = new Dictionary<string, List<string>>();
if (Model!.Classification == "Defense" &&
string.IsNullOrEmpty(Model.Description))
{
errors.Add(nameof(Model.Description),
new() { "For a 'Defense' ship classification, " +
"'Description' is required." });
}
if (errors.Any())
{
customValidation?.DisplayErrors(errors);
}
else
{
Logger.LogInformation("Submit called: Processing the form");
}
}
}
Remarque
Au lieu d'utiliser des composants de validation, des attributs de validation d’annotation de données peuvent être utilisés. Les attributs personnalisés appliqués au modèle du formulaire s’activent avec l’utilisation du composant DataAnnotationsValidator. Lorsqu’ils sont utilisés avec la validation du serveur, les attributs doivent être exécutables sur le serveur. Pour plus d'informations, consultez la section Attributs de validation personnalisés.
Validation du serveur avec un composant validateur
Cette section se concentre sur les scénarios Blazor Web App hébergés, mais l’approche de n’importe quel type d’application utilisant la validation du serveur avec une API web adopte la même approche générale.
Cette section se concentre sur les scénarios Blazor WebAssembly hébergés, mais l’approche de n’importe quel type d’application utilisant la validation du serveur avec une API web adopte la même approche générale.
La validation du serveur est prise en charge en plus de la validation du client :
- Traitez la validation du client dans le formulaire avec le composant DataAnnotationsValidator.
- Lorsque le formulaire passe la validation du client (OnValidSubmit est appelé), envoyez le EditContext.Model à une API de serveur principal pour le traitement des formulaires.
- Validation du modèle de processus sur le serveur.
- L’API serveur inclut à la fois la validation des annotations de données d’infrastructure intégrée et la logique de validation personnalisée fournie par le développeur. Si la validation est réussie sur le serveur, le formulaire est traité et un code d’état de réussite (
200 - OK
) est renvoyé. Si la validation échoue, un code d’état d’échec (400 - Bad Request
) et les erreurs de validation des champs sont renvoyés. - Désactivez le formulaire en cas de réussite ou affichez les erreurs.
La validation de base est utile dans les cas où le modèle du formulaire est défini dans le composant hébergeant le formulaire, soit en tant que membres directement sur le composant, soit dans une sous-classe. L’utilisation d’un composant validateur est recommandée lorsqu’une classe de modèle indépendante est utilisée sur plusieurs composants.
L'exemple suivant est basé sur :
- Une Blazor Web App avec des composants WebAssembly interactifs créés à partir du modèle de projet Blazor Web App.
- Le modèle
Starship
(Starship.cs
) de la section Exemple de formulaire de l’article Composants d’entrée. - Le composant
CustomValidation
indiqué dans la section Composants du validateur.
Placez le modèle Starship
(Starship.cs
) dans un projet de bibliothèque de classes partagée pour que les projets client et serveur puissent utiliser le modèle. Ajoutez ou mettez à jour l’espace de noms pour qu’il corresponde à l’espace de noms de l’application partagée (par exemple, namespace BlazorSample.Shared
). Comme le modèle nécessite des annotations de données, vérifiez que la bibliothèque de classes partagée utilise le framework partagé ou ajoutez le package System.ComponentModel.Annotations
au projet partagé.
Remarque
Pour obtenir des conseils sur l’ajout de packages à des applications .NET, consultez les articles figurant sous Installer et gérer des packages dans Flux de travail de la consommation des packages (documentation NuGet). Vérifiez les versions du package sur NuGet.org.
Dans le projet principal de la Blazor Web App, ajoutez un contrôleur pour traiter les demandes de validation des vaisseaux et pour retourner des messages pour les validations ayant échoué. Mettez à jour les espaces de noms dans la dernière instruction using
pour le projet de bibliothèque de classes partagée et le namespace
pour la classe du contrôleur. En plus de la validation des annotations de données du client et du serveur, le contrôleur vérifie qu’une valeur est fournie pour la description du navire (Description
) si l’utilisateur sélectionne la classification du navire Defense
(Classification
).
- La Blazor WebAssemblysolution hébergée créée à partir du Blazor WebAssemblymodèle de projet. L’approche est prise en charge pour toutes les solutions Blazor hébergées sécurisées décrites dans la documentation sur la sécurité Blazor WebAssembly hébergée.
- Le modèle
Starship
(Starship.cs
) de la section Exemple de formulaire de l’article Composants d’entrée. - Le composant
CustomValidation
indiqué dans la section Composants du validateur.
Placez le modèle Starship
(Starship.cs
) dans le projet de la solution Shared
afin que les applications clientes et serveur puissent utiliser le modèle. Ajoutez ou mettez à jour l’espace de noms pour qu’il corresponde à l’espace de noms de l’application partagée (par exemple, namespace BlazorSample.Shared
). Étant donné que le modèle nécessite des annotations de données, ajoutez le package System.ComponentModel.Annotations
au projet Shared
.
Remarque
Pour obtenir des conseils sur l’ajout de packages à des applications .NET, consultez les articles figurant sous Installer et gérer des packages dans Flux de travail de la consommation des packages (documentation NuGet). Vérifiez les versions du package sur NuGet.org.
Dans le projet Server, ajoutez un contrôleur pour traiter les requêtes de validation des vaisseaux et renvoyer les messages de validation ayant échoué. Mettez à jour les espaces de noms dans la dernière instruction using
pour le projet Shared
et le namespace
pour la classe contrôleur. En plus de la validation des annotations de données du client et du serveur, le contrôleur vérifie qu’une valeur est fournie pour la description du navire (Description
) si l’utilisateur sélectionne la classification du navire Defense
(Classification
).
La validation de la classification des navires Defense
se produit uniquement sur le serveur dans le contrôleur, car le formulaire à venir n’effectue pas la même validation côté client lorsque le formulaire est envoyé au serveur. La validation du serveur sans validation côté client est courante dans les applications qui nécessitent une validation de la logique métier privée de l’entrée utilisateur sur le serveur. Par exemple, des informations privées provenant de données stockées pour un utilisateur peuvent être requises pour valider l’entrée utilisateur. Les données privées ne peuvent évidemment pas être envoyées au client pour une validation du client.
Remarque
Le contrôleur StarshipValidation
dans cette section utilise Microsoft Identity 2.0. L’API web accepte uniquement les jetons pour les utilisateurs qui ont l’étendue « API.Access
» pour cette API. Une personnalisation supplémentaire est requise si le nom d’étendue de l’API est différent de API.Access
.
Pour plus d’informations sur la sécurité, consultez :
- Authentification et autorisation Blazor ASP.NET Core (et les autres articles de la section BlazorSécurité et nœud Identity)
- Documentation de la plateforme identity Microsoft
Controllers/StarshipValidation.cs
:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using BlazorSample.Shared;
namespace BlazorSample.Server.Controllers;
[Authorize]
[ApiController]
[Route("[controller]")]
public class StarshipValidationController(
ILogger<StarshipValidationController> logger)
: ControllerBase
{
static readonly string[] scopeRequiredByApi = new[] { "API.Access" };
[HttpPost]
public async Task<IActionResult> Post(Starship model)
{
HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);
try
{
if (model.Classification == "Defense" &&
string.IsNullOrEmpty(model.Description))
{
ModelState.AddModelError(nameof(model.Description),
"For a 'Defense' ship " +
"classification, 'Description' is required.");
}
else
{
logger.LogInformation("Processing the form asynchronously");
// async ...
return Ok(ModelState);
}
}
catch (Exception ex)
{
logger.LogError("Validation Error: {Message}", ex.Message);
}
return BadRequest(ModelState);
}
}
Vérifiez ou mettez à jour l’espace de noms du contrôleur précédent (BlazorSample.Server.Controllers
) pour qu’il corresponde à l’espace de noms des contrôleurs de l’application.
Lorsqu’une erreur de validation de la liaison de modèle se produit sur le serveur, un ApiController
(ApiControllerAttribute) renvoie normalement une réponse de requête incorrecte par défaut avec un ValidationProblemDetails. La réponse contient plus de données que les erreurs de validation, comme le montre l’exemple suivant lorsque tous les champs du formulaire Starfleet Starship Database
ne sont pas envoyés et que la validation du formulaire échoue :
{
"title": "One or more validation errors occurred.",
"status": 400,
"errors": {
"Id": ["The Id field is required."],
"Classification": ["The Classification field is required."],
"IsValidatedDesign": ["This form disallows unapproved ships."],
"MaximumAccommodation": ["Accommodation invalid (1-100000)."]
}
}
Remarque
Pour illustrer la réponse JSON précédente, vous devez soit désactiver la validation du client du formulaire pour autoriser l’envoi du formulaire avec des champs vides, soit utiliser un outil pour envoyer une demande directement à l’API serveur, par exemple Firefox Browser Developer.
Si l’API serveur renvoie la réponse JSON par défaut précédente, il est possible pour le client d’analyser la réponse dans le code du développeur pour obtenir les enfants du nœud errors
pour le traitement des erreurs de validation des formulaires. Il n’est pas pratique d’écrire du code développeur pour analyser le fichier. L’analyse manuelle du JSON nécessite la production manuelle d’un Dictionary<string, List<string>>
d’erreurs après l’appel de ReadFromJsonAsync. Idéalement, l’API du serveur ne devrait retourner que les erreurs de validation, comme le montre l’exemple suivant :
{
"Id": ["The Id field is required."],
"Classification": ["The Classification field is required."],
"IsValidatedDesign": ["This form disallows unapproved ships."],
"MaximumAccommodation": ["Accommodation invalid (1-100000)."]
}
Pour modifier la réponse de l’API serveur afin qu’elle renvoie uniquement les erreurs de validation, modifiez le délégué invoqué sur les actions annotées avec ApiControllerAttribute dans le fichier Program
. Pour le point de terminaison de l’API (/StarshipValidation
), retournez un BadRequestObjectResult avec ModelStateDictionary. Pour tous les autres points de terminaison de l’API, conservez le comportement par défaut en renvoyant le résultat de l’objet avec un nouveau ValidationProblemDetails.
Ajoutez l’espace de noms Microsoft.AspNetCore.Mvc en haut du fichier Program
dans le projet principal de la Blazor Web App :
using Microsoft.AspNetCore.Mvc;
Dans le fichier Program
, ajoutez ou mettez à jour la méthode d’extension AddControllersWithViews suivante, et ajoutez l’appel suivant à ConfigureApiBehaviorOptions :
builder.Services.AddControllersWithViews()
.ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = context =>
{
if (context.HttpContext.Request.Path == "/StarshipValidation")
{
return new BadRequestObjectResult(context.ModelState);
}
else
{
return new BadRequestObjectResult(
new ValidationProblemDetails(context.ModelState));
}
};
});
Si vous ajoutez des contrôleurs au projet principal de la Blazor Web App pour la première fois, mappez les points de terminaison du contrôleur quand vous placez le code précédent qui inscrit des services pour les contrôleurs. L’exemple suivant utilise des routes de contrôleur par défaut :
app.MapDefaultControllerRoute();
Remarque
L’exemple précédent inscrit explicitement les services de contrôleur en appelant AddControllersWithViews pour atténuer les attaques XSRF/CSRF (Cross-Site Request Forgery) automatiquement. Si vous utilisez simplement AddControllers, l’anti-falsification n’est pas activée automatiquement.
Pour plus d’informations sur les réponses d’erreur d’échec de routage et de validation de contrôleur, consultez les ressources suivantes :
- Routage vers les actions du contrôleur dans ASP.NET Core
- Gérer les erreurs d’API web basées sur contrôleur ASP.NET Core
Dans le projet .Client
, ajoutez le composant CustomValidation
indiqué dans la section Composants du validateur. Mettez à jour l’espace de noms pour qu’il corresponde à l’application (par exemple, namespace BlazorSample.Client
).
Dans le projet .Client
, le formulaire Starfleet Starship Database
est mis à jour pour afficher les erreurs de validation du serveur avec l’aide du composant CustomValidation
. Lorsque l’API serveur renvoie des messages de validation, ils sont ajoutés aux ValidationMessageStore du composant CustomValidation
. Les erreurs sont disponibles dans le formulaire EditContext pour être affichées par le résumé de validation du formulaire.
Dans le composant suivant, mettez à jour l’espace de noms du projet partagé (@using BlazorSample.Shared
) pour qu’il corresponde à l’espace de noms du projet partagé. Notez que le formulaire nécessite une autorisation. L’utilisateur doit donc être connecté à l’application pour accéder au formulaire.
Ajoutez l’espace de noms Microsoft.AspNetCore.Mvc en haut du fichier Program
dans l’application Server :
using Microsoft.AspNetCore.Mvc;
Dans le fichier Program
, recherchez la méthode d’extension AddControllersWithViews et ajoutez l’appel suivant à ConfigureApiBehaviorOptions :
builder.Services.AddControllersWithViews()
.ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = context =>
{
if (context.HttpContext.Request.Path == "/StarshipValidation")
{
return new BadRequestObjectResult(context.ModelState);
}
else
{
return new BadRequestObjectResult(
new ValidationProblemDetails(context.ModelState));
}
};
});
Remarque
L’exemple précédent inscrit explicitement les services de contrôleur en appelant AddControllersWithViews pour atténuer les attaques XSRF/CSRF (Cross-Site Request Forgery) automatiquement. Si vous utilisez simplement AddControllers, l’anti-falsification n’est pas activée automatiquement.
Dans le projet Client, ajoutez le composant CustomValidation
indiqué dans la section Composants du validateur. Mettez à jour l’espace de noms pour qu’il corresponde à l’application (par exemple, namespace BlazorSample.Client
).
Dans le projet Client, le formulaire Starfleet Starship Database
est mis à jour pour afficher les erreurs de validation du serveur avec l’aide du composant CustomValidation
. Lorsque l’API serveur renvoie des messages de validation, ils sont ajoutés aux ValidationMessageStore du composant CustomValidation
. Les erreurs sont disponibles dans le formulaire EditContext pour être affichées par le résumé de validation du formulaire.
Dans le composant suivant, mettez à jour l’espace de noms du projet Shared
(@using BlazorSample.Shared
) vers l’espace de noms du projet partagé. Notez que le formulaire nécessite une autorisation. L’utilisateur doit donc être connecté à l’application pour accéder au formulaire.
Starship10.razor
:
Remarque
Les formulaires basés sur EditForm activent automatiquement la prise en charge de l’anti-falsification. Le contrôleur doit utiliser AddControllersWithViews pour inscrire les services du contrôleur et activer automatiquement la prise en charge de l’anti-falsification pour l’API web.
@page "/starship-10"
@rendermode InteractiveWebAssembly
@using System.Net
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using BlazorSample.Shared
@attribute [Authorize]
@inject HttpClient Http
@inject ILogger<Starship10> Logger
<h1>Starfleet Starship Database</h1>
<h2>New Ship Entry Form</h2>
<EditForm FormName="Starship10" Model="Model" OnValidSubmit="Submit">
<DataAnnotationsValidator />
<CustomValidation @ref="customValidation" />
<ValidationSummary />
<div>
<label>
Identifier:
<InputText @bind-Value="Model!.Id" disabled="@disabled" />
</label>
</div>
<div>
<label>
Description (optional):
<InputTextArea @bind-Value="Model!.Description"
disabled="@disabled" />
</label>
</div>
<div>
<label>
Primary Classification:
<InputSelect @bind-Value="Model!.Classification" disabled="@disabled">
<option value="">Select classification ...</option>
<option value="Exploration">Exploration</option>
<option value="Diplomacy">Diplomacy</option>
<option value="Defense">Defense</option>
</InputSelect>
</label>
</div>
<div>
<label>
Maximum Accommodation:
<InputNumber @bind-Value="Model!.MaximumAccommodation"
disabled="@disabled" />
</label>
</div>
<div>
<label>
Engineering Approval:
<InputCheckbox @bind-Value="Model!.IsValidatedDesign"
disabled="@disabled" />
</label>
</div>
<div>
<label>
Production Date:
<InputDate @bind-Value="Model!.ProductionDate" disabled="@disabled" />
</label>
</div>
<div>
<button type="submit" disabled="@disabled">Submit</button>
</div>
<div style="@messageStyles">
@message
</div>
</EditForm>
@code {
private CustomValidation? customValidation;
private bool disabled;
private string? message;
private string messageStyles = "visibility:hidden";
[SupplyParameterFromForm]
private Starship? Model { get; set; }
protected override void OnInitialized() =>
Model ??= new() { ProductionDate = DateTime.UtcNow };
private async Task Submit(EditContext editContext)
{
customValidation?.ClearErrors();
try
{
var response = await Http.PostAsJsonAsync<Starship>(
"StarshipValidation", (Starship)editContext.Model);
var errors = await response.Content
.ReadFromJsonAsync<Dictionary<string, List<string>>>() ??
new Dictionary<string, List<string>>();
if (response.StatusCode == HttpStatusCode.BadRequest &&
errors.Any())
{
customValidation?.DisplayErrors(errors);
}
else if (!response.IsSuccessStatusCode)
{
throw new HttpRequestException(
$"Validation failed. Status Code: {response.StatusCode}");
}
else
{
disabled = true;
messageStyles = "color:green";
message = "The form has been processed.";
}
}
catch (AccessTokenNotAvailableException ex)
{
ex.Redirect();
}
catch (Exception ex)
{
Logger.LogError("Form processing error: {Message}", ex.Message);
disabled = true;
messageStyles = "color:red";
message = "There was an error processing the form.";
}
}
}
Le projet .Client
d’une Blazor Web App doit également inscrire un HttpClient pour les demandes HTTP POST auprès d’un contrôleur d’API web back-end. Vérifiez ou ajoutez ce qui suit au fichier Program
du projet .Client
:
builder.Services.AddScoped(sp =>
new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
L’exemple précédent définit l’adresse de base avec builder.HostEnvironment.BaseAddress
(IWebAssemblyHostEnvironment.BaseAddress). Cette propriété récupère l’adresse de base de l’application. Elle est généralement dérivée de la valeur href
de la balise <base>
dans la page hôte.
@page "/starship-10"
@using System.Net
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using BlazorSample.Shared
@attribute [Authorize]
@inject HttpClient Http
@inject ILogger<Starship10> Logger
<h1>Starfleet Starship Database</h1>
<h2>New Ship Entry Form</h2>
<EditForm Model="Model" OnValidSubmit="Submit">
<DataAnnotationsValidator />
<CustomValidation @ref="customValidation" />
<ValidationSummary />
<div>
<label>
Identifier:
<InputText @bind-Value="Model!.Id" disabled="@disabled" />
</label>
</div>
<div>
<label>
Description (optional):
<InputTextArea @bind-Value="Model!.Description"
disabled="@disabled" />
</label>
</div>
<div>
<label>
Primary Classification:
<InputSelect @bind-Value="Model!.Classification" disabled="@disabled">
<option value="">Select classification ...</option>
<option value="Exploration">Exploration</option>
<option value="Diplomacy">Diplomacy</option>
<option value="Defense">Defense</option>
</InputSelect>
</label>
</div>
<div>
<label>
Maximum Accommodation:
<InputNumber @bind-Value="Model!.MaximumAccommodation"
disabled="@disabled" />
</label>
</div>
<div>
<label>
Engineering Approval:
<InputCheckbox @bind-Value="Model!.IsValidatedDesign"
disabled="@disabled" />
</label>
</div>
<div>
<label>
Production Date:
<InputDate @bind-Value="Model!.ProductionDate" disabled="@disabled" />
</label>
</div>
<div>
<button type="submit" disabled="@disabled">Submit</button>
</div>
<div style="@messageStyles">
@message
</div>
</EditForm>
@code {
private CustomValidation? customValidation;
private bool disabled;
private string? message;
private string messageStyles = "visibility:hidden";
public Starship? Model { get; set; }
protected override void OnInitialized() =>
Model ??= new() { ProductionDate = DateTime.UtcNow };
private async Task Submit(EditContext editContext)
{
customValidation?.ClearErrors();
try
{
var response = await Http.PostAsJsonAsync<Starship>(
"StarshipValidation", (Starship)editContext.Model);
var errors = await response.Content
.ReadFromJsonAsync<Dictionary<string, List<string>>>() ??
new Dictionary<string, List<string>>();
if (response.StatusCode == HttpStatusCode.BadRequest &&
errors.Any())
{
customValidation?.DisplayErrors(errors);
}
else if (!response.IsSuccessStatusCode)
{
throw new HttpRequestException(
$"Validation failed. Status Code: {response.StatusCode}");
}
else
{
disabled = true;
messageStyles = "color:green";
message = "The form has been processed.";
}
}
catch (AccessTokenNotAvailableException ex)
{
ex.Redirect();
}
catch (Exception ex)
{
Logger.LogError("Form processing error: {Message}", ex.Message);
disabled = true;
messageStyles = "color:red";
message = "There was an error processing the form.";
}
}
}
Remarque
Au lieu d'utiliser un composant de validation, des attributs de validation d’annotation de données peuvent être utilisés. Les attributs personnalisés appliqués au modèle du formulaire s’activent avec l’utilisation du composant DataAnnotationsValidator. Lorsqu’ils sont utilisés avec la validation du serveur, les attributs doivent être exécutables sur le serveur. Pour plus d'informations, consultez la section Attributs de validation personnalisés.
Remarque
L’approche de validation du serveur de cette section convient à tous les exemples de solution hébergée Blazor WebAssembly de cet ensemble de documentation :
InputText
en fonction de l’événement d’entrée
Utilisez le composant InputText pour créer un composant personnalisé qui utilise l’événement oninput
(input
) au lieu de l’événement onchange
(change
). L’utilisation de la validation de champ des déclencheurs d’événements input
à chaque séquence de touches.
Le composant CustomInputText
suivant hérite du composant InputText
de l’infrastructure et définit la liaison d’événement à l’événement oninput
(input
).
CustomInputText.razor
:
@inherits InputText
<input @attributes="AdditionalAttributes"
class="@CssClass"
@bind="CurrentValueAsString"
@bind:event="oninput" />
Le composant CustomInputText
peut être utilisé n’importe où InputText est utilisé. Le composant suivant utilise le composant CustomInputText
partagé.
Starship11.razor
:
@page "/starship-11"
@using System.ComponentModel.DataAnnotations
@inject ILogger<Starship11> Logger
<EditForm Model="Model" OnValidSubmit="Submit" FormName="Starship11">
<DataAnnotationsValidator />
<ValidationSummary />
<div>
<label>
Identifier:
<CustomInputText @bind-Value="Model!.Id" />
</label>
</div>
<div>
<button type="submit">Submit</button>
</div>
</EditForm>
<div>
CurrentValue: @Model?.Id
</div>
@code {
[SupplyParameterFromForm]
private Starship? Model { get; set; }
protected override void OnInitialized() => Model ??= new();
private void Submit() => Logger.LogInformation("Submit: Processing form");
public class Starship
{
[Required]
[StringLength(10, ErrorMessage = "Id is too long.")]
public string? Id { get; set; }
}
}
@page "/starship-11"
@using System.ComponentModel.DataAnnotations
@inject ILogger<Starship11> Logger
<EditForm Model="Model" OnValidSubmit="Submit" FormName="Starship11">
<DataAnnotationsValidator />
<ValidationSummary />
<div>
<label>
Identifier:
<CustomInputText @bind-Value="Model!.Id" />
</label>
</div>
<div>
<button type="submit">Submit</button>
</div>
</EditForm>
<div>
CurrentValue: @Model?.Id
</div>
@code {
[SupplyParameterFromForm]
private Starship? Model { get; set; }
protected override void OnInitialized() => Model ??= new();
private void Submit() => Logger.LogInformation("Submit: Processing form");
public class Starship
{
[Required]
[StringLength(10, ErrorMessage = "Id is too long.")]
public string? Id { get; set; }
}
}
@page "/starship-11"
@using System.ComponentModel.DataAnnotations
@inject ILogger<Starship11> Logger
<EditForm Model="Model" OnValidSubmit="Submit">
<DataAnnotationsValidator />
<ValidationSummary />
<CustomInputText @bind-Value="Model!.Id" />
<button type="submit">Submit</button>
</EditForm>
<div>
CurrentValue: @Model?.Id
</div>
@code {
public Starship? Model { get; set; }
protected override void OnInitialized() => Model ??= new();
private void Submit()
{
Logger.LogInformation("Submit called: Processing the form");
}
public class Starship
{
[Required]
[StringLength(10, ErrorMessage = "Id is too long.")]
public string? Id { get; set; }
}
}
Composants du résumé de validation et du message de validation
Le composant ValidationSummary résume tous les messages de validation, ce qui est similaire à l’Assistant de balise de résumé de validation :
<ValidationSummary />
Messages de validation de sortie pour un modèle spécifique avec le paramètre Model
:
<ValidationSummary Model="Model" />
Le composant ValidationMessage<TValue> affiche des messages de validation pour un champ spécifique, ce qui est similaire à l’Assistant de balise de message de validation. Spécifiez le champ à valider avec l’attribut For et une expression lambda nommant la propriété de modèle :
<ValidationMessage For="@(() => Model!.MaximumAccommodation)" />
Les composants ValidationMessage<TValue> et ValidationSummary prennent en charge les attributs arbitraires. Tout attribut qui ne correspond pas à un paramètre de composant est ajouté à l’élément <div>
ou <ul>
généré.
Contrôlez le style des messages de validation dans la feuille de style de l’application (wwwroot/css/app.css
ou wwwroot/css/site.css
). La classe validation-message
par défaut définit la couleur du texte des messages de validation en rouge :
.validation-message {
color: red;
}
Déterminer si un champ de formulaire est valide
Utilisez EditContext.IsValid pour déterminer si un champ est valide sans obtenir de messages de validation.
pris en charge, mais pas recommandé :
var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();
recommandé :
var isValid = editContext.IsValid(fieldIdentifier);
Attributs de validation personnalisés
Pour vous assurer qu’un résultat de validation est correctement associé à un champ lors de l’utilisation d’un attribut de validation personnalisé, transmettez le MemberName du contexte de validation lors de la création du ValidationResult.
CustomValidator.cs
:
using System;
using System.ComponentModel.DataAnnotations;
public class CustomValidator : ValidationAttribute
{
protected override ValidationResult IsValid(object value,
ValidationContext validationContext)
{
...
return new ValidationResult("Validation message to user.",
new[] { validationContext.MemberName });
}
}
Injectez des services dans des attributs de validation personnalisés via le ValidationContext. L’exemple suivant illustre un formulaire « salad chef » qui valide l’entrée de l’utilisateur avec l’injection de dépendance (DI).
La classe SaladChef
indique la liste des navires approuvés pour une salade Ten Forward.
SaladChef.cs
:
namespace BlazorSample;
public class SaladChef
{
public string[] SaladToppers = { "Horva", "Kanda Root", "Krintar", "Plomeek",
"Syto Bean" };
}
Inscrivez SaladChef
dans le conteneur du DI de l’application dans le fichier Program
:
builder.Services.AddTransient<SaladChef>();
La méthode IsValid
de la classe SaladChefValidatorAttribute
suivante obtient le service SaladChef
à partir du DI pour vérifier l’entrée de l’utilisateur.
SaladChefValidatorAttribute.cs
:
using System.ComponentModel.DataAnnotations;
namespace BlazorSample;
public class SaladChefValidatorAttribute : ValidationAttribute
{
protected override ValidationResult? IsValid(object? value,
ValidationContext validationContext)
{
var saladChef = validationContext.GetRequiredService<SaladChef>();
if (saladChef.SaladToppers.Contains(value?.ToString()))
{
return ValidationResult.Success;
}
return new ValidationResult("Is that a Vulcan salad topper?! " +
"The following toppers are available for a Ten Forward salad: " +
string.Join(", ", saladChef.SaladToppers));
}
}
Le composant suivant valide l’entrée utilisateur en appliquant le SaladChefValidatorAttribute
([SaladChefValidator]
) à la chaîne d’ingrédients de la salade (SaladIngredient
).
Starship12.razor
:
@page "/starship-12"
@inject SaladChef SaladChef
<EditForm Model="this" autocomplete="off" FormName="Starship12">
<DataAnnotationsValidator />
<div>
<label>
Salad topper (@saladToppers):
<input @bind="SaladIngredient" />
</label>
</div>
<div>
<button type="submit">Submit</button>
</div>
<ul>
@foreach (var message in context.GetValidationMessages())
{
<li class="validation-message">@message</li>
}
</ul>
</EditForm>
@code {
private string? saladToppers;
[SaladChefValidator]
public string? SaladIngredient { get; set; }
protected override void OnInitialized() =>
saladToppers ??= string.Join(", ", SaladChef.SaladToppers);
}
@page "/starship-12"
@inject SaladChef SaladChef
<EditForm Model="this" autocomplete="off" FormName="Starship12">
<DataAnnotationsValidator />
<div>
<label>
Salad topper (@saladToppers):
<input @bind="SaladIngredient" />
</label>
</div>
<div>
<button type="submit">Submit</button>
</div>
<ul>
@foreach (var message in context.GetValidationMessages())
{
<li class="validation-message">@message</li>
}
</ul>
</EditForm>
@code {
private string? saladToppers;
[SaladChefValidator]
public string? SaladIngredient { get; set; }
protected override void OnInitialized() =>
saladToppers ??= string.Join(", ", SaladChef.SaladToppers);
}
@page "/starship-12"
@inject SaladChef SaladChef
<EditForm Model="this" autocomplete="off">
<DataAnnotationsValidator />
<p>
<label>
Salad topper (@saladToppers):
<input @bind="SaladIngredient" />
</label>
</p>
<button type="submit">Submit</button>
<ul>
@foreach (var message in context.GetValidationMessages())
{
<li class="validation-message">@message</li>
}
</ul>
</EditForm>
@code {
private string? saladToppers;
[SaladChefValidator]
public string? SaladIngredient { get; set; }
protected override void OnInitialized() =>
saladToppers ??= string.Join(", ", SaladChef.SaladToppers);
}
Attributs de classe CSS de validation personnalisée
Les attributs de classe CSS de validation personnalisée sont utiles lors de l’intégration à des infrastructures CSS, telles que Bootstrap.
Pour spécifier des attributs de classe CSS de validation personnalisée, commencez par fournir des styles CSS pour la validation personnalisée. Dans l’exemple suivant, les styles valides (validField
) et non valides (invalidField
) sont spécifiés.
Ajoutez les classes CSS suivantes à la feuille de style de l’application :
.validField {
border-color: lawngreen;
}
.invalidField {
background-color: tomato;
}
Créez une classe dérivée de FieldCssClassProvider qui vérifie les messages de validation de champ et applique le style valide ou non valide approprié.
CustomFieldClassProvider.cs
:
using Microsoft.AspNetCore.Components.Forms;
public class CustomFieldClassProvider : FieldCssClassProvider
{
public override string GetFieldCssClass(EditContext editContext,
in FieldIdentifier fieldIdentifier)
{
var isValid = editContext.IsValid(fieldIdentifier);
return isValid ? "validField" : "invalidField";
}
}
using Microsoft.AspNetCore.Components.Forms;
public class CustomFieldClassProvider : FieldCssClassProvider
{
public override string GetFieldCssClass(EditContext editContext,
in FieldIdentifier fieldIdentifier)
{
var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();
return isValid ? "validField" : "invalidField";
}
}
Définissez la classe CustomFieldClassProvider
en tant que Fournisseur de classes CSS de champ sur l’instance EditContext du formulaire avec SetFieldCssClassProvider.
Starship13.razor
:
@page "/starship-13"
@using System.ComponentModel.DataAnnotations
@inject ILogger<Starship13> Logger
<EditForm EditContext="editContext" OnValidSubmit="Submit" FormName="Starship13">
<DataAnnotationsValidator />
<ValidationSummary />
<div>
<label>
Identifier:
<InputText @bind-Value="Model!.Id" />
</label>
</div>
<div>
<button type="submit">Submit</button>
</div>
</EditForm>
@code {
private EditContext? editContext;
[SupplyParameterFromForm]
private Starship? Model { get; set; }
protected override void OnInitialized()
{
Model ??= new();
editContext = new(Model);
editContext.SetFieldCssClassProvider(new CustomFieldClassProvider());
}
private void Submit() => Logger.LogInformation("Submit: Processing form");
public class Starship
{
[Required]
[StringLength(10, ErrorMessage = "Id is too long.")]
public string? Id { get; set; }
}
}
@page "/starship-13"
@using System.ComponentModel.DataAnnotations
@inject ILogger<Starship13> Logger
<EditForm EditContext="editContext" OnValidSubmit="Submit" FormName="Starship13">
<DataAnnotationsValidator />
<ValidationSummary />
<div>
<label>
Identifier:
<InputText @bind-Value="Model!.Id" />
</label>
</div>
<div>
<button type="submit">Submit</button>
</div>
</EditForm>
@code {
private EditContext? editContext;
[SupplyParameterFromForm]
private Starship? Model { get; set; }
protected override void OnInitialized()
{
Model ??= new();
editContext = new(Model);
editContext.SetFieldCssClassProvider(new CustomFieldClassProvider());
}
private void Submit() => Logger.LogInformation("Submit: Processing form");
public class Starship
{
[Required]
[StringLength(10, ErrorMessage = "Id is too long.")]
public string? Id { get; set; }
}
}
@page "/starship-13"
@using System.ComponentModel.DataAnnotations
@inject ILogger<Starship13> Logger
<EditForm EditContext="editContext" OnValidSubmit="Submit">
<DataAnnotationsValidator />
<ValidationSummary />
<InputText @bind-Value="Model!.Id" />
<button type="submit">Submit</button>
</EditForm>
@code {
private EditContext? editContext;
public Starship? Model { get; set; }
protected override void OnInitialized()
{
Model ??= new();
editContext = new(Model);
editContext.SetFieldCssClassProvider(new CustomFieldClassProvider());
}
private void Submit()
{
Logger.LogInformation("Submit called: Processing the form");
}
public class Starship
{
[Required]
[StringLength(10, ErrorMessage = "Id is too long.")]
public string? Id { get; set; }
}
}
L’exemple précédent vérifie la validité de tous les champs de formulaire et applique un style à chaque champ. Si le formulaire doit appliquer uniquement des styles personnalisés à un sous-ensemble de champs, faites en sorte que CustomFieldClassProvider
applique les styles de manière conditionnelle. L’exemple CustomFieldClassProvider2
suivant applique uniquement un style au champ Name
. Pour tous les champs dont les noms ne correspondent pas à Name
, string.Empty
est renvoyé et aucun style n’est appliqué. À l’aide de la réflexion, le champ est mis en correspondance avec la propriété ou le nom de champ du membre du modèle, pas avec un id
affecté à l’entité HTML.
CustomFieldClassProvider2.cs
:
using Microsoft.AspNetCore.Components.Forms;
public class CustomFieldClassProvider2 : FieldCssClassProvider
{
public override string GetFieldCssClass(EditContext editContext,
in FieldIdentifier fieldIdentifier)
{
if (fieldIdentifier.FieldName == "Name")
{
var isValid = editContext.IsValid(fieldIdentifier);
return isValid ? "validField" : "invalidField";
}
return string.Empty;
}
}
using Microsoft.AspNetCore.Components.Forms;
public class CustomFieldClassProvider2 : FieldCssClassProvider
{
public override string GetFieldCssClass(EditContext editContext,
in FieldIdentifier fieldIdentifier)
{
if (fieldIdentifier.FieldName == "Name")
{
var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();
return isValid ? "validField" : "invalidField";
}
return string.Empty;
}
}
Remarque
La correspondance du nom de champ dans l’exemple précédent respecte la casse. Par conséquent, un membre de la propriété du modèle désigné «Name
» doit correspondre à une vérification conditionnelle sur «Name
» :
- Correspondance parfaite :
fieldId.FieldName == "Name"
- Échec de la correspondance :
fieldId.FieldName == "name"
- Échec de la correspondance :
fieldId.FieldName == "NAME"
- Échec de la correspondance :
fieldId.FieldName == "nAmE"
Ajoutez une propriété supplémentaire à Model
, par exemple :
[StringLength(10, ErrorMessage = "Description is too long.")]
public string? Description { get; set; }
Ajoutez le Description
au formulaire du composant CustomValidationForm
:
<InputText @bind-Value="Model!.Description" />
Mettez à jour l’instance EditContext dans la méthode OnInitialized
du composant pour utiliser le nouveau Fournisseur de classes CSS de champ :
editContext?.SetFieldCssClassProvider(new CustomFieldClassProvider2());
Étant donné qu’une classe de validation CSS n’est pas appliquée au champ Description
, il n’a pas de style. Toutefois, la validation de champ s’exécute normalement. Si plus de 10 caractères sont fournis, le résumé de validation indique l’erreur :
La description est trop longue.
Dans l’exemple suivant :
Le style CSS personnalisé est appliqué au champ
Name
.Tous les autres champs appliquent une logique similaire à la logique par défaut de Blazor et utilisent les styles de validation CSS de champ par défaut de Blazor,
modified
avecvalid
ouinvalid
. Notez que pour les styles par défaut, vous n’avez pas besoin de les ajouter à la feuille de style de l’application si l’application est basée sur un modèle de projet Blazor. Pour les applications non basées sur un modèle de projet Blazor, les styles par défaut peuvent être ajoutés à la feuille de style de l’application :.valid.modified:not([type=checkbox]) { outline: 1px solid #26b050; } .invalid { outline: 1px solid red; }
CustomFieldClassProvider3.cs
:
using Microsoft.AspNetCore.Components.Forms;
public class CustomFieldClassProvider3 : FieldCssClassProvider
{
public override string GetFieldCssClass(EditContext editContext,
in FieldIdentifier fieldIdentifier)
{
var isValid = editContext.IsValid(fieldIdentifier);
if (fieldIdentifier.FieldName == "Name")
{
return isValid ? "validField" : "invalidField";
}
else
{
if (editContext.IsModified(fieldIdentifier))
{
return isValid ? "modified valid" : "modified invalid";
}
else
{
return isValid ? "valid" : "invalid";
}
}
}
}
using Microsoft.AspNetCore.Components.Forms;
public class CustomFieldClassProvider3 : FieldCssClassProvider
{
public override string GetFieldCssClass(EditContext editContext,
in FieldIdentifier fieldIdentifier)
{
var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();
if (fieldIdentifier.FieldName == "Name")
{
return isValid ? "validField" : "invalidField";
}
else
{
if (editContext.IsModified(fieldIdentifier))
{
return isValid ? "modified valid" : "modified invalid";
}
else
{
return isValid ? "valid" : "invalid";
}
}
}
}
Mettez à jour l’instance EditContext dans la méthode OnInitialized
du composant pour utiliser le Fournisseur de classes CSS de champ précédent :
editContext.SetFieldCssClassProvider(new CustomFieldClassProvider3());
À l’aide de CustomFieldClassProvider3
:
- Le champ
Name
utilise les styles CSS de validation personnalisée de l’application. - Le champ
Description
utilise une logique similaire à la logique de Blazor et aux styles de validation CSS de champ par défaut de Blazor.
Validation au niveau de la classe avec IValidatableObject
La validation au niveau de la classe avec IValidatableObject
(documentation de l’API) est prise en charge pour les modèles de formulaire Blazor. La validation IValidatableObject s’exécute uniquement lorsque le formulaire est envoyé et uniquement si toutes les autres validations réussissent.
Package de validation des annotations de donnéesBlazor
Le Microsoft.AspNetCore.Components.DataAnnotations.Validation
est un package qui comble les lacunes en matière d’expérience de validation à l’aide du composant DataAnnotationsValidator. Le package est actuellement expérimental.
Warning
Le package Microsoft.AspNetCore.Components.DataAnnotations.Validation
dispose de la dernière version de la version Release Candidate sur NuGet.org. Continuez à utiliser le package expérimental de la version Release Candidate pour l’instant. Des fonctionnalités expérimentales sont fournies pour explorer la viabilité des fonctionnalités et peuvent ne pas être livrées dans une version stable. Regardez le référentiel GitHub Annonces, le référentiel GitHub dotnet/aspnetcore
, ou cette section de rubrique pour de plus amples informations.
Attribut [CompareProperty]
CompareAttribute ne fonctionne pas correctement avec le composant DataAnnotationsValidator, car DataAnnotationsValidator n’associe pas le résultat de validation à un membre spécifique. Cela peut entraîner un comportement incohérent entre la validation au niveau du champ et la validation du modèle entier lors d’une soumission. Le package expérimental Microsoft.AspNetCore.Components.DataAnnotations.Validation
introduit un attribut de validation supplémentaire, ComparePropertyAttribute
, qui fonctionne autour de ces limitations. Dans une Blazorapplication, [CompareProperty]
est un remplacement direct de [Compare]
l’attribut.
Modèles imbriqués, types de collection et types complexes
Blazor prend en charge la validation de l’entrée de formulaire à l’aide d’annotations de données avec le intégré DataAnnotationsValidator. Toutefois, le DataAnnotationsValidator valide uniquement les propriétés de niveau supérieur du modèle liées au formulaire qui ne sont pas des propriétés de type collection ou de type complexe.
Pour valider l’ensemble du graphe d’objets du modèle lié, y compris les propriétés de type collection et de type complexe, utilisez le ObjectGraphDataAnnotationsValidator
fourni par le package expérimental Microsoft.AspNetCore.Components.DataAnnotations.Validation
:
<EditForm ...>
<ObjectGraphDataAnnotationsValidator />
...
</EditForm>
Annotez les propriétés du modèle avec [ValidateComplexType]
. Dans les classes de modèle suivantes, la classe ShipDescription
contient des annotations de données supplémentaires à valider lorsque le modèle est lié au formulaire :
Starship.cs
:
using System;
using System.ComponentModel.DataAnnotations;
public class Starship
{
...
[ValidateComplexType]
public ShipDescription ShipDescription { get; set; } = new();
...
}
ShipDescription.cs
:
using System;
using System.ComponentModel.DataAnnotations;
public class ShipDescription
{
[Required]
[StringLength(40, ErrorMessage = "Description too long (40 char).")]
public string? ShortDescription { get; set; }
[Required]
[StringLength(240, ErrorMessage = "Description too long (240 char).")]
public string? LongDescription { get; set; }
}
Activez le bouton d’envoi en fonction de la validation du formulaire
Pour activer et désactiver le bouton d’envoi en fonction de la validation de formulaire, l’exemple suivant :
- Utilise une version abrégée du formulaire
Starfleet Starship Database
précédent (composantStarship3
) de la section Exemple de formulaire de l’article Composants d’entrée qui accepte seulement une valeur pour l’ID du vaisseau. Les autres propriétésStarship
reçoivent des valeurs par défaut valides lorsqu’une instance du typeStarship
est créée. - Utilise le formulaire EditContext pour affecter le modèle lorsque le composant est initialisé.
- Valide le formulaire dans le rappel OnFieldChanged du contexte pour activer et désactiver le bouton d’envoi.
- Implémente IDisposable et désinscrit le gestionnaire d’événements dans la méthode
Dispose
. Pour plus d’informations, consultez le cycle de vie des composants Razor ASP.NET Core.
Remarque
Lors de l’affectation à EditForm.EditContext, n’affectez pas non plus un EditForm.Model au EditForm.
Starship14.razor
:
@page "/starship-14"
@implements IDisposable
@inject ILogger<Starship14> Logger
<EditForm EditContext="editContext" OnValidSubmit="Submit" FormName="Starship14">
<DataAnnotationsValidator />
<ValidationSummary />
<div>
<label>
Identifier:
<InputText @bind-Value="Model!.Id" />
</label>
</div>
<div>
<button type="submit" disabled="@formInvalid">Submit</button>
</div>
</EditForm>
@code {
private bool formInvalid = false;
private EditContext? editContext;
[SupplyParameterFromForm]
private Starship? Model { get; set; }
protected override void OnInitialized()
{
Model ??=
new()
{
Id = "NCC-1701",
Classification = "Exploration",
MaximumAccommodation = 150,
IsValidatedDesign = true,
ProductionDate = new DateTime(2245, 4, 11)
};
editContext = new(Model);
editContext.OnFieldChanged += HandleFieldChanged;
}
private void HandleFieldChanged(object? sender, FieldChangedEventArgs e)
{
if (editContext is not null)
{
formInvalid = !editContext.Validate();
StateHasChanged();
}
}
private void Submit() => Logger.LogInformation("Submit: Processing form");
public void Dispose()
{
if (editContext is not null)
{
editContext.OnFieldChanged -= HandleFieldChanged;
}
}
}
@page "/starship-14"
@implements IDisposable
@inject ILogger<Starship14> Logger
<EditForm EditContext="editContext" OnValidSubmit="Submit" FormName="Starship14">
<DataAnnotationsValidator />
<ValidationSummary />
<div>
<label>
Identifier:
<InputText @bind-Value="Model!.Id" />
</label>
</div>
<div>
<button type="submit" disabled="@formInvalid">Submit</button>
</div>
</EditForm>
@code {
private bool formInvalid = false;
private EditContext? editContext;
[SupplyParameterFromForm]
private Starship? Model { get; set; }
protected override void OnInitialized()
{
Model ??=
new()
{
Id = "NCC-1701",
Classification = "Exploration",
MaximumAccommodation = 150,
IsValidatedDesign = true,
ProductionDate = new DateTime(2245, 4, 11)
};
editContext = new(Model);
editContext.OnFieldChanged += HandleFieldChanged;
}
private void HandleFieldChanged(object? sender, FieldChangedEventArgs e)
{
if (editContext is not null)
{
formInvalid = !editContext.Validate();
StateHasChanged();
}
}
private void Submit() => Logger.LogInformation("Submit: Processing form");
public void Dispose()
{
if (editContext is not null)
{
editContext.OnFieldChanged -= HandleFieldChanged;
}
}
}
@page "/starship-14"
@implements IDisposable
@inject ILogger<Starship14> Logger
<EditForm EditContext="editContext" OnValidSubmit="Submit">
<DataAnnotationsValidator />
<ValidationSummary />
<div>
<label>
Identifier:
<InputText @bind-Value="Model!.Id" />
</label>
</div>
<div>
<button type="submit" disabled="@formInvalid">Submit</button>
</div>
</EditForm>
@code {
private bool formInvalid = false;
private EditContext? editContext;
private Starship? Model { get; set; }
protected override void OnInitialized()
{
Model ??=
new()
{
Id = "NCC-1701",
Classification = "Exploration",
MaximumAccommodation = 150,
IsValidatedDesign = true,
ProductionDate = new DateTime(2245, 4, 11)
};
editContext = new(Model);
editContext.OnFieldChanged += HandleFieldChanged;
}
private void HandleFieldChanged(object? sender, FieldChangedEventArgs e)
{
if (editContext is not null)
{
formInvalid = !editContext.Validate();
StateHasChanged();
}
}
private void Submit()
{
Logger.LogInformation("Submit called: Processing the form");
}
public void Dispose()
{
if (editContext is not null)
{
editContext.OnFieldChanged -= HandleFieldChanged;
}
}
}
Si un formulaire n’est pas préchargé avec des valeurs valides et que vous souhaitez désactiver le bouton Submit
lors du chargement du formulaire, définissez formInvalid
sur true
.
L’un des effets secondaires de l’approche précédente consiste à remplir un résumé de validation (composant ValidationSummary) avec des champs non valides après que l’utilisateur a interagi avec un champ. Traitez ce scénario de l’une des manières suivantes :
- N’utilisez pas de composant ValidationSummary sur le formulaire.
- Rendez le composant ValidationSummary visible lorsque le bouton d’envoi est sélectionné (par exemple, dans une méthode
Submit
).
<EditForm ... EditContext="editContext" OnValidSubmit="Submit" ...>
<DataAnnotationsValidator />
<ValidationSummary style="@displaySummary" />
...
<button type="submit" disabled="@formInvalid">Submit</button>
</EditForm>
@code {
private string displaySummary = "display:none";
...
private void Submit()
{
displaySummary = "display:block";
}
}