pengikatan formulir inti Blazor ASP.NET

Catatan

Ini bukan versi terbaru dari artikel ini. Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.

Penting

Informasi ini berkaitan dengan produk pra-rilis yang mungkin dimodifikasi secara substansial sebelum dirilis secara komersial. Microsoft tidak memberikan jaminan, tersirat maupun tersurat, sehubungan dengan informasi yang diberikan di sini.

Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.

Artikel ini menjelaskan cara menggunakan pengikatan dalam Blazor formulir.

EditForm/EditContext Model

Membuat EditForm berdasarkan objek yang EditContext ditetapkan sebagai nilai berskala untuk komponen lain dalam formulir. Metadata EditContext trek tentang proses edit, termasuk bidang formulir mana yang telah dimodifikasi dan pesan validasi saat ini. Menetapkan ke EditForm.Model atau dapat EditForm.EditContext mengikat formulir ke data.

Pengikatan model

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

Catatan

Sebagian besar contoh model formulir artikel ini mengikat formulir ke properti C#, tetapi pengikatan bidang C# juga didukung.

Pengikatan konteks

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

Tetapkan atau EditContextModelke .EditForm Jika keduanya ditetapkan, kesalahan runtime dilemparkan.

Jenis yang didukung

Pengikatan mendukung:

  • Jenis primitif
  • Koleksi
  • Jenis kompleks
  • Jenis rekursif
  • Jenis dengan konstruktor
  • Enums

Anda juga dapat menggunakan [DataMember] atribut dan [IgnoreDataMember] untuk menyesuaikan pengikatan model. Gunakan atribut ini untuk mengganti nama properti, mengabaikan properti, dan menandai properti sesuai kebutuhan.

Opsi pengikatan tambahan

Opsi pengikatan model tambahan tersedia saat RazorComponentsServiceOptions memanggil AddRazorComponents:

Berikut ini menunjukkan nilai default yang ditetapkan oleh kerangka kerja:

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

Nama formulir

FormName Gunakan parameter untuk menetapkan nama formulir. Nama formulir harus unik untuk mengikat data model. Formulir berikut diberi nama RomulanAle:

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

Menyediakan nama formulir:

  • Diperlukan untuk semua formulir yang dikirimkan oleh komponen sisi server yang dirender secara statis.
  • Tidak diperlukan untuk formulir yang dikirimkan oleh komponen yang dirender secara interaktif, yang mencakup formulir dalam Blazor WebAssembly aplikasi dan komponen dengan mode render interaktif. Namun, sebaiknya berikan nama formulir unik untuk setiap formulir untuk mencegah kesalahan posting formulir runtime jika interaktivitas pernah dihilangkan untuk formulir.

Nama formulir hanya diperiksa ketika formulir diposting ke titik akhir sebagai permintaan HTTP POST tradisional dari komponen sisi server yang dirender secara statis. Kerangka kerja tidak melemparkan pengecualian pada titik penyajian formulir, tetapi hanya pada saat HTTP POST tiba dan tidak menentukan nama formulir.

Secara default, ada cakupan formulir yang tidak disebutkan namanya (string kosong) di atas komponen akar aplikasi, yang cukup ketika tidak ada tabrakan nama formulir di aplikasi. Jika tabrakan nama formulir dimungkinkan, seperti saat menyertakan formulir dari pustaka dan Anda tidak memiliki kontrol atas nama formulir yang digunakan oleh pengembang pustaka, berikan cakupan nama formulir dengan FormMappingScope komponen dalam Blazor proyek utama Aplikasi Web.

Dalam contoh berikut, HelloFormFromLibrary komponen memiliki formulir bernama Hello dan berada di pustaka.

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

Komponen berikut NamedFormsWithScope menggunakan komponen pustaka HelloFormFromLibrary dan juga memiliki formulir bernama Hello. Nama FormMappingScope cakupan komponen adalah ParentContext untuk formulir apa pun yang disediakan oleh HelloFormFromLibrary komponen. Meskipun kedua formulir dalam contoh ini memiliki nama formulir (Hello), nama formulir tidak bertabrakan dan peristiwa dirutekan ke formulir yang benar untuk peristiwa POST formulir.

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

Menyediakan parameter dari formulir ([SupplyParameterFromForm])

Atribut [SupplyParameterFromForm] menunjukkan bahwa nilai properti terkait harus disediakan dari data formulir untuk formulir. Data dalam permintaan yang cocok dengan nama properti terikat ke properti . Input berdasarkan InputBase<TValue> hasilkan nama nilai formulir yang cocok dengan nama Blazor yang digunakan untuk pengikatan model.

Anda dapat menentukan parameter pengikatan formulir berikut ke [SupplyParameterFromForm] atribut :

  • Name: Mendapatkan atau mengatur nama untuk parameter . Nama ini digunakan untuk menentukan awalan yang akan digunakan untuk mencocokkan data formulir dan memutuskan apakah nilai perlu diikat atau tidak.
  • FormName: Mendapatkan atau mengatur nama untuk handler. Nama digunakan untuk mencocokkan parameter dengan formulir menurut nama formulir untuk memutuskan apakah nilai perlu diikat atau tidak.

Contoh berikut secara independen mengikat dua formulir ke modelnya berdasarkan nama formulir.

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

Formulir berlapis dan mengikat

Panduan berikut menunjukkan cara bersarang dan mengikat formulir anak.

Kelas detail pengiriman berikut (ShipDetails) menyimpan deskripsi dan panjang untuk subformulir.

ShipDetails.cs:

namespace BlazorSample;

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

Kelas berikut menamai Ship pengidentifikasi (Id) dan menyertakan detail pengiriman.

Ship.cs:

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

Subformulir berikut digunakan untuk mengedit nilai jenis ShipDetails . Ini diimplementasikan dengan mewarisi Editor<T> di bagian atas komponen. Editor<T> memastikan bahwa komponen anak menghasilkan nama bidang formulir yang benar berdasarkan model (T), di mana T dalam contoh berikut adalah 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>

Bentuk utama terikat ke Ship kelas . Komponen StarshipSubform ini digunakan untuk mengedit detail pengiriman, terikat sebagai 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);
    }
}

Skenario kesalahan pemetaan formulir tingkat lanjut

Kerangka kerja membuat instans dan mengisi FormMappingContext formulir, yang merupakan konteks yang terkait dengan operasi pemetaan formulir tertentu. Setiap cakupan pemetaan (ditentukan oleh FormMappingScope komponen) membuat instans FormMappingContext. Setiap kali meminta konteks untuk nilai, kerangka kerja mengisi FormMappingContext dengan nilai yang [SupplyParameterFromForm] dicoba dan kesalahan pemetaan apa pun.

Pengembang tidak diharapkan untuk berinteraksi secara FormMappingContext langsung, karena ini terutama sumber data untuk InputBase<TValue>, EditContext, dan implementasi internal lainnya untuk menunjukkan kesalahan pemetaan sebagai kesalahan validasi. Dalam skenario kustom tingkat lanjut, pengembang dapat mengakses FormMappingContext langsung sebagai [CascadingParameter] untuk menulis kode kustom yang menggunakan nilai yang dicoba dan kesalahan pemetaan.

Tombol radio

Contoh di bagian ini didasarkan pada Starfleet Starship Database formulir (Starship3 komponen) dari bagian Formulir contoh dari artikel ini.

Tambahkan jenis berikut enum ke aplikasi. Buat file baru untuk menahannya atau menambahkannya ke 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 }
}

ComponentEnums Buat kelas dapat diakses oleh:

  • Starship model di Starship.cs (misalnya, using static ComponentEnums;).
  • Starfleet Starship Database form (Starship3.razor) (misalnya, @using static ComponentEnums).

Gunakan InputRadio<TValue> komponen dengan InputRadioGroup<TValue> komponen untuk membuat grup tombol radio. Dalam contoh berikut, properti ditambahkan ke Starship model yang dijelaskan di bagian Formulir contoh dari artikel Komponen 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;

Starfleet Starship Database Perbarui formulir (Starship3 komponen) dari bagian Formulir contoh dari artikel Komponen input. Tambahkan komponen yang akan dihasilkan:

  • Grup tombol radio untuk produsen kapal.
  • Grup tombol radio berlapis untuk mesin dan warna pengiriman.

Catatan

Grup tombol radio berlapis tidak sering digunakan dalam formulir karena dapat mengakibatkan tata letak kontrol formulir yang tidak terorganisir yang dapat membingungkan pengguna. Namun, ada kasus ketika mereka masuk akal dalam desain UI, seperti dalam contoh berikut yang memasangkan rekomendasi untuk dua input pengguna, mesin pengiriman dan warna kapal. Satu mesin dan satu warna diperlukan oleh validasi formulir. Tata letak formulir menggunakan sarang berlapis untuk memasangkan InputRadioGroup<TValue>mesin dan rekomendasi warna. Namun, pengguna dapat menggabungkan mesin apa pun dengan warna apa pun untuk mengirimkan formulir.

Catatan

Pastikan untuk membuat ComponentEnums kelas tersedia untuk komponen untuk contoh berikut:

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

Catatan

Jika Name dihilangkan, InputRadio<TValue> komponen dikelompokkan oleh leluhur terbarunya.

Jika Anda menerapkan markup sebelumnya Razor di Starship3 komponen bagian Formulir contoh dari artikel Komponen input, perbarui pengelogan untuk Submit metode :

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

Saat bekerja dengan tombol radio dalam bentuk, pengikatan data ditangani secara berbeda dari elemen lain karena tombol radio dievaluasi sebagai grup. Nilai setiap tombol radio diperbaiki, tetapi nilai grup tombol radio adalah nilai tombol radio yang dipilih. Contoh berikut menunjukkan cara membatalkan pekerjaan.

  • Menangani pengikatan data untuk grup tombol radio.
  • Mendukung validasi menggunakan komponen kustom 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;
        }
    }
}

Untuk informasi selengkapnya tentang parameter jenis generik (@typeparam), lihat artikel berikut ini:

Gunakan contoh model berikut.

StarshipModel.cs:

using System.ComponentModel.DataAnnotations;

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

Komponen berikut RadioButtonExample menggunakan komponen sebelumnya InputRadio untuk mendapatkan dan memvalidasi peringkat dari pengguna:

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