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 10 di questo articolo.

Avviso

Questa versione di ASP.NET Core non è più supportata. Per altre informazioni, vedere i criteri di supporto di .NET e .NET Core. Per la versione corrente, vedere la versione .NET 10 di questo articolo.

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

EditForm / EditContext modello

Un EditForm crea un EditContext basato sull'oggetto assegnato come valore a cascata per gli altri componenti del modulo. Il EditContext tiene traccia dei metadati relativi al processo di modifica, inclusi i campi del modulo che sono stati modificati e gli attuali messaggi di convalida. L'assegnazione a un EditForm.Model o a un EditForm.EditContext può collegare un modulo ai dati.

Associazione di modelli

Assegnazione a EditForm.Model:

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

@code {
    [SupplyParameterFromForm]
    private 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]
    private 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 o un oppure un a un . Se vengono assegnati entrambi, viene generato un errore di runtime.

Tipi supportati

Supporti di rilegatura:

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

Puoi anche utilizzare gli attributi [DataMember] e [IgnoreDataMember] per personalizzare il binding del modello. Usare questi attributi per rinominare le proprietà, ignorare le proprietà e contrassegnare le proprietà come richiesto.

Quando si associa un tipo con parametri del costruttore, se un parametro del costruttore corrisponde a una proprietà in base al nome, il parametro del costruttore ha la precedenza. Il mapper usa il valore esplicito DataMember.Name della proprietà, se presente, come nome del campo del modulo, ma altrimenti ignora gli attributi di mapping della proprietà. I parametri del costruttore sono sempre obbligatori.

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 form viene controllato solo quando il form viene inviato a un endpoint tramite una tradizionale richiesta HTTP POST da un componente lato server renderizzato staticamente. 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.

Esiste 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 collisioni di nomi di form, ad esempio quando si include un form da una libreria e non si ha alcun controllo sul nome del form utilizzato dallo sviluppatore della libreria, specificare un ambito per il nome del form con il componente FormMappingScope nel progetto principale di Blazor Web App.

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]
    private 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 nome dell'ambito del componente FormMappingScope è ParentContext per tutti i moduli forniti dal componente HelloFormFromLibrary. 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]
    private 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à . Gli input basati su InputBase<TValue> generano nomi di valori del modulo che corrispondono ai nomi usati da Blazor per il binding del modello. A differenza delle proprietà dei parametri del componente ([Parameter]), le proprietà annotate con [SupplyParameterFromForm] non devono essere contrassegnate come public.

Blazor Il mapping dei moduli con [SupplyParameterFromForm] non usa l'associazione di modelli MVC. Gli attributi nello spazio dei nomi Microsoft.AspNetCore.Mvc.ModelBinding, ad esempio [BindNever] e [BindRequired], non sono supportati. Non usare questi attributi per impedire l'overposting. Usare invece un modello di modulo dedicato, un modello di visualizzazione o un oggetto DTO (Data Transfer Object) che include solo le proprietà che gli utenti possono modificare. Per ulteriori informazioni, vedi Mitigare gli attacchi di overposting.

È 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 dell'handler. 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")]
    private Holodeck? Model1 { get; set; }

    [SupplyParameterFromForm(FormName = "Holodeck2")]
    private 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; }
    }
}
@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")]
    private Holodeck? Model1 { get; set; }

    [SupplyParameterFromForm(FormName = "Holodeck2")]
    private 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 collegare moduli

Le indicazioni seguenti illustrano come annidare e associare moduli figlio.

La seguente classe dei dettagli di spedizione (ShipDetails) contiene una descrizione e una lunghezza per un sottomodulo.

ShipDetails.cs:

namespace BlazorSample;

public class ShipDetails
{
    public string? Description { get; set; }
    public int? Length { get; set; }
}
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();
    }
}
namespace BlazorSample
{
    public class Ship
    {
        public string? Id { get; set; }
        public ShipDetails Details { get; set; } = new();
    }
}

La sottomaschera seguente viene utilizzata per modificare i valori del tipo ShipDetails. 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>
@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]
    private 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);
}
@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]
    private 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);
}

Inizializza i dati del modulo con SSR statica

Quando un componente adotta SSR statico, i metodi del ciclo di vita OnInitialized{Async} e vengono attivati quando il componente viene inizialmente sottoposto a rendering e a ogni POST di modulo al server. Per inizializzare i valori del modello di modulo, verificare se il modello dispone già di dati prima di assegnare nuovi valori del modello in OnParametersSet{Async}, come illustrato nell'esempio seguente.

StarshipInit.razor:

@page "/starship-init"
@inject ILogger<StarshipInit> Logger

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

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

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

    protected override void OnParametersSet()
    {
        if (Model!.Id == default)
        {
            LoadData();
        }
    }

    private void LoadData()
    {
        Model!.Id = "Set by LoadData";
    }

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

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

Scenari di errore avanzati della mappatura dei moduli

Il framework istanzia e popola FormMappingContext per un modulo, ovvero il contesto associato all'operazione di mapping di un determinato modulo. Ogni ambito di mapping (definito da un componente FormMappingScope) crea un'istanza di FormMappingContext. Ogni volta che un [SupplyParameterFromForm] richiede un valore al contesto, il framework popola FormMappingContext con il valore fornito nel tentativo 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 a FormMappingContext direttamente come [CascadingParameter] per scrivere codice personalizzato che utilizza i valori immessi e gli errori di mappatura.

Componenti di input personalizzati

Per scenari di elaborazione di input personalizzati, le sottosezioni seguenti illustrano i componenti di input personalizzati:

Ti consigliamo di far derivare i componenti di input personalizzati da InputBase<TValue>, a meno che requisiti specifici non lo impediscano. La InputBase<TValue> classe viene gestita attivamente dal team di ASP.NET Core, assicurandosi che rimanga aggiornata con le funzionalità e le modifiche del framework più recenti Blazor .

Componente di input basato su InputBase<T>

Componente di esempio seguente:

  • Eredita dall'oggetto InputBase<TValue>. I componenti che ereditano da InputBase<TValue> devono essere usati all'interno di un Blazor form (EditForm).
  • Accetta l'input booleano da una casella di controllo.
  • Imposta il colore di sfondo del contenitore <div> in base allo stato della casella di controllo, che si verifica quando il metodo viene eseguito dopo l'associazione AfterChange (@bind:after).
  • È necessario per eseguire l'override del metodo della classe base TryParseValueFromString, ma non elabora i dati di input di tipo stringa perché una casella di controllo non fornisce dati di tipo stringa. Sono disponibili nel codice sorgente di riferimento di ASP.NET Core implementazioni di esempio di TryParseValueFromString per altri tipi di componenti di input che elaborano input di stringhe.

Nota

I collegamenti della documentazione al codice sorgente di riferimento di .NET in genere caricano il ramo predefinito del repository, che rappresenta lo sviluppo attuale per la versione successiva di .NET. Per selezionare un tag per una release specifica, utilizza il menu a discesa Switch branches or tags. Per altre informazioni, vedere How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205) (Come selezionare un tag di versione del codice sorgente di ASP.NET - dotnet/AspNetCore.Docs #26205).

EngineeringApprovalInputDerived.razor:

@using System.Diagnostics.CodeAnalysis
@inherits InputBase<bool>

<div class="@divCssClass">
    <label>
        Engineering Approval:
        <input @bind="CurrentValue" @bind:after="AfterChange" class="@CssClass" 
            type="checkbox" />
    </label>
</div>

@code {
    private string? divCssClass;

    private void AfterChange()
    {
        divCssClass = CurrentValue ? "bg-success text-white" : null;
    }

    protected override bool TryParseValueFromString(
        string? value, out bool result, 
        [NotNullWhen(false)] out string? validationErrorMessage)
            => throw new NotSupportedException(
                "This component does not parse string inputs. " +
                $"Bind to the '{nameof(CurrentValue)}' property, " +
                $"not '{nameof(CurrentValueAsString)}'.");
}

Per usare il componente precedente nel modulo di esempio starship (Starship3.razor/Starship.cs), sostituire il blocco <div> per il campo di approvazione tecnica con un'istanza del componente EngineeringApprovalInputDerived associata alla proprietà IsValidatedDesign del modello:

- <div>
-     <label>
-         Engineering Approval: 
-         <InputCheckbox @bind-Value="Model!.IsValidatedDesign" />
-     </label>
- </div>
+ <EngineeringApprovalInputDerived @bind-Value="Model!.IsValidatedDesign" />

Se viene eseguito il rendering statico del componente che eredita da InputBase<TValue>, assegnare la proprietà InputBase<TValue>.NameAttributeValue all'attributo name degli elementi <input>:

<input @bind="CurrentValue" @bind:after="AfterChange" class="@CssClass"
    type="checkbox" name="@NameAttributeValue" />

L'assegnazione precedente non è necessaria se il componente viene sempre eseguito in modo interattivo.

Componente di input con controllo completo per sviluppatori

Componente di esempio seguente:

  • Non eredita da InputBase<TValue>. Il componente acquisisce il controllo completo dell'elaborazione dell'input, tra cui associazione, callback e convalida. Il componente può essere utilizzato all'interno o all'esterno di un Blazor form (EditForm).
  • Accetta l'input booleano da una casella di controllo.
  • Modifica il colore di sfondo se la casella di controllo è selezionata.

Il codice nel componente include:

  • La Value proprietà viene utilizzata con l'associazione bidirezionale per ottenere o impostare il valore dell'input. ValueChanged è il callback che aggiorna il valore associato.

  • Quando viene utilizzato in un modulo Blazor:

    • EditContext è un valore a catena.
    • fieldCssClass formatta il campo in base al risultato di EditContext convalida.
    • ValueExpression è un'espressione (Expression<Func<T>>) assegnata dal framework che identifica il valore associato.
    • FieldIdentifier identifica in modo univoco un singolo campo che può essere modificato, in genere corrispondente a una proprietà del modello. L'identificatore del campo viene creato con l'espressione che identifica il valore associato (ValueExpression).
  • OnChange Nel gestore eventi:

    • Il valore dell'input della casella di controllo viene ottenuto da InputFileChangeEventArgs.
    • Vengono impostati il colore di sfondo e il colore del testo dell'elemento contenitore <div> .
    • EventCallback.InvokeAsync richiama il delegato associato al binding e invia una notifica ai consumer per segnalare che il valore è cambiato.
    • Se il componente viene usato in un EditForm (la proprietà EditContext non è null), viene chiamato EditContext.NotifyFieldChanged per attivare la convalida.

EngineeringApprovalInputStandalone.razor:

@using System.Globalization
@using System.Linq.Expressions

<div class="@divCssClass">
    <label>
        Engineering Approval:
        <input class="@fieldCssClass" @onchange="OnChange" type="checkbox" 
            value="@Value" />
    </label>
</div>

@code {
    private string? divCssClass;
    private FieldIdentifier fieldIdentifier;
    private string? fieldCssClass => EditContext?.FieldCssClass(fieldIdentifier);

    [CascadingParameter]
    private EditContext? EditContext { get; set; }

    [Parameter]
    public bool? Value { get; set; }

    [Parameter]
    public EventCallback<bool> ValueChanged { get; set; }

    [Parameter]
    public Expression<Func<bool>>? ValueExpression { get; set; }

    protected override void OnInitialized()
    {
        fieldIdentifier = FieldIdentifier.Create(ValueExpression!);
    }

    private async Task OnChange(ChangeEventArgs args)
    {
        BindConverter.TryConvertToBool(args.Value, CultureInfo.CurrentCulture, 
            out var value);

        divCssClass = value ? "bg-success text-white" : null;

        await ValueChanged.InvokeAsync(value);
        EditContext?.NotifyFieldChanged(fieldIdentifier);
    }
}

Per usare il componente precedente nel form di esempio Starship (Starship3.razor/Starship.cs), sostituisci il blocco <div> per il campo di approvazione dell'ingegneria con un'istanza del componente EngineeringApprovalInputStandalone associata alla proprietà IsValidatedDesign del modello:

- <div>
-     <label>
-         Engineering Approval: 
-         <InputCheckbox @bind-Value="Model!.IsValidatedDesign" />
-     </label>
- </div>
+ <EngineeringApprovalInputStandalone @bind-Value="Model!.IsValidatedDesign" />

Il EngineeringApprovalInputStandalone componente è anche funzionale all'esterno di un oggetto EditForm:

<EngineeringApprovalInputStandalone @bind-Value="ValidDesign" />

<div>
    <b>ValidDesign:</b> @ValidDesign
</div>

@code {
    private bool ValidDesign { get; set; }
}

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 seguenti enum tipi 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 ComponentEnums classe accessibile a:

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

Usa i componenti InputRadio<TValue> insieme al componente InputRadioGroup<TValue> per creare un gruppo di pulsanti di scelta. 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 (componente Starship3) della sezione Modulo di esempio dell'articolo Componenti di input. Aggiungere i componenti da produrre:

  • Gruppo di pulsanti di opzione per il costruttore navale.
  • Un gruppo di pulsanti di opzione annidati per il motore e per il colore della nave.

Nota

I gruppi annidati di pulsanti di scelta non sono spesso utilizzati nei moduli perché possono comportare una disposizione disordinata dei controlli del modulo che può confondere gli utenti. Tuttavia, ci sono casi in cui hanno senso nella progettazione dell’interfaccia utente, come nell’esempio seguente, che abbina suggerimenti per due input dell’utente: motore della nave e colore della nave. Un motore e un colore sono richiesti dalla convalida del modulo. La struttura del modulo usa tag InputRadioGroup<TValue> annidati per abbinare le raccomandazioni su motore e colore. 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 hai implementato il markup precedente Razor nel componente Starship3 della sezione modulo di esempio dell'articolo Componenti di input, aggiorna la registrazione del metodo 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);

Quando si lavora con i pulsanti di opzione in un modulo, l'associazione dei dati viene gestita in modo diverso rispetto agli altri elementi perché i pulsanti di opzione vengono valutati come un 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:

  • Gestisci l'associazione dati per un gruppo di pulsanti di scelta.
  • 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");
    }
}