ASP.NET binding dei moduli principali Blazor

Nota

Questa non è la versione più recente di questo articolo. Per la versione corrente, vedere la versione .NET 8 di questo articolo.

Importante

Queste informazioni si riferiscono a un prodotto non definitive che può essere modificato in modo sostanziale prima che venga rilasciato commercialmente. Microsoft non riconosce alcuna garanzia, espressa o implicita, in merito alle informazioni qui fornite.

Per la versione corrente, vedere la versione .NET 8 di questo articolo.

Questo articolo illustra come usare l'associazione nei Blazor moduli.

EditForm/EditContext modello

Un EditForm oggetto crea un EditContext oggetto basato sull'oggetto assegnato come valore a catena per altri componenti nel form. Tiene EditContext traccia dei metadati relativi al processo di modifica, inclusi i campi modulo modificati e i messaggi di convalida correnti. L'assegnazione a un oggetto EditForm.Model o può EditForm.EditContext associare un modulo ai dati.

Associazione di modelli

Assegnazione a 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();
}

Nota

La maggior parte degli esempi di modelli di modulo di questo articolo associa moduli alle proprietà C#, ma è supportata anche l'associazione di campi C#.

Associazione di contesto

Assegnazione a 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);
    }
}

Assegnare un oggetto EditContexto a Model un oggetto EditForm. Se vengono assegnati entrambi, viene generato un errore di runtime.

Tipi supportati

L'associazione supporta:

  • Tipi primitivi
  • Raccolte
  • Tipi complessi
  • Tipi ricorsivi
  • Tipi con costruttori
  • Enumerazioni

È anche possibile usare gli attributi e [IgnoreDataMember] per personalizzare l'associazione [DataMember] di modelli. Usare questi attributi per rinominare le proprietà, ignorare le proprietà e contrassegnare le proprietà come richiesto.

Opzioni di associazione aggiuntive

Sono disponibili opzioni aggiuntive per l'associazione di modelli da RazorComponentsServiceOptions quando si chiama AddRazorComponents:

Di seguito vengono illustrati i valori predefiniti assegnati dal framework:

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

Nomi dei moduli

Usare il FormName parametro per assegnare un nome di modulo. I nomi dei moduli devono essere univoci per associare i dati del modello. Il formato seguente è denominato RomulanAle:

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

Specificare un nome di modulo:

  • È necessario per tutti i moduli inviati da componenti lato server sottoposti a rendering statico.
  • Non è necessario per i moduli inviati da componenti di cui è stato eseguito il rendering interattivo, che include moduli nelle app e nei Blazor WebAssembly componenti con una modalità di rendering interattiva. È tuttavia consigliabile specificare un nome di modulo univoco per ogni modulo per evitare errori di pubblicazione dei moduli in fase di esecuzione se l'interattività viene mai eliminata per un modulo.

Il nome del modulo viene controllato solo quando il modulo viene inviato a un endpoint come richiesta HTTP POST tradizionale da un componente lato server sottoposto a rendering statico. Il framework non genera un'eccezione al momento del rendering di un modulo, ma solo nel momento in cui arriva un HTTP POST e non specifica un nome di modulo.

Per impostazione predefinita, è presente un ambito di modulo senza nome (stringa vuota) sopra il componente radice dell'app, che è sufficiente quando non sono presenti conflitti di nomi di modulo nell'app. Se sono possibili conflitti di nome modulo, ad esempio quando si include un modulo da una libreria e non si dispone di alcun controllo del nome del modulo usato dallo sviluppatore della libreria, fornire un ambito di nome modulo con il FormMappingScope componente nel Blazor progetto principale dell'app Web.

Nell'esempio seguente il HelloFormFromLibrary componente ha un form denominato Hello e si trova in una libreria.

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;
}

Il componente seguente NamedFormsWithScope usa il componente della HelloFormFromLibrary libreria e ha anche un modulo denominato Hello. Il FormMappingScope nome dell'ambito del componente è ParentContext per tutti i moduli forniti dal HelloFormFromLibrary componente. Anche se entrambi i moduli in questo esempio hanno il nome del modulo (Hello), i nomi dei moduli non si scontrano e gli eventi vengono indirizzati al modulo corretto per gli eventi POST del modulo.

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;
}

Specificare un parametro dal modulo ([SupplyParameterFromForm])

L'attributo [SupplyParameterFromForm] indica che il valore della proprietà associata deve essere fornito dai dati del modulo per il modulo. I dati nella richiesta che corrispondono al nome della proprietà sono associati alla proprietà . Input basati su InputBase<TValue> genera nomi di valori di modulo che corrispondono ai nomi Blazor usati per l'associazione di modelli.

È possibile specificare i parametri di associazione del modulo seguenti all'attributo [SupplyParameterFromForm]:

  • Name: ottiene o imposta il nome del parametro. Il nome viene utilizzato per determinare il prefisso da utilizzare per trovare le corrispondenze con i dati del modulo e decidere se il valore deve essere associato o meno.
  • FormName: ottiene o imposta il nome del gestore. Il nome viene utilizzato per associare il parametro al formato in base al nome del modulo per decidere se il valore deve essere associato o meno.

Nell'esempio seguente vengono associati in modo indipendente due forme ai relativi modelli in base al nome del modulo.

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; }
    }
}

Annidare e associare moduli

Le indicazioni seguenti illustrano come annidare e associare moduli figlio.

La classe dettagli spedizione seguente (ShipDetails) contiene una descrizione e una lunghezza per una sottomaschera.

ShipDetails.cs:

namespace BlazorSample;

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

La classe seguente Ship assegna un nome a un identificatore (Id) e include i dettagli della spedizione.

Ship.cs:

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

La sottomaschera seguente viene utilizzata per la modifica dei valori del ShipDetails tipo. Questa operazione viene implementata ereditando Editor<T> nella parte superiore del componente. Editor<T> garantisce che il componente figlio generi i nomi dei campi modulo corretti in base al modello (T), dove T nell'esempio seguente è ShipDetails.

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>

Il form principale è associato alla Ship classe . Il StarshipSubform componente viene usato per modificare i dettagli della spedizione, associati come 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);
    }
}

Scenari avanzati di errore di mapping dei moduli

Il framework crea un'istanza e popola l'oggetto FormMappingContext per una maschera, ovvero il contesto associato all'operazione di mapping di un modulo specificato. Ogni ambito di mapping (definito da un componente) crea FormMappingContextun'istanza FormMappingScope di . Ogni volta che un chiede [SupplyParameterFromForm] al contesto un valore, il framework popola FormMappingContext con il valore tentato ed eventuali errori di mapping.

Gli sviluppatori non devono interagire direttamente con FormMappingContext , perché è principalmente un'origine di dati per InputBase<TValue>, EditContexte altre implementazioni interne per mostrare gli errori di mapping come errori di convalida. Negli scenari personalizzati avanzati gli sviluppatori possono accedere FormMappingContext direttamente come oggetto [CascadingParameter] per scrivere codice personalizzato che utilizza i valori tentati e gli errori di mapping.

Pulsanti di opzione

L'esempio in questa sezione si basa sul Starfleet Starship Database modulo (Starship3 componente) della sezione Modulo di esempio di questo articolo.

Aggiungere i tipi seguenti enum all'app. Creare un nuovo file per memorizzarli o aggiungerli al Starship.cs file.

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 }
}

Rendere la enums classe accessibile a:

  • Starship modello in Starship.cs (ad esempio, using static ComponentEnums;).
  • Starfleet Starship Database form () (Starship3.razorad esempio, @using static ComponentEnums).

Usare InputRadio<TValue> i componenti con il InputRadioGroup<TValue> componente per creare un gruppo di pulsanti di opzione. Nell'esempio seguente le proprietà vengono aggiunte al Starship modello descritto nella sezione Modulo di esempio dell'articolo Componenti di input:

[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;

Aggiornare il Starfleet Starship Database modulo (Starship3 componente) della sezione Modulo di esempio dell'articolo Componenti di input. Aggiungere i componenti da produrre:

  • Gruppo di pulsanti di opzione per il produttore della nave.
  • Gruppo di pulsanti di opzione annidati per il motore e il colore della nave.

Nota

I gruppi di pulsanti di opzione annidati non vengono spesso usati nei moduli perché possono comportare un layout disorganizzato dei controlli modulo che possono confondere gli utenti. Tuttavia, esistono casi in cui hanno senso nella progettazione dell'interfaccia utente, ad esempio nell'esempio seguente che associa raccomandazioni per due input utente, motore di spedizione e colore spedizione. Un motore e un colore sono richiesti dalla convalida del modulo. Il layout del modulo usa s annidati InputRadioGroup<TValue>per associare le raccomandazioni relative al motore e ai colori. Tuttavia, l'utente può combinare qualsiasi motore con qualsiasi colore per inviare il modulo.

Nota

Assicurarsi di rendere disponibile la ComponentEnums classe per il componente per l'esempio seguente:

@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>

Nota

Se Name viene omesso, InputRadio<TValue> i componenti vengono raggruppati in base al predecessore più recente.

Se il markup precedente Razor è stato implementato nel Starship3 componente della sezione Modulo di esempio dell'articolo Componenti di input, aggiornare la registrazione per il Submit metodo :

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

Quando si usano pulsanti di opzione in un modulo, il data binding viene gestito in modo diverso rispetto ad altri elementi perché i pulsanti di opzione vengono valutati come gruppo. Il valore di ogni pulsante di opzione è fisso, ma il valore del gruppo di pulsanti di opzione è il valore del pulsante di opzione selezionato. L'esempio seguente illustra come:

  • Gestire il data binding per un gruppo di pulsanti di opzione.
  • Supportare la convalida usando un componente personalizzato InputRadio<TValue> .

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;
        }
    }
}

Per altre informazioni sui parametri di tipo generico (@typeparam), vedere gli articoli seguenti:

Usare il modello di esempio seguente.

StarshipModel.cs:

using System.ComponentModel.DataAnnotations;

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

Il componente seguente RadioButtonExample usa il componente precedente InputRadio per ottenere e convalidare una classificazione dall'utente:

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");
    }
}