wiązanie formularzy ASP.NET Core Blazor

Uwaga

Nie jest to najnowsza wersja tego artykułu. Aby zapoznać się z aktualną wersją, zobacz artykuł w wersji .NET 10.

Ostrzeżenie

Ta wersja ASP.NET Core nie jest już obsługiwana. Aby uzyskać więcej informacji, zobacz zasady pomocy technicznej platformy .NET i platformy .NET Core. Aby zapoznać się z aktualną wersją, zobacz artykuł w wersji .NET 10.

W tym artykule wyjaśniono, jak używać powiązania w Blazor formularzach.

EditForm / EditContext model

EditForm tworzy element EditContext na podstawie przypisanego obiektu jako wartość kaskadową dla innych komponentów w formularzu. Śledzi EditContext metadane dotyczące procesu edycji, w tym pola formularza, które zostały zmodyfikowane, oraz bieżące komunikaty sprawdzania poprawności. Przypisanie do obiektu EditForm.Model lub EditForm.EditContext może powiązać formularz z danymi.

Wiązanie modelu

Przypisanie do 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();
}

Uwaga

Większość przykładów modelu formularzy tego artykułu wiąże formularze z właściwościami języka C#, ale obsługiwane jest również powiązanie pól języka C#.

Powiązanie kontekstu

Przypisanie do 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);
    }
}

Przypisz do albo , albo . Jeśli oba te elementy zostaną przypisane, zostanie zgłoszony błąd środowiska uruchomieniowego.

Obsługiwane typy

Obsługiwane powiązania:

  • Typy pierwotne
  • Kolekcje
  • Typy złożone
  • Typy rekursywne
  • Typy z konstruktorami
  • Wyliczenia

Można również użyć atrybutów [DataMember] i [IgnoreDataMember], aby dostosować wiązanie modelu. Użyj tych atrybutów, aby zmienić nazwy właściwości, zignorować właściwości i oznaczyć właściwości zgodnie z potrzebami.

W przypadku powiązania typu z parametrami konstruktora, jeśli parametr konstruktora ma taką samą nazwę jak właściwość, parametr konstruktora ma priorytet. Moduł mapujący używa jawnie określonego elementu DataMember.Name, jeśli jest obecny, jako nazwy pola formularza, ale poza tym ignoruje atrybuty mapowania właściwości. Parametry konstruktora są zawsze wymagane.

Dodatkowe opcje powiązania

Dodatkowe opcje wiązania modelu są dostępne w elemencie RazorComponentsServiceOptions podczas wywoływania AddRazorComponents:

Poniżej przedstawiono wartości domyślne przypisane przez strukturę:

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

Nazwy formularzy

Użyj parametru , FormName aby przypisać nazwę formularza. Nazwy formularzy muszą być unikatowe w celu powiązania danych modelu. Następujący formularz nosi nazwę RomulanAle:

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

Podawanie nazwy formularza:

  • Jest to wymagane w przypadku wszystkich formularzy wysyłanych przez statycznie renderowane komponenty serwerowe.
  • Nie jest wymagane w przypadku formularzy przesyłanych przez składniki renderowane interaktywnie, w tym formularze w Blazor WebAssembly aplikacjach i składnikach z trybem renderowania interakcyjnego. Zalecamy jednak nadanie unikalnej nazwy każdemu formularzowi, aby zapobiec błędom podczas przesyłania formularza w czasie działania, jeśli formularz kiedykolwiek utraci interaktywność.

Nazwa formularza jest sprawdzana tylko wtedy, gdy formularz jest publikowany w punkcie końcowym jako tradycyjne żądanie HTTP POST ze statycznie renderowanego składnika po stronie serwera. Struktura nie zgłasza wyjątku w momencie renderowania formularza, ale tylko w momencie nadejścia żądania HTTP POST i nie określa nazwy formularza.

Nad głównym komponentem aplikacji istnieje nienazwany zakres formularzy (pusty ciąg znaków), co jest wystarczające, jeśli w aplikacji nie występują kolizje nazw formularzy. Jeśli możliwe są konflikty nazw formularzy, na przykład podczas dołączania formularza z biblioteki, a nie masz wpływu na nazwę formularza używaną przez twórcę biblioteki, określ zakres nazw formularzy za pomocą komponentu FormMappingScope w głównym projekcie Blazor Web App.

W poniższym przykładzie HelloFormFromLibrary składnik ma postać o nazwie Hello i znajduje się w bibliotece.

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

Poniższy NamedFormsWithScope składnik używa składnika biblioteki HelloFormFromLibrary , a także ma formularz o nazwie Hello. Nazwa zakresu komponentu FormMappingScope dla wszystkich formularzy dostarczanych przez komponent HelloFormFromLibrary to ParentContext. Mimo że oba formularze w tym przykładzie mają tę samą nazwę formularza (Hello), nazwy formularzy nie powodują konfliktu, a zdarzenia są kierowane do właściwego formularza w przypadku zdarzeń POST formularza.

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

Podaj parametr z formularza ([SupplyParameterFromForm])

Atrybut [SupplyParameterFromForm] wskazuje, że wartość skojarzonej właściwości powinna pochodzić z danych tego formularza. Dane w żądaniu, które pasują do nazwy właściwości, są powiązane z właściwością. Pola wejściowe oparte na InputBase<TValue> generują nazwy wartości formularza zgodne z nazwami używanymi przez Blazor do wiązania modelu. W przeciwieństwie do właściwości parametrów składnika ([Parameter]) właściwości oznaczone adnotacją [SupplyParameterFromForm] nie muszą być oznaczone jako public.

Blazor mapowanie formularza przy użyciu [SupplyParameterFromForm] nie używa wiązania modelu MVC. Atrybuty w Microsoft.AspNetCore.Mvc.ModelBinding przestrzeni nazw, takie jak [BindNever] i [BindRequired], nie są obsługiwane. Nie używaj tych atrybutów, aby zapobiec przepostowaniu. Zamiast tego należy użyć dedykowanego modelu formularza, modelu wyświetlania lub obiektu transferu danych (DTO), który zawiera tylko właściwości, które użytkownicy mogą modyfikować. Aby uzyskać więcej informacji, zobacz Ograniczanie ataków typu overposting.

Do atrybutu [SupplyParameterFromForm]można określić następujące parametry powiązania formularza:

  • Name: Pobiera lub ustawia nazwę parametru. Nazwa służy do określania prefiksu używanego do dopasowania danych formularza i decydowania, czy wartość musi być powiązana.
  • FormName: Pobiera lub ustawia nazwę programu obsługi. Nazwa jest używana do dopasowania parametru do formularza na podstawie nazwy formularza w celu określenia, czy wartość należy powiązać.

Poniższy przykład niezależnie powiązuje dwa formularze z odpowiednimi modelami na podstawie nazwy formularza.

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

Zagnieżdżanie i wiązanie formularzy

Poniższe instrukcje pokazują, jak zagnieżdżać i powiązać formularze podrzędne.

Poniższa klasa szczegółów statku (ShipDetails) zawiera opis oraz długość podformularza.

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

Poniższa klasa Ship określa identyfikator (Id) i zawiera szczegóły statku.

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

Poniższy podformularz jest używany do edytowania wartości typu ShipDetails. Jest to realizowane przez dziedziczenie Editor<T> na początku komponentu. Editor<T>gwarantuje, że składnik podrzędny generuje poprawne nazwy pól formularza na podstawie modelu (T), gdzie T w poniższym przykładzie jest .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>

Formularz główny jest powiązany z klasą Ship . Komponent StarshipSubform służy do edytowania szczegółów statku, powiązany jako 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);
}

Zainicjuj dane formularza za pomocą statycznego SSR

Gdy składnik używa statycznego SSR, metoda cyklu życia OnInitialized{Async} lifecycle method oraz metoda cyklu życia OnParametersSet{Async} lifecycle method są wywoływane podczas początkowego renderowania składnika oraz przy każdym żądaniu POST formularza wysyłanym do serwera. Aby zainicjować wartości modelu formularza, upewnij się, że model ma już dane, zanim przypiszesz nowe wartości modelu w OnParametersSet{Async}, co ilustruje poniższy przykład.

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

Zaawansowane scenariusze błędów mapowania formularzy

Framework tworzy instancję i wypełnia element FormMappingContext dla formularza, który jest kontekstem skojarzonym z operacją mapowania danego formularza. Każdy zakres mapowania (zdefiniowany przez komponent FormMappingScope) tworzy instancję FormMappingContext. Za każdym razem, gdy element [SupplyParameterFromForm] żąda od kontekstu wartości, platforma wypełnia element FormMappingContext próbowaną wartością oraz wszelkimi błędami mapowania.

Nie oczekuje się, że deweloperzy będą wchodzić w bezpośrednią interakcję z FormMappingContext, ponieważ jest to głównie źródło danych dla InputBase<TValue>, EditContext i innych wewnętrznych implementacji do wyświetlania błędów mapowania jako błędów walidacji. W zaawansowanych niestandardowych scenariuszach deweloperzy mogą uzyskać bezpośredni dostęp do FormMappingContext jako [CascadingParameter], aby pisać niestandardowy kod, który wykorzystuje wartości, których próbowano użyć, oraz błędy mapowania.

Niestandardowe składniki wejściowe

W przypadku niestandardowych scenariuszy przetwarzania danych wejściowych w poniższych podsekcjach przedstawiono niestandardowe składniki wejściowe:

Zalecamy, aby niestandardowe komponenty wejściowe wyprowadzać z InputBase<TValue>, chyba że uniemożliwiają to określone wymagania. Klasa InputBase<TValue> jest aktywnie utrzymywana przez zespół ASP.NET Core, zapewniając, że jest aktualna dzięki najnowszym Blazor funkcjom i zmianom struktury.

Komponent wprowadzania oparty na InputBase<T>

Poniższy przykładowy składnik:

  • Dziedziczy po InputBase<TValue>. Komponenty dziedziczące po InputBase<TValue> muszą być używane w formularzu Blazor (EditForm).
  • Pobiera dane wejściowe logiczne z pola wyboru.
  • Ustawia kolor tła kontenera <div> na podstawie stanu pola wyboru, co następuje, gdy po powiązaniu danych (@bind:after) wykonywana jest metoda AfterChange.
  • Należy przesłonić metodę TryParseValueFromString klasy bazowej, ale nie przetwarza ona danych wejściowych w postaci ciągu znaków, ponieważ pole wyboru nie dostarcza takich danych. Przykładowe implementacje TryParseValueFromString dla innych typów komponentów wejściowych, które przetwarzają dane wejściowe w postaci ciągu, są dostępne w kodzie źródłowym dokumentacji referencyjnej platformy ASP.NET Core.

Uwaga

Linki w dokumentacji do referencyjnego kodu źródłowego platformy .NET zwykle otwierają domyślną gałąź repozytorium, która odzwierciedla bieżący stan prac rozwojowych nad następną wersją platformy .NET. Aby wybrać tag dla określonej wersji, użyj listy rozwijanej Przełącz gałęzie lub tagi. Aby uzyskać więcej informacji, zobacz Jak wybrać tag wersji kodu źródłowego platformy ASP.NET Core (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)}'.");
}

Aby użyć poprzedniego składnika w przykładowym formularzu przykładu starship (Starship3.razor/Starship.cs), zastąp blok <div> dla pola zatwierdzenia inżynieryjnego wystąpieniem składnika EngineeringApprovalInputDerived, powiązanym z właściwością IsValidatedDesign modelu:

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

Jeśli składnik dziedziczony z InputBase<TValue> jest zawsze renderowany statycznie, przypisz właściwość InputBase<TValue>.NameAttributeValue do atrybutu name elementów <input>:

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

Powyższe przypisanie nie jest konieczne, jeśli składnik ma gwarancję, że zawsze jest renderowany interaktywnie.

Komponent do wprowadzania danych zapewniający programiście pełną kontrolę

Poniższy przykładowy składnik:

  • Nie dziedziczy po InputBase<TValue>. Składnik przejmuje pełną kontrolę nad przetwarzaniem wejściowym, w tym powiązaniem, wywołaniami zwrotnymi i walidacją. Składnik może być używany wewnątrz lub na zewnątrz Blazor formularza (EditForm).
  • Pobiera dane wejściowe logiczne z pola wyboru.
  • Zmienia kolor tła, jeśli pole wyboru jest zaznaczone.

Kod w komponencie zawiera:

  • Właściwość Value jest używana z powiązaniem dwukierunkowym, aby uzyskać lub ustawić wartość danych wejściowych. ValueChanged to wywołanie zwrotne, które aktualizuje powiązaną wartość.

  • W przypadku użycia w formularzu Blazor :

    • EditContext jest wartością kaskadową.
    • fieldCssClass stylizuje pole na podstawie wyniku walidacji EditContext.
    • ValueExpression jest wyrażeniem (Expression<Func<T>>) przypisanym przez strukturę identyfikującą powiązaną wartość.
    • FieldIdentifier unikatowo identyfikuje jedno pole, które można edytować, zwykle odpowiadające właściwości modelu. Identyfikator pola jest tworzony za pomocą wyrażenia identyfikującego powiązaną wartość (ValueExpression).
  • W programie obsługi zdarzeń OnChange :

    • Wartość pola wyboru jest pobierana z elementu InputFileChangeEventArgs.
    • Ustawiono kolor tła i kolor tekstu elementu kontenera <div> .
    • EventCallback.InvokeAsync wywołuje delegata skojarzonego z powiązaniem i wysyła do odbiorców powiadomienie o zdarzeniu informujące, że wartość uległa zmianie.
    • Jeśli komponent jest używany w elemencie EditForm (właściwość EditContext nie ma wartości null), wywoływana jest metoda EditContext.NotifyFieldChanged, aby uruchomić walidację.

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

Aby skorzystać z poprzedniego składnika w przykładowym formularzu starship example form (Starship3.razor/Starship.cs), zastąp blok <div> pola zatwierdzenia inżynieryjnego instancją komponentu EngineeringApprovalInputStandalone powiązaną z właściwością modelu IsValidatedDesign:

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

Składnik EngineeringApprovalInputStandalone działa również poza elementem EditForm:

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

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

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

Przycisków

Przykład w tej sekcji jest oparty na formularzu Starfleet Starship Database (Starship3 składnik) sekcji Przykładowy formularz tego artykułu.

Dodaj następujące enum typy do aplikacji. Utwórz nowy plik do przechowywania lub dodaj go do Starship.cs pliku.

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

Nadaj klasie ComponentEnums dostęp dla:

  • Starship model w Starship.cs (na przykład using static ComponentEnums;).
  • Starfleet Starship Database formularz (Starship3.razor) (na przykład @using static ComponentEnums).

Użyj InputRadio<TValue> składników ze składnikiem InputRadioGroup<TValue> , aby utworzyć grupę przycisków radiowych. W poniższym przykładzie właściwości są dodawane do Starship modelu opisanego w sekcji Przykładowy formularz artykułu Składniki wejściowe:

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

Starfleet Starship Database Zaktualizuj formularz (Starship3składnik) sekcji Przykładowy formularz artykułu Składniki wejściowe. Dodaj komponenty, aby utworzyć:

  • Grupa przycisków radiowych dla producenta statku.
  • Zagnieżdżona grupa przycisków radiowych dla silnika oraz koloru statku.

Uwaga

Zagnieżdżone grupy przycisków radiowych nie są często używane w formularzach, ponieważ mogą one spowodować dezorganizowany układ kontrolek formularzy, które mogą mylić użytkowników. Istnieją jednak przypadki, w których mają one sens w projektowaniu interfejsu użytkownika, na przykład w poniższym przykładzie, który łączy rekomendacje dla dwóch pól wejściowych użytkownika: silnika statku i koloru statku. Formularz wymaga wybrania jednego silnika i jednego koloru. Układ formularza wykorzystuje zagnieżdżone InputRadioGroup<TValue>, aby zestawić rekomendacje dotyczące silnika i koloru. Jednak użytkownik może połączyć dowolny silnik z dowolnym kolorem, aby wysłać formularz.

Uwaga

Upewnij się, że ComponentEnums klasa jest dostępna dla składnika w następującym przykładzie:

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

Uwaga

Jeśli Name zostanie pominięty, komponenty InputRadio<TValue> są grupowane według ich najbliższego przodka.

Jeśli zaimplementowano powyższy kod znaczników Razor w składniku Starship3 w sekcji Przykładowy formularz artykułu Składniki wejściowe, zaktualizuj rejestrowanie w metodzie 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);

Podczas pracy z przyciskami radiowymi w formularzu powiązanie danych jest obsługiwane inaczej niż inne elementy, ponieważ przyciski radiowe są oceniane jako grupa. Wartość każdego przycisku radiowego jest stała, ale wartość grupy przycisków radiowych jest wartością wybranego przycisku radiowego. W poniższym przykładzie pokazano, jak:

  • Obsługa powiązania danych dla grupy przycisków radiowych.
  • Obsługa walidacji przy użyciu składnika niestandardowego 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;
        }
    }
}

Aby uzyskać więcej informacji na temat ogólnych parametrów typu (@typeparam), zobacz następujące artykuły:

Użyj następującego przykładowego modelu.

StarshipModel.cs:

using System.ComponentModel.DataAnnotations;

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

RadioButtonExample Poniższy składnik używa poprzedniego InputRadio składnika do uzyskania i zweryfikowania oceny od użytkownika:

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