powiązanie formularzy ASP.NET Core Blazor

Uwaga

Nie jest to najnowsza wersja tego artykułu. Aby zapoznać się z bieżącą wersją, zapoznaj się z wersją tego artykułu platformy .NET 8.

Ważne

Te informacje odnoszą się do produktu w wersji wstępnej, który może zostać znacząco zmodyfikowany, zanim zostanie wydany komercyjnie. Firma Microsoft nie udziela żadnych gwarancji, jawnych lub domniemanych, w odniesieniu do informacji podanych w tym miejscu.

Aby zapoznać się z bieżącą wersją, zapoznaj się z wersją tego artykułu platformy .NET 8.

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

EditForm/EditContext model

Obiekt EditForm tworzy EditContext obiekt oparty na przypisanym obiekcie jako wartość kaskadową dla innych składnikó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.

Powiązanie modelu

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

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

Przypisz element EditContextlub do Model elementu EditForm. Jeśli oba te elementy zostaną przypisane, zostanie zgłoszony błąd środowiska uruchomieniowego.

Obsługiwane typy

Powiązanie obsługuje:

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

Można również użyć [DataMember] atrybutów i [IgnoreDataMember] , aby dostosować powią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.

Dodatkowe opcje powiązania

Dodatkowe opcje powiązania modelu są dostępne podczas RazorComponentsServiceOptions wywoływania elementu 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 wymagany dla wszystkich formularzy przesyłanych przez statycznie renderowane składniki po stronie serwera.
  • 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 podanie unikatowej nazwy formularza dla każdego formularza, aby zapobiec wysyłaniu błędów formularza w czasie wykonywania, jeśli kiedykolwiek przerywana jest interakcyjność dla formularza.

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.

Domyślnie istnieje zakres formularza bez nazwy (pusty ciąg) powyżej składnika głównego aplikacji, który wystarczy, gdy w aplikacji nie ma kolizji nazw formularzy. Jeśli możliwe są kolizje nazw formularzy, na przykład w przypadku dołączania formularza z biblioteki i nie masz kontroli nad nazwą formularza używaną przez dewelopera biblioteki, podaj zakres nazw formularzy ze FormMappingScope składnikiem w Blazor głównym projekcie aplikacji internetowej.

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]
    public 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. FormMappingScope Nazwa zakresu składnika dotyczy ParentContext dowolnych formularzy dostarczonych przez HelloFormFromLibrary składnik. Mimo że obie formularze w tym przykładzie mają nazwę formularza (Hello), nazwy formularzy nie zderzają się, a zdarzenia są kierowane do poprawnego formularza dla 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]
    public 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 być dostarczana z danych formularza dla formularza. Dane w żądaniu, które pasują do nazwy właściwości, są powiązane z właściwością. Dane wejściowe oparte na InputBase<TValue> generowaniu nazw wartości formularza, które są zgodne z nazwami Blazor używanymi do powiązania modelu.

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 według nazwy formularza, aby zdecydować, czy wartość musi być powiązana.

Poniższy przykład niezależnie wiąże dwa formularze z modelami według 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")]
    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; }
    }
}

Zagnieżdżanie i wiązanie formularzy

Poniższe wskazówki pokazują, jak zagnieżdżać i wiązać formularze podrzędne.

Następująca klasa szczegółów wysyłki (ShipDetails) zawiera opis i długość podformularza.

ShipDetails.cs:

namespace BlazorSample;

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

Poniższa Ship klasa nazywa identyfikator (Id) i zawiera szczegóły wysyłki.

Ship.cs:

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

Poniższy podformularz jest używany do edytowania ShipDetails wartości typu. Jest to implementowane przez dziedziczenie Editor<T> w górnej części składnika. 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>

Formularz główny jest powiązany z klasą Ship . Składnik służy do edytowania StarshipSubform szczegółów wysyłki powiązanej 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]
    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);
    }
}

Zaawansowane scenariusze błędów mapowania formularzy

Struktura tworzy wystąpienie i wypełnia FormMappingContext formularz, który jest kontekstem skojarzonym z operacją mapowania danego formularza. Każdy zakres mapowania (zdefiniowany przez FormMappingScope składnik) tworzy wystąpienie FormMappingContext. Za każdym razem, gdy [SupplyParameterFromForm] element pyta kontekst o wartość, struktura wypełnia FormMappingContext element wartością próbną i wszelkimi błędami mapowania.

Deweloperzy nie powinni korzystać bezpośrednio, FormMappingContext ponieważ jest to głównie źródło danych dla InputBase<TValue>programu , EditContexti innych wewnętrznych implementacji w celu wyświetlania błędów mapowania jako błędów walidacji. W zaawansowanych scenariuszach niestandardowych deweloperzy mogą uzyskiwać dostęp FormMappingContext bezpośrednio jako element [CascadingParameter] do pisania kodu niestandardowego, który korzysta z próbowanych wartości i błędów mapowania.

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

Udostępnij klasę enums :

  • Starship model in Starship.cs (na przykład using static ComponentEnums;).
  • Starfleet Starship Database form () (Starship3.razorna 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 składniki do utworzenia:

  • Grupa przycisków radiowych dla producenta statku.
  • Zagnieżdżona grupa przycisków radiowych dla silnika i koloru wysyłki.

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, gdy mają sens w projekcie interfejsu użytkownika, na przykład w poniższym przykładzie, które łączą zalecenia dotyczące dwóch danych wejściowych użytkownika, silnika wysyłkowego i koloru wysyłki. Jeden aparat i jeden kolor są wymagane przez walidację formularza. Układ formularza używa zagnieżdżonych InputRadioGroup<TValue>elementów do parowania aparatu i zaleceń dotyczących kolorów. Jednak użytkownik może połączyć dowolny aparat z dowolnym kolorem, aby przesł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 pominięto, InputRadio<TValue> składniki są pogrupowane według ich najnowszego modułu ancestor.

Jeśli zaimplementowano powyższy Razor znacznik w Starship3 składniku sekcji Przykładowy formularz artykułu Składniki wejściowe, zaktualizuj rejestrowanie dla Submit metody :

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