ASP.NET Core Blazor のフォームと入力コンポーネント
注意
これは、この記事の最新バージョンではありません。 最新のバージョンに切り替えるには、目次の上部にある ASP.NET Core バージョン セレクターを使用します。
セレクターが狭いブラウザー ウィンドウに表示されない場合は、ウィンドウの幅を広げるか、垂直省略記号 ([⋮]) >[目次] の順に選択します。
Blazor フレームワークはフォームをサポートし、組み込みの入力コンポーネントが用意されています。
- データ注釈を使用するモデルにバインドされた EditForm コンポーネント
- 組み込みの入力コンポーネント
Microsoft.AspNetCore.Components.Forms 名前空間では以下が提供されます。
- フォームの要素、状態、検証を管理するためのクラス。
- Blazor アプリで使用できる、組み込み Input* コンポーネントへのアクセス。
Blazor プロジェクト テンプレートから作成されたプロジェクトには、既定でこの名前空間がアプリの _Imports.razor
ファイルに含まれています。これにより、アプリのすべての Razor コンポーネント ファイル (.razor
) で、明示的な @using
ディレクティブなしでこの名前空間を使用できます。
@using Microsoft.AspNetCore.Components.Forms
EditForm コンポーネントのしくみを示すために、次の例を考えてみましょう。 ExampleModel
はフォームにバインドされたデータ モデルを表し、Name
プロパティが定義されています。これは、ユーザーが指定したフォームの name
フィールドの値を格納するために使います。
ExampleModel.cs
:
public class ExampleModel
{
public string? Name { get; set; }
}
EditForm コンポーネントでデータ注釈の検証がどのように動作するかを示すために、次の ExampleModel
型を考えてみます。 Name
プロパティは、RequiredAttribute で必須とマークされており、StringLengthAttribute で最大文字列長の制限とエラー メッセージが指定されています。
ExampleModel.cs
:
using System.ComponentModel.DataAnnotations;
public class ExampleModel
{
[Required]
[StringLength(10, ErrorMessage = "Name is too long.")]
public string? Name { get; set; }
}
EditForm コンポーネントでデータ注釈の検証がどのように動作するかを示すために、次の ExampleModel
型を考えてみます。 Name
プロパティは、RequiredAttribute で必須とマークされており、StringLengthAttribute で最大文字列長の制限とエラー メッセージが指定されています。
ExampleModel.cs
:
using System.ComponentModel.DataAnnotations;
public class ExampleModel
{
[Required]
[StringLength(10, ErrorMessage = "Name is too long.")]
public string Name { get; set; }
}
EditForm コンポーネントでデータ注釈の検証がどのように動作するかを示すために、次の ExampleModel
型を考えてみます。 Name
プロパティは、RequiredAttribute で必須とマークされており、StringLengthAttribute で最大文字列長の制限とエラー メッセージが指定されています。
ExampleModel.cs
:
using System.ComponentModel.DataAnnotations;
public class ExampleModel
{
[Required]
[StringLength(10, ErrorMessage = "Name is too long.")]
public string Name { get; set; }
}
フォームは、Blazor フレームワークの EditForm コンポーネントを使用して定義されます。 次の Razor コンポーネントでは、EditForm コンポーネントを使用して Web フォームをレンダリングするための一般的な要素、コンポーネント、Razor コードが示されており、これは前の ExampleModel
型にバインドされています。
Pages/FormExample1.razor
:
@page "/form-example-1"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample1> Logger
<EditForm Model="@exampleModel" OnSubmit="@HandleSubmit">
<InputText id="name" @bind-Value="exampleModel.Name" />
<button type="submit">Submit</button>
</EditForm>
@code {
private ExampleModel exampleModel = new();
private void HandleSubmit()
{
Logger.LogInformation("HandleSubmit called");
// Process the form
}
}
前の FormExample1
コンポーネントは次のようなものです。
- EditForm コンポーネントは、
<EditForm>
要素が出現する場所にレンダリングされます。 - モデルはコンポーネントの
@code
ブロック内に作成され、プライベート フィールド (exampleModel
) に保持されます。 フィールドは、<EditForm>
要素の EditForm.Model の属性 (Model
) に割り当てられます。 - InputText コンポーネント (
id="name"
) は、文字列値を編集するための入力コンポーネントです。@bind-Value
ディレクティブ属性により、exampleModel.Name
モデル プロパティは、InputTextコンポーネントの Value プロパティにバインドされます。 HandleSubmit
メソッドは、OnSubmit コールバックのハンドラーとして登録されます。 このハンドラーは、ユーザーがフォームを送信すると呼び出されます。
前の EditForm コンポーネントがデータ注釈の検証でどのように機能するかを示すために:
- 上記の
ExampleModel
では、System.ComponentModel.DataAnnotations 名前空間を使用しています。 ExampleModel
のName
プロパティは RequiredAttribute によって必須とマークされており、StringLengthAttribute によって最大文字列長の制限とエラー メッセージが指定されています。
ExampleModel.cs
:
using System.ComponentModel.DataAnnotations;
public class ExampleModel
{
[Required]
[StringLength(10, ErrorMessage = "Name is too long.")]
public string? Name { get; set; }
}
以前の FormExample1
コンポーネントが変更されています。
- OnSubmit は OnValidSubmit に置き換えられています。これは、ユーザーが送信したときにフォームが有効である場合に、割り当てられたイベント ハンドラーを処理します。 メソッド名は
HandleValidSubmit
に変更されています。これは、フォームが有効である場合にメソッドが呼び出されることを反映しています。 - ValidationSummary コンポーネントが追加されており、フォームの送信時にフォームが無効な場合に検証メッセージが表示されます。
- データ注釈検証コントロール (DataAnnotationsValidator コンポーネント†) は、データ注釈を使用して検証サポートをアタッチします。
Submit
ボタンが選択されたときに<input>
フォーム フィールドが空白のままになっている場合、検証の概要 (ValidationSummary コンポーネント‡) にエラーが表示され ("The Name field is required.
")、HandleValidSubmit
は呼び出されません。Submit
ボタンが選択されているときに<input>
フォーム フィールドに 10 文字より多く含まれている場合は、検証の概要にエラーが表示され ("Name is too long.
")、HandleValidSubmit
が呼び出されません。Submit
ボタンが選択されたときに<input>
フォーム フィールドに有効な値が含まれている場合、HandleValidSubmit
が呼び出されます。
†DataAnnotationsValidator コンポーネントについては、「検証コンポーネント」セクションを参照してください。 ‡ValidationSummary コンポーネントについては、「検証概要コンポーネントと検証メッセージ コンポーネント」セクションを参照してください。 プロパティのバインドについて詳しくは、「ASP.NET Core Blazor のデータ バインディング」をご覧ください。
Pages/FormExample1.razor
:
@page "/form-example-1"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample1> Logger
<EditForm Model="@exampleModel" OnValidSubmit="@HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<InputText id="name" @bind-Value="exampleModel.Name" />
<button type="submit">Submit</button>
</EditForm>
@code {
private ExampleModel exampleModel = new();
private void HandleValidSubmit()
{
Logger.LogInformation("HandleValidSubmit called");
// Process the valid form
}
}
@page "/form-example-1"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample1> Logger
<EditForm Model="@exampleModel" OnValidSubmit="@HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<InputText id="name" @bind-Value="exampleModel.Name" />
<button type="submit">Submit</button>
</EditForm>
@code {
private ExampleModel exampleModel = new();
private void HandleValidSubmit()
{
Logger.LogInformation("HandleValidSubmit called");
// Process the valid form
}
}
@page "/form-example-1"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample1> Logger
<EditForm Model="@exampleModel" OnValidSubmit="@HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<InputText id="name" @bind-Value="exampleModel.Name" />
<button type="submit">Submit</button>
</EditForm>
@code {
private ExampleModel exampleModel = new();
private void HandleValidSubmit()
{
Logger.LogInformation("HandleValidSubmit called");
// Process the valid form
}
}
@page "/form-example-1"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample1> Logger
<EditForm Model="@exampleModel" OnValidSubmit="@HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<InputText id="name" @bind-Value="exampleModel.Name" />
<button type="submit">Submit</button>
</EditForm>
@code {
private ExampleModel exampleModel = new ExampleModel();
private void HandleValidSubmit()
{
Logger.LogInformation("HandleValidSubmit called");
// Process the valid form
}
}
前の FormExample1
コンポーネントは次のようなものです。
- EditForm コンポーネントは、
<EditForm>
要素が出現する場所にレンダリングされます。 - モデルはコンポーネントの
@code
ブロック内に作成され、プライベート フィールド (exampleModel
) に保持されます。 フィールドは、<EditForm>
要素の EditForm.Model の属性 (Model
) に割り当てられます。 - InputText コンポーネント (
id="name"
) は、文字列値を編集するための入力コンポーネントです。@bind-Value
ディレクティブ属性により、exampleModel.Name
モデル プロパティは、InputTextコンポーネントの Value プロパティにバインドされます。 HandleValidSubmit
メソッドは OnValidSubmit に割り当てられます。 フォームが検証に合格すると、ハンドラーが呼び出されます。- データ注釈検証コントロール (DataAnnotationsValidator コンポーネント†) は、データ注釈を使用して検証サポートをアタッチします。
Submit
ボタンが選択されたときに<input>
フォーム フィールドが空白のままになっている場合、検証の概要 (ValidationSummary コンポーネント‡) にエラーが表示され ("The Name field is required.
")、HandleValidSubmit
は呼び出されません。Submit
ボタンが選択されているときに<input>
フォーム フィールドに 10 文字より多く含まれている場合は、検証の概要にエラーが表示され ("Name is too long.
")、HandleValidSubmit
が呼び出されません。Submit
ボタンが選択されたときに<input>
フォーム フィールドに有効な値が含まれている場合、HandleValidSubmit
が呼び出されます。
†DataAnnotationsValidator コンポーネントについては、「検証コンポーネント」セクションを参照してください。 ‡ValidationSummary コンポーネントについては、「検証概要コンポーネントと検証メッセージ コンポーネント」セクションを参照してください。 プロパティのバインドについて詳しくは、「ASP.NET Core Blazor のデータ バインディング」をご覧ください。
フォームのバインド
割り当てられているモデル インスタンスに基づいて、EditForm により EditContext がフォーム内の他のコンポーネントに対するカスケード値として作成されます。 EditContext により、変更されたフィールドと現在の検証メッセージを含む、編集プロセスに関するメタデータが追跡されます。 EditForm.Model または EditForm.EditContext のいずれかに割り当てると、フォームをデータにバインドできます。
EditForm.Model への割り当て:
<EditForm Model="@exampleModel" ...>
@code {
private ExampleModel exampleModel = new() { ... };
}
EditForm.EditContext への割り当て:
<EditForm EditContext="@editContext" ...>
@code {
private ExampleModel exampleModel = new() { ... };
private EditContext? editContext;
protected override void OnInitialized()
{
editContext = new(exampleModel);
}
}
<EditForm EditContext="@editContext" ...>
@code {
private ExampleModel exampleModel = new() { ... };
private EditContext editContext;
protected override void OnInitialized()
{
editContext = new(exampleModel);
}
}
EditContextまたはModel のいずれかを EditForm に割り当てます。 両方とも割り当てることはサポートされておらず、ランタイム エラーが生成されます。
ハンドルされない例外レンダリング コンポーネント: EditForm には、Model パラメーターまたは EditContext パラメーター (両方ではなく) が必要です。
フォームの送信を処理する
EditForm には、フォームの送信を処理するための次のコールバックが用意されています。
- OnValidSubmit は、有効なフィールドを含むフォームが送信されたときに実行するイベント ハンドラーを割り当てるために使用します。
- OnInvalidSubmit は、無効なフィールドを含むフォームが送信されたときに実行するイベント ハンドラーを割り当てるために使用します。
- OnSubmit は、フォーム フィールドの検証状態に関係なく実行するイベント ハンドラーを割り当てるために使用します。 フォームは、イベント ハンドラー メソッドで EditContext.Validate を呼び出すことによって検証されます。 Validate によって
true
が返された場合、フォームは有効です。
組み込みの入力コンポーネント
Blazor フレームワークには、ユーザー入力を受け取って検証するための組み込みの入力コンポーネントが用意されています。 次の表で示す組み込みの入力コンポーネントは、EditContext である EditForm でサポートされます。
表のコンポーネントは、Razor コンポーネント マークアップのフォームの外部でもサポートされます。 入力は、それらが変更されたときとフォームが送信されたときに検証されます。
入力コンポーネント | ... とレンダリング |
---|---|
InputCheckbox | <input type="checkbox"> |
InputDate<TValue> | <input type="date"> |
InputFile | <input type="file"> |
InputNumber<TValue> | <input type="number"> |
InputRadio<TValue> | <input type="radio"> |
InputRadioGroup<TValue> | 子 InputRadio<TValue> のグループ |
InputSelect<TValue> | <select> |
InputText | <input> |
InputTextArea | <textarea> |
InputFile コンポーネントについて詳しくは、「ASP.NET Core Blazor ファイルのアップロード」をご覧ください。
入力コンポーネント | ... とレンダリング |
---|---|
InputCheckbox | <input type="checkbox"> |
InputDate<TValue> | <input type="date"> |
InputNumber<TValue> | <input type="number"> |
InputSelect<TValue> | <select> |
InputText | <input> |
InputTextArea | <textarea> |
Note
InputRadio<TValue> と InputRadioGroup<TValue> コンポーネントは、ASP.NET Core 5.0 以降で使用できます。 詳細については、この記事のバージョン 5.0 以降を選択してください。
すべての入力コンポーネント (EditForm を含む) で、任意の属性がサポートされています。 コンポーネント パラメーターに一致しない属性は、レンダリングされる HTML 要素に追加されます。
入力コンポーネントによって、フィールドが変更されたときに検証するための既定の動作が提供されます。
- EditContext を含むフォーム内の入力コンポーネントの場合、既定の検証動作には、基になる HTML 要素の検証スタイルを使用して、フィールドの状態を有効または無効として反映するように、フィールド CSS クラスを更新することが含まれます。
- EditContext がないコントロールの場合、既定の検証には有効または無効な状態が反映されますが、基になる HTML 要素に対する検証スタイルは提供 "されません"。
一部のコンポーネントには、便利な解析ロジックが含まれます。 たとえば、InputDate<TValue> と InputNumber<TValue> では、解析不能な値を検証エラーとして登録することによって、解析不能な値を適切に処理します。 null 値を受け入れることができる型では、ターゲット フィールドの null 値の許容もサポートされています (たとえば、null 許容整数の場合は int?
)。
InputFile コンポーネントについて詳しくは、「ASP.NET Core Blazor ファイルのアップロード」をご覧ください。
フォームの例
この記事のいくつかの例で使用されている次の Starship
型では、データ注釈を持つさまざまなプロパティのセットが定義されています。
Identifier
は、RequiredAttribute で注釈が付けられているため必須です。Identifier
には、StringLengthAttribute を使用して、1 文字以上 16 文字以下の値が要求されています。Description
は、RequiredAttribute で注釈が付けられていないため、省略可能です。Classification
は必須です。MaximumAccommodation
プロパティは、既定値は 0 ですが、RangeAttribute により 1 から 100,000 の値が必要です。IsValidatedDesign
により、プロパティの値がtrue
であることが要求されています。これは、プロパティが UI のチェック ボックス (<input type="checkbox">
) にバインドされているときは、選択された状態と一致します。ProductionDate
は DateTime であり、必須です。
Starship.cs
:
using System.ComponentModel.DataAnnotations;
public class Starship
{
[Required]
[StringLength(16, ErrorMessage = "Identifier too long (16 character limit).")]
public string? Identifier { get; set; }
public string? Description { get; set; }
[Required]
public string? Classification { get; set; }
[Range(1, 100000, ErrorMessage = "Accommodation invalid (1-100000).")]
public int MaximumAccommodation { get; set; }
[Required]
[Range(typeof(bool), "true", "true",
ErrorMessage = "This form disallows unapproved ships.")]
public bool IsValidatedDesign { get; set; }
[Required]
public DateTime ProductionDate { get; set; }
}
using System.ComponentModel.DataAnnotations;
public class Starship
{
[Required]
[StringLength(16, ErrorMessage = "Identifier too long (16 character limit).")]
public string? Identifier { get; set; }
public string? Description { get; set; }
[Required]
public string? Classification { get; set; }
[Range(1, 100000, ErrorMessage = "Accommodation invalid (1-100000).")]
public int MaximumAccommodation { get; set; }
[Required]
[Range(typeof(bool), "true", "true",
ErrorMessage = "This form disallows unapproved ships.")]
public bool IsValidatedDesign { get; set; }
[Required]
public DateTime ProductionDate { get; set; }
}
using System;
using System.ComponentModel.DataAnnotations;
public class Starship
{
[Required]
[StringLength(16, ErrorMessage = "Identifier too long (16 character limit).")]
public string Identifier { get; set; }
public string Description { get; set; }
[Required]
public string Classification { get; set; }
[Range(1, 100000, ErrorMessage = "Accommodation invalid (1-100000).")]
public int MaximumAccommodation { get; set; }
[Required]
[Range(typeof(bool), "true", "true",
ErrorMessage = "This form disallows unapproved ships.")]
public bool IsValidatedDesign { get; set; }
[Required]
public DateTime ProductionDate { get; set; }
}
using System;
using System.ComponentModel.DataAnnotations;
public class Starship
{
[Required]
[StringLength(16, ErrorMessage = "Identifier too long (16 character limit).")]
public string Identifier { get; set; }
public string Description { get; set; }
[Required]
public string Classification { get; set; }
[Range(1, 100000, ErrorMessage = "Accommodation invalid (1-100000).")]
public int MaximumAccommodation { get; set; }
[Required]
[Range(typeof(bool), "true", "true",
ErrorMessage = "This form disallows unapproved ships.")]
public bool IsValidatedDesign { get; set; }
[Required]
public DateTime ProductionDate { get; set; }
}
以下のフォームでは、次のものを使用してユーザー入力が受け入れられて検証されます。
- 前の
Starship
モデルで定義されているプロパティと検証。 - Blazor の複数の組み込みの入力コンポーネント。
Pages/FormExample2.razor
:
@page "/form-example-2"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample2> Logger
<h1>Starfleet Starship Database</h1>
<h2>New Ship Entry Form</h2>
<EditForm Model="@starship" OnValidSubmit="@HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<p>
<label>
Identifier:
<InputText @bind-Value="starship.Identifier" />
</label>
</p>
<p>
<label>
Description (optional):
<InputTextArea @bind-Value="starship.Description" />
</label>
</p>
<p>
<label>
Primary Classification:
<InputSelect @bind-Value="starship.Classification">
<option value="">Select classification ...</option>
<option value="Exploration">Exploration</option>
<option value="Diplomacy">Diplomacy</option>
<option value="Defense">Defense</option>
</InputSelect>
</label>
</p>
<p>
<label>
Maximum Accommodation:
<InputNumber @bind-Value="starship.MaximumAccommodation" />
</label>
</p>
<p>
<label>
Engineering Approval:
<InputCheckbox @bind-Value="starship.IsValidatedDesign" />
</label>
</p>
<p>
<label>
Production Date:
<InputDate @bind-Value="starship.ProductionDate" />
</label>
</p>
<button type="submit">Submit</button>
<p>
<a href="http://www.startrek.com/">Star Trek</a>,
©1966-2019 CBS Studios, Inc. and
<a href="https://www.paramount.com">Paramount Pictures</a>
</p>
</EditForm>
@code {
private Starship starship = new() { ProductionDate = DateTime.UtcNow };
private void HandleValidSubmit()
{
Logger.LogInformation("HandleValidSubmit called");
// Process the valid form
}
}
@page "/form-example-2"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample2> Logger
<h1>Starfleet Starship Database</h1>
<h2>New Ship Entry Form</h2>
<EditForm Model="@starship" OnValidSubmit="@HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<p>
<label>
Identifier:
<InputText @bind-Value="starship.Identifier" />
</label>
</p>
<p>
<label>
Description (optional):
<InputTextArea @bind-Value="starship.Description" />
</label>
</p>
<p>
<label>
Primary Classification:
<InputSelect @bind-Value="starship.Classification">
<option value="">Select classification ...</option>
<option value="Exploration">Exploration</option>
<option value="Diplomacy">Diplomacy</option>
<option value="Defense">Defense</option>
</InputSelect>
</label>
</p>
<p>
<label>
Maximum Accommodation:
<InputNumber @bind-Value="starship.MaximumAccommodation" />
</label>
</p>
<p>
<label>
Engineering Approval:
<InputCheckbox @bind-Value="starship.IsValidatedDesign" />
</label>
</p>
<p>
<label>
Production Date:
<InputDate @bind-Value="starship.ProductionDate" />
</label>
</p>
<button type="submit">Submit</button>
<p>
<a href="http://www.startrek.com/">Star Trek</a>,
©1966-2019 CBS Studios, Inc. and
<a href="https://www.paramount.com">Paramount Pictures</a>
</p>
</EditForm>
@code {
private Starship starship = new() { ProductionDate = DateTime.UtcNow };
private void HandleValidSubmit()
{
Logger.LogInformation("HandleValidSubmit called");
// Process the valid form
}
}
@page "/form-example-2"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample2> Logger
<h1>Starfleet Starship Database</h1>
<h2>New Ship Entry Form</h2>
<EditForm Model="@starship" OnValidSubmit="@HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<p>
<label>
Identifier:
<InputText @bind-Value="starship.Identifier" />
</label>
</p>
<p>
<label>
Description (optional):
<InputTextArea @bind-Value="starship.Description" />
</label>
</p>
<p>
<label>
Primary Classification:
<InputSelect @bind-Value="starship.Classification">
<option value="">Select classification ...</option>
<option value="Exploration">Exploration</option>
<option value="Diplomacy">Diplomacy</option>
<option value="Defense">Defense</option>
</InputSelect>
</label>
</p>
<p>
<label>
Maximum Accommodation:
<InputNumber @bind-Value="starship.MaximumAccommodation" />
</label>
</p>
<p>
<label>
Engineering Approval:
<InputCheckbox @bind-Value="starship.IsValidatedDesign" />
</label>
</p>
<p>
<label>
Production Date:
<InputDate @bind-Value="starship.ProductionDate" />
</label>
</p>
<button type="submit">Submit</button>
<p>
<a href="http://www.startrek.com/">Star Trek</a>,
©1966-2019 CBS Studios, Inc. and
<a href="https://www.paramount.com">Paramount Pictures</a>
</p>
</EditForm>
@code {
private Starship starship = new() { ProductionDate = DateTime.UtcNow };
private void HandleValidSubmit()
{
Logger.LogInformation("HandleValidSubmit called");
// Process the valid form
}
}
@page "/form-example-2"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample2> Logger
<h1>Starfleet Starship Database</h1>
<h2>New Ship Entry Form</h2>
<EditForm Model="@starship" OnValidSubmit="@HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<p>
<label>
Identifier:
<InputText @bind-Value="starship.Identifier" />
</label>
</p>
<p>
<label>
Description (optional):
<InputTextArea @bind-Value="starship.Description" />
</label>
</p>
<p>
<label>
Primary Classification:
<InputSelect @bind-Value="starship.Classification">
<option value="">Select classification ...</option>
<option value="Exploration">Exploration</option>
<option value="Diplomacy">Diplomacy</option>
<option value="Defense">Defense</option>
</InputSelect>
</label>
</p>
<p>
<label>
Maximum Accommodation:
<InputNumber @bind-Value="starship.MaximumAccommodation" />
</label>
</p>
<p>
<label>
Engineering Approval:
<InputCheckbox @bind-Value="starship.IsValidatedDesign" />
</label>
</p>
<p>
<label>
Production Date:
<InputDate @bind-Value="starship.ProductionDate" />
</label>
</p>
<button type="submit">Submit</button>
<p>
<a href="http://www.startrek.com/">Star Trek</a>,
©1966-2019 CBS Studios, Inc. and
<a href="https://www.paramount.com">Paramount Pictures</a>
</p>
</EditForm>
@code {
private Starship starship = new Starship() { ProductionDate = DateTime.UtcNow };
private void HandleValidSubmit()
{
Logger.LogInformation("HandleValidSubmit called");
// Process the valid form
}
}
前の例の EditForm により、割り当てられた Starship
インスタンス (EditContext) に基づいて Model="@starship"
が作成され、有効なフォームが処理されます。 次の例 (FormExample3
コンポーネント) では、EditContext をフォームに割り当て、フォームが送信されるときに検証する方法を示します。
次に例を示します。
- 前の
Starfleet Starship Database
フォーム (FormExample2
コンポーネント) の簡略版が使用され、宇宙船の識別子の値のみが受け入れられます。Starship
の他のプロパティは、Starship
型のインスタンスが作成されるときに、有効な既定値を受け取ります。 Submit
ボタンが選択されると、HandleSubmit
メソッドが実行されます。- フォームは、
HandleSubmit
メソッドで EditContext.Validate を呼び出すことによって検証されます。 - 検証結果に応じて、ログ記録が実行されます。
Note
FormExample3
コンポーネントの HandleSubmit
は、フォーム値の格納で非同期呼び出し (await ...
) が使用されることが多いので、非同期メソッドとして示されています。 示されているようにフォームがテスト アプリで使用される場合は、HandleSubmit
は単に同期的に実行されます。 テスト目的の場合は、次のビルド警告を無視します。
This async method lacks 'await' operators and will run synchronously. ...
Pages/FormExample3.razor
:
@page "/form-example-3"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample3> Logger
<EditForm EditContext="@editContext" OnSubmit="@HandleSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<p>
<label>
Identifier:
<InputText @bind-Value="starship.Identifier" />
</label>
</p>
<button type="submit">Submit</button>
<p>
<a href="http://www.startrek.com/">Star Trek</a>,
©1966-2019 CBS Studios, Inc. and
<a href="https://www.paramount.com">Paramount Pictures</a>
</p>
</EditForm>
@code {
private Starship starship =
new()
{
Identifier = "NCC-1701",
Classification = "Exploration",
MaximumAccommodation = 150,
IsValidatedDesign = true,
ProductionDate = new DateTime(2245, 4, 11)
};
private EditContext? editContext;
protected override void OnInitialized()
{
editContext = new(starship);
}
private async Task HandleSubmit()
{
if (editContext != null && editContext.Validate())
{
Logger.LogInformation("HandleSubmit called: Form is valid");
// Process the valid form
// await ...
await Task.CompletedTask;
}
else
{
Logger.LogInformation("HandleSubmit called: Form is INVALID");
}
}
}
@page "/form-example-3"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample3> Logger
<EditForm EditContext="@editContext" OnSubmit="@HandleSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<p>
<label>
Identifier:
<InputText @bind-Value="starship.Identifier" />
</label>
</p>
<button type="submit">Submit</button>
<p>
<a href="http://www.startrek.com/">Star Trek</a>,
©1966-2019 CBS Studios, Inc. and
<a href="https://www.paramount.com">Paramount Pictures</a>
</p>
</EditForm>
@code {
private Starship starship =
new()
{
Identifier = "NCC-1701",
Classification = "Exploration",
MaximumAccommodation = 150,
IsValidatedDesign = true,
ProductionDate = new DateTime(2245, 4, 11)
};
private EditContext? editContext;
protected override void OnInitialized()
{
editContext = new(starship);
}
private async Task HandleSubmit()
{
if (editContext != null && editContext.Validate())
{
Logger.LogInformation("HandleSubmit called: Form is valid");
// Process the valid form
// await ...
await Task.CompletedTask;
}
else
{
Logger.LogInformation("HandleSubmit called: Form is INVALID");
}
}
}
@page "/form-example-3"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample3> Logger
<EditForm EditContext="@editContext" OnSubmit="@HandleSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<p>
<label>
Identifier:
<InputText @bind-Value="starship.Identifier" />
</label>
</p>
<button type="submit">Submit</button>
<p>
<a href="http://www.startrek.com/">Star Trek</a>,
©1966-2019 CBS Studios, Inc. and
<a href="https://www.paramount.com">Paramount Pictures</a>
</p>
</EditForm>
@code {
private Starship starship =
new()
{
Identifier = "NCC-1701",
Classification = "Exploration",
MaximumAccommodation = 150,
IsValidatedDesign = true,
ProductionDate = new DateTime(2245, 4, 11)
};
private EditContext editContext;
protected override void OnInitialized()
{
editContext = new(starship);
}
private async Task HandleSubmit()
{
if (editContext.Validate())
{
Logger.LogInformation("HandleSubmit called: Form is valid");
// Process the valid form
// await ...
}
else
{
Logger.LogInformation("HandleSubmit called: Form is INVALID");
}
}
}
@page "/form-example-3"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample3> Logger
<EditForm EditContext="@editContext" OnSubmit="@HandleSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<p>
<label>
Identifier:
<InputText @bind-Value="starship.Identifier" />
</label>
</p>
<button type="submit">Submit</button>
<p>
<a href="http://www.startrek.com/">Star Trek</a>,
©1966-2019 CBS Studios, Inc. and
<a href="https://www.paramount.com">Paramount Pictures</a>
</p>
</EditForm>
@code {
private Starship starship =
new Starship()
{
Identifier = "NCC-1701",
Classification = "Exploration",
MaximumAccommodation = 150,
IsValidatedDesign = true,
ProductionDate = new DateTime(2245, 4, 11)
};
private EditContext editContext;
protected override void OnInitialized()
{
editContext = new EditContext(starship);
}
private async Task HandleSubmit()
{
if (editContext.Validate())
{
Logger.LogInformation("HandleSubmit called: Form is valid");
// Process the valid form
// await ...
}
else
{
Logger.LogInformation("HandleSubmit called: Form is INVALID");
}
}
}
注意
割り当てられた後で EditContext を変更することは、サポートされていません。
InputSelect
コンポーネントを使用した複数のオプションの選択
バインドでは、InputSelect<TValue> コンポーネントを使用した multiple
オプションの選択がサポートされます。 @onchange
イベントでは、イベント引数 (ChangeEventArgs
) を介して選択したオプションの配列が提供されます。 その値は配列型にバインドされる必要があり、配列型にバインドすると InputSelect<TValue> タグで multiple
属性が省略可能になります。
次の例では、ユーザーは少なくとも 2 つの宇宙船の分類を選択する必要がありますが、3 つの分類以下でなければなりません。
Pages/BindMultipleWithInputSelect.razor
:
@page "/bind-multiple-with-inputselect"
@using System.ComponentModel.DataAnnotations
@using Microsoft.Extensions.Logging
@inject ILogger<BindMultipleWithInputSelect> Logger
<h1>Bind Multiple <code>InputSelect</code>Example</h1>
<EditForm EditContext="@editContext" OnValidSubmit="@HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<p>
<label>
Select classifications (Minimum: 2, Maximum: 3):
<InputSelect @bind-Value="starship.SelectedClassification">
<option value="@Classification.Exploration">Exploration</option>
<option value="@Classification.Diplomacy">Diplomacy</option>
<option value="@Classification.Defense">Defense</option>
<option value="@Classification.Research">Research</option>
</InputSelect>
</label>
</p>
<button type="submit">Submit</button>
</EditForm>
<p>
Selected Classifications:
@string.Join(", ", starship.SelectedClassification)
</p>
@code {
private EditContext? editContext;
private Starship starship = new();
protected override void OnInitialized()
{
editContext = new(starship);
}
private void HandleValidSubmit()
{
Logger.LogInformation("HandleValidSubmit called");
}
private class Starship
{
[Required, MinLength(2), MaxLength(3)]
public Classification[] SelectedClassification { get; set; } =
new[] { Classification.Diplomacy };
}
private enum Classification { Exploration, Diplomacy, Defense, Research }
}
空の文字列と null
値がデータ バインディングでどのように処理されるかについては、「InputSelect
オプションを C# オブジェクトの null
値にバインドする」セクションを参照してください。
InputSelect
オプションを C# オブジェクトの null
値にバインドする
空の文字列と null
値がデータ バインディングでどのように処理されるかについては、「ASP.NET Core Blazor のデータ バインディング」をご覧ください。
表示名のサポート
いくつかの組み込みコンポーネントでは、InputBase<TValue>.DisplayName パラメーターを使用した表示名がサポートされています。
「フォームの例」セクションの Starfleet Starship Database
フォーム (FormExample2
コンポーネント) で、新しい宇宙船の製造日には表示名が指定されていません。
<label>
Production Date:
<InputDate @bind-Value="starship.ProductionDate" />
</label>
フォームの送信時にフィールドに無効な日付が含まれている場合、エラー メッセージにはわかりやすい名前が表示されません。 フィールド名 "ProductionDate
" は、検証の概要に表示されるときに、"Production
" と "Date
" の間に空白がありません。
ProductionDate フィールドは日付である必要があります。
DisplayName プロパティには、"Production
" と "Date
" の間にスペースがあるフレンドリ名を設定します。
<label>
Production Date:
<InputDate @bind-Value="starship.ProductionDate"
DisplayName="Production Date" />
</label>
フィールドの値が無効な場合、検証の概要にはフレンドリ名が表示されます。
Production Date フィールドは日付である必要があります。
エラー メッセージ テンプレートのサポート
InputDate<TValue> と InputNumber<TValue> では、エラー メッセージ テンプレートがサポートされています。
わかりやすい表示名が割り当てられた「フォームの例」セクションの Starfleet Starship Database
フォーム (FormExample2
コンポーネント) の Production Date
フィールドでは、次の既定のエラー メッセージ テンプレートを使用してエラー メッセージが生成されます。
The {0} field must be a date.
{0}
プレースホルダーの位置は、エラーがユーザーに表示されるときに、DisplayName プロパティの値が表示される場所です。
<label>
Production Date:
<InputDate @bind-Value="starship.ProductionDate"
DisplayName="Production Date" />
</label>
Production Date フィールドは日付である必要があります。
カスタム メッセージを提供するには、カスタム テンプレートを ParsingErrorMessage に割り当てます。
<label>
Production Date:
<InputDate @bind-Value="starship.ProductionDate"
DisplayName="Production Date"
ParsingErrorMessage="The {0} field has an incorrect date value." />
</label>
Production Date フィールドに無効な日付値が指定されています。
「フォームの例」セクションの Starfleet Starship Database
フォーム (FormExample2
コンポーネント) では、既定のエラー メッセージ テンプレートが使用されています。
The {0} field must be a date.
{0}
プレースホルダーの位置は、エラーがユーザーに表示されるときに、DisplayName プロパティの値が表示される場所です。
<label>
Production Date:
<InputDate @bind-Value="starship.ProductionDate" />
</label>
ProductionDate フィールドは日付である必要があります。
カスタム メッセージを提供するには、カスタム テンプレートを ParsingErrorMessage に割り当てます。
<label>
Production Date:
<InputDate @bind-Value="starship.ProductionDate"
ParsingErrorMessage="The {0} field has an incorrect date value." />
</label>
ProductionDate フィールドに無効な日付値が指定されています。
基本検証
基本的なフォーム検証シナリオでは、EditForm インスタンスは宣言された EditContext と ValidationMessageStore インスタンスを使用してフォーム フィールドを検証できます。 EditContext の OnValidationRequested イベントのハンドラーにより、カスタム検証ロジックが実行されます。 ハンドラーの結果によって ValidationMessageStore インスタンスが更新されます。
基本的なフォーム検証は、フォームをホストするコンポーネント内でフォームのモデルが定義されている場合に便利です。これは、コンポーネントのメンバーとして直接、またはサブクラスで行います。 複数のコンポーネントで独立したモデル クラスを使用する場合は、検証コンポーネントの使用をお勧めします。
次の FormExample4
コンポーネントの場合、HandleValidationRequested
ハンドラー メソッドでは、フォームを検証する前に ValidationMessageStore.Clear を呼び出すことによって、既存の検証メッセージをクリアします。
Pages/FormExample4.razor
:
@page "/form-example-4"
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<FormExample4> Logger
<h2>Ship Holodecks</h2>
<EditForm EditContext="editContext" OnValidSubmit="@HandleValidSubmit">
<label>
Type 1:
<InputCheckbox @bind-Value="holodeck.Type1" />
</label>
<label>
Type 2:
<InputCheckbox @bind-Value="holodeck.Type2" />
</label>
<button type="submit">Update</button>
<ValidationMessage For="() => holodeck.Options" />
<p>
<a href="http://www.startrek.com/">Star Trek</a>,
©1966-2019 CBS Studios, Inc. and
<a href="https://www.paramount.com">Paramount Pictures</a>
</p>
</EditForm>
@code {
private EditContext? editContext;
private Holodeck holodeck = new();
private ValidationMessageStore? messageStore;
protected override void OnInitialized()
{
editContext = new(holodeck);
editContext.OnValidationRequested += HandleValidationRequested;
messageStore = new(editContext);
}
private void HandleValidationRequested(object? sender,
ValidationRequestedEventArgs args)
{
messageStore?.Clear();
// Custom validation logic
if (!holodeck.Options)
{
messageStore?.Add(() => holodeck.Options, "Select at least one.");
}
}
private void HandleValidSubmit()
{
Logger.LogInformation("HandleValidSubmit called: Processing the form");
// Process the form
}
public class Holodeck
{
public bool Type1 { get; set; }
public bool Type2 { get; set; }
public bool Options => Type1 || Type2;
}
public void Dispose()
{
if (editContext is not null)
{
editContext.OnValidationRequested -= HandleValidationRequested;
}
}
}
@page "/form-example-4"
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<FormExample4> Logger
<h2>Ship Holodecks</h2>
<EditForm EditContext="editContext" OnValidSubmit="@HandleValidSubmit">
<label>
Type 1:
<InputCheckbox @bind-Value="holodeck.Type1" />
</label>
<label>
Type 2:
<InputCheckbox @bind-Value="holodeck.Type2" />
</label>
<button type="submit">Update</button>
<ValidationMessage For="() => holodeck.Options" />
<p>
<a href="http://www.startrek.com/">Star Trek</a>,
©1966-2019 CBS Studios, Inc. and
<a href="https://www.paramount.com">Paramount Pictures</a>
</p>
</EditForm>
@code {
private EditContext? editContext;
private Holodeck holodeck = new();
private ValidationMessageStore? messageStore;
protected override void OnInitialized()
{
editContext = new(holodeck);
editContext.OnValidationRequested += HandleValidationRequested;
messageStore = new(editContext);
}
private void HandleValidationRequested(object? sender,
ValidationRequestedEventArgs args)
{
messageStore?.Clear();
// Custom validation logic
if (!holodeck.Options)
{
messageStore?.Add(() => holodeck.Options, "Select at least one.");
}
}
private void HandleValidSubmit()
{
Logger.LogInformation("HandleValidSubmit called: Processing the form");
// Process the form
}
public class Holodeck
{
public bool Type1 { get; set; }
public bool Type2 { get; set; }
public bool Options => Type1 || Type2;
}
public void Dispose()
{
if (editContext is not null)
{
editContext.OnValidationRequested -= HandleValidationRequested;
}
}
}
@page "/form-example-4"
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<FormExample4> Logger
<h2>Ship Holodecks</h2>
<EditForm EditContext="editContext" OnValidSubmit="@HandleValidSubmit">
<label>
Type 1:
<InputCheckbox @bind-Value="holodeck.Type1" />
</label>
<label>
Type 2:
<InputCheckbox @bind-Value="holodeck.Type2" />
</label>
<button type="submit">Update</button>
<ValidationMessage For="() => holodeck.Options" />
<p>
<a href="http://www.startrek.com/">Star Trek</a>,
©1966-2019 CBS Studios, Inc. and
<a href="https://www.paramount.com">Paramount Pictures</a>
</p>
</EditForm>
@code {
private EditContext editContext;
private Holodeck holodeck = new();
private ValidationMessageStore messageStore;
protected override void OnInitialized()
{
editContext = new(holodeck);
editContext.OnValidationRequested += HandleValidationRequested;
messageStore = new(editContext);
}
private void HandleValidationRequested(object sender,
ValidationRequestedEventArgs args)
{
messageStore.Clear();
// Custom validation logic
if (!holodeck.Options)
{
messageStore.Add(() => holodeck.Options, "Select at least one.");
}
}
private void HandleValidSubmit()
{
Logger.LogInformation("HandleValidSubmit called: Processing the form");
// Process the form
}
public class Holodeck
{
public bool Type1 { get; set; }
public bool Type2 { get; set; }
public bool Options => Type1 || Type2;
}
public void Dispose()
{
editContext.OnValidationRequested -= HandleValidationRequested;
}
}
@page "/form-example-4"
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<FormExample4> Logger
<h2>Ship Holodecks</h2>
<EditForm EditContext="editContext" OnValidSubmit="@HandleValidSubmit">
<label>
Type 1:
<InputCheckbox @bind-Value="holodeck.Type1" />
</label>
<label>
Type 2:
<InputCheckbox @bind-Value="holodeck.Type2" />
</label>
<button type="submit">Update</button>
<ValidationMessage For="() => holodeck.Options" />
<p>
<a href="http://www.startrek.com/">Star Trek</a>,
©1966-2019 CBS Studios, Inc. and
<a href="https://www.paramount.com">Paramount Pictures</a>
</p>
</EditForm>
@code {
private EditContext editContext;
private Holodeck holodeck = new Holodeck();
private ValidationMessageStore messageStore;
protected override void OnInitialized()
{
editContext = new EditContext(holodeck);
editContext.OnValidationRequested += HandleValidationRequested;
messageStore = new ValidationMessageStore(editContext);
}
private void HandleValidationRequested(object sender,
ValidationRequestedEventArgs args)
{
messageStore.Clear();
// Custom validation logic
if (!holodeck.Options)
{
messageStore.Add(() => holodeck.Options, "Select at least one.");
}
}
private void HandleValidSubmit()
{
Logger.LogInformation("HandleValidSubmit called: Processing the form");
// Process the form
}
public class Holodeck
{
public bool Type1 { get; set; }
public bool Type2 { get; set; }
public bool Options => Type1 || Type2;
}
public void Dispose()
{
editContext.OnValidationRequested -= HandleValidationRequested;
}
}
データ注釈検証コンポーネントとカスタム検証
DataAnnotationsValidator コンポーネントにより、データ注釈検証がカスケードされる EditContext にアタッチされます。 データ注釈の検証を有効にするには、DataAnnotationsValidator コンポーネントが必要です。 データ注釈と異なる検証システムを使用するには、DataAnnotationsValidator コンポーネントの代わりにカスタム実装を使用します。 参照ソースでの検査には、DataAnnotationsValidator のフレームワークの実装を使用できます。
注意
通常、.NET 参照ソースへのドキュメント リンクを使用すると、リポジトリの既定のブランチが読み込まれます。このブランチは、.NET の次回リリースに向けて行われている現在の開発を表します。 特定のリリースのタグを選択するには、[Switch branches or tags](ブランチまたはタグの切り替え) ドロップダウン リストを使います。 詳細については、「ASP.NET Core ソース コードのバージョン タグを選択する方法」 (dotnet/AspNetCore.Docs #26205) を参照してください。
Blazor は 2 種類の検証を実行します。
- フィールド検証 は、ユーザーがタブでフィールドを離れたときに実行されます。 フィールドの検証時に、DataAnnotationsValidator コンポーネントによって、報告されたすべての検証結果がフィールドに関連付けられます。
- モデル検証は、ユーザーがフォームを送信したときに実行されます。 モデルの検証時に、DataAnnotationsValidator コンポーネントは、検証結果で報告されたメンバー名に基づいてフィールドを判断しようとします。 個々のメンバーに関連付けられていない検証結果は、フィールドではなくモデルに関連付けられます。
検証コンポーネント
検証コンポーネントでは、フォームの EditContextの ValidationMessageStore を管理することで、フォームの検証をサポートします。
Blazor フレームワークでは、検証属性 (データ注釈)に基づいて検証サポートをフォームにアタッチする DataAnnotationsValidator コンポーネントを提供します。 カスタム検証コンポーネントを作成して、同じページの異なるフォームの検証メッセージを処理するか、フォーム処理の異なるステップ (たとえば、クライアント側の検証の後のサーバー側の検証) で同じフォームの検証メッセージを処理できます。 このセクションで示す検証コンポーネントの例 (CustomValidation
) は、この記事の次のセクションで使用します。
Note
多くの場合、カスタムのデータ注釈検証属性をカスタム検証コンポーネントの代わりに使用できます。 フォームのモデルに適用されるカスタム属性は、DataAnnotationsValidator コンポーネントを使用してアクティブ化されます。 サーバー側の検証で使用する場合、モデルに適用されるカスタム属性はすべてサーバー上で実行可能である必要があります。 詳細については、「ASP.NET Core MVC でのモデルの検証」を参照してください。
ComponentBaseから検証コンポーネントを作成します。
- フォームの EditContext は、コンポーネントのカスケード パラメーターです。
- 検証コンポーネントが初期化されると、フォーム エラーの現在の一覧を保持するために新しい ValidationMessageStore が作成されます。
- フォームのコンポーネント内の開発者コードで
DisplayErrors
メソッドが呼び出されると、メッセージ ストアでエラーが受け取られます。 そのエラーは、Dictionary<string, List<string>>
内のDisplayErrors
メソッドに渡されます。 ディクショナリでは、キーは、1 つ以上のエラーがあるフォーム フィールドの名前です。 値は、エラー一覧です。 - 次のいずれかが発生すると、メッセージはクリアされます。
- EditContext で検証が要求され、OnValidationRequested イベントが発生した場合。 すべてのエラーがクリアされます。
- フォームのフィールドが変更され、OnFieldChanged イベントが発生した場合。 そのフィールドのエラーのみがクリアされます。
- 開発者コードによって
ClearErrors
メソッドが呼び出された場合。 すべてのエラーがクリアされます。
CustomValidation.cs
(テスト アプリで使用する場合は、名前空間 BlazorSample
をアプリの名前空間に一致するように変更します):
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
namespace BlazorSample;
public class CustomValidation : ComponentBase
{
private ValidationMessageStore? messageStore;
[CascadingParameter]
private EditContext? CurrentEditContext { get; set; }
protected override void OnInitialized()
{
if (CurrentEditContext is null)
{
throw new InvalidOperationException(
$"{nameof(CustomValidation)} requires a cascading " +
$"parameter of type {nameof(EditContext)}. " +
$"For example, you can use {nameof(CustomValidation)} " +
$"inside an {nameof(EditForm)}.");
}
messageStore = new(CurrentEditContext);
CurrentEditContext.OnValidationRequested += (s, e) =>
messageStore?.Clear();
CurrentEditContext.OnFieldChanged += (s, e) =>
messageStore?.Clear(e.FieldIdentifier);
}
public void DisplayErrors(Dictionary<string, List<string>> errors)
{
if (CurrentEditContext is not null)
{
foreach (var err in errors)
{
messageStore?.Add(CurrentEditContext.Field(err.Key), err.Value);
}
CurrentEditContext.NotifyValidationStateChanged();
}
}
public void ClearErrors()
{
messageStore?.Clear();
CurrentEditContext?.NotifyValidationStateChanged();
}
}
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
namespace BlazorSample;
public class CustomValidation : ComponentBase
{
private ValidationMessageStore? messageStore;
[CascadingParameter]
private EditContext? CurrentEditContext { get; set; }
protected override void OnInitialized()
{
if (CurrentEditContext is null)
{
throw new InvalidOperationException(
$"{nameof(CustomValidation)} requires a cascading " +
$"parameter of type {nameof(EditContext)}. " +
$"For example, you can use {nameof(CustomValidation)} " +
$"inside an {nameof(EditForm)}.");
}
messageStore = new(CurrentEditContext);
CurrentEditContext.OnValidationRequested += (s, e) =>
messageStore?.Clear();
CurrentEditContext.OnFieldChanged += (s, e) =>
messageStore?.Clear(e.FieldIdentifier);
}
public void DisplayErrors(Dictionary<string, List<string>> errors)
{
if (CurrentEditContext is not null)
{
foreach (var err in errors)
{
messageStore?.Add(CurrentEditContext.Field(err.Key), err.Value);
}
CurrentEditContext.NotifyValidationStateChanged();
}
}
public void ClearErrors()
{
messageStore?.Clear();
CurrentEditContext?.NotifyValidationStateChanged();
}
}
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
namespace BlazorSample
{
public class CustomValidation : ComponentBase
{
private ValidationMessageStore messageStore;
[CascadingParameter]
private EditContext CurrentEditContext { get; set; }
protected override void OnInitialized()
{
if (CurrentEditContext == null)
{
throw new InvalidOperationException(
$"{nameof(CustomValidation)} requires a cascading " +
$"parameter of type {nameof(EditContext)}. " +
$"For example, you can use {nameof(CustomValidation)} " +
$"inside an {nameof(EditForm)}.");
}
messageStore = new(CurrentEditContext);
CurrentEditContext.OnValidationRequested += (s, e) =>
messageStore.Clear();
CurrentEditContext.OnFieldChanged += (s, e) =>
messageStore.Clear(e.FieldIdentifier);
}
public void DisplayErrors(Dictionary<string, List<string>> errors)
{
foreach (var err in errors)
{
messageStore.Add(CurrentEditContext.Field(err.Key), err.Value);
}
CurrentEditContext.NotifyValidationStateChanged();
}
public void ClearErrors()
{
messageStore.Clear();
CurrentEditContext.NotifyValidationStateChanged();
}
}
}
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
namespace BlazorSample
{
public class CustomValidation : ComponentBase
{
private ValidationMessageStore messageStore;
[CascadingParameter]
private EditContext CurrentEditContext { get; set; }
protected override void OnInitialized()
{
if (CurrentEditContext == null)
{
throw new InvalidOperationException(
$"{nameof(CustomValidation)} requires a cascading " +
$"parameter of type {nameof(EditContext)}. " +
$"For example, you can use {nameof(CustomValidation)} " +
$"inside an {nameof(EditForm)}.");
}
messageStore = new ValidationMessageStore(CurrentEditContext);
CurrentEditContext.OnValidationRequested += (s, e) =>
messageStore.Clear();
CurrentEditContext.OnFieldChanged += (s, e) =>
messageStore.Clear(e.FieldIdentifier);
}
public void DisplayErrors(Dictionary<string, List<string>> errors)
{
foreach (var err in errors)
{
messageStore.Add(CurrentEditContext.Field(err.Key), err.Value);
}
CurrentEditContext.NotifyValidationStateChanged();
}
public void ClearErrors()
{
messageStore.Clear();
CurrentEditContext.NotifyValidationStateChanged();
}
}
}
重要
ComponentBase から派生する場合は、名前空間を指定する必要があります。 名前空間を指定しないと、ビルド エラーが発生します。
" " 文字が含まれるため、タグ ヘルパーでタグ名 "<グローバル名前空間>.{クラス名}" をターゲットにすることはできません。
{CLASS NAME}
プレースホルダーは、コンポーネント クラスの名前です。 このセクションのカスタム検証の例では、例の名前空間 BlazorSample
を指定します。
Note
匿名のラムダ式は、OnValidationRequested と前の例の OnFieldChanged に対して登録されているイベント ハンドラーです。 このシナリオでは、IDisposable を実装したり、イベント デリゲートの登録を解除したりする必要はありません。 詳しくは、「ASP.NET Core Razor コンポーネントのライフサイクル」をご覧ください。
検証コンポーネントを使用したビジネス ロジック検証
一般的なビジネス ロジックの検証では、ディクショナリ内のフォーム エラーを受け取る検証コンポーネントを使用します。
基本検証は、フォームをホストするコンポーネント内でフォームのモデルが定義されている場合に便利です。これは、コンポーネントのメンバーとして直接、またはサブクラスで行います。 複数のコンポーネントで独立したモデル クラスを使用する場合は、検証コンポーネントの使用をお勧めします。
次に例を示します。
- 宇宙船の分類と説明のみを受け入れる、「フォームの例」セクションの
Starfleet Starship Database
フォーム (FormExample2
コンポーネント) の簡略版が使用されています。DataAnnotationsValidator
コンポーネントがフォームに含まれていないため、フォームの送信時にデータ注釈の検証はトリガーされません。 - この記事の「検証コンポーネント」セクションの
CustomValidation
コンポーネントが使用されています。 - 検証では、ユーザーが宇宙船の分類 (
Classification
) でDefense
を選択した場合、宇宙船の説明 (Description
) の値が要求されます。
検証メッセージをコンポーネントで設定すると、検証の ValidationMessageStore に追加され、EditForm の検証の概要に表示されます。
Pages/FormExample5.razor
:
@page "/form-example-5"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample5> Logger
<h1>Starfleet Starship Database</h1>
<h2>New Ship Entry Form</h2>
<EditForm Model="@starship" OnValidSubmit="@HandleValidSubmit">
<CustomValidation @ref="customValidation" />
<ValidationSummary />
<p>
<label>
Primary Classification:
<InputSelect @bind-Value="starship.Classification">
<option value="">Select classification ...</option>
<option value="Exploration">Exploration</option>
<option value="Diplomacy">Diplomacy</option>
<option value="Defense">Defense</option>
</InputSelect>
</label>
</p>
<p>
<label>
Description (optional):
<InputTextArea @bind-Value="starship.Description" />
</label>
</p>
<button type="submit">Submit</button>
<p>
<a href="http://www.startrek.com/">Star Trek</a>,
©1966-2019 CBS Studios, Inc. and
<a href="https://www.paramount.com">Paramount Pictures</a>
</p>
</EditForm>
@code {
private CustomValidation? customValidation;
private Starship starship = new() { ProductionDate = DateTime.UtcNow };
private void HandleValidSubmit()
{
customValidation?.ClearErrors();
var errors = new Dictionary<string, List<string>>();
if (starship.Classification == "Defense" &&
string.IsNullOrEmpty(starship.Description))
{
errors.Add(nameof(starship.Description),
new() { "For a 'Defense' ship classification, " +
"'Description' is required." });
}
if (errors.Any())
{
customValidation?.DisplayErrors(errors);
}
else
{
Logger.LogInformation("HandleValidSubmit called: Processing the form");
// Process the valid form
}
}
}
@page "/form-example-5"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample5> Logger
<h1>Starfleet Starship Database</h1>
<h2>New Ship Entry Form</h2>
<EditForm Model="@starship" OnValidSubmit="@HandleValidSubmit">
<CustomValidation @ref="customValidation" />
<ValidationSummary />
<p>
<label>
Primary Classification:
<InputSelect @bind-Value="starship.Classification">
<option value="">Select classification ...</option>
<option value="Exploration">Exploration</option>
<option value="Diplomacy">Diplomacy</option>
<option value="Defense">Defense</option>
</InputSelect>
</label>
</p>
<p>
<label>
Description (optional):
<InputTextArea @bind-Value="starship.Description" />
</label>
</p>
<button type="submit">Submit</button>
<p>
<a href="http://www.startrek.com/">Star Trek</a>,
©1966-2019 CBS Studios, Inc. and
<a href="https://www.paramount.com">Paramount Pictures</a>
</p>
</EditForm>
@code {
private CustomValidation? customValidation;
private Starship starship = new() { ProductionDate = DateTime.UtcNow };
private void HandleValidSubmit()
{
customValidation?.ClearErrors();
var errors = new Dictionary<string, List<string>>();
if (starship.Classification == "Defense" &&
string.IsNullOrEmpty(starship.Description))
{
errors.Add(nameof(starship.Description),
new() { "For a 'Defense' ship classification, " +
"'Description' is required." });
}
if (errors.Any())
{
customValidation?.DisplayErrors(errors);
}
else
{
Logger.LogInformation("HandleValidSubmit called: Processing the form");
// Process the valid form
}
}
}
@page "/form-example-5"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample5> Logger
<h1>Starfleet Starship Database</h1>
<h2>New Ship Entry Form</h2>
<EditForm Model="@starship" OnValidSubmit="@HandleValidSubmit">
<CustomValidation @ref="customValidation" />
<ValidationSummary />
<p>
<label>
Primary Classification:
<InputSelect @bind-Value="starship.Classification">
<option value="">Select classification ...</option>
<option value="Exploration">Exploration</option>
<option value="Diplomacy">Diplomacy</option>
<option value="Defense">Defense</option>
</InputSelect>
</label>
</p>
<p>
<label>
Description (optional):
<InputTextArea @bind-Value="starship.Description" />
</label>
</p>
<button type="submit">Submit</button>
<p>
<a href="http://www.startrek.com/">Star Trek</a>,
©1966-2019 CBS Studios, Inc. and
<a href="https://www.paramount.com">Paramount Pictures</a>
</p>
</EditForm>
@code {
private CustomValidation customValidation;
private Starship starship = new() { ProductionDate = DateTime.UtcNow };
private void HandleValidSubmit()
{
customValidation.ClearErrors();
var errors = new Dictionary<string, List<string>>();
if (starship.Classification == "Defense" &&
string.IsNullOrEmpty(starship.Description))
{
errors.Add(nameof(starship.Description),
new() { "For a 'Defense' ship classification, " +
"'Description' is required." });
}
if (errors.Any())
{
customValidation.DisplayErrors(errors);
}
else
{
Logger.LogInformation("HandleValidSubmit called: Processing the form");
// Process the valid form
}
}
}
@page "/form-example-5"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample5> Logger
<h1>Starfleet Starship Database</h1>
<h2>New Ship Entry Form</h2>
<EditForm Model="@starship" OnValidSubmit="@HandleValidSubmit">
<CustomValidation @ref="customValidation" />
<ValidationSummary />
<p>
<label>
Primary Classification:
<InputSelect @bind-Value="starship.Classification">
<option value="">Select classification ...</option>
<option value="Exploration">Exploration</option>
<option value="Diplomacy">Diplomacy</option>
<option value="Defense">Defense</option>
</InputSelect>
</label>
</p>
<p>
<label>
Description (optional):
<InputTextArea @bind-Value="starship.Description" />
</label>
</p>
<button type="submit">Submit</button>
<p>
<a href="http://www.startrek.com/">Star Trek</a>,
©1966-2019 CBS Studios, Inc. and
<a href="https://www.paramount.com">Paramount Pictures</a>
</p>
</EditForm>
@code {
private CustomValidation customValidation;
private Starship starship = new Starship() { ProductionDate = DateTime.UtcNow };
private void HandleValidSubmit()
{
customValidation.ClearErrors();
var errors = new Dictionary<string, List<string>>();
if (starship.Classification == "Defense" &&
string.IsNullOrEmpty(starship.Description))
{
errors.Add(nameof(starship.Description),
new List<string>() { "For a 'Defense' ship classification, " +
"'Description' is required." });
}
if (errors.Any())
{
customValidation.DisplayErrors(errors);
}
else
{
Logger.LogInformation("HandleValidSubmit called: Processing the form");
// Process the valid form
}
}
}
注意
検証コンポーネントを使用する代わりに、データ注釈検証属性を使用することもできます。 フォームのモデルに適用されるカスタム属性は、DataAnnotationsValidator コンポーネントを使用してアクティブ化されます。 サーバー側の検証で使用する場合、属性はサーバーで実行できる必要があります。 詳細については、「ASP.NET Core MVC でのモデルの検証」を参照してください。
検証コンポーネントを使用したサーバー検証
クライアント側の検証に加えて、サーバーの検証がサポートされています。
- DataAnnotationsValidator コンポーネントを使用して、フォーム内のクライアント側の検証を処理します。
- フォームによってクライアント側の検証が渡されると (OnValidSubmit が呼び出されると)、フォーム処理のために、EditContext.Model がバックエンド サーバー API に送信されます。
- サーバーでモデルの検証を処理します。
- サーバー API には、組み込みのフレームワーク データ注釈検証と開発者によって提供されるカスタムの検証ロジックの両方が含まれています。 サーバーで検証が成功すると、フォームが処理され、成功の状態コード (
200 - OK
) が返されます。 検証が失敗すると、失敗の状態コード (400 - Bad Request
) とフィールド検証エラーが返されます。 - 成功時にフォームを無効にするか、エラーを表示します。
基本検証は、フォームをホストするコンポーネント内でフォームのモデルが定義されている場合に便利です。これは、コンポーネントのメンバーとして直接、またはサブクラスで行います。 複数のコンポーネントで独立したモデル クラスを使用する場合は、検証コンポーネントの使用をお勧めします。
次の例は、以下のものに基づいています。
- Blazor WebAssembly プロジェクト テンプレートから作成された、ホストされている Blazor WebAssemblyソリューション。 このアプローチは、Blazor WebAssembly のセキュリティのドキュメントで説明されている、安全にホストされるすべての Blazor でサポートされています。
- 「フォームの例」セクションの
Starship
モデル (Starship.cs
)。 - 「検証コンポーネント」セクションで示した
CustomValidation
コンポーネント。
Starship
モデル (Starship.cs
) をソリューションの Shared
プロジェクトに配置して、クライアントとサーバーの両方のアプリでモデルを使用できるようにします。 共有アプリの名前空間と一致するように、名前空間を追加または更新します (たとえば、namespace BlazorSample.Shared
)。 モデルにはデータ注釈が必要であるため、System.ComponentModel.Annotations
パッケージを Shared
プロジェクトに追加します。
Note
.NET アプリへのパッケージの追加に関するガイダンスについては、「パッケージ利用のワークフロー」 (NuGet ドキュメント) の "パッケージのインストールと管理" に関する記事を参照してください。 NuGet.org で正しいパッケージ バージョンを確認します。
Server プロジェクトで、宇宙船の検証要求を処理し、失敗した検証のメッセージを返すコントローラーを追加します。 Shared
プロジェクトの最後の using
ステートメントの名前空間と、コントローラー クラスの namespace
を更新します。 データ注釈の検証 (クライアント側とサーバー側) に加えて、コントローラーでは、ユーザーが宇宙船の分類 (Classification
) で Defense
を選択した場合に、宇宙船の説明 (Description
) に値が指定されているかどうかが検証されます。
宇宙船の分類 Defense
の検証は、コントローラーのサーバー側でのみ行われます。これは、フォームがサーバーに送信されるときに、今後のフォームではクライアント側で同じ検証が実行されないためです。 クライアント側の検証を伴わないサーバー側の検証は、サーバーでのユーザー入力のプライベート ビジネス ロジックの検証を必要とするアプリで一般的です。 たとえば、ユーザー用に保存されたデータからの個人情報が、ユーザー入力の検証に必要になる場合があります。 クライアント側の検証のために、プライベート データをクライアントに送信することはできません。
Note
このセクションの StarshipValidation
コントローラーでは、Microsoft Identity 2.0 が使用されます。 Web API は、この API で "API.Access
" スコープを持つユーザーのトークンのみが受け入れられます。 API のスコープ名が API.Access
と異なる場合は、追加のカスタマイズが必要です。 Microsoft Identity 1.0 およびバージョン 5.0 より前の ASP.NET Core で動作するコントローラーのバージョンについては、この記事の前のバージョンを参照してください。
セキュリティの詳細については、以下を参照してください。
- ASP.NET Core Blazor の認証と承認 (および、Blazor の "セキュリティと Identity" ノードの他の記事)
- Microsoft ID プラットフォームのドキュメント
Controllers/StarshipValidation.cs
:
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Identity.Web.Resource;
using BlazorSample.Shared;
namespace BlazorSample.Server.Controllers;
[Authorize]
[ApiController]
[Route("[controller]")]
public class StarshipValidationController : ControllerBase
{
private readonly ILogger<StarshipValidationController> logger;
public StarshipValidationController(
ILogger<StarshipValidationController> logger)
{
this.logger = logger;
}
static readonly string[] scopeRequiredByApi = new[] { "API.Access" };
[HttpPost]
public async Task<IActionResult> Post(Starship starship)
{
HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);
try
{
if (starship.Classification == "Defense" &&
string.IsNullOrEmpty(starship.Description))
{
ModelState.AddModelError(nameof(starship.Description),
"For a 'Defense' ship " +
"classification, 'Description' is required.");
}
else
{
logger.LogInformation("Processing the form asynchronously");
// Process the valid form
// async ...
return Ok(ModelState);
}
}
catch (Exception ex)
{
logger.LogError("Validation Error: {Message}", ex.Message);
}
return BadRequest(ModelState);
}
}
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Identity.Web.Resource;
using BlazorSample.Shared;
namespace BlazorSample.Server.Controllers
{
[Authorize]
[ApiController]
[Route("[controller]")]
public class StarshipValidationController : ControllerBase
{
private readonly ILogger<StarshipValidationController> logger;
public StarshipValidationController(
ILogger<StarshipValidationController> logger)
{
this.logger = logger;
}
static readonly string[] scopeRequiredByApi = new[] { "API.Access" };
[HttpPost]
public async Task<IActionResult> Post(Starship starship)
{
HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);
try
{
if (starship.Classification == "Defense" &&
string.IsNullOrEmpty(starship.Description))
{
ModelState.AddModelError(nameof(starship.Description),
"For a 'Defense' ship " +
"classification, 'Description' is required.");
}
else
{
logger.LogInformation("Processing the form asynchronously");
// Process the valid form
// async ...
return Ok(ModelState);
}
}
catch (Exception ex)
{
logger.LogError("Validation Error: {Message}", ex.Message);
}
return BadRequest(ModelState);
}
}
}
ホストされている Blazor WebAssembly アプリで以前のコントローラーを使用している場合は、アプリのコントローラーの名前空間と一致するように名前空間 (BlazorSample.Server.Controllers
) を更新します。
サーバーでモデル バインド検証エラーが発生した場合、通常、ApiController
(ApiControllerAttribute) により、既定の無効な要求応答と ValidationProblemDetails が返されます。 Starfleet Starship Database
フォームのすべてのフィールドが送信されず、フォームの検証が失敗した場合、次の例に示すように、応答には、検証エラー以外のデータも含まれます。
{
"title": "One or more validation errors occurred.",
"status": 400,
"errors": {
"Identifier": ["The Identifier field is required."],
"Classification": ["The Classification field is required."],
"IsValidatedDesign": ["This form disallows unapproved ships."],
"MaximumAccommodation": ["Accommodation invalid (1-100000)."]
}
}
Note
上記の JSON 応答を実際に示すには、フォームのクライアント側の検証を無効にして空のフィールド フォームの送信を許可するか、ツール (Firefox Browser Developer、または Postman など) を使用してサーバー API に要求を直接送信する必要があります。
サーバー API によって、前述の既定の JSON 応答が返された場合、クライアントを使用して、開発者コードで応答を解析して、フォーム検証エラーの処理のために errors
ノードの子を取得することができます。 ファイルを解析するための開発者コードを記述するのは不便です。 JSON を手動で解析するには、ReadFromJsonAsync を呼び出した後でエラーの Dictionary<string, List<string>>
を生成する必要があります。 サーバー API で検証エラーのみを返すのが理想的です。
{
"Identifier": ["The Identifier field is required."],
"Classification": ["The Classification field is required."],
"IsValidatedDesign": ["This form disallows unapproved ships."],
"MaximumAccommodation": ["Accommodation invalid (1-100000)."]
}
サーバー API の応答を変更して、検証エラーのみが返されるようにするには、Program.cs
の ApiControllerAttribute で注釈が付けられたアクションで呼び出されるデリゲートを変更します。 API エンドポイント (/StarshipValidation
) の場合、BadRequestObjectResult と ModelStateDictionary が返されます。 他の API エンドポイントの場合、オブジェクトの結果と新しい ValidationProblemDetailsが返され、既定の動作が保持されます。
Server アプリの Program.cs
ファイルの先頭に Microsoft.AspNetCore.Mvc 名前空間を追加します。
using Microsoft.AspNetCore.Mvc;
Program.cs
で AddControllersWithViews 拡張メソッドを見つけて、次の ConfigureApiBehaviorOptions の呼び出しを追加します。
builder.Services.AddControllersWithViews()
.ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = context =>
{
if (context.HttpContext.Request.Path == "/StarshipValidation")
{
return new BadRequestObjectResult(context.ModelState);
}
else
{
return new BadRequestObjectResult(
new ValidationProblemDetails(context.ModelState));
}
};
});
詳細については、「ASP.NET Core Web API のエラーを処理する」を参照してください。
Client プロジェクトで、「検証コンポーネント」セクションに示されている CustomValidation
コンポーネントを追加します。 アプリと一致するように名前空間を更新します (たとえば、namespace BlazorSample.Client
)。
Client プロジェクトで、Starfleet Starship Database
フォームが、CustomValidation
コンポーネントを使用してサーバー検証エラーを示すように更新されます。 サーバー API によって検証メッセージが返されると、それらは、CustomValidation
コンポーネントの ValidationMessageStoreに追加されます。 エラーは、フォームの検証の概要によって表示するため、フォームの EditContext で使用できます。
次の FormExample6
コンポーネントで、 Shared
プロジェクトの名前空間 (@using BlazorSample.Shared
) を、共有プロジェクトの名前空間に更新します。 フォームには承認が必要なため、ユーザーは、フォームに移動するには、アプリにサインインする必要があることに注意してください。
Pages/FormExample6.razor
:
@page "/form-example-6"
@using System.Net
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using Microsoft.Extensions.Logging
@using BlazorSample.Shared
@attribute [Authorize]
@inject HttpClient Http
@inject ILogger<FormExample6> Logger
<h1>Starfleet Starship Database</h1>
<h2>New Ship Entry Form</h2>
<EditForm Model="@starship" OnValidSubmit="@HandleValidSubmit">
<DataAnnotationsValidator />
<CustomValidation @ref="customValidation" />
<ValidationSummary />
<p>
<label>
Identifier:
<InputText @bind-Value="starship.Identifier" disabled="@disabled" />
</label>
</p>
<p>
<label>
Description (optional):
<InputTextArea @bind-Value="starship.Description"
disabled="@disabled" />
</label>
</p>
<p>
<label>
Primary Classification:
<InputSelect @bind-Value="starship.Classification" disabled="@disabled">
<option value="">Select classification ...</option>
<option value="Exploration">Exploration</option>
<option value="Diplomacy">Diplomacy</option>
<option value="Defense">Defense</option>
</InputSelect>
</label>
</p>
<p>
<label>
Maximum Accommodation:
<InputNumber @bind-Value="starship.MaximumAccommodation"
disabled="@disabled" />
</label>
</p>
<p>
<label>
Engineering Approval:
<InputCheckbox @bind-Value="starship.IsValidatedDesign"
disabled="@disabled" />
</label>
</p>
<p>
<label>
Production Date:
<InputDate @bind-Value="starship.ProductionDate" disabled="@disabled" />
</label>
</p>
<button type="submit" disabled="@disabled">Submit</button>
<p style="@messageStyles">
@message
</p>
<p>
<a href="http://www.startrek.com/">Star Trek</a>,
©1966-2019 CBS Studios, Inc. and
<a href="https://www.paramount.com">Paramount Pictures</a>
</p>
</EditForm>
@code {
private bool disabled;
private string? message;
private string? messageStyles = "visibility:hidden";
private CustomValidation? customValidation;
private Starship starship = new() { ProductionDate = DateTime.UtcNow };
private async Task HandleValidSubmit(EditContext editContext)
{
customValidation?.ClearErrors();
try
{
var response = await Http.PostAsJsonAsync<Starship>(
"StarshipValidation", (Starship)editContext.Model);
var errors = await response.Content
.ReadFromJsonAsync<Dictionary<string, List<string>>>() ??
new Dictionary<string, List<string>>();
if (response.StatusCode == HttpStatusCode.BadRequest &&
errors.Any())
{
customValidation?.DisplayErrors(errors);
}
else if (!response.IsSuccessStatusCode)
{
throw new HttpRequestException(
$"Validation failed. Status Code: {response.StatusCode}");
}
else
{
disabled = true;
messageStyles = "color:green";
message = "The form has been processed.";
}
}
catch (AccessTokenNotAvailableException ex)
{
ex.Redirect();
}
catch (Exception ex)
{
Logger.LogError("Form processing error: {Message}", ex.Message);
disabled = true;
messageStyles = "color:red";
message = "There was an error processing the form.";
}
}
}
@page "/form-example-6"
@using System.Net
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using Microsoft.Extensions.Logging
@using BlazorSample.Shared
@attribute [Authorize]
@inject HttpClient Http
@inject ILogger<FormExample6> Logger
<h1>Starfleet Starship Database</h1>
<h2>New Ship Entry Form</h2>
<EditForm Model="@starship" OnValidSubmit="@HandleValidSubmit">
<DataAnnotationsValidator />
<CustomValidation @ref="customValidation" />
<ValidationSummary />
<p>
<label>
Identifier:
<InputText @bind-Value="starship.Identifier" disabled="@disabled" />
</label>
</p>
<p>
<label>
Description (optional):
<InputTextArea @bind-Value="starship.Description"
disabled="@disabled" />
</label>
</p>
<p>
<label>
Primary Classification:
<InputSelect @bind-Value="starship.Classification" disabled="@disabled">
<option value="">Select classification ...</option>
<option value="Exploration">Exploration</option>
<option value="Diplomacy">Diplomacy</option>
<option value="Defense">Defense</option>
</InputSelect>
</label>
</p>
<p>
<label>
Maximum Accommodation:
<InputNumber @bind-Value="starship.MaximumAccommodation"
disabled="@disabled" />
</label>
</p>
<p>
<label>
Engineering Approval:
<InputCheckbox @bind-Value="starship.IsValidatedDesign"
disabled="@disabled" />
</label>
</p>
<p>
<label>
Production Date:
<InputDate @bind-Value="starship.ProductionDate" disabled="@disabled" />
</label>
</p>
<button type="submit" disabled="@disabled">Submit</button>
<p style="@messageStyles">
@message
</p>
<p>
<a href="http://www.startrek.com/">Star Trek</a>,
©1966-2019 CBS Studios, Inc. and
<a href="https://www.paramount.com">Paramount Pictures</a>
</p>
</EditForm>
@code {
private bool disabled;
private string message;
private string messageStyles = "visibility:hidden";
private CustomValidation customValidation;
private Starship starship = new() { ProductionDate = DateTime.UtcNow };
private async Task HandleValidSubmit(EditContext editContext)
{
customValidation.ClearErrors();
try
{
var response = await Http.PostAsJsonAsync<Starship>(
"StarshipValidation", (Starship)editContext.Model);
var errors = await response.Content
.ReadFromJsonAsync<Dictionary<string, List<string>>>();
if (response.StatusCode == HttpStatusCode.BadRequest &&
errors.Any())
{
customValidation.DisplayErrors(errors);
}
else if (!response.IsSuccessStatusCode)
{
throw new HttpRequestException(
$"Validation failed. Status Code: {response.StatusCode}");
}
else
{
disabled = true;
messageStyles = "color:green";
message = "The form has been processed.";
}
}
catch (AccessTokenNotAvailableException ex)
{
ex.Redirect();
}
catch (Exception ex)
{
Logger.LogError("Form processing error: {Message}", ex.Message);
disabled = true;
messageStyles = "color:red";
message = "There was an error processing the form.";
}
}
}
注意
検証コンポーネントを使用する代わりに、データ注釈検証属性を使用することもできます。 フォームのモデルに適用されるカスタム属性は、DataAnnotationsValidator コンポーネントを使用してアクティブ化されます。 サーバー側の検証で使用する場合、属性はサーバーで実行できる必要があります。 詳細については、「ASP.NET Core MVC でのモデルの検証」を参照してください。
Note
このセクションで説明したサーバー側の検証は、このドキュメント セットにある、Blazor WebAssembly でホストされるどのソリューション例にも適しています。
入力イベントに基づく InputText
onchange
イベント (change
) ではなく、oninput
イベント (input
) を使用するカスタム コンポーネントを作成するには、InputText コンポーネントを使用します。 各キー入力で、input
イベント トリガーのフィールド検証を使用します。
次の例では、ExampleModel
クラスを使用しています。
ExampleModel.cs
:
using System.ComponentModel.DataAnnotations;
public class ExampleModel
{
[Required]
[StringLength(10, ErrorMessage = "Name is too long.")]
public string? Name { get; set; }
}
using System.ComponentModel.DataAnnotations;
public class ExampleModel
{
[Required]
[StringLength(10, ErrorMessage = "Name is too long.")]
public string? Name { get; set; }
}
using System.ComponentModel.DataAnnotations;
public class ExampleModel
{
[Required]
[StringLength(10, ErrorMessage = "Name is too long.")]
public string Name { get; set; }
}
using System.ComponentModel.DataAnnotations;
public class ExampleModel
{
[Required]
[StringLength(10, ErrorMessage = "Name is too long.")]
public string Name { get; set; }
}
次の CustomInputText
コンポーネントによってフレームワークの InputText
コンポーネントが継承され、イベント バインディングが oninput
イベント (input
) に設定されます。
Shared/CustomInputText.razor
:
@inherits InputText
<input @attributes="AdditionalAttributes"
class="@CssClass"
@bind="CurrentValueAsString"
@bind:event="oninput" />
@inherits InputText
<input @attributes="AdditionalAttributes"
class="@CssClass"
@bind="CurrentValueAsString"
@bind:event="oninput" />
@inherits InputText
<input @attributes="AdditionalAttributes"
class="@CssClass"
@bind="CurrentValueAsString"
@bind:event="oninput" />
@inherits InputText
<input @attributes="AdditionalAttributes"
class="@CssClass"
@bind="CurrentValueAsString"
@bind:event="oninput" />
CustomInputText
コンポーネントは、InputText が使用される場所であればどこでも使用できます。 次の FormExample7
コンポーネントでは、共有 CustomInputText
コンポーネントが使用されています。
Pages/FormExample7.razor
:
@page "/form-example-7"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample7> Logger
<EditForm Model="@exampleModel" OnValidSubmit="@HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<CustomInputText @bind-Value="exampleModel.Name" />
<button type="submit">Submit</button>
</EditForm>
<p>
CurrentValue: @exampleModel.Name
</p>
@code {
private ExampleModel exampleModel = new();
private void HandleValidSubmit()
{
Logger.LogInformation("HandleValidSubmit called");
// Process the valid form
}
}
@page "/form-example-7"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample7> Logger
<EditForm Model="@exampleModel" OnValidSubmit="@HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<CustomInputText @bind-Value="exampleModel.Name" />
<button type="submit">Submit</button>
</EditForm>
<p>
CurrentValue: @exampleModel.Name
</p>
@code {
private ExampleModel exampleModel = new();
private void HandleValidSubmit()
{
Logger.LogInformation("HandleValidSubmit called");
// Process the valid form
}
}
@page "/form-example-7"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample7> Logger
<EditForm Model="@exampleModel" OnValidSubmit="@HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<CustomInputText @bind-Value="exampleModel.Name" />
<button type="submit">Submit</button>
</EditForm>
<p>
CurrentValue: @exampleModel.Name
</p>
@code {
private ExampleModel exampleModel = new();
private void HandleValidSubmit()
{
Logger.LogInformation("HandleValidSubmit called");
// Process the valid form
}
}
@page "/form-example-7"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample7> Logger
<EditForm Model="@exampleModel" OnValidSubmit="@HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<CustomInputText @bind-Value="exampleModel.Name" />
<button type="submit">Submit</button>
</EditForm>
<p>
CurrentValue: @exampleModel.Name
</p>
@code {
private ExampleModel exampleModel = new ExampleModel();
private void HandleValidSubmit()
{
Logger.LogInformation("HandleValidSubmit called");
// Process the valid form
}
}
ラジオ ボタン
このセクションの例は、この記事の「フォームの例」セクションの Starfleet Starship Database
フォームが基になっています。
アプリに以下の 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 }
}
以下から enums
にアクセスできるようにします。
Starship.cs
のStarship
モデル (たとえば、enums
クラスがComponentEnums
という名前の場合はusing static ComponentEnums;
)。Starfleet Starship Database
フォーム (たとえば、enums クラスがComponentEnums
という名前の場合は@using static ComponentEnums
)。
ラジオ ボタン グループを作成するには、InputRadioGroup<TValue> コンポーネントと共に InputRadio<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;
[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
フォーム (FormExample2
コンポーネント) を更新します。 生成するコンポーネントを追加します。
- 船舶製造元のラジオ ボタン グループ。
- エンジンと船の色に関する入れ子になったラジオ ボタン グループ。
Note
入れ子になったラジオ ボタン グループは、フォーム コントロールのレイアウトが乱れ、ユーザーを混乱させる可能性があるため、フォームではあまり使用されません。 ただし、船のエンジンと船の色の 2 つのユーザー入力に関する推奨事項を組み合わせる次の例のように、UI の設計において意味のある場合があります。 フォームの検証では、1 つのエンジンと 1 つの色が必要です。 フォームのレイアウトでは、入れ子になった InputRadioGroup<TValue> を使用して、エンジンと色の推奨設定が組み合わされています。 ただし、ユーザーは任意のエンジンを任意の色と組み合わせて、フォームを送信できます。
<fieldset>
<legend>Manufacturer</legend>
<InputRadioGroup @bind-Value="starship.Manufacturer">
@foreach (var manufacturer in (Manufacturer[])Enum
.GetValues(typeof(Manufacturer)))
{
<label>
<InputRadio Value="@manufacturer" />
<text> </text>@manufacturer
</label>
}
</InputRadioGroup>
<fieldset>
<p>
Select one engine and one color. Recommendations are paired but any
combination of engine and color is allowed:<br>
<InputRadioGroup Name="engine" @bind-Value="starship.Engine">
<InputRadioGroup Name="color" @bind-Value="starship.Color">
<InputRadio Name="engine" Value="@Engine.Ion" />
Engine: Ion<br>
<InputRadio Name="color" Value="@Color.ImperialRed" />
Color: Imperial Red<br><br>
<InputRadio Name="engine" Value="@Engine.Plasma" />
Engine: Plasma<br>
<InputRadio Name="color" Value="@Color.SpacecruiserGreen" />
Color: Spacecruiser Green<br><br>
<InputRadio Name="engine" Value="@Engine.Fusion" />
Engine: Fusion<br>
<InputRadio Name="color" Value="@Color.StarshipBlue" />
Color: Starship Blue<br><br>
<InputRadio Name="engine" Value="@Engine.Warp" />
Engine: Warp<br>
<InputRadio Name="color" Value="@Color.VoyagerOrange" />
Color: Voyager Orange
</InputRadioGroup>
</InputRadioGroup>
</p>
Note
Name
を省略した場合、InputRadio<TValue> コンポーネントは最新の先祖を基準にグループ化されます。
フォームでオプション ボタンを使用する場合、オプション ボタンはグループとして評価されるため、データ バインディングが他の要素と異なる方法で処理されます。 各オプション ボタンの値は固定ですが、オプション ボタン グループの値は、選択されたオプション ボタンの値です。 以下の例では、次のことを行っています。
- オプション ボタン グループのデータバインディングを処理する。
- カスタム InputRadio<TValue> コンポーネントを使用した検証をサポートする。
Shared/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 = $"{FieldIdentifier.FieldName} field isn't valid.";
return false;
}
}
}
ジェネリック型パラメーター (@typeparam
) の詳細については、次の記事を参照してください。
次の RadioButtonExample
コンポーネントでは、前の InputRadio
コンポーネントを使用して、ユーザーから評価を取得して検証しています。
Pages/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++)
{
<label>
<InputRadio name="rate" SelectedValue="@i" @bind-Value="model.Rating" />
@i
</label>
}
<button type="submit">Submit</button>
</EditForm>
<p>You chose: @model.Rating</p>
@code {
private Model model = new();
private void HandleValidSubmit()
{
Logger.LogInformation("HandleValidSubmit called");
// Process the valid form
}
public class Model
{
[Range(1, 5)]
public int Rating { get; set; }
}
}
検証概要コンポーネントと検証メッセージ コンポーネント
ValidationSummary コンポーネントは、すべての検証メッセージを要約します。これは検証概要タグヘルパーと似ています。
<ValidationSummary />
Model
パラメーターを使用して、特定のモデルの検証メッセージを出力します。
<ValidationSummary Model="@starship" />
ValidationMessage<TValue> コンポーネントは、特定のフィールドの検証メッセージを表示します。これは、検証メッセージ タグ ヘルパーに似ています。 For 属性と、モデル プロパティに名前を付けるラムダ式で、検証するフィールドを指定します。
<ValidationMessage For="@(() => starship.MaximumAccommodation)" />
ValidationMessage<TValue> コンポーネントと ValidationSummary コンポーネントでは、任意の属性をサポートしています。 コンポーネント パラメーターに一致しない属性は、生成された <div>
要素または <ul>
要素に追加されます。
アプリのスタイルシート (wwwroot/css/app.css
または wwwroot/css/site.css
) での検証メッセージのスタイルを制御します。 既定の validation-message
クラスでは、検証メッセージのテキストの色が赤に設定されます。
.validation-message {
color: red;
}
カスタム検証属性
カスタム検証属性を使用するときに、検証結果がフィールドに正しく関連付けられるようにするには、ValidationResult の作成時に検証コンテキストの MemberName を渡します。
CustomValidator.cs
:
using System;
using System.ComponentModel.DataAnnotations;
public class CustomValidator : ValidationAttribute
{
protected override ValidationResult IsValid(object value,
ValidationContext validationContext)
{
...
return new ValidationResult("Validation message to user.",
new[] { validationContext.MemberName });
}
}
ValidationContext を使用してカスタム検証属性にサービスを挿入します。 次の例は、依存関係の挿入 (DI) を使用してユーザー入力を検証する、サラダ シェフ用のフォームを示しています。
SaladChef
クラスは、サラダ用に承認されているフルーツの材料のリストを示します。
SaladChef.cs
:
public class SaladChef
{
public string[] ThingsYouCanPutInASalad = { "Strawberries", "Pineapple",
"Honeydew", "Watermelon", "Grapes" };
}
Program.cs
でアプリの DI コンテナーに SaladChef
を登録します。
builder.Services.AddTransient<SaladChef>();
次の SaladChefValidatorAttribute
クラスの IsValid
メソッドでは、DI から SaladChef
サービスを取得して、ユーザーの入力をチェックします。
SaladChefValidatorAttribute.cs
:
using System.ComponentModel.DataAnnotations;
public class SaladChefValidatorAttribute : ValidationAttribute
{
protected override ValidationResult? IsValid(object? value,
ValidationContext validationContext)
{
var saladChef = validationContext.GetRequiredService<SaladChef>();
if (saladChef.ThingsYouCanPutInASalad.Contains(value?.ToString()))
{
return ValidationResult.Success;
}
return new ValidationResult("You should not put that in a salad!");
}
}
using System.ComponentModel.DataAnnotations;
public class SaladChefValidatorAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value,
ValidationContext validationContext)
{
var saladChef = validationContext.GetRequiredService<SaladChef>();
if (saladChef.ThingsYouCanPutInASalad.Contains(value?.ToString()))
{
return ValidationResult.Success;
}
return new ValidationResult("You should not put that in a salad!");
}
}
次の ValidationWithDI
コンポーネントでは、SaladChefValidatorAttribute
([SaladChefValidator]
) をサラダの材料の文字列 (SaladIngredient
) に適用して、ユーザー入力を検証します。
Pages/ValidationWithDI.razor
:
@page "/validation-with-di"
@using System.ComponentModel.DataAnnotations
@using Microsoft.AspNetCore.Components.Forms
<EditForm Model="@this" autocomplete="off">
<DataAnnotationsValidator />
<p>
Name something you can put in a salad:
<input @bind="SaladIngredient" />
</p>
<button type="submit">Submit</button>
<ul>
@foreach (var message in context.GetValidationMessages())
{
<li class="validation-message">@message</li>
}
</ul>
</EditForm>
@code {
[SaladChefValidator]
public string? SaladIngredient { get; set; }
}
@page "/validation-with-di"
@using System.ComponentModel.DataAnnotations
@using Microsoft.AspNetCore.Components.Forms
<EditForm Model="@this" autocomplete="off">
<DataAnnotationsValidator />
<p>
Name something you can put in a salad:
<input @bind="SaladIngredient" />
</p>
<button type="submit">Submit</button>
<ul>
@foreach (var message in context.GetValidationMessages())
{
<li class="validation-message">@message</li>
}
</ul>
</EditForm>
@code {
[SaladChefValidator]
public string SaladIngredient { get; set; }
}
カスタム検証 CSS クラスの属性
カスタム検証 CSS クラスの属性は、Bootstrap などの CSS フレームワークと統合する場合に便利です。
次の例では、ExampleModel
クラスを使用しています。
ExampleModel.cs
:
using System.ComponentModel.DataAnnotations;
public class ExampleModel
{
[Required]
[StringLength(10, ErrorMessage = "Name is too long.")]
public string? Name { get; set; }
}
カスタム検証 CSS クラス属性を指定するには、まず、カスタム検証用の CSS スタイルを提供します。 次の例では、有効なスタイル (validField
) と無効なスタイル (invalidField
) が指定されています。
wwwroot/css/app.css
(Blazor WebAssembly) または wwwroot/css/site.css
(Blazor Server):
.validField {
border-color: lawngreen;
}
.invalidField {
background-color: tomato;
}
フィールド検証メッセージをチェックし、有効なスタイルと無効なスタイルを適切に適用する FieldCssClassProvider から派生したクラスを作成します。
CustomFieldClassProvider.cs
:
using Microsoft.AspNetCore.Components.Forms;
public class CustomFieldClassProvider : FieldCssClassProvider
{
public override string GetFieldCssClass(EditContext editContext,
in FieldIdentifier fieldIdentifier)
{
var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();
return isValid ? "validField" : "invalidField";
}
}
SetFieldCssClassProvider を使用して、CustomFieldClassProvider
クラスをフォームの EditContext インスタンスのフィールド CSS クラス プロバイダーとして設定します。
Pages/FormExample8.razor
:
@page "/form-example-8"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample8> Logger
<EditForm EditContext="@editContext" OnValidSubmit="@HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<InputText id="name" @bind-Value="exampleModel.Name" />
<button type="submit">Submit</button>
</EditForm>
@code {
private ExampleModel exampleModel = new();
private EditContext? editContext;
protected override void OnInitialized()
{
editContext = new(exampleModel);
editContext.SetFieldCssClassProvider(new CustomFieldClassProvider());
}
private void HandleValidSubmit()
{
Logger.LogInformation("HandleValidSubmit called");
// Process the valid form
}
}
前の例では、すべてのフォーム フィールドの有効性をチェックし、各フィールドにスタイルを適用しています。 フォームを使用してフィールドのサブセットにのみカスタム スタイルを適用する場合は、スタイルが CustomFieldClassProvider
によって条件的に適用されるようにします。 次の CustomFieldClassProvider2
の例では、スタイルが Name
フィールドに適用されるだけです。 名前が Name
と一致しないフィールドの場合は、string.Empty
が返され、スタイルは適用されません。
CustomFieldClassProvider2.cs
:
using Microsoft.AspNetCore.Components.Forms;
public class CustomFieldClassProvider2 : FieldCssClassProvider
{
public override string GetFieldCssClass(EditContext editContext,
in FieldIdentifier fieldIdentifier)
{
if (fieldIdentifier.FieldName == "Name")
{
var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();
return isValid ? "validField" : "invalidField";
}
return string.Empty;
}
}
追加のプロパティを ExampleModel
に追加します。次に例を示します。
[StringLength(10, ErrorMessage = "Description is too long.")]
public string? Description { get; set; }
ExampleForm7
コンポーネントのフォームに Description
を追加します。
<InputText id="description" @bind-Value="exampleModel.Description" />
新しいフィールド CSS クラス プロバイダーを使用するように、コンポーネントの OnInitialized
メソッドで EditContext
インスタンスを更新します。
editContext?.SetFieldCssClassProvider(new CustomFieldClassProvider2());
CSS 検証クラスは Description
フィールド (id="description"
) に適用されないため、スタイルは設定されません。 ただし、フィールドの検証は普通に実行されます。 入力が 10 文字を超えた場合、検証の概要でエラーが示されます。
Description is too long.
次に例を示します。
カスタム CSS スタイルが
Name
フィールドに適用されます。その他のフィールドでは Blazor の既定のロジックに似たロジックが適用され、
modified
をvalid
またはinvalid
にして Blazor の既定のフィールド CSS 検証スタイルが使用されます。 既定のスタイルの場合、アプリが Blazor プロジェクト テンプレートに基づいている場合は、アプリのスタイルシートに追加する必要がないことに注意してください。 Blazor プロジェクト テンプレートに基づいていないアプリの場合は、既定のスタイルをアプリのスタイルシートに追加できます。.valid.modified:not([type=checkbox]) { outline: 1px solid #26b050; } .invalid { outline: 1px solid red; }
CustomFieldClassProvider3.cs
:
using Microsoft.AspNetCore.Components.Forms;
public class CustomFieldClassProvider3 : FieldCssClassProvider
{
public override string GetFieldCssClass(EditContext editContext,
in FieldIdentifier fieldIdentifier)
{
var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();
if (fieldIdentifier.FieldName == "Name")
{
return isValid ? "validField" : "invalidField";
}
else
{
if (editContext.IsModified(fieldIdentifier))
{
return isValid ? "modified valid" : "modified invalid";
}
else
{
return isValid ? "valid" : "invalid";
}
}
}
}
前記のフィールド CSS クラス プロバイダーを使用するように、コンポーネントの OnInitialized
メソッドで EditContext
インスタンスを更新します。
editContext.SetFieldCssClassProvider(new CustomFieldClassProvider3());
CustomFieldClassProvider3
の使用
Name
フィールドには、アプリのカスタム検証 CSS スタイルが使用されます。Description
フィールドには、Blazor のロジックに似たロジックと、Blazor の既定のフィールド CSS 検証スタイルが使用されます。
カスタム検証 CSS クラスの属性は、Bootstrap などの CSS フレームワークと統合する場合に便利です。
次の例では、ExampleModel
クラスを使用しています。
ExampleModel.cs
:
using System.ComponentModel.DataAnnotations;
public class ExampleModel
{
[Required]
[StringLength(10, ErrorMessage = "Name is too long.")]
public string? Name { get; set; }
}
カスタム検証 CSS クラス属性を指定するには、まず、カスタム検証用の CSS スタイルを提供します。 次の例では、有効なスタイル (validField
) と無効なスタイル (invalidField
) が指定されています。
wwwroot/css/app.css
(Blazor WebAssembly) または wwwroot/css/site.css
(Blazor Server):
.validField {
border-color: lawngreen;
}
.invalidField {
background-color: tomato;
}
フィールド検証メッセージをチェックし、有効なスタイルと無効なスタイルを適切に適用する FieldCssClassProvider から派生したクラスを作成します。
CustomFieldClassProvider.cs
:
using Microsoft.AspNetCore.Components.Forms;
public class CustomFieldClassProvider : FieldCssClassProvider
{
public override string GetFieldCssClass(EditContext editContext,
in FieldIdentifier fieldIdentifier)
{
var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();
return isValid ? "validField" : "invalidField";
}
}
SetFieldCssClassProvider を使用して、CustomFieldClassProvider
クラスをフォームの EditContext インスタンスのフィールド CSS クラス プロバイダーとして設定します。
Pages/FormExample8.razor
:
@page "/form-example-8"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample8> Logger
<EditForm EditContext="@editContext" OnValidSubmit="@HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<InputText id="name" @bind-Value="exampleModel.Name" />
<button type="submit">Submit</button>
</EditForm>
@code {
private ExampleModel exampleModel = new();
private EditContext? editContext;
protected override void OnInitialized()
{
editContext = new(exampleModel);
editContext.SetFieldCssClassProvider(new CustomFieldClassProvider());
}
private void HandleValidSubmit()
{
Logger.LogInformation("HandleValidSubmit called");
// Process the valid form
}
}
前の例では、すべてのフォーム フィールドの有効性をチェックし、各フィールドにスタイルを適用しています。 フォームを使用してフィールドのサブセットにのみカスタム スタイルを適用する場合は、スタイルが CustomFieldClassProvider
によって条件的に適用されるようにします。 次の CustomFieldClassProvider2
の例では、スタイルが Name
フィールドに適用されるだけです。 名前が Name
と一致しないフィールドの場合は、string.Empty
が返され、スタイルは適用されません。
CustomFieldClassProvider2.cs
:
using Microsoft.AspNetCore.Components.Forms;
public class CustomFieldClassProvider2 : FieldCssClassProvider
{
public override string GetFieldCssClass(EditContext editContext,
in FieldIdentifier fieldIdentifier)
{
if (fieldIdentifier.FieldName == "Name")
{
var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();
return isValid ? "validField" : "invalidField";
}
return string.Empty;
}
}
追加のプロパティを ExampleModel
に追加します。次に例を示します。
[StringLength(10, ErrorMessage = "Description is too long.")]
public string? Description { get; set; }
ExampleForm7
コンポーネントのフォームに Description
を追加します。
<InputText id="description" @bind-Value="exampleModel.Description" />
新しいフィールド CSS クラス プロバイダーを使用するように、コンポーネントの OnInitialized
メソッドで EditContext
インスタンスを更新します。
editContext?.SetFieldCssClassProvider(new CustomFieldClassProvider2());
CSS 検証クラスは Description
フィールド (id="description"
) に適用されないため、スタイルは設定されません。 ただし、フィールドの検証は普通に実行されます。 入力が 10 文字を超えた場合、検証の概要でエラーが示されます。
Description is too long.
次に例を示します。
カスタム CSS スタイルが
Name
フィールドに適用されます。その他のフィールドでは Blazor の既定のロジックに似たロジックが適用され、
modified
をvalid
またはinvalid
にして Blazor の既定のフィールド CSS 検証スタイルが使用されます。 既定のスタイルの場合、アプリが Blazor プロジェクト テンプレートに基づいている場合は、アプリのスタイルシートに追加する必要がないことに注意してください。 Blazor プロジェクト テンプレートに基づいていないアプリの場合は、既定のスタイルをアプリのスタイルシートに追加できます。.valid.modified:not([type=checkbox]) { outline: 1px solid #26b050; } .invalid { outline: 1px solid red; }
CustomFieldClassProvider3.cs
:
using Microsoft.AspNetCore.Components.Forms;
public class CustomFieldClassProvider3 : FieldCssClassProvider
{
public override string GetFieldCssClass(EditContext editContext,
in FieldIdentifier fieldIdentifier)
{
var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();
if (fieldIdentifier.FieldName == "Name")
{
return isValid ? "validField" : "invalidField";
}
else
{
if (editContext.IsModified(fieldIdentifier))
{
return isValid ? "modified valid" : "modified invalid";
}
else
{
return isValid ? "valid" : "invalid";
}
}
}
}
前記のフィールド CSS クラス プロバイダーを使用するように、コンポーネントの OnInitialized
メソッドで EditContext
インスタンスを更新します。
editContext.SetFieldCssClassProvider(new CustomFieldClassProvider3());
CustomFieldClassProvider3
の使用
Name
フィールドには、アプリのカスタム検証 CSS スタイルが使用されます。Description
フィールドには、Blazor のロジックに似たロジックと、Blazor の既定のフィールド CSS 検証スタイルが使用されます。
カスタム検証 CSS クラスの属性は、Bootstrap などの CSS フレームワークと統合する場合に便利です。
次の例では、ExampleModel
クラスを使用しています。
ExampleModel.cs
:
using System.ComponentModel.DataAnnotations;
public class ExampleModel
{
[Required]
[StringLength(10, ErrorMessage = "Name is too long.")]
public string Name { get; set; }
}
カスタム検証 CSS クラス属性を指定するには、まず、カスタム検証用の CSS スタイルを提供します。 次の例では、有効なスタイル (validField
) と無効なスタイル (invalidField
) が指定されています。
wwwroot/css/app.css
(Blazor WebAssembly) または wwwroot/css/site.css
(Blazor Server):
.validField {
border-color: lawngreen;
}
.invalidField {
background-color: tomato;
}
フィールド検証メッセージをチェックし、有効なスタイルと無効なスタイルを適切に適用する FieldCssClassProvider から派生したクラスを作成します。
CustomFieldClassProvider.cs
:
using System.Linq;
using Microsoft.AspNetCore.Components.Forms;
public class CustomFieldClassProvider : FieldCssClassProvider
{
public override string GetFieldCssClass(EditContext editContext,
in FieldIdentifier fieldIdentifier)
{
var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();
return isValid ? "validField" : "invalidField";
}
}
SetFieldCssClassProvider を使用して、CustomFieldClassProvider
クラスをフォームの EditContext インスタンスのフィールド CSS クラス プロバイダーとして設定します。
Pages/FormExample8.razor
:
@page "/form-example-8"
@using Microsoft.Extensions.Logging
@inject ILogger<FormExample8> Logger
<EditForm EditContext="@editContext" OnValidSubmit="@HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<InputText id="name" @bind-Value="exampleModel.Name" />
<button type="submit">Submit</button>
</EditForm>
@code {
private ExampleModel exampleModel = new();
private EditContext editContext;
protected override void OnInitialized()
{
editContext = new(exampleModel);
editContext.SetFieldCssClassProvider(new CustomFieldClassProvider());
}
private void HandleValidSubmit()
{
Logger.LogInformation("HandleValidSubmit called");
// Process the valid form
}
}
前の例では、すべてのフォーム フィールドの有効性をチェックし、各フィールドにスタイルを適用しています。 フォームを使用してフィールドのサブセットにのみカスタム スタイルを適用する場合は、スタイルが CustomFieldClassProvider
によって条件的に適用されるようにします。 次の CustomFieldClassProvider2
の例では、スタイルが Name
フィールドに適用されるだけです。 名前が Name
と一致しないフィールドの場合は、string.Empty
が返され、スタイルは適用されません。
CustomFieldClassProvider2.cs
:
using System.Linq;
using Microsoft.AspNetCore.Components.Forms;
public class CustomFieldClassProvider2 : FieldCssClassProvider
{
public override string GetFieldCssClass(EditContext editContext,
in FieldIdentifier fieldIdentifier)
{
if (fieldIdentifier.FieldName == "Name")
{
var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();
return isValid ? "validField" : "invalidField";
}
return string.Empty;
}
}
追加のプロパティを ExampleModel
に追加します。次に例を示します。
[StringLength(10, ErrorMessage = "Description is too long.")]
public string Description { get; set; }
ExampleForm7
コンポーネントのフォームに Description
を追加します。
<InputText id="description" @bind-Value="exampleModel.Description" />
新しいフィールド CSS クラス プロバイダーを使用するように、コンポーネントの OnInitialized
メソッドで EditContext
インスタンスを更新します。
editContext.SetFieldCssClassProvider(new CustomFieldClassProvider2());
CSS 検証クラスは Description
フィールド (id="description"
) に適用されないため、スタイルは設定されません。 ただし、フィールドの検証は普通に実行されます。 入力が 10 文字を超えた場合、検証の概要でエラーが示されます。
Description is too long.
次に例を示します。
カスタム CSS スタイルが
Name
フィールドに適用されます。その他のフィールドでは Blazor の既定のロジックに似たロジックが適用され、
modified
をvalid
またはinvalid
にして Blazor の既定のフィールド CSS 検証スタイルが使用されます。 既定のスタイルの場合、アプリが Blazor プロジェクト テンプレートに基づいている場合は、アプリのスタイルシートに追加する必要がないことに注意してください。 Blazor プロジェクト テンプレートに基づいていないアプリの場合は、既定のスタイルをアプリのスタイルシートに追加できます。.valid.modified:not([type=checkbox]) { outline: 1px solid #26b050; } .invalid { outline: 1px solid red; }
CustomFieldClassProvider3.cs
:
using System.Linq;
using Microsoft.AspNetCore.Components.Forms;
public class CustomFieldClassProvider3 : FieldCssClassProvider
{
public override string GetFieldCssClass(EditContext editContext,
in FieldIdentifier fieldIdentifier)
{
var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();
if (fieldIdentifier.FieldName == "Name")
{
return isValid ? "validField" : "invalidField";
}
else
{
if (editContext.IsModified(fieldIdentifier))
{
return isValid ? "modified valid" : "modified invalid";
}
else
{
return isValid ? "valid" : "invalid";
}
}
}
}
前記のフィールド CSS クラス プロバイダーを使用するように、コンポーネントの OnInitialized
メソッドで EditContext
インスタンスを更新します。
editContext.SetFieldCssClassProvider(new CustomFieldClassProvider3());
CustomFieldClassProvider3
の使用
Name
フィールドには、アプリのカスタム検証 CSS スタイルが使用されます。Description
フィールドには、Blazor のロジックに似たロジックと、Blazor の既定のフィールド CSS 検証スタイルが使用されます。
Blazor データ注釈検証パッケージ
Microsoft.AspNetCore.Components.DataAnnotations.Validation
は、DataAnnotationsValidator コンポーネントを使用して検証エクスペリエンスのギャップを埋めるパッケージです。 パッケージは現在、試験段階です。
警告
Microsoft.AspNetCore.Components.DataAnnotations.Validation
パッケージには、NuGet.org に "リリース候補" の最新バージョンが含まれています。現時点では、"試験段階" リリース候補パッケージを引き続きお使いください。 試験段階の機能は、機能の有効性を調べる目的で提供されており、安定バージョンには含まれていない場合があります。 更新の詳細については、Announcements GitHub リポジトリ、dotnet/aspnetcore GitHub リポジトリ、またはこのトピック セクションを参照してください。
[CompareProperty]
属性
CompareAttribute は、検証結果を特定のメンバーに関連付けないため、DataAnnotationsValidator コンポーネントで正しく機能しません。 これにより、フィールドレベルの検証と、送信時のモデル全体が検証されたときの動作に一貫性がなくなることがあります。 Microsoft.AspNetCore.Components.DataAnnotations.Validation
"試験的" パッケージでは、これらの制限を回避する追加の検証属性 ComparePropertyAttribute
が導入されています。 Blazor アプリでは、[CompareProperty]
は [Compare]
属性の直接の代わりとなるものです。
入れ子になったモデル、コレクション型、および複合型
Blazor では、組み込みの DataAnnotationsValidator によるデータ注釈を使用したフォーム入力の検証をサポートしています。 ただし、DataAnnotationsValidatorで は、コレクション型または複合型のプロパティではないフォームにバインドされているモデルの最上位レベルのプロパティのみが検証されます。
コレクション型と複合型のプロパティを含む、バインドされたモデルのオブジェクト グラフ全体を検証するには、"試験的" Microsoft.AspNetCore.Components.DataAnnotations.Validation
パッケージによって提供される ObjectGraphDataAnnotationsValidator
を使用します。
<EditForm Model="@model" OnValidSubmit="@HandleValidSubmit">
<ObjectGraphDataAnnotationsValidator />
...
</EditForm>
[ValidateComplexType]
でモデルのプロパティに注釈を付けます。 次のモデル クラスでは、ShipDescription
クラスに、モデルがフォームにバインドされたときに検証する追加のデータ注釈が含まれています。
Starship.cs
:
using System;
using System.ComponentModel.DataAnnotations;
public class Starship
{
...
[ValidateComplexType]
public ShipDescription ShipDescription { get; set; } = new();
...
}
using System;
using System.ComponentModel.DataAnnotations;
public class Starship
{
...
[ValidateComplexType]
public ShipDescription ShipDescription { get; set; } = new ShipDescription();
...
}
ShipDescription.cs
:
using System;
using System.ComponentModel.DataAnnotations;
public class ShipDescription
{
[Required]
[StringLength(40, ErrorMessage = "Description too long (40 char).")]
public string? ShortDescription { get; set; }
[Required]
[StringLength(240, ErrorMessage = "Description too long (240 char).")]
public string? LongDescription { get; set; }
}
using System;
using System.ComponentModel.DataAnnotations;
public class ShipDescription
{
[Required]
[StringLength(40, ErrorMessage = "Description too long (40 char).")]
public string ShortDescription { get; set; }
[Required]
[StringLength(240, ErrorMessage = "Description too long (240 char).")]
public string LongDescription { get; set; }
}
フォームの検証に基づいて送信ボタンを有効にする
フォームの検証に基づいて送信ボタンを有効または無効にするため、以下の例では次のことが行われています。
- 宇宙船の識別子の値のみが受け入れられる、前の
Starfleet Starship Database
フォーム (FormExample2
コンポーネント) の簡略版を使用します。Starship
の他のプロパティは、Starship
型のインスタンスが作成されるときに、有効な既定値を受け取ります。 - コンポーネントを初期化するときに、フォームの EditContext を使用してモデルを割り当てます。
- コンテキストの OnFieldChanged コールバックでフォームを検証して、送信ボタンを有効または無効にします。
- IDisposable を実装し、
Dispose
メソッドでイベント ハンドラーの登録を解除します。 詳しくは、「ASP.NET Core Razor コンポーネントのライフサイクル」をご覧ください。
Note
EditForm.EditContext に割り当てるとき、EditForm には EditForm.Model を割り当てないでください。
Pages/FormExample9.razor
:
@page "/form-example-9"
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<FormExample9> Logger
<EditForm EditContext="@editContext" OnValidSubmit="@HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<p>
<label>
Identifier:
<InputText @bind-Value="starship.Identifier" />
</label>
</p>
<button type="submit" disabled="@formInvalid">Submit</button>
</EditForm>
@code {
private Starship starship =
new()
{
Identifier = "NCC-1701",
Classification = "Exploration",
MaximumAccommodation = 150,
IsValidatedDesign = true,
ProductionDate = new DateTime(2245, 4, 11)
};
private bool formInvalid = false;
private EditContext? editContext;
protected override void OnInitialized()
{
editContext = new(starship);
editContext.OnFieldChanged += HandleFieldChanged;
}
private void HandleFieldChanged(object? sender, FieldChangedEventArgs e)
{
if (editContext is not null)
{
formInvalid = !editContext.Validate();
StateHasChanged();
}
}
private void HandleValidSubmit()
{
Logger.LogInformation("HandleValidSubmit called");
// Process the valid form
}
public void Dispose()
{
if (editContext is not null)
{
editContext.OnFieldChanged -= HandleFieldChanged;
}
}
}
@page "/form-example-9"
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<FormExample9> Logger
<EditForm EditContext="@editContext" OnValidSubmit="@HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<p>
<label>
Identifier:
<InputText @bind-Value="starship.Identifier" />
</label>
</p>
<button type="submit" disabled="@formInvalid">Submit</button>
</EditForm>
@code {
private Starship starship =
new()
{
Identifier = "NCC-1701",
Classification = "Exploration",
MaximumAccommodation = 150,
IsValidatedDesign = true,
ProductionDate = new DateTime(2245, 4, 11)
};
private bool formInvalid = false;
private EditContext? editContext;
protected override void OnInitialized()
{
editContext = new(starship);
editContext.OnFieldChanged += HandleFieldChanged;
}
private void HandleFieldChanged(object? sender, FieldChangedEventArgs e)
{
if (editContext is not null)
{
formInvalid = !editContext.Validate();
StateHasChanged();
}
}
private void HandleValidSubmit()
{
Logger.LogInformation("HandleValidSubmit called");
// Process the valid form
}
public void Dispose()
{
if (editContext is not null)
{
editContext.OnFieldChanged -= HandleFieldChanged;
}
}
}
@page "/form-example-9"
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<FormExample9> Logger
<EditForm EditContext="@editContext" OnValidSubmit="@HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<p>
<label>
Identifier:
<InputText @bind-Value="starship.Identifier" />
</label>
</p>
<button type="submit" disabled="@formInvalid">Submit</button>
</EditForm>
@code {
private Starship starship =
new()
{
Identifier = "NCC-1701",
Classification = "Exploration",
MaximumAccommodation = 150,
IsValidatedDesign = true,
ProductionDate = new DateTime(2245, 4, 11)
};
private bool formInvalid = false;
private EditContext editContext;
protected override void OnInitialized()
{
editContext = new(starship);
editContext.OnFieldChanged += HandleFieldChanged;
}
private void HandleFieldChanged(object sender, FieldChangedEventArgs e)
{
formInvalid = !editContext.Validate();
StateHasChanged();
}
private void HandleValidSubmit()
{
Logger.LogInformation("HandleValidSubmit called");
// Process the valid form
}
public void Dispose()
{
editContext.OnFieldChanged -= HandleFieldChanged;
}
}
@page "/form-example-9"
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<FormExample9> Logger
<EditForm EditContext="@editContext" OnValidSubmit="@HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<p>
<label>
Identifier:
<InputText @bind-Value="starship.Identifier" />
</label>
</p>
<button type="submit" disabled="@formInvalid">Submit</button>
</EditForm>
@code {
private Starship starship =
new Starship()
{
Identifier = "NCC-1701",
Classification = "Exploration",
MaximumAccommodation = 150,
IsValidatedDesign = true,
ProductionDate = new DateTime(2245, 4, 11)
};
private bool formInvalid = false;
private EditContext editContext;
protected override void OnInitialized()
{
editContext = new EditContext(starship);
editContext.OnFieldChanged += HandleFieldChanged;
}
private void HandleFieldChanged(object sender, FieldChangedEventArgs e)
{
formInvalid = !editContext.Validate();
StateHasChanged();
}
private void HandleValidSubmit()
{
Logger.LogInformation("HandleValidSubmit called");
// Process the valid form
}
public void Dispose()
{
editContext.OnFieldChanged -= HandleFieldChanged;
}
}
フォームに有効な値が事前に読み込まれておらず、フォームの読み込みで Submit
ボタンを無効にする場合は、formInvalid
を true
に設定します。
上記の方法の副作用として、ユーザーがいずれかのフィールドを操作した後、検証の概要 (ValidationSummary コンポーネント) に無効なフィールドが設定されます。 このシナリオには、次のいずれかの方法で対処します。
- フォームでは ValidationSummary コンポーネントを使用しないでください。
- 送信ボタンが選択された (たとえば、
HandleValidSubmit
メソッドで) ときに ValidationSummary コンポーネントを表示します。
<EditForm EditContext="@editContext" OnValidSubmit="@HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary style="@displaySummary" />
...
<button type="submit" disabled="@formInvalid">Submit</button>
</EditForm>
@code {
private string displaySummary = "display:none";
...
private void HandleValidSubmit()
{
displaySummary = "display:block";
}
}
トラブルシューティング
InvalidOperationException:EditForm には、Model パラメーターまたは EditContext パラメーター (両方ではなく) が必要です。
EditForm によって ModelまたはEditContext が割り当てられることを確認します。 同じフォームに両方を使用しないでください。
Model に割り当てるときは、次の例に示すように、そのモデルの種類がインスタンス化されていることを確認します。
private ExampleModel exampleModel = new();
private ExampleModel exampleModel = new ExampleModel();
その他のリソース
- ASP.NET Core Blazor ファイルのアップロード
- ASP.NET Core Blazor WebAssembly でホストされるアプリを Azure Active Directory でセキュリティ保護する
- ASP.NET Core Blazor WebAssembly でホストされるアプリを Azure Active Directory B2C でセキュリティ保護する
- ホストされている ASP.NET Core Blazor WebAssembly アプリを Identity Server でセキュリティ保護する
- Blazor サンプル GitHub リポジトリ (
dotnet/blazor-samples
)