ASP.NET Core Blazor 表單繫結

注意

這不是這篇文章的最新版本。 關於目前版本,請參閱 本文的 .NET 10 版本。

警告

不再支援此版本的 ASP.NET Core。 如需詳細資訊,請參閱 .NET 和 .NET Core 支持原則。 關於目前版本,請參閱 本文的 .NET 10 版本。

本文說明如何在 Blazor 表單中使用繫結。

EditForm / EditContext 模型

EditForm 會根據指派的物件建立 EditContext,做為表單中其他元件的串聯值。 EditContext 會追蹤有關編輯程序的中繼資料,包括哪些表單欄位已修改和目前的驗證訊息。 將值指定給 EditForm.Model 或 EditForm.EditContext 其中之一,即可將表單繫結到資料。

模型繫結

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

注意

本文的大部分表單模型範例會將表單繫結至 C# 屬性,但也支援 C# 欄位繫結。

當不同的物件執行個體被指派給 EditForm 的 Model 參數時,該表單會建立新的 EditContext。 新的上下文從沒有修改欄位或驗證訊息開始。

情境繫結

指派給 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);
    }
}

將 EditContext或Model 指派給 EditForm。 如果兩者都已指派,則會引發執行階段錯誤。

支援的類型

繫結支援:

  • 基本類型
  • 收藏集
  • 複雜類型
  • 遞迴型別
  • 具有建構函式的型別
  • 列舉

您也可以使用 [DataMember] 和 [IgnoreDataMember] 屬性來自訂模型繫結。 使用這些屬性來重新命名屬性、忽略屬性,以及視需要標記屬性。

當將型別與建構參數綁定時,若建構參數名稱與屬性相符,則建構參數優先。 對應器會使用該屬性明確指定的 DataMember.Name(若有)作為表單欄位名稱,但除此之外,會忽略該屬性的對應屬性。 建構參數始終是必需的。

其他繫結選項

呼叫 RazorComponentsServiceOptions 時,可從 AddRazorComponents 取得其他模型繫結選項:

以下示範架構指派的預設值:

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

表單名稱

使用 FormName 參數來指派表單名稱。 表單名稱必須是唯一的,才能繫結模型資料。 下列表單名為 RomulanAle:

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

提供表單名稱:

  • 所有由靜態渲染的伺服器端元件送出的表單都必須具備此項要求。
  • 對於由以互動方式呈現的元件所提交的表單而言,並非必要;這包括 Blazor WebAssembly 應用程式中的表單,以及具有互動式轉譯模式的元件中的表單。 不過,我們建議為每個表單提供唯一的表單名稱,以避免在表單的互動功能遭到捨棄時發生執行階段表單張貼錯誤。

只有在表單以傳統 HTTP POST 要求的形式 (來自靜態轉譯的伺服器端元件) 張貼到端點時,才會檢查表單名稱。 架構不會在轉譯表單時擲出例外狀況,而是只會在 HTTP POST 到達且未指定表單名稱時擲出例外狀況。

應用程式的根元件上方有一個未命名的 (空字串) 表單範圍,當應用程式中沒有表單名稱衝突時,此範圍就已足夠。 若有可能發生表單名稱衝突,(例如,若您納入某個程式庫中的表單,但無權控制程式庫開發人員所使用的表單名稱),請為 FormMappingScope 主要專案中的 Blazor Web App 元件提供表單名稱範圍。

在下列範例中,HelloFormFromLibrary 元件具有名為 Hello 的表單,且位於程式庫中。

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

下列 NamedFormsWithScope 元件會使用程式庫的 HelloFormFromLibrary 元件,而且也有名為 Hello 的表單。 對於 FormMappingScope 元件所提供的任何表單,ParentContext 元件的範圍名稱皆為 HelloFormFromLibrary。 雖然此範例中的兩個表單都有表單名稱 (Hello),但這些表單名稱並未衝突,且事件會路由至表單 POST 事件的正確表單。

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

從表單提供參數 ([SupplyParameterFromForm])

[SupplyParameterFromForm] 屬性表示,相關聯屬性的值應從該表單的表單資料提供。 請求中名稱與該屬性相符的資料會繫結至該屬性。 以 InputBase<TValue> 為基礎的輸入欄位會產生與 Blazor 用於模型繫結的名稱相符的表單值名稱。 不同於元件參數屬性 ([Parameter]), 使用 [SupplyParameterFromForm] 附註的屬性不需要標示為 public。

Blazor 表單映射 with [SupplyParameterFromForm] 並不需要 MVC 模型綁定。 命名空間中的 Microsoft.AspNetCore.Mvc.ModelBinding 屬性,如 [BindNever] 和 [BindRequired],不被支援。 不要用這些屬性來防止過度貼文。 相反地,請使用專用的表單模型、檢視模型或資料傳輸物件(DTO),只包含使用者可修改的屬性。 欲了解更多資訊,請參閱「減輕過度貼文攻擊」。

您可以將下列表單繫結參數指定給 [SupplyParameterFromForm] 屬性:

  • Name:取得或設定參數的名稱。 該名稱會用來決定用於比對表單資料的前綴,並判定是否需要繫結該值。
  • FormName:取得或設定處理常式的名稱。 該名稱會用來依表單名稱比對表單的參數,以決定是否需要繫結值。

下列範例會依表單名稱,將兩個表單獨立繫結至其模型。

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

巢狀和繫結表單

下列說明示範如何將子表單巢狀排列並進行繫結。

下列船舶詳細資料類別 (ShipDetails) 包含子表單的描述與長度。

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

下列 Ship 類別會命名識別碼 (Id),並包含飛船詳細資料。

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

下列子表單用於編輯 ShipDetails 型別的值。 這是透過在元件頂部繼承 Editor<T> 來實現的。 Editor<T> 可確保子元件會根據模型產生正確的表單欄位名稱 (T),其中 T 在下列範例中為 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>

主要表單會繫結至 Ship 類別。 StarshipSubform 元件會用來編輯飛船詳細資料,繫結為 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);
}

使用靜態 SSR 初始化表格資料

當元件採用靜態 SSR 時,OnInitialized{Async} 生命週期方法和OnParametersSet{Async} 生命週期方法會在元件初始呈現和每次表單 POST 到伺服器時啟動。 若要初始化表單模型值,在 OnParametersSet{Async} 中指定新的模型值之前,先確認模型是否已經有資料,就像下面的範例所展示的一樣。

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

進階表單對應錯誤案例

該架構會為表單建立並填入 FormMappingContext,這是與指定表單之映射操作相關聯的上下文。 每個對應範圍 (由 FormMappingScope 元件定義) 都會具現化 FormMappingContext。 每次 [SupplyParameterFromForm] 向內容脈絡要求值時,框架都會在 FormMappingContext 中填入嘗試的值以及任何映射錯誤。

開發人員預期不會直接與 FormMappingContext 互動,因為它主要是 InputBase<TValue>、EditContext 和其他內部實作的資料來源,以將對應錯誤顯示為驗證錯誤。 在進階自訂情境中,開發人員可直接將 FormMappingContext 作為 [CascadingParameter] 存取,以撰寫使用嘗試輸入的值和對應錯誤的自訂程式碼。

根據輸入事件的 InputText

使用 InputText 元件來建立使用 oninput 事件 (input) 而不是 onchange 事件 (change) 的自訂元件。 在每次按鍵輸入時使用input事件觸發欄位驗證。

下列 CustomInputText 元件會繼承架構的 InputText 元件,並將事件繫結設定為 oninput 事件 (input)。

CustomInputText.razor:

@inherits InputText

<input @attributes="AdditionalAttributes" 
       class="@CssClass" 
       @bind="CurrentValueAsString" 
       @bind:event="oninput" />

您可以在使用 CustomInputText 的任何位置使用 InputText 元件。 下列元件使用共用的 CustomInputText 元件。

Starship11.razor:

@page "/starship-11"
@using System.ComponentModel.DataAnnotations
@inject ILogger<Starship11> Logger

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

<div>
    CurrentValue: @Model?.Id
</div>

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

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

    private void Submit() => Logger.LogInformation("Submit: Processing form");

    public class Starship
    {
        [Required]
        [StringLength(10, ErrorMessage = "Id is too long.")]
        public string? Id { get; set; }
    }
}
@page "/starship-11"
@using System.ComponentModel.DataAnnotations
@inject ILogger<Starship11> Logger

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

<div>
    CurrentValue: @Model?.Id
</div>

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

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

    private void Submit() => Logger.LogInformation("Submit: Processing form");

    public class Starship
    {
        [Required]
        [StringLength(10, ErrorMessage = "Id is too long.")]
        public string? Id { get; set; }
    }
}
@page "/starship-11"
@using System.ComponentModel.DataAnnotations
@inject ILogger<Starship11> Logger

<EditForm Model="Model" OnValidSubmit="Submit">
    <DataAnnotationsValidator />
    <ValidationSummary />
    <CustomInputText @bind-Value="Model!.Id" />
    <button type="submit">Submit</button>
</EditForm>

<div>
    CurrentValue: @Model?.Id
</div>

@code {
    public Starship? Model { get; set; }

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

    private void Submit()
    {
        Logger.LogInformation("Submit called: Processing the form");
    }

    public class Starship
    {
        [Required]
        [StringLength(10, ErrorMessage = "Id is too long.")]
        public string? Id { get; set; }
    }
}

自訂輸入元件

對於自訂輸入處理案例,下列子區段示範自訂輸入元件:

建議您從 InputBase<TValue> 衍生自訂輸入元件,除非特定需求阻止執行此動作。 ASP.NET Core 小組會主動維護此 InputBase<TValue> 類別,確保其隨時掌握最新的 Blazor 功能和架構變更。

以 InputBase<T> 為基礎的輸入元件

下列範例元件:

  • 繼承自 InputBase<TValue>。 繼承自 InputBase<TValue> 的元件必須在 Blazor 表單中使用(EditForm)。
  • 接受來自核取方塊的布林輸入。
  • 根據核取方塊的狀態設定其容器 <div> 的背景色彩,當 AfterChange 方法在繫結之後 (@bind:after) 執行時會發生這種情況。
  • 需要覆寫基底類別的 TryParseValueFromString 方法,但不會處理字串輸入資料,因為核取方塊不提供字串資料。 其他處理字串輸入的輸入元件類型,其 TryParseValueFromString 的實作範例可在 ASP.NET Core 參考來源 中找到。

注意

文件中連往 .NET 參考原始程式碼的連結通常會載入存放庫的預設分支,而該分支代表 .NET 下一個版本目前的開發狀態。 若要為特定發行版本選取標籤,請使用 切換分支或標籤 下拉式選單。 如需詳細資訊,請參閱如何選取 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)}'.");
}

若要在星際飛船範例表單 (Starship3.razor/Starship.cs) 中使用上述元件,請使用繫結至模型之 <div> 屬性的 EngineeringApprovalInputDerived 元件執行個體,取代工程核准欄位的 IsValidatedDesign 區塊:

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

如果從InputBase<TValue>繼承的元件是靜態渲染的,則應將InputBase<TValue>.NameAttributeValue屬性賦予name元素的<input>屬性。

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

如果元件保證總是互動式渲染,上述指派就不必了。

具有完整開發人員控制項的輸入元件

下列範例元件:

  • 不會繼承自 InputBase<TValue>。 元件完全掌控輸入處理,包括繫結、回呼和驗證。 元件可以在 Blazor 表單 (EditForm) 內部或外部使用。
  • 接受來自核取方塊的布林輸入。
  • 如果核取方塊已勾選,則變更背景顏色。

元件中的程式碼包括:

  • Value 屬性搭配使用雙向繫結,以取得或設定輸入的值。 ValueChanged 是更新繫結值的回呼。

  • 用於 Blazor 表單中時:

    • EditContext 是串聯值。
    • fieldCssClass 根據 EditContext 驗證的結果設定欄位的樣式。
    • ValueExpression 是由識別繫結值的架構所指派的運算式 (Expression<Func<T>>)。
    • FieldIdentifier 能唯一識別可編輯的單一欄位,該欄位通常對應至模型屬性。 欄位識別碼的建立方式為,使用識別繫結值 (ValueExpression) 的運算式。
  • 在 OnChange 事件處理常式中:

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

若要在星際飛船範例表單 (Starship3.razor/Starship.cs) 中使用上述元件,請使用繫結至模型之 <div> 屬性的 EngineeringApprovalInputStandalone 元件執行個體,取代工程核准欄位的 IsValidatedDesign 區塊:

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

EngineeringApprovalInputStandalone 元件也可以在 EditForm 外部運作:

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

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

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

單選按鈕

本節中的範例是以本文 Example form 一節中的 Starfleet Starship Database 表單(Starship3 元件)為基礎。

將下列 enum 型別新增至應用程式。 建立新的檔案來保存它們,或將它們新增至 Starship.cs 檔案。

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 類別可供存取:

  • Starship 中的 Starship.cs 模型 (例如,using static ComponentEnums;)。
  • Starfleet Starship Database 表單 (Starship3.razor) (例如,@using static ComponentEnums)。

使用 InputRadio<TValue> 元件搭配 InputRadioGroup<TValue> 元件來建立選項按鈕群組。 在下列範例中,屬性會新增至 Input components 文章中 Example form 一節所述的 Starship 模型:

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

更新Input components一文中Example form小節的Starfleet Starship Database表單(Starship3元件)。 新增要產生的元件:

  • 船舶製造商的單選按鈕群組。
  • 用於引擎與飛船顏色的巢狀單選按鈕群組。

注意

巢狀單選按鈕群組在表單中並不常使用,因為它們可能會使表單控制項的配置顯得雜亂無章,因而讓使用者感到困惑。 不過,在某些情況下,這種做法在 UI 設計中是有意義的,例如下例將建議與兩個使用者輸入(飛船引擎和飛船顏色)配對。 表單的驗證規則要求必須有一個引擎和一種顏色。 表單的版面配置使用巢狀的 InputRadioGroup<TValue> 元素,將引擎建議與色彩建議配對。 不過,使用者可以結合任何引擎與任何色彩來提交表單。

注意

針對下列範例,務必將 ComponentEnums 類別提供給元件:

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

注意

如果省略 Name,InputRadio<TValue> 元件會依其最近的祖先分組。

如果您已在 Input components 文章的 Example form 區段中,於 Starship3 元件實作上述 Razor 標記,請更新 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);

在表單中使用選項按鈕時,資料繫結的處理方式與其他元素不同,因為選項按鈕是以群組方式進行判定。 每個選項按鈕的值都是固定的,但選項按鈕群組的值是所選選項按鈕的值。 下列範例顯示如何:

  • 處理單選按鈕群組的資料繫結。
  • 支援使用自訂 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;
        }
    }
}

如需泛型型別參數 (@typeparam) 的詳細資訊,請參閱下列文章:

使用下列範例模型。

StarshipModel.cs:

using System.ComponentModel.DataAnnotations;

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

下列 RadioButtonExample 元件會使用上述 InputRadio 元件,以從使用者取得及驗證評等:

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