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

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 des liaisons dans des formulaires Blazor.

EditForm/EditContext modèle

Un EditForm crée un EditContext en fonction de l’objet affecté comme valeur en cascade pour d’autres composants du formulaire. Le EditContext suit les métadonnées relatives au processus de modification, y compris les champs de formulaire qui ont été modifiés et les messages de validation actuels. L’affectation à un EditForm.Model ou à un EditForm.EditContext peut lier un formulaire à des données.

Liaison de données

Affectation à EditForm.Model :

<EditForm ... Model="Model" ...>
    ...
</EditForm>

@code {
    [SupplyParameterFromForm]
    public Starship? Model { get; set; }

    protected override void OnInitialized() => Model ??= new();
}
<EditForm ... Model="Model" ...>
    ...
</EditForm>

@code {
    public Starship? Model { get; set; }

    protected override void OnInitialized() => Model ??= new();
}

Remarque

La plupart des exemples de modèles de formulaire de cet article lient des formulaires aux propriétés C#, mais la liaison de champ C# est également prise en charge.

Liaison du contexte

Affectation à EditForm.EditContext :

<EditForm ... EditContext="editContext" ...>
    ...
</EditForm>

@code {
    private EditContext? editContext;

    [SupplyParameterFromForm]
    public Starship? Model { get; set; }

    protected override void OnInitialized()
    {
        Model ??= new();
        editContext = new(Model);
    }
}
<EditForm ... EditContext="editContext" ...>
    ...
</EditForm>

@code {
    private EditContext? editContext;

    public Starship? Model { get; set; }

    protected override void OnInitialized()
    {
        Model ??= new();
        editContext = new(Model);
    }
}

Affectez soit un EditContextou un Model à un EditForm. Si les deux sont affectés, une erreur d’exécution est levée.

Types pris en charge

La liaison prend en charge :

  • Types primitifs
  • Collections
  • Types complexes
  • Types récursifs
  • Types avec constructeurs
  • Enums

Vous pouvez également utiliser les attributs [DataMember] et [IgnoreDataMember] pour personnaliser la liaison de données. Utilisez ces attributs pour renommer, ignorer et marquer les propriétés en fonction des besoins.

Options supplémentaires de liaison

Des options supplémentaires de liaison de données sont disponibles auprès de RazorComponentsServiceOptions lors de l’appel à AddRazorComponents :

Les valeurs par défaut affectées par l’infrastructure sont les suivantes :

builder.Services.AddRazorComponents(options =>
{
    options.FormMappingUseCurrentCulture = true;
    options.MaxFormMappingCollectionSize = 1024;
    options.MaxFormMappingErrorCount = 200;
    options.MaxFormMappingKeySize = 1024 * 2;
    options.MaxFormMappingRecursionDepth = 64;
}).AddInteractiveServerComponents();

Noms de formulaires

Utilisez le paramètre FormName pour attribuer un nom du formulaire. Les noms de formulaires doivent être uniques pour lier des données du modèle. Le formulaire suivant est nommé RomulanAle :

<EditForm ... FormName="RomulanAle" ...>
    ...
</EditForm>

Ajout d’un nom de formulaire :

  • Est nécessaire pour tous les formulaires soumis par des composants côté serveur rendus statiquement.
  • N’est pas nécessaire pour les formulaires soumis par des composants rendus interactivement, ce qui inclut les formulaires dans des applications et des composants Blazor WebAssembly avec un mode de rendu interactif. Nous vous recommandons cependant de fournir un nom du formulaire unique pour chaque formulaire afin d’éviter les erreurs de publication si l’interactivité était supprimée pour un formulaire.

Le nom du formulaire est vérifie seulement quand le formulaire est publié sur un point de terminaison sous la forme d’une requête HTTP POST traditionnelle depuis un composant côté serveur rendu statiquement. L’infrastructure ne lève aucune exception au moment du rendu d’un formulaire, mais uniquement lorsqu’une requête HTTP POST arrive sans spécifier de nom du formulaire.

Par défaut, il existe une étendue de formulaires non nommés (chaîne vide) au-dessus du composant racine de l’application, ce qui suffit quand il n’y a pas de collisions de noms de formulaire dans l’application. Si des collisions de noms de formulaire sont possibles, par exemple quand vous incluez un formulaire depuis une bibliothèque et que vous n’avez aucun contrôle sur le nom de formulaire utilisé par le développeur de la bibliothèque, fournissez une étendue de noms de formulaires avec le composant FormMappingScope dans le projet principal de l’application web Blazor.

Dans l’exemple suivant, le composant HelloFormFromLibrary a un formulaire nommé Hello et se trouve dans une bibliothèque.

HelloFormFromLibrary.razor:

<EditForm FormName="Hello" Model="this" OnSubmit="Submit">
    <InputText @bind-Value="Name" />
    <button type="submit">Submit</button>
</EditForm>

@if (submitted)
{
    <p>Hello @Name from the library's form!</p>
}

@code {
    bool submitted = false;

    [SupplyParameterFromForm]
    public string? Name { get; set; }

    private void Submit() => submitted = true;
}

Le composant NamedFormsWithScope suivant utilise le composant HelloFormFromLibrary de la bibliothèque et a aussi un formulaire nommé Hello. Le nom de l’étendue du composant FormMappingScope est ParentContext pour les formulaires fournis par le composant HelloFormFromLibrary. Bien que les deux formulaires de cet exemple aient le même nom de formulaire (Hello), les noms de formulaire n’entrent pas en collision et les événements sont routés vers le formulaire approprié pour les événements POST des formulaires.

NamedFormsWithScope.razor:

@page "/named-forms-with-scope"

<div>Hello form from a library</div>

<FormMappingScope Name="ParentContext">
    <HelloFormFromLibrary />
</FormMappingScope>

<div>Hello form using the same form name</div>

<EditForm FormName="Hello" Model="this" OnSubmit="Submit">
    <InputText @bind-Value="Name" />
    <button type="submit">Submit</button>
</EditForm>

@if (submitted)
{
    <p>Hello @Name from the app form!</p>
}

@code {
    bool submitted = false;

    [SupplyParameterFromForm]
    public string? Name { get; set; }

    private void Submit() => submitted = true;
}

Fournir un paramètre à partir du formulaire ([SupplyParameterFromForm])

L’attribut [SupplyParameterFromForm] indique que la valeur de la propriété associée doit être fournie à partir des données du formulaire. Les données de la requête, correspondant au nom de la propriété, sont liées à la propriété. Les entrées, basées sur InputBase<TValue>, génèrent des noms de valeur de formulaire correspondant aux noms Blazor utilisés pour la liaison de modèle.

Vous pouvez spécifier les paramètres de liaison de formulaire suivants à l'[SupplyParameterFromForm]attribut :

  • Name : Obtient ou définit le nom du paramètre. Le nom sert à déterminer le préfixe à utiliser pour correspondre aux données du formulaire et décider de lier (ou pas) la valeur.
  • FormName : Obtient ou définit le nom du gestionnaire. Le nom est utilisé pour faire correspondre le paramètre au formulaire par le nom du formulaire, afin de décider de lier (ou pas) la valeur.

L’exemple suivant lie indépendamment deux formulaires à leurs modèles par le nom du formulaire.

Starship6.razor:

@page "/starship-6"
@inject ILogger<Starship6> Logger

<EditForm Model="Model1" OnSubmit="Submit1" FormName="Holodeck1">
    <div>
        <label>
            Holodeck 1 Identifier: 
            <InputText @bind-Value="Model1!.Id" />
        </label>
    </div>
    <div>
        <button type="submit">Submit</button>
    </div>
</EditForm>

<EditForm Model="Model2" OnSubmit="Submit2" FormName="Holodeck2">
    <div>
        <label>
            Holodeck 2 Identifier: 
            <InputText @bind-Value="Model2!.Id" />
        </label>
    </div>
    <div>
        <button type="submit">Submit</button>
    </div>
</EditForm>

@code {
    [SupplyParameterFromForm(FormName = "Holodeck1")]
    public Holodeck? Model1 { get; set; }

    [SupplyParameterFromForm(FormName = "Holodeck2")]
    public Holodeck? Model2 { get; set; }

    protected override void OnInitialized()
    {
        Model1 ??= new();
        Model2 ??= new();
    }

    private void Submit1()
    {
        Logger.LogInformation("Submit1: Id = {Id}", Model1?.Id);
    }

    private void Submit2()
    {
        Logger.LogInformation("Submit2: Id = {Id}", Model2?.Id);
    }

    public class Holodeck
    {
        public string? Id { get; set; }
    }
}

Imbriquer et lier des formulaires

Les instructions suivantes montrent comment imbriquer et lier des formulaires enfants.

La classe de détails du navire (ShipDetails) suivante contient une description et une longueur d’un sous-formulaire.

ShipDetails.cs:

namespace BlazorSample;

public class ShipDetails
{
    public string? Description { get; set; }
    public int? Length { get; set; }
}

La classe Ship suivante nomme un identificateur (Id) et inclut les détails du navire.

Ship.cs:

namespace BlazorSample
{
    public class Ship
    {
        public string? Id { get; set; }
        public ShipDetails Details { get; set; } = new();
    }
}

Le sous-formulaire suivant sert à modifier les valeurs du type ShipDetails. Cette opération est implémentée en héritant d’un Editor<T> au-dessus du composant. Editor<T> garantit que le composant enfant génère les noms de champs de formulaire appropriés en fonction du modèle (T), avec T qui a pour valeur ShipDetails dans l’exemple suivant.

StarshipSubform.razor:

@inherits Editor<ShipDetails>

<div>
    <label>
        Description: 
        <InputText @bind-Value="Value!.Description" />
    </label>
</div>
<div>
    <label>
        Length: 
        <InputNumber @bind-Value="Value!.Length" />
    </label>
</div>

Le formulaire principal est lié à la classe Ship. Le composant StarshipSubform est utilisé pour modifier les détails du navire, liés en tant que Model!.Details.

Starship7.razor:

@page "/starship-7"
@inject ILogger<Starship7> Logger

<EditForm Model="Model" OnSubmit="Submit" FormName="Starship7">
    <div>
        <label>
            Identifier: 
            <InputText @bind-Value="Model!.Id" />
        </label>
    </div>
    <StarshipSubform @bind-Value="Model!.Details" />
    <div>
        <button type="submit">Submit</button>
    </div>
</EditForm>

@code {
    [SupplyParameterFromForm]
    public Ship? Model { get; set; }

    protected override void OnInitialized() => Model ??= new();

    private void Submit()
    {
        Logger.LogInformation("Id = {Id} Desc = {Description} Length = {Length}",
            Model?.Id, Model?.Details?.Description, Model?.Details?.Length);
    }
}

Scénarios avancés d’erreur de mappage du formulaire

L’infrastructure instancie et remplit le FormMappingContext pour un formulaire, ce qui est le contexte associé à l’opération de mappage d’un formulaire donné. Chaque étendue de mappage (définie par un composant FormMappingScope) instancie FormMappingContext. Chaque fois qu’un [SupplyParameterFromForm] demande le contexte d’une valeur, l’infrastructure remplit le FormMappingContext par la valeur essayée et toutes les erreurs de mappage.

Les développeurs ne sont pas censés interagir directement avec FormMappingContext, car il s’agit principalement d’une source de données pour InputBase<TValue>, EditContext et d’autres implémentations internes pour afficher les erreurs de mappage en tant qu’erreurs de validation. Dans les scénarios personnalisés avancés, les développeurs peuvent accéder directement à FormMappingContext en tant que [CascadingParameter] pour écrire du code personnalisé qui consomme les valeurs essayées et les erreurs de mappage.

Cases d’option

L’exemple de cette section est basé sur le formulaire Starfleet Starship Database (composant Starship3) de la section Exemple de formulaire de cet article.

Ajoutez les types enum suivants à l’application. Créez un nouveau fichier pour les contenir ou ajoutez-les au fichier Starship.cs.

public class ComponentEnums
{
    public enum Manufacturer { SpaceX, NASA, ULA, VirginGalactic, Unknown }
    public enum Color { ImperialRed, SpacecruiserGreen, StarshipBlue, VoyagerOrange }
    public enum Engine { Ion, Plasma, Fusion, Warp }
}

Rendre la classe ComponentEnums accessible au :

  • Modèle Starship dans Starship.cs (par exemple using static ComponentEnums;).
  • Formulaire Starfleet Starship Database (Starship3.razor) (par exemple @using static ComponentEnums).

Utilisez des composants InputRadio<TValue> avec le composant InputRadioGroup<TValue> pour créer un groupe de cases d’option. Dans l’exemple suivant, des propriétés sont ajoutées au modèle Starship décrit dans la section Exemple de formulaire de l’article Composants d’entrée :

[Required]
[Range(typeof(Manufacturer), nameof(Manufacturer.SpaceX), 
    nameof(Manufacturer.VirginGalactic), ErrorMessage = "Pick a manufacturer.")]
public Manufacturer Manufacturer { get; set; } = Manufacturer.Unknown;

[Required, EnumDataType(typeof(Color))]
public Color? Color { get; set; } = null;

[Required, EnumDataType(typeof(Engine))]
public Engine? Engine { get; set; } = null;

Mettez à jour le formulaire Starfleet Starship Database ( composant Starship3) de la section Exemple de formulaire de l’article Composants d’entrée. Ajoutez les composants à produire :

  • Un groupe de cases d’option pour le fabricant du navire.
  • Un groupe de cases d’option imbriquées pour la couleur du moteur et du navire.

Remarque

Les groupes de cases d’option imbriquées ne sont pas souvent utilisés dans les formulaires, car ils peuvent entraîner une disposition désorganisée des contrôles de formulaire pouvant perturber les utilisateurs. Toutefois, dans certains cas, elles ont du sens dans la conception de l’interface utilisateur, comme dans l’exemple suivant qui associe des recommandations pour deux entrées utilisateur, le moteur du navire et la couleur du navire. Un moteur et une couleur sont requis par la validation du formulaire. La disposition du formulaire utilise des InputRadioGroup<TValue> imbriquées pour coupler les recommandations de moteur et de couleur. Toutefois, l’utilisateur peut combiner n’importe quel moteur avec n’importe quelle couleur pour envoyer le formulaire.

Remarque

Assurez-vous de rendre la classe ComponentEnums disponible pour le composant de l’exemple suivant :

@using static ComponentEnums
<fieldset>
    <legend>Manufacturer</legend>
    <InputRadioGroup @bind-Value="Model!.Manufacturer">
        @foreach (var manufacturer in Enum.GetValues<Manufacturer>())
        {
            <div>
                <label>
                    <InputRadio Value="manufacturer" />
                    @manufacturer
                </label>
            </div>
        }
    </InputRadioGroup>
</fieldset>

<fieldset>
    <legend>Engine and Color</legend>
    <p>
        Engine and color pairs are recommended, but any
        combination of engine and color is allowed.
    </p>
    <InputRadioGroup Name="engine" @bind-Value="Model!.Engine">
        <InputRadioGroup Name="color" @bind-Value="Model!.Color">
            <div style="margin-bottom:5px">
                <div>
                    <label>
                        <InputRadio Name="engine" Value="Engine.Ion" />
                        Ion
                    </label>
                </div>
                <div>
                    <label>
                        <InputRadio Name="color" Value="Color.ImperialRed" />
                        Imperial Red
                    </label>
                </div>
            </div>
            <div style="margin-bottom:5px">
                <div>
                    <label>
                        <InputRadio Name="engine" Value="Engine.Plasma" />
                        Plasma
                    </label>
                </div>
                <div>
                    <label>
                        <InputRadio Name="color" Value="Color.SpacecruiserGreen" />
                        Spacecruiser Green
                    </label>
                </div>
            </div>
            <div style="margin-bottom:5px">
                <div>
                    <label>
                        <InputRadio Name="engine" Value="Engine.Fusion" />
                        Fusion
                    </label>
                </div>
                <div>
                    <label>
                        <InputRadio Name="color" Value="Color.StarshipBlue" />
                        Starship Blue
                    </label>
                </div>
            </div>
            <div style="margin-bottom:5px">
                <div>
                    <label>
                        <InputRadio Name="engine" Value="Engine.Warp" />
                        Warp
                    </label>
                </div>
                <div>
                    <label>
                        <InputRadio Name="color" Value="Color.VoyagerOrange" />
                        Voyager Orange
                    </label>
                </div>
            </div>
        </InputRadioGroup>
    </InputRadioGroup>
</fieldset>

Remarque

Si Name est omis, les composants InputRadio<TValue> sont regroupés par leur ancêtre le plus récent.

Si vous avez implémenté le balisage Razor précédent dans le composant Starship3 de la section Exemple de formulaire de l’article Composants d’entrée, mettez à jour la journalisation pour la méthode Submit :

Logger.LogInformation("Id = {Id} Description = {Description} " +
    "Classification = {Classification} MaximumAccommodation = " +
    "{MaximumAccommodation} IsValidatedDesign = " +
    "{IsValidatedDesign} ProductionDate = {ProductionDate} " +
    "Manufacturer = {Manufacturer}, Engine = {Engine}, " +
    "Color = {Color}",
    Model?.Id, Model?.Description, Model?.Classification,
    Model?.MaximumAccommodation, Model?.IsValidatedDesign,
    Model?.ProductionDate, Model?.Manufacturer, Model?.Engine, 
    Model?.Color);

Lorsque vous utilisez des cases d’option dans un formulaire, la liaison de données est gérée différemment des autres éléments, car les cases d’option sont évaluées en tant que groupe. La valeur de chaque case d’option est fixe, mais la valeur du groupe de cases d’option est la valeur de la case d’option sélectionnée. L’exemple suivant montre comment vous :

  • Gérez la liaison de données pour un groupe de cases d’option.
  • Prise en charge de la validation à l’aide d’un composant InputRadio<TValue> personnalisé.

InputRadio.razor:

@using System.Globalization
@inherits InputBase<TValue>
@typeparam TValue

<input @attributes="AdditionalAttributes" type="radio" value="@SelectedValue" 
       checked="@(SelectedValue.Equals(Value))" @onchange="OnChange" />

@code {
    [Parameter]
    public TValue SelectedValue { get; set; }

    private void OnChange(ChangeEventArgs args)
    {
        CurrentValueAsString = args.Value.ToString();
    }

    protected override bool TryParseValueFromString(string value, 
        out TValue result, out string errorMessage)
    {
        var success = BindConverter.TryConvertTo<TValue>(
            value, CultureInfo.CurrentCulture, out var parsedValue);
        if (success)
        {
            result = parsedValue;
            errorMessage = null;

            return true;
        }
        else
        {
            result = default;
            errorMessage = "The field isn't valid.";

            return false;
        }
    }
}

Pour plus d’informations sur les paramètres de type générique (@typeparam), consultez les articles suivants :

Utilisez l’exemple de modèle suivant.

StarshipModel.cs:

using System.ComponentModel.DataAnnotations;

namespace BlazorServer80
{
    public class Model
    {
        [Range(1, 5)]
        public int Rating { get; set; }
    }
}

Le composant RadioButtonExample suivant utilise le composant InputRadio précédent pour obtenir et valider une évaluation de l’utilisateur :

RadioButtonExample.razor:

@page "/radio-button-example"
@using System.ComponentModel.DataAnnotations
@using Microsoft.Extensions.Logging
@inject ILogger<RadioButtonExample> Logger

<h1>Radio Button Example</h1>

<EditForm Model="Model" OnValidSubmit="HandleValidSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary />

    @for (int i = 1; i <= 5; i++)
    {
        <div>
            <label>
                <InputRadio name="rate" SelectedValue="i" 
                    @bind-Value="Model.Rating" />
                @i
            </label>
        </div>
    }

    <div>
        <button type="submit">Submit</button>
    </div>
</EditForm>

<div>@Model.Rating</div>

@code {
    public StarshipModel Model { get; set; }

    protected override void OnInitialized() => Model ??= new();

    private void HandleValidSubmit()
    {
        Logger.LogInformation("HandleValidSubmit called");
    }
}