次の方法で共有


ASP.NET Core Blazor フォームのバインド

Note

これは、この記事の最新バージョンではありません。 現在のリリースについては、 この記事の .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();
}

Note

この記事のフォーム モデルの例のほとんどでフォームを C# プロパティにバインドしますが、C# フィールド バインドもサポートされています。

コンテキスト バインド

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 ModelEditForm に割り当てます。 両方が割り当てられている場合、ランタイム エラーがスローされます。

サポートされている型

バインドでサポートされるもの:

  • プリミティブ型
  • コレクション
  • 複合型
  • 再帰型
  • コンストラクターのある型
  • 列挙体

[DataMember] 属性と [IgnoreDataMember] 属性を使用してモデル バインドをカスタマイズすることもできます。 これらの属性を使用してプロパティの名前を変更したり、プロパティを無視したり、必要に応じてプロパティをマークしたりします。

その他のバインド オプション

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 とマークする必要はありません。

[SupplyParameterFromForm] 属性には、次のフォーム バインド パラメーターを指定できます。

  • Name: パラメーターの名前を取得または設定します。 この名前は、フォーム データとの照合に使用するプレフィックスを決定し、値をバインドする必要があるかどうか判断するために使用されます。
  • FormName: ハンドラーの名前を取得または設定します。 この名前は、フォーム名でパラメーターとフォームを照合し、値をバインドする必要があるかどうか判断するために使用されます。

次の例では、2 つのフォームを他に依存せずフォーム名でモデルにバインドします。

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> により確実に、次の例のTT であるモデル (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] としてアクセスできます。

カスタム入力コンポーネント

カスタム入力処理シナリオでは、次のサブセクションでカスタム入力コンポーネントを示します。

特定の要件のためにできない場合を除き、InputBase<TValue> からカスタム入力コンポーネントを引き出すことをお勧めします。 InputBase<TValue> クラスは、ASP.NET Core チームによって積極的に維持され、最新の Blazor 機能とフレームワークの変更を確実に最新の状態に保ちます。

InputBase<T> に基づく入力コンポーネント

次のコンポーネント例では:

  • InputBase<TValue> から継承されます。 InputBase<TValue> から継承するコンポーネントは、Blazor 形式 (EditForm) で使用する必要があります。
  • チェック ボックスからブール値の入力を受け取ります。
  • バインド (<div>) 後に AfterChange メソッドが実行されたときに発生する、チェックボックスの状態に基づいて、コンテナー @bind:afterの背景色を設定します。
  • 基底クラスの TryParseValueFromString メソッドをオーバーライドするために必要ですが、チェックボックスでは文字列データが提供されないため、文字列入力データは処理されません。 文字列入力を処理する他の種類の入力コンポーネントに対する TryParseValueFromString の実装例は、ASP.NET Core 参照ソースで使用できます。

Note

通常、.NET 参照ソースへのドキュメント リンクを使用すると、リポジトリの既定のブランチが読み込まれます。このブランチは、.NET の次回リリースに向けて行われている現在の開発を表します。 特定のリリースのタグを選択するには、[Switch branches or tags](ブランチまたはタグの切り替え) ドロップダウン リストを使います。 詳細については、「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)}'.");
}

starship example form (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は、cascading 値です。
    • fieldCssClass では、 EditContext 検証の結果に基づいてフィールドのスタイルが設定されます。
    • ValueExpression は、バインドされた値を識別するフレームワークによって割り当てられた式 (Expression<Func<T>>) です。
    • FieldIdentifier は、編集できる 1 つのフィールド (通常はモデル プロパティに対応) を一意に識別します。 フィールド識別子は、バインドされた値 (ValueExpression) を識別する式で作成されます。
  • OnChange イベント ハンドラーで:

    • チェックボックス入力の値は、InputFileChangeEventArgs から取得されます。
    • コンテナー <div> 要素の背景色とテキストの色が設定されます。
    • EventCallback.InvokeAsync は、バインディングに関連付けられているデリゲートを呼び出し、値が変更されたことをコンシューマーにイベント通知をディスパッチします。
    • コンポーネントが EditForm で使用されている場合 ( EditContext プロパティが null でない場合)、検証をトリガーするために EditContext.NotifyFieldChanged が呼び出されます。

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

starship example form (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; }
}

ラジオ ボタン

このセクションの例は、この記事の「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 クラスにアクセスできるようにします。

  • StarshipStarship.cs モデル (たとえば、using static ComponentEnums;)。
  • Starfleet Starship Database フォーム (Starship3.razor) (たとえば、@using static ComponentEnums)。

ラジオ ボタン グループを作成するには、InputRadio<TValue> コンポーネントと共に InputRadioGroup<TValue> コンポーネントを使用します。 次の例では、「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;

Starfleet Starship Database」記事の 「Starship3」セクションの フォーム ( コンポーネント) を更新します。 生成するコンポーネントを追加します。

  • 船舶製造元のラジオ ボタン グループ。
  • エンジンと船の色に関する入れ子になったラジオ ボタン グループ。

Note

入れ子になったラジオ ボタン グループは、フォーム コントロールのレイアウトが乱れ、ユーザーを混乱させる可能性があるため、フォームではあまり使用されません。 ただし、船のエンジンと船の色の 2 つのユーザー入力に関する推奨事項を組み合わせる次の例のように、UI の設計において意味のある場合があります。 フォームの検証では、1 つのエンジンと 1 つの色が必要です。 フォームのレイアウトでは、入れ子になった InputRadioGroup<TValue> を使用して、エンジンと色の推奨設定が組み合わされています。 ただし、ユーザーは任意のエンジンを任意の色と組み合わせて、フォームを送信できます。

Note

次の例では、必ずコンポーネントで 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>

Note

Name を省略した場合、InputRadio<TValue> コンポーネントは最新の先祖を基準にグループ化されます。

「Razor」記事の「Starship3」セクションの コンポーネントに上記の マークアップを実装した場合は、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");
    }
}