Partage via


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 :

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éthode DisplayErrors dans un Dictionary<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 (composant Starship3) 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 :

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).

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 :

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 :

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 avec valid ou invalid. 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 (composant Starship3) 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és Starship reçoivent des valeurs par défaut valides lorsqu’une instance du type Starship 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";
    }
}