注
これは、この記事の最新バージョンではありません。 現在のリリースについては、 この記事の .NET 10 バージョンを参照してください。
警告
このバージョンの ASP.NET Core はサポート対象から除外されました。 詳細については、 .NET および .NET Core サポート ポリシーを参照してください。 現在のリリースについては、この記事の .NET 9 バージョンを参照してください。
この記事では、Blazor フォームで検証を使う方法について説明します。
フォーム検証
基本的なフォーム検証シナリオでは、EditForm インスタンスは宣言された EditContext と ValidationMessageStore インスタンスを使用してフォーム フィールドを検証できます。 OnValidationRequested の EditContext イベントのハンドラーにより、カスタム検証ロジックが実行されます。 ハンドラーの結果によって ValidationMessageStore インスタンスが更新されます。
基本的なフォーム検証は、フォームをホストするコンポーネント内でフォームのモデルが定義されている場合に便利です。これは、コンポーネントのメンバーとして直接、またはサブクラスで行います。 複数のコンポーネントで独立したモデル クラスを使用する場合は、検証コンポーネントの使用をお勧めします。
Blazor Web App において、クライアント側の検証にはアクティブな BlazorSignalR 回線が必要です。 静的サーバー側レンダリング (静的 SSR) を採用したコンポーネントのフォームでは、クライアント側の検証を利用できません。 静的 SSR を採用するフォームは、フォームの送信後にサーバー上で検証されます。
次のコンポーネントの場合、HandleValidationRequested ハンドラー メソッドでは、フォームを検証する前に ValidationMessageStore.Clear を呼び出すことによって、既存の検証メッセージをクリアします。
Starship8.razor:
@page "/starship-8"
@implements IDisposable
@inject ILogger<Starship8> Logger
<h2>Holodeck Configuration</h2>
<EditForm EditContext="editContext" OnValidSubmit="Submit" FormName="Starship8">
<div>
<label>
<InputCheckbox @bind-Value="Model!.Subsystem1" />
Safety Subsystem
</label>
</div>
<div>
<label>
<InputCheckbox @bind-Value="Model!.Subsystem2" />
Emergency Shutdown Subsystem
</label>
</div>
<div>
<ValidationMessage For="() => Model!.Options" />
</div>
<div>
<button type="submit">Update</button>
</div>
</EditForm>
@code {
private EditContext? editContext;
[SupplyParameterFromForm]
private Holodeck? Model { get; set; }
private ValidationMessageStore? messageStore;
protected override void OnInitialized()
{
Model ??= new();
editContext = new(Model);
editContext.OnValidationRequested += HandleValidationRequested;
messageStore = new(editContext);
}
private void HandleValidationRequested(object? sender,
ValidationRequestedEventArgs args)
{
messageStore?.Clear();
// Custom validation logic
if (!Model!.Options)
{
messageStore?.Add(() => Model.Options, "Select at least one.");
}
}
private void Submit() => Logger.LogInformation("Submit: Processing form");
public class Holodeck
{
public bool Subsystem1 { get; set; }
public bool Subsystem2 { get; set; }
public bool Options => Subsystem1 || Subsystem2;
}
public void Dispose()
{
if (editContext is not null)
{
editContext.OnValidationRequested -= HandleValidationRequested;
}
}
}
@page "/starship-8"
@implements IDisposable
@inject ILogger<Starship8> Logger
<h2>Holodeck Configuration</h2>
<EditForm EditContext="editContext" OnValidSubmit="Submit" FormName="Starship8">
<div>
<label>
<InputCheckbox @bind-Value="Model!.Subsystem1" />
Safety Subsystem
</label>
</div>
<div>
<label>
<InputCheckbox @bind-Value="Model!.Subsystem2" />
Emergency Shutdown Subsystem
</label>
</div>
<div>
<ValidationMessage For="() => Model!.Options" />
</div>
<div>
<button type="submit">Update</button>
</div>
</EditForm>
@code {
private EditContext? editContext;
[SupplyParameterFromForm]
private Holodeck? Model { get; set; }
private ValidationMessageStore? messageStore;
protected override void OnInitialized()
{
Model ??= new();
editContext = new(Model);
editContext.OnValidationRequested += HandleValidationRequested;
messageStore = new(editContext);
}
private void HandleValidationRequested(object? sender,
ValidationRequestedEventArgs args)
{
messageStore?.Clear();
// Custom validation logic
if (!Model!.Options)
{
messageStore?.Add(() => Model.Options, "Select at least one.");
}
}
private void Submit() => Logger.LogInformation("Submit: Processing form");
public class Holodeck
{
public bool Subsystem1 { get; set; }
public bool Subsystem2 { get; set; }
public bool Options => Subsystem1 || Subsystem2;
}
public void Dispose()
{
if (editContext is not null)
{
editContext.OnValidationRequested -= HandleValidationRequested;
}
}
}
@page "/starship-8"
@implements IDisposable
@inject ILogger<Starship8> Logger
<h2>Holodeck Configuration</h2>
<EditForm EditContext="editContext" OnValidSubmit="Submit">
<div>
<label>
<InputCheckbox @bind-Value="Model!.Subsystem1" />
Safety Subsystem
</label>
</div>
<div>
<label>
<InputCheckbox @bind-Value="Model!.Subsystem2" />
Emergency Shutdown Subsystem
</label>
</div>
<div>
<ValidationMessage For="() => Model!.Options" />
</div>
<div>
<button type="submit">Update</button>
</div>
</EditForm>
@code {
private EditContext? editContext;
public Holodeck? Model { get; set; }
private ValidationMessageStore? messageStore;
protected override void OnInitialized()
{
Model ??= new();
editContext = new(Model);
editContext.OnValidationRequested += HandleValidationRequested;
messageStore = new(editContext);
}
private void HandleValidationRequested(object? sender,
ValidationRequestedEventArgs args)
{
messageStore?.Clear();
// Custom validation logic
if (!Model!.Options)
{
messageStore?.Add(() => Model.Options, "Select at least one.");
}
}
private void Submit()
{
Logger.LogInformation("Submit called: Processing the form");
}
public class Holodeck
{
public bool Subsystem1 { get; set; }
public bool Subsystem2 { get; set; }
public bool Options => Subsystem1 || Subsystem2;
}
public void Dispose()
{
if (editContext is not null)
{
editContext.OnValidationRequested -= HandleValidationRequested;
}
}
}
データ注釈検証コンポーネントとカスタム検証
DataAnnotationsValidator コンポーネントにより、データ注釈検証がカスケードされる EditContext にアタッチされます。 データ注釈の検証を有効にするには、DataAnnotationsValidator コンポーネントが必要です。 データ注釈と異なる検証システムを使用するには、DataAnnotationsValidator コンポーネントの代わりにカスタム実装を使用します。 参照ソースでの検査には、DataAnnotationsValidator のフレームワークの実装を使用できます。
検証動作の詳細については、「 DataAnnotationsValidator 検証動作 」セクションを参照してください。
コード内の EditContext に対するデータ注釈検証のサポートを有効にする必要がある場合は、EnableDataAnnotationsValidationに挿入された IServiceProvider (@inject IServiceProvider ServiceProvider) を使用して EditContext を呼び出します。 高度な例については、ASP.NET Core NotifyPropertyChangedValidationComponent フレームワークの Blazor (GitHub リポジトリBasicTestApp) dotnet/aspnetcore コンポーネントを参照してください。 この例の実稼働バージョンでは、サービス プロバイダーの new TestServiceProvider() 引数を挿入された IServiceProviderに置き換えます。
注
通常、.NET 参照ソースへのドキュメント リンクを使用すると、リポジトリの既定のブランチが読み込まれます。このブランチは、.NET の次回リリースに向けて行われている現在の開発を表します。 特定のリリースのタグを選択するには、[Switch branches or tags](ブランチまたはタグの切り替え) ドロップダウン リストを使います。 詳細については、「ASP.NET Core ソース コードのバージョン タグを選択する方法」 (dotnet/AspNetCore.Docs #26205) を参照してください。
Blazor は 2 種類の検証を実行します。
- フィールド検証 は、ユーザーがタブでフィールドを離れたときに実行されます。 フィールドの検証時に、DataAnnotationsValidator コンポーネントによって、報告されたすべての検証結果がフィールドに関連付けられます。
- モデル検証は、ユーザーがフォームを送信したときに実行されます。 モデルの検証時に、DataAnnotationsValidator コンポーネントは、検証結果で報告されたメンバー名に基づいてフィールドを判断しようとします。 個々のメンバーに関連付けられていない検証結果は、フィールドではなくモデルに関連付けられます。
カスタム検証シナリオでは、次の操作を行います。
- フォームの ValidationMessageStore 用の EditContext を検証が管理します。
- DataAnnotationsValidator コンポーネントは、検証属性 (データ注釈)に基づいてフォームに検証サポートをアタッチするために使用されます。
カスタム検証を実現するための一般的な方法は 2 つあります。これについては、この記事の次の 2 つのセクションで説明します。
-
OnValidationRequestedイベントを使用した手動検証: OnValidationRequested イベントに割り当てられたイベント ハンドラーを介して検証が要求されたときに、データ注釈の検証とフィールド チェックのカスタム コードを使用してフォームのフィールドを手動で検証します。 - 検証コンポーネント: 1 つ以上のカスタム検証コンポーネントを使用して、同じページまたは同じフォーム上の異なるフォームの検証をフォーム処理の異なる手順 (クライアント検証の後にサーバー検証など) で処理できます。
OnValidationRequested イベントを使用した手動検証
EditContext.OnValidationRequested イベントに割り当てられたカスタム イベント ハンドラーを使用してフォームを手動で検証し、ValidationMessageStoreを管理できます。
Blazor フレームワークは、DataAnnotationsValidatorに基づいてフォームに追加の検証サポートをアタッチする コンポーネントを提供します。
前の Starship8 コンポーネントの例を思い出すと、HandleValidationRequested メソッドが OnValidationRequestedに割り当てられ、C# コードで手動検証を実行できます。 いくつかの変更では、既存の手動検証と、DataAnnotationsValidator を使用したデータ注釈の検証と、Holodeck モデルに適用される検証属性の組み合わせが示されています。
コンポーネント定義ファイルの先頭にあるコンポーネントの System.ComponentModel.DataAnnotations ディレクティブで、Razor 名前空間を参照します。
@using System.ComponentModel.DataAnnotations
検証属性を使用して Id モデルに Holodeck プロパティを追加して、文字列の長さを 6 文字に制限します。
[StringLength(6)]
public string? Id { get; set; }
DataAnnotationsValidator コンポーネント (<DataAnnotationsValidator />) をフォームに追加します。 通常、コンポーネントは <EditForm> タグのすぐ下に配置されますが、フォーム内の任意の場所に配置できます。
<DataAnnotationsValidator />
<EditForm> タグのフォームの送信動作を OnSubmit から OnValidSubmitに変更します。これにより、割り当てられたイベント ハンドラー メソッドを実行する前にフォームが有効になります。
- OnSubmit="Submit"
+ OnValidSubmit="Submit"
<EditForm>で、Id プロパティのフィールドを追加します。
<div>
<label>
<InputText @bind-Value="Model!.Id" />
ID (6 characters max)
</label>
<ValidationMessage For="() => Model!.Id" />
</div>
上記の変更を行った後、フォームの動作は次の仕様に一致します。
-
Idプロパティのデータ注釈の検証では、Idフィールドがフォーカスを失っただけでは検証エラーはトリガーされません。 検証は、ユーザーが [Update] ボタンを選択したときに実行されます。 - フォームの
HandleValidationRequestedイベントに割り当てられた OnValidationRequested メソッドで実行する手動検証は、ユーザーがフォームの [Update] ボタンを選択したときに実行されます。Starship8コンポーネントの例の既存のコードでは、ユーザーはいずれかのチェック ボックスまたは両方のチェック ボックスをオンにしてフォームを検証する必要があります。 - フォームは、データ注釈と手動検証の両方が成功するまで、
Submitメソッドを処理しません。
検証コンポーネント
検証コンポーネントでは、フォームの ValidationMessageStoreの EditContext を管理することで、フォームの検証をサポートします。
Blazor フレームワークは、DataAnnotationsValidatorに基づいてフォームに検証サポートを付加するための コンポーネントを提供します。 カスタム検証コンポーネントを作成して、同じページの異なるフォームの検証メッセージを処理するか、フォーム処理の異なるステップ (たとえば、クライアント検証の後のサーバー検証) で同じフォームの検証メッセージを処理できます。 このセクションで示す検証コンポーネントの例 (CustomValidation) は、この記事の次のセクションで使用します。
データ注釈の組み込み検証コントロールのうち、[Remote]ではのみがサポートされていません。
注
多くの場合、カスタムのデータ注釈検証属性をカスタム検証コンポーネントの代わりに使用できます。 フォームのモデルに適用されるカスタム属性は、DataAnnotationsValidator コンポーネントを使用してアクティブ化されます。 サーバー検証で使用する場合、モデルに適用されるカスタム属性はすべてサーバー上で実行可能である必要があります。 詳しくは、「カスタム検証属性」セクションをご覧ください。
ComponentBaseから検証コンポーネントを作成します。
- フォームの EditContext は、コンポーネントのカスケード パラメーターです。
- 検証コンポーネントが初期化されると、フォーム エラーの現在の一覧を保持するために新しい ValidationMessageStore が作成されます。
- フォームのコンポーネント内の開発者コードで
DisplayErrorsメソッドが呼び出されると、メッセージ ストアでエラーが受け取られます。 そのエラーは、DisplayErrors内のDictionary<string, List<string>>メソッドに渡されます。 ディクショナリでは、キーは、1 つ以上のエラーがあるフォーム フィールドの名前です。 値は、エラー一覧です。 - 次のいずれかが発生すると、メッセージはクリアされます。
- EditContext で検証が要求され、OnValidationRequested イベントが発生した場合。 すべてのエラーがクリアされます。
- OnFieldChanged イベントが発生すると、フォームのフィールドが変更されます。 そのフィールドのエラーのみがクリアされます。
- 開発者コードによって
ClearErrorsメソッドが呼び出された場合。 すべてのエラーがクリアされます。
アプリの名前空間と一致するよう、次のクラスの名前空間を更新します。
CustomValidation.cs:
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();
}
}
重要
から派生する場合、名前空間を指定することがComponentBaseです。 名前空間を指定しないと、ビルド エラーが発生します。
Tag helpers cannot target tag name '<global namespace>.{CLASS NAME}' because it contains a ' ' character.
{CLASS NAME} プレースホルダーは、コンポーネント クラスの名前です。 このセクションのカスタム検証の例では、例の名前空間 BlazorSample を指定します。
注
匿名のラムダ式は、OnValidationRequested と前の例の OnFieldChanged に対して登録されているイベント ハンドラーです。 このシナリオでは、IDisposable を実装したり、イベント デリゲートの登録を解除したりする必要はありません。 詳細については、ASP.NET Core Razor コンポーネントの破棄を参照してください。
検証コンポーネントを使用したビジネス ロジック検証
一般的なビジネス ロジックの検証では、ディクショナリ内のフォーム エラーを受け取る検証コンポーネントを使用します。
基本検証は、フォームをホストするコンポーネント内でフォームのモデルが定義されている場合に便利です。これは、コンポーネントのメンバーとして直接、またはサブクラスで行います。 複数のコンポーネントで独立したモデル クラスを使用する場合は、検証コンポーネントの使用をお勧めします。
次に例を示します。
-
記事の
Starfleet Starship Databaseセクションには、Starship3フォームの簡略版が使用されていますが、これは宇宙船の分類と説明のみを受け付けます。 DataAnnotationsValidator コンポーネントがフォームに含まれていないため、フォームの送信時にデータ注釈の検証はトリガーされません。 - この記事の「Validator コンポーネント」セクションの
CustomValidationコンポーネントが使用されています。 - 検証では、ユーザーが宇宙船の分類 (
Description) でDefenseを選択した場合、宇宙船の説明 (Classification) の値が要求されます。
検証メッセージをコンポーネントで設定すると、検証の ValidationMessageStore に追加され、EditForm の検証の概要に表示されます。
Starship9.razor:
@page "/starship-9"
@inject ILogger<Starship9> Logger
<h1>Starfleet Starship Database</h1>
<h2>New Ship Entry Form</h2>
<EditForm Model="Model" OnValidSubmit="Submit" FormName="Starship9">
<CustomValidation @ref="customValidation" />
<ValidationSummary />
<div>
<label>
Primary Classification:
<InputSelect @bind-Value="Model!.Classification">
<option value="">
Select classification ...
</option>
<option checked="@(Model!.Classification == "Exploration")"
value="Exploration">
Exploration
</option>
<option checked="@(Model!.Classification == "Diplomacy")"
value="Diplomacy">
Diplomacy
</option>
<option checked="@(Model!.Classification == "Defense")"
value="Defense">
Defense
</option>
</InputSelect>
</label>
</div>
<div>
<label>
Description (optional):
<InputTextArea @bind-Value="Model!.Description" />
</label>
</div>
<div>
<button type="submit">Submit</button>
</div>
</EditForm>
@code {
private CustomValidation? customValidation;
[SupplyParameterFromForm]
private Starship? Model { get; set; }
protected override void OnInitialized() =>
Model ??= new() { ProductionDate = DateTime.UtcNow };
private void Submit()
{
customValidation?.ClearErrors();
var errors = new Dictionary<string, List<string>>();
if (Model!.Classification == "Defense" &&
string.IsNullOrEmpty(Model.Description))
{
errors.Add(nameof(Model.Description),
[ "For a 'Defense' ship classification, " +
"'Description' is required." ]);
}
if (errors.Any())
{
customValidation?.DisplayErrors(errors);
}
else
{
Logger.LogInformation("Submit called: Processing the form");
}
}
}
@page "/starship-9"
@inject ILogger<Starship9> Logger
<h1>Starfleet Starship Database</h1>
<h2>New Ship Entry Form</h2>
<EditForm Model="Model" OnValidSubmit="Submit" FormName="Starship9">
<CustomValidation @ref="customValidation" />
<ValidationSummary />
<div>
<label>
Primary Classification:
<InputSelect @bind-Value="Model!.Classification">
<option value="">
Select classification ...
</option>
<option checked="@(Model!.Classification == "Exploration")"
value="Exploration">
Exploration
</option>
<option checked="@(Model!.Classification == "Diplomacy")"
value="Diplomacy">
Diplomacy
</option>
<option checked="@(Model!.Classification == "Defense")"
value="Defense">
Defense
</option>
</InputSelect>
</label>
</div>
<div>
<label>
Description (optional):
<InputTextArea @bind-Value="Model!.Description" />
</label>
</div>
<div>
<button type="submit">Submit</button>
</div>
</EditForm>
@code {
private CustomValidation? customValidation;
[SupplyParameterFromForm]
private Starship? Model { get; set; }
protected override void OnInitialized() =>
Model ??= new() { ProductionDate = DateTime.UtcNow };
private void Submit()
{
customValidation?.ClearErrors();
var errors = new Dictionary<string, List<string>>();
if (Model!.Classification == "Defense" &&
string.IsNullOrEmpty(Model.Description))
{
errors.Add(nameof(Model.Description),
[ "For a 'Defense' ship classification, " +
"'Description' is required." ]);
}
if (errors.Any())
{
customValidation?.DisplayErrors(errors);
}
else
{
Logger.LogInformation("Submit called: Processing the form");
}
}
}
@page "/starship-9"
@inject ILogger<Starship9> Logger
<h1>Starfleet Starship Database</h1>
<h2>New Ship Entry Form</h2>
<EditForm Model="Model" OnValidSubmit="Submit">
<CustomValidation @ref="customValidation" />
<ValidationSummary />
<div>
<label>
Primary Classification:
<InputSelect @bind-Value="Model!.Classification">
<option value="">Select classification ...</option>
<option value="Exploration">Exploration</option>
<option value="Diplomacy">Diplomacy</option>
<option value="Defense">Defense</option>
</InputSelect>
</label>
</div>
<div>
<label>
Description (optional):
<InputTextArea @bind-Value="Model!.Description" />
</label>
</div>
<div>
<button type="submit">Submit</button>
</div>
</EditForm>
@code {
private CustomValidation? customValidation;
public Starship? Model { get; set; }
protected override void OnInitialized() =>
Model ??= new() { ProductionDate = DateTime.UtcNow };
private void Submit()
{
customValidation?.ClearErrors();
var errors = new Dictionary<string, List<string>>();
if (Model!.Classification == "Defense" &&
string.IsNullOrEmpty(Model.Description))
{
errors.Add(nameof(Model.Description),
new() { "For a 'Defense' ship classification, " +
"'Description' is required." });
}
if (errors.Any())
{
customValidation?.DisplayErrors(errors);
}
else
{
Logger.LogInformation("Submit called: Processing the form");
}
}
}
注
検証コンポーネントを使用する代わりに、データ注釈検証属性を使用することもできます。 フォームのモデルに適用されるカスタム属性は、DataAnnotationsValidator コンポーネントを使用してアクティブ化されます。 サーバー検証で使用する場合、属性はサーバー上で実行可能である必要があります。 詳しくは、「カスタム検証属性」セクションをご覧ください。
検証コンポーネントを使用したサーバー検証
このセクションでは、Blazor Web App シナリオに焦点を当てていますが、Web API でサーバー検証を使用するあらゆる種類のアプリのアプローチには、同じ一般的なアプローチが採用されています。
このセクションでは、ホストされている Blazor WebAssembly シナリオに焦点を当てていますが、Web API でサーバー検証を使用するあらゆる種類のアプリのアプローチには、同じ一般的なアプローチが採用されています。
クライアント検証に加えて、サーバー検証がサポートされています。
- DataAnnotationsValidator コンポーネントを使用して、フォーム内のクライアント検証を処理します。
- フォームによってクライアント検証が渡されると (OnValidSubmit が呼び出されると)、フォーム処理のために、EditContext.Model がバックエンド サーバー API に送信されます。
- サーバーでモデルの検証を処理します。
- サーバー API には、組み込みのフレームワーク データ注釈検証と開発者によって提供されるカスタムの検証ロジックの両方が含まれています。 サーバーで検証が成功すると、フォームが処理され、成功の状態コード (
200 - OK) が返されます。 検証が失敗すると、失敗の状態コード (400 - Bad Request) とフィールド検証エラーが返されます。 - 成功時にフォームを無効にするか、エラーを表示します。
基本検証は、フォームをホストするコンポーネント内でフォームのモデルが定義されている場合に便利です。これは、コンポーネントのメンバーとして直接、またはサブクラスで行います。 複数のコンポーネントで独立したモデル クラスを使用する場合は、検証コンポーネントの使用をお勧めします。
次の例は、以下のものに基づいています。
- Blazor Web Appから作成された対話型 WebAssembly コンポーネントを含む Blazor Web App。
- 「
Starship」記事の「Starship.cs」セクションの モデル ()。 - 「
CustomValidation」セクションで示した コンポーネント。
Starship モデル (Starship.cs) を共有クラス ライブラリ プロジェクトに配置して、クライアントとサーバーの両方のアプリでモデルを使用できるようにします。 共有アプリの名前空間と一致するように、名前空間を追加または更新します (たとえば、namespace BlazorSample.Shared)。 モデルにはデータ注釈が必要であるため、共有クラス ライブラリでは共有フレームワークを使用するか、System.ComponentModel.Annotations パッケージを共有プロジェクトに追加します。
注
.NET アプリへのパッケージの追加に関するガイダンスについては、「パッケージ利用のワークフロー」 (NuGet ドキュメント) の "パッケージのインストールと管理" に関する記事を参照してください。 NuGet.org で正しいパッケージ バージョンを確認します。
Blazor Web App のメイン プロジェクトで、宇宙船の検証要求を処理し、失敗した検証のメッセージを返すコントローラーを追加します。 共有クラス ライブラリ プロジェクトの最後の using ステートメントの名前空間と、コントローラー クラスの namespace を更新します。 クライアントとサーバーのデータ注釈の検証に加えて、コントローラーでは、ユーザーが宇宙船の分類 (Description) で Defense を選択した場合に、宇宙船の説明 (Classification) に値が指定されているかどうかが検証されます。
- Blazor WebAssembly プロジェクト テンプレートから作成された、ホストされている Blazor WebAssemblyソリューション。 このアプローチは、Blazorで説明されている安全にホストされるすべてのBlazor WebAssemblyソリューションでサポートされています。
- 「
Starship」記事の「Starship.cs」セクションの モデル ()。 - 「
CustomValidation」セクションで示した コンポーネント。
Starship モデル (Starship.cs) をソリューションの Shared プロジェクトに配置して、クライアントとサーバーの両方のアプリでモデルを使用できるようにします。 共有アプリの名前空間と一致するように、名前空間を追加または更新します (たとえば、namespace BlazorSample.Shared)。 モデルにはデータ注釈が必要であるため、System.ComponentModel.Annotations パッケージを Shared プロジェクトに追加します。
注
.NET アプリへのパッケージの追加に関するガイダンスについては、「パッケージ利用のワークフロー」 (NuGet ドキュメント) の "パッケージのインストールと管理" に関する記事を参照してください。 NuGet.org で正しいパッケージ バージョンを確認します。
Server プロジェクトで、宇宙船の検証要求を処理し、失敗した検証のメッセージを返すコントローラーを追加します。
using プロジェクトの最後の Shared ステートメントの名前空間と、コントローラー クラスの namespace を更新します。 クライアントとサーバーのデータ注釈の検証に加えて、コントローラーでは、ユーザーが宇宙船の分類 (Description) で Defense を選択した場合に、宇宙船の説明 (Classification) に値が指定されているかどうかが検証されます。
宇宙船の分類 Defense の検証は、コントローラーのサーバーでのみ行われます。これは、フォームがサーバーに送信されるときに、今後のフォームではクライアント側で同じ検証が実行されないためです。 クライアント検証を伴わないサーバー検証は、サーバーでのユーザー入力のプライベート ビジネス ロジックの検証を必要とするアプリで一般的です。 たとえば、ユーザー用に保存されたデータからの個人情報が、ユーザー入力の検証に必要になる場合があります。 クライアント検証のために、プライベート データをクライアントに送信することはできません。
注
このセクションの StarshipValidation コントローラーでは、Microsoft Identity 2.0 が使用されます。 Web API は、この API で "API.Access" スコープを持つユーザーのトークンのみが受け入れられます。 API のスコープ名が API.Access と異なる場合は、追加のカスタマイズが必要です。
セキュリティの詳細については、以下を参照してください。
- ASP.NET Core Blazor の認証と承認 (および、Blazor の "セキュリティと Identity" ノードの他の記事)
- Microsoft ID プラットフォームのドキュメント
Controllers/StarshipValidation.cs:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using BlazorSample.Shared;
namespace BlazorSample.Server.Controllers;
[Authorize]
[ApiController]
[Route("[controller]")]
public class StarshipValidationController(
ILogger<StarshipValidationController> logger)
: ControllerBase
{
static readonly string[] scopeRequiredByApi = [ "API.Access" ];
[HttpPost]
public async Task<IActionResult> Post(Starship model)
{
HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);
try
{
if (model.Classification == "Defense" &&
string.IsNullOrEmpty(model.Description))
{
ModelState.AddModelError(nameof(model.Description),
"For a 'Defense' ship " +
"classification, 'Description' is required.");
}
else
{
logger.LogInformation("Processing the form asynchronously");
// async ...
return Ok(ModelState);
}
}
catch (Exception ex)
{
logger.LogError("Validation Error: {Message}", ex.Message);
}
return BadRequest(ModelState);
}
}
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using BlazorSample.Shared;
namespace BlazorSample.Server.Controllers;
[Authorize]
[ApiController]
[Route("[controller]")]
public class StarshipValidationController(
ILogger<StarshipValidationController> logger)
: ControllerBase
{
static readonly string[] scopeRequiredByApi = new[] { "API.Access" };
[HttpPost]
public async Task<IActionResult> Post(Starship model)
{
HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);
try
{
if (model.Classification == "Defense" &&
string.IsNullOrEmpty(model.Description))
{
ModelState.AddModelError(nameof(model.Description),
"For a 'Defense' ship " +
"classification, 'Description' is required.");
}
else
{
logger.LogInformation("Processing the form asynchronously");
// async ...
return Ok(ModelState);
}
}
catch (Exception ex)
{
logger.LogError("Validation Error: {Message}", ex.Message);
}
return BadRequest(ModelState);
}
}
アプリのコントローラーの名前と一致するように、上記のコントローラー (BlazorSample.Server.Controllers) の名前空間を確認または更新します。
サーバーでモデル バインド検証エラーが発生した場合、通常、ApiController (ApiControllerAttribute) により、既定の無効な要求応答と ValidationProblemDetails が返されます。
Starfleet Starship Database フォームのすべてのフィールドが送信されず、フォームの検証が失敗した場合、次の例に示すように、応答には、検証エラー以外のデータも含まれます。
{
"title": "One or more validation errors occurred.",
"status": 400,
"errors": {
"Id": [ "The Id field is required." ],
"Classification": [ "The Classification field is required." ],
"IsValidatedDesign": [ "This form disallows unapproved ships." ],
"MaximumAccommodation": [ "Accommodation invalid (1-100000)." ]
}
}
注
上記の JSON 応答を実際に確認するには、フォームのクライアント検証を無効にして空のフィールド フォームの送信を許可するか、Firefox Browser Developer などのツールを使ってサーバー API に要求を直接送信する必要があります。
サーバー API によって、前述の既定の JSON 応答が返された場合、クライアントでは、開発者コードで応答を解析して、フォーム検証エラーの処理のために errors ノードの子を取得することができます。 ファイルを解析するための開発者コードを記述するのは不便です。 JSON を手動で解析するには、Dictionary<string, List<string>> を呼び出した後でエラーの ReadFromJsonAsync を生成する必要があります。 次の例に示すように、サーバー API で検証エラーのみを返すのが理想的です。
{
"Id": [ "The Id field is required." ],
"Classification": [ "The Classification field is required." ],
"IsValidatedDesign": [ "This form disallows unapproved ships." ],
"MaximumAccommodation": [ "Accommodation invalid (1-100000)." ]
}
サーバー API の応答を変更して、検証エラーのみが返されるようにするには、ApiControllerAttribute ファイルの Program で注釈が付けられたアクションで呼び出されるデリゲートを変更します。 API エンドポイント (/StarshipValidation) の場合、BadRequestObjectResult と ModelStateDictionary が返されます。 他の API エンドポイントの場合、オブジェクトの結果と新しい ValidationProblemDetailsが返され、既定の動作が保持されます。
Microsoft.AspNetCore.Mvc のメイン プロジェクトで Program ファイルの先頭に Blazor Web App 名前空間を追加します。
using Microsoft.AspNetCore.Mvc;
Program ファイルで次の 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));
}
};
});
Blazor Web App のメイン プロジェクトに初めてコントローラーを追加する場合は、コントローラーのサービスを追加する上記のコードを配置するときにコントローラー エンドポイントをマップします。 次の例では、既定のコントローラー ルートを使用します。
app.MapDefaultControllerRoute();
注
前の例では、AddControllersWithViews を呼び出して、自動的にクロスサイト リクエスト フォージェリ (XSRF/CSRF) 攻撃を軽減することで、コントローラー サービスを明示的に登録します。 単に AddControllers を使用する場合、偽造防止は自動的には有効になりません。
コントローラー ルーティングと検証失敗エラーの応答の詳細については、次のリソースを参照してください。
.Client プロジェクトで、「バリデーターコンポーネント」セクションに示されている CustomValidation コンポーネントを追加します。 アプリと一致するように名前空間を更新します (たとえば、namespace BlazorSample.Client)。
.Client プロジェクトで、Starfleet Starship Database フォームが、CustomValidation コンポーネントを使用してサーバー検証エラーを示すように更新されます。 サーバー API によって検証メッセージが返されると、それらは、CustomValidation コンポーネントの ValidationMessageStoreに追加されます。 エラーは、フォームの検証の概要によって表示するため、フォームの EditContext で使用できます。
次のコンポーネントで、共有プロジェクトの名前空間 (@using BlazorSample.Shared) を、共有プロジェクトの名前空間に更新します。 フォームには承認が必要なため、ユーザーは、フォームに移動するには、アプリにサインインする必要があることに注意してください。
Microsoft.AspNetCore.Mvc アプリの Program ファイルの先頭に Server 名前空間を追加します。
using Microsoft.AspNetCore.Mvc;
Program ファイルで 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));
}
};
});
注
前の例では、AddControllersWithViews を呼び出して、自動的にクロスサイト リクエスト フォージェリ (XSRF/CSRF) 攻撃を軽減することで、コントローラー サービスを明示的に登録します。 単に AddControllers を使用する場合、偽造防止は自動的には有効になりません。
Client プロジェクトで、「バリデーターコンポーネント」セクションに示されている CustomValidation コンポーネントを追加します。 アプリと一致するように名前空間を更新します (たとえば、namespace BlazorSample.Client)。
Client プロジェクトで、Starfleet Starship Database フォームが、CustomValidation コンポーネントを使用してサーバー検証エラーを示すように更新されます。 サーバー API によって検証メッセージが返されると、それらは、CustomValidation コンポーネントの ValidationMessageStoreに追加されます。 エラーは、フォームの検証の概要によって表示するため、フォームの EditContext で使用できます。
次のコンポーネントで、Shared プロジェクトの名前空間 (@using BlazorSample.Shared) を共有プロジェクトの名前空間に更新します。 フォームには承認が必要なため、ユーザーは、フォームに移動するには、アプリにサインインする必要があることに注意してください。
Starship10.razor:
注
既定では、EditForm に基づくフォームでは、偽造防止サポートが自動的に有効になります。 コントローラーでは、コントローラー サービスを登録し、Web API の偽造防止サポートを自動的に有効にするために、AddControllersWithViews を使用する必要があります。
@page "/starship-10"
@rendermode InteractiveWebAssembly
@using System.Net
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using BlazorSample.Shared
@attribute [Authorize]
@inject HttpClient Http
@inject ILogger<Starship10> Logger
<h1>Starfleet Starship Database</h1>
<h2>New Ship Entry Form</h2>
<EditForm FormName="Starship10" Model="Model" OnValidSubmit="Submit">
<DataAnnotationsValidator />
<CustomValidation @ref="customValidation" />
<ValidationSummary />
<div>
<label>
Identifier:
<InputText @bind-Value="Model!.Id" disabled="@disabled" />
</label>
</div>
<div>
<label>
Description (optional):
<InputTextArea @bind-Value="Model!.Description"
disabled="@disabled" />
</label>
</div>
<div>
<label>
Primary Classification:
<InputSelect @bind-Value="Model!.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>
</div>
<div>
<label>
Maximum Accommodation:
<InputNumber @bind-Value="Model!.MaximumAccommodation"
disabled="@disabled" />
</label>
</div>
<div>
<label>
Engineering Approval:
<InputCheckbox @bind-Value="Model!.IsValidatedDesign"
disabled="@disabled" />
</label>
</div>
<div>
<label>
Production Date:
<InputDate @bind-Value="Model!.ProductionDate" disabled="@disabled" />
</label>
</div>
<div>
<button type="submit" disabled="@disabled">Submit</button>
</div>
<div style="@messageStyles">
@message
</div>
</EditForm>
@code {
private CustomValidation? customValidation;
private bool disabled;
private string? message;
private string messageStyles = "visibility:hidden";
[SupplyParameterFromForm]
private Starship? Model { get; set; }
protected override void OnInitialized() =>
Model ??= new() { ProductionDate = DateTime.UtcNow };
private async Task Submit(EditContext editContext)
{
customValidation?.ClearErrors();
try
{
using 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.";
}
}
}
.Client の Blazor Web App プロジェクトでは、バックエンド Web API コントローラーに対する HTTP POST 要求の HttpClient も登録する必要があります。
.Client プロジェクトの Program ファイルで次の行を確認するか、追加します。
builder.Services.AddScoped(sp =>
new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
上記の例では、ベース アドレスの設定に builder.HostEnvironment.BaseAddress (IWebAssemblyHostEnvironment.BaseAddress) を使っています。これを使って、アプリのベース アドレス (通常は、ホスト ページの <base> タグの href 値に由来します) を取得できます。
@page "/starship-10"
@using System.Net
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using BlazorSample.Shared
@attribute [Authorize]
@inject HttpClient Http
@inject ILogger<Starship10> Logger
<h1>Starfleet Starship Database</h1>
<h2>New Ship Entry Form</h2>
<EditForm Model="Model" OnValidSubmit="Submit">
<DataAnnotationsValidator />
<CustomValidation @ref="customValidation" />
<ValidationSummary />
<div>
<label>
Identifier:
<InputText @bind-Value="Model!.Id" disabled="@disabled" />
</label>
</div>
<div>
<label>
Description (optional):
<InputTextArea @bind-Value="Model!.Description"
disabled="@disabled" />
</label>
</div>
<div>
<label>
Primary Classification:
<InputSelect @bind-Value="Model!.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>
</div>
<div>
<label>
Maximum Accommodation:
<InputNumber @bind-Value="Model!.MaximumAccommodation"
disabled="@disabled" />
</label>
</div>
<div>
<label>
Engineering Approval:
<InputCheckbox @bind-Value="Model!.IsValidatedDesign"
disabled="@disabled" />
</label>
</div>
<div>
<label>
Production Date:
<InputDate @bind-Value="Model!.ProductionDate" disabled="@disabled" />
</label>
</div>
<div>
<button type="submit" disabled="@disabled">Submit</button>
</div>
<div style="@messageStyles">
@message
</div>
</EditForm>
@code {
private CustomValidation? customValidation;
private bool disabled;
private string? message;
private string messageStyles = "visibility:hidden";
public Starship? Model { get; set; }
protected override void OnInitialized() =>
Model ??= new() { ProductionDate = DateTime.UtcNow };
private async Task Submit(EditContext editContext)
{
customValidation?.ClearErrors();
try
{
using 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.";
}
}
}
注
検証コンポーネントを使用する代わりに、データ注釈検証属性を使用することもできます。 フォームのモデルに適用されるカスタム属性は、DataAnnotationsValidator コンポーネントを使用してアクティブ化されます。 サーバー検証で使用する場合、属性はサーバー上で実行可能である必要があります。 詳しくは、「カスタム検証属性」セクションをご覧ください。
注
このセクションで説明したサーバー検証の手法は、このドキュメント セットにある、Blazor WebAssembly でホストされるどのソリューション例にも適しています。
入力イベントに基づく InputText
InputText イベント (oninput) ではなく、input イベント (onchange) を使用するカスタム コンポーネントを作成するには、change コンポーネントを使用します。 各キー入力で、input イベント トリガーのフィールド検証を使用します。
次の CustomInputText コンポーネントによってフレームワークの InputText コンポーネントが継承され、イベント バインディングが oninput イベント (input) に設定されます。
CustomInputText.razor:
@inherits InputText
<input @attributes="AdditionalAttributes"
class="@CssClass"
@bind="CurrentValueAsString"
@bind:event="oninput" />
CustomInputText コンポーネントは、InputText が使用される場所であればどこでも使用できます。 次のコンポーネントでは、共有 CustomInputText コンポーネントが使用されています。
Starship11.razor:
@page "/starship-11"
@using System.ComponentModel.DataAnnotations
@inject ILogger<Starship11> Logger
<EditForm Model="Model" OnValidSubmit="Submit" FormName="Starship11">
<DataAnnotationsValidator />
<ValidationSummary />
<div>
<label>
Identifier:
<CustomInputText @bind-Value="Model!.Id" />
</label>
</div>
<div>
<button type="submit">Submit</button>
</div>
</EditForm>
<div>
CurrentValue: @Model?.Id
</div>
@code {
[SupplyParameterFromForm]
private Starship? Model { get; set; }
protected override void OnInitialized() => Model ??= new();
private void Submit() => Logger.LogInformation("Submit: Processing form");
public class Starship
{
[Required]
[StringLength(10, ErrorMessage = "Id is too long.")]
public string? Id { get; set; }
}
}
@page "/starship-11"
@using System.ComponentModel.DataAnnotations
@inject ILogger<Starship11> Logger
<EditForm Model="Model" OnValidSubmit="Submit" FormName="Starship11">
<DataAnnotationsValidator />
<ValidationSummary />
<div>
<label>
Identifier:
<CustomInputText @bind-Value="Model!.Id" />
</label>
</div>
<div>
<button type="submit">Submit</button>
</div>
</EditForm>
<div>
CurrentValue: @Model?.Id
</div>
@code {
[SupplyParameterFromForm]
private Starship? Model { get; set; }
protected override void OnInitialized() => Model ??= new();
private void Submit() => Logger.LogInformation("Submit: Processing form");
public class Starship
{
[Required]
[StringLength(10, ErrorMessage = "Id is too long.")]
public string? Id { get; set; }
}
}
@page "/starship-11"
@using System.ComponentModel.DataAnnotations
@inject ILogger<Starship11> Logger
<EditForm Model="Model" OnValidSubmit="Submit">
<DataAnnotationsValidator />
<ValidationSummary />
<CustomInputText @bind-Value="Model!.Id" />
<button type="submit">Submit</button>
</EditForm>
<div>
CurrentValue: @Model?.Id
</div>
@code {
public Starship? Model { get; set; }
protected override void OnInitialized() => Model ??= new();
private void Submit()
{
Logger.LogInformation("Submit called: Processing the form");
}
public class Starship
{
[Required]
[StringLength(10, ErrorMessage = "Id is too long.")]
public string? Id { get; set; }
}
}
検証概要コンポーネントと検証メッセージ コンポーネント
ValidationSummary コンポーネントは、すべての検証メッセージを要約します。これは検証概要タグヘルパーと似ています。
<ValidationSummary />
Model パラメーターを使用して、特定のモデルの検証メッセージを出力します。
<ValidationSummary Model="Model" />
ValidationMessage<TValue> コンポーネントは、特定のフィールドの検証メッセージを表示します。これは、検証メッセージ タグ ヘルパーに似ています。 For 属性と、モデル プロパティに名前を付けるラムダ式で、検証するフィールドを指定します。
<ValidationMessage For="@(() => Model!.MaximumAccommodation)" />
ValidationMessage<TValue> コンポーネントと ValidationSummary コンポーネントでは、任意の属性をサポートしています。 コンポーネント パラメーターに一致しない属性は、生成された <div> 要素または <ul> 要素に追加されます。
アプリのスタイルシート (wwwroot/css/app.css または wwwroot/css/site.css) での検証メッセージのスタイルを制御します。 既定の validation-message クラスでは、検証メッセージのテキストの色が赤に設定されます。
.validation-message {
color: red;
}
フォーム フィールドが有効かどうかを判断する
検証メッセージを取得せずにフィールドが有効かどうかを判断するには、EditContext.IsValid を使用します。
サポートされていますが、推奨されません:
var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();
推奨:
var isValid = editContext.IsValid(fieldIdentifier);
カスタム検証属性
カスタム検証属性を使用するときに、検証結果がフィールドに正しく関連付けられるようにするには、MemberName の作成時に検証コンテキストの ValidationResult を渡します。
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.",
[ validationContext.MemberName! ]);
}
}
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! });
}
}
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 クラスは、Ten Forward サラダに承認されている宇宙船の材料リストを示しています。
SaladChef.cs:
namespace BlazorSample;
public class SaladChef
{
public string[] SaladToppers = { "Horva", "Kanda Root", "Krintar", "Plomeek",
"Syto Bean" };
}
SaladChef ファイルでアプリの DI コンテナーに Program を登録します。
builder.Services.AddTransient<SaladChef>();
次の IsValid クラスの SaladChefValidatorAttribute メソッドでは、DI から SaladChef サービスを取得して、ユーザーの入力をチェックします。
SaladChefValidatorAttribute.cs:
using System.ComponentModel.DataAnnotations;
namespace BlazorSample;
public class SaladChefValidatorAttribute : ValidationAttribute
{
protected override ValidationResult? IsValid(object? value,
ValidationContext validationContext)
{
var saladChef = validationContext.GetRequiredService<SaladChef>();
if (saladChef.SaladToppers.Contains(value?.ToString()))
{
return ValidationResult.Success;
}
return new ValidationResult("Is that a Vulcan salad topper?! " +
"The following toppers are available for a Ten Forward salad: " +
string.Join(", ", saladChef.SaladToppers));
}
}
次のコンポーネントでは、SaladChefValidatorAttribute ([SaladChefValidator]) をサラダの材料の文字列 (SaladIngredient) に適用し、ユーザー入力を検証します。
Starship12.razor:
@page "/starship-12"
@inject SaladChef SaladChef
<EditForm Model="this" autocomplete="off" FormName="Starship12">
<DataAnnotationsValidator />
<div>
<label>
Salad topper (@saladToppers):
<input @bind="SaladIngredient" />
</label>
</div>
<div>
<button type="submit">Submit</button>
</div>
<ul>
@foreach (var message in context.GetValidationMessages())
{
<li class="validation-message">@message</li>
}
</ul>
</EditForm>
@code {
private string? saladToppers;
[SaladChefValidator]
public string? SaladIngredient { get; set; }
protected override void OnInitialized() =>
saladToppers ??= string.Join(", ", SaladChef.SaladToppers);
}
@page "/starship-12"
@inject SaladChef SaladChef
<EditForm Model="this" autocomplete="off" FormName="Starship12">
<DataAnnotationsValidator />
<div>
<label>
Salad topper (@saladToppers):
<input @bind="SaladIngredient" />
</label>
</div>
<div>
<button type="submit">Submit</button>
</div>
<ul>
@foreach (var message in context.GetValidationMessages())
{
<li class="validation-message">@message</li>
}
</ul>
</EditForm>
@code {
private string? saladToppers;
[SaladChefValidator]
public string? SaladIngredient { get; set; }
protected override void OnInitialized() =>
saladToppers ??= string.Join(", ", SaladChef.SaladToppers);
}
@page "/starship-12"
@inject SaladChef SaladChef
<EditForm Model="this" autocomplete="off">
<DataAnnotationsValidator />
<p>
<label>
Salad topper (@saladToppers):
<input @bind="SaladIngredient" />
</label>
</p>
<button type="submit">Submit</button>
<ul>
@foreach (var message in context.GetValidationMessages())
{
<li class="validation-message">@message</li>
}
</ul>
</EditForm>
@code {
private string? saladToppers;
[SaladChefValidator]
public string? SaladIngredient { get; set; }
protected override void OnInitialized() =>
saladToppers ??= string.Join(", ", SaladChef.SaladToppers);
}
カスタム検証用の CSS クラス属性
カスタム検証 CSS クラスの属性は、Bootstrap などの CSS フレームワークと統合する場合に便利です。
カスタム検証 CSS クラス属性を指定するには、まず、カスタム検証用の CSS スタイルを提供します。 次の例では、有効なスタイル (validField) と無効なスタイル (invalidField) が指定されています。
アプリのスタイルシートに次の CSS クラスを追加します。
.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.IsValid(fieldIdentifier);
return isValid ? "validField" : "invalidField";
}
}
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";
}
}
フォームの CustomFieldClassProvider インスタンスに対して、EditContext クラスをフィールド CSS クラス プロバイダーとして設定します。
Starship13.razor:
@page "/starship-13"
@using System.ComponentModel.DataAnnotations
@inject ILogger<Starship13> Logger
<EditForm EditContext="editContext" OnValidSubmit="Submit" FormName="Starship13">
<DataAnnotationsValidator />
<ValidationSummary />
<div>
<label>
Identifier:
<InputText @bind-Value="Model!.Id" />
</label>
</div>
<div>
<button type="submit">Submit</button>
</div>
</EditForm>
@code {
private EditContext? editContext;
[SupplyParameterFromForm]
private Starship? Model { get; set; }
protected override void OnInitialized()
{
Model ??= new();
editContext = new(Model);
editContext.SetFieldCssClassProvider(new CustomFieldClassProvider());
}
private void Submit() => Logger.LogInformation("Submit: Processing form");
public class Starship
{
[Required]
[StringLength(10, ErrorMessage = "Id is too long.")]
public string? Id { get; set; }
}
}
@page "/starship-13"
@using System.ComponentModel.DataAnnotations
@inject ILogger<Starship13> Logger
<EditForm EditContext="editContext" OnValidSubmit="Submit" FormName="Starship13">
<DataAnnotationsValidator />
<ValidationSummary />
<div>
<label>
Identifier:
<InputText @bind-Value="Model!.Id" />
</label>
</div>
<div>
<button type="submit">Submit</button>
</div>
</EditForm>
@code {
private EditContext? editContext;
[SupplyParameterFromForm]
private Starship? Model { get; set; }
protected override void OnInitialized()
{
Model ??= new();
editContext = new(Model);
editContext.SetFieldCssClassProvider(new CustomFieldClassProvider());
}
private void Submit() => Logger.LogInformation("Submit: Processing form");
public class Starship
{
[Required]
[StringLength(10, ErrorMessage = "Id is too long.")]
public string? Id { get; set; }
}
}
@page "/starship-13"
@using System.ComponentModel.DataAnnotations
@inject ILogger<Starship13> Logger
<EditForm EditContext="editContext" OnValidSubmit="Submit">
<DataAnnotationsValidator />
<ValidationSummary />
<InputText @bind-Value="Model!.Id" />
<button type="submit">Submit</button>
</EditForm>
@code {
private EditContext? editContext;
public Starship? Model { get; set; }
protected override void OnInitialized()
{
Model ??= new();
editContext = new(Model);
editContext.SetFieldCssClassProvider(new CustomFieldClassProvider());
}
private void Submit()
{
Logger.LogInformation("Submit called: Processing the form");
}
public class Starship
{
[Required]
[StringLength(10, ErrorMessage = "Id is too long.")]
public string? Id { get; set; }
}
}
前の例では、すべてのフォーム フィールドの有効性をチェックし、各フィールドにスタイルを適用しています。 フォームを使用してフィールドのサブセットにのみカスタム スタイルを適用する場合は、スタイルが CustomFieldClassProvider によって条件的に適用されるようにします。 次の CustomFieldClassProvider2 の例では、スタイルが Name フィールドに適用されるだけです。 名前が Name と一致しないフィールドの場合は、string.Empty が返され、スタイルは適用されません。
リフレクションを使用すると、フィールドが、HTML エンティティに割り当てられたid ではなく、モデル メンバーのプロパティまたはフィールド名と照合されます。
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.IsValid(fieldIdentifier);
return isValid ? "validField" : "invalidField";
}
return string.Empty;
}
}
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;
}
}
注
前の例のフィールド名の照合では大文字と小文字が区別されるため、モデル プロパティ メンバーで指定された "Name" が "Name" の条件付きのチェックと一致する必要があります。
-
正しく一致します:
fieldId.FieldName == "Name" -
一致しません:
fieldId.FieldName == "name" -
一致しません:
fieldId.FieldName == "NAME" -
一致しません:
fieldId.FieldName == "nAmE"
追加のプロパティを Model に追加します。次に例を示します。
[StringLength(10, ErrorMessage = "Description is too long.")]
public string? Description { get; set; }
Description コンポーネントのフォームに CustomValidationForm を追加します。
<InputText @bind-Value="Model!.Description" />
新しいフィールド CSS クラス プロバイダーを使用するように、コンポーネントの EditContext メソッドで OnInitialized インスタンスを更新します。
editContext?.SetFieldCssClassProvider(new CustomFieldClassProvider2());
CSS 検証クラスは Description フィールドに適用されないため、スタイルは設定されません。 ただし、フィールドの検証は普通に実行されます。 入力が 10 文字を超えた場合、検証の概要でエラーが示されます。
説明が長すぎます。
次に例を示します。
カスタム CSS スタイルが
Nameフィールドに適用されます。その他のフィールドでは Blazor の既定のロジックに似たロジックが適用され、Blazor を
modifiedまたはvalidにしてinvalidの既定のフィールド 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.IsValid(fieldIdentifier);
if (fieldIdentifier.FieldName == "Name")
{
return isValid ? "validField" : "invalidField";
}
else
{
if (editContext.IsModified(fieldIdentifier))
{
return isValid ? "modified valid" : "modified invalid";
}
else
{
return isValid ? "valid" : "invalid";
}
}
}
}
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 クラス プロバイダーを使用するように、コンポーネントの EditContext メソッドで OnInitialized インスタンスを更新します。
editContext.SetFieldCssClassProvider(new CustomFieldClassProvider3());
CustomFieldClassProvider3の使用
-
Nameフィールドには、アプリのカスタム検証 CSS スタイルが使用されます。 -
Descriptionフィールドには、Blazor のロジックに似たロジックと、Blazor の既定のフィールド CSS 検証スタイルが使用されます。
IValidatableObject でのクラスレベルの検証
IValidatableObject
を使用したクラスレベルの検証 (API ドキュメント) は、Blazor フォーム モデルでサポートされています。
IValidatableObject 検証は、フォームが送信されたときにのみ実行され、他のすべての検証が成功した場合にのみ実行されます。
Blazor データ注釈検証パッケージ
注
Microsoft.AspNetCore.Components.DataAnnotations.Validation パッケージは、.NET 10 以降を対象とするアプリでは推奨されなくなりました。 詳細については、「 入れ子になったオブジェクト、コレクション型、複合型 」セクションを参照してください。
Microsoft.AspNetCore.Components.DataAnnotations.Validation パッケージは、DataAnnotationsValidator コンポーネントを使用して検証エクスペリエンスのギャップを埋めます。 パッケージは現在、試験段階です。
警告
Microsoft.AspNetCore.Components.DataAnnotations.Validation パッケージには、最新バージョンのリリース候補が NuGet.org。現時点では、試験段階のリリース候補パッケージを引き続き使用してください。 試験段階の機能は、機能の有効性を調べる目的で提供されており、安定バージョンには含まれていない場合があります。 更新の詳細については、Announcements GitHub リポジトリ、dotnet/aspnetcoreGitHub リポジトリ、またはこのトピック セクションを参照してください。
[CompareProperty] 属性
CompareAttribute は、DataAnnotationsValidator が検証結果を特定のメンバーに関連付けないため、DataAnnotationsValidator コンポーネントで正しく機能しません。 これにより、フィールドレベルの検証と、送信時のモデル全体が検証されたときの動作に一貫性がなくなることがあります。
Microsoft.AspNetCore.Components.DataAnnotations.Validation
experimental パッケージには、これらの制限に対応する追加の検証属性 (ComparePropertyAttribute) が導入されています。
Blazor アプリでは、[CompareProperty] は [Compare] 属性の直接の代わりとなるものです。
入れ子になったオブジェクトとコレクション型
Blazor フォーム検証には、組み込みの DataAnnotationsValidatorを使用して入れ子になったオブジェクトとコレクション項目のプロパティを検証するためのサポートが含まれています。
検証済みのフォームを作成するには、前と同様に、DataAnnotationsValidator コンポーネント内でEditForm コンポーネントを使用します。
入れ子になったオブジェクトとコレクション型の検証機能をオプトインするには:
- サービスが登録されているAddValidation ファイルで、
Program拡張メソッドを呼び出します。 -
Razor コンポーネント (
.razor) ではなく、C# クラス ファイルでフォーム モデル型を宣言します。 -
[ValidatableType]属性を使用して、ルート フォーム モデルの種類に注釈を付けます。
前の手順に従わないと、フォーム検証の動作には、入れ子になったモデルとコレクション型の検証は含まれません。
次の例は、フォームの検証が改善された顧客の注文を示しています (簡潔にするために詳細は省略されています)。
Program.csで、サービス コレクションのAddValidationを呼び出します。
builder.Services.AddValidation();
次の Order クラスでは、最上位モデル型に [ValidatableType] 属性が必要です。 その他の型は自動的に検出されます。
OrderItem と ShippingAddress は簡潔にするために表示されませんが、入れ子になった検証とコレクションの検証は、これらの型で表示された場合と同じように動作します。
Order.cs:
using System.ComponentModel.DataAnnotations;
[ValidatableType]
public class Order
{
public Customer Customer { get; set; } = new();
public List<OrderItem> OrderItems { get; set; } = [];
}
public class Customer
{
[Required(ErrorMessage = "Name is required.")]
public string? FullName { get; set; }
[Required(ErrorMessage = "Email is required.")]
public string? Email { get; set; }
public ShippingAddress ShippingAddress { get; set; } = new();
}
次の OrderPage コンポーネントでは、 DataAnnotationsValidator コンポーネントが EditForm コンポーネントに存在します。
OrderPage.razor:
<EditForm Model="Model">
<DataAnnotationsValidator />
<h3>Customer Details</h3>
<div class="mb-3">
<label>
Full Name
<InputText @bind-Value="Model!.Customer.FullName" />
</label>
<ValidationMessage For="@(() => Model!.Customer.FullName)" />
</div>
// ... form continues ...
</EditForm>
@code {
public Order? Model { get; set; }
protected override void OnInitialized() => Model ??= new();
}
Razor コンポーネント (.razor ファイル) の外部でモデル型を宣言する必要がある理由は、新しい検証機能とRazor コンパイラ自体の両方がソース ジェネレーターを使用しているためです。 現在、あるソース ジェネレーターの出力を別のソース ジェネレーターの入力として使用することはできません。
別のアセンブリからの検証モデルの使用に関するガイダンスについては、「別のアセンブリ の検証モデルを使用する 」セクションを参照してください。
入れ子になったオブジェクト、コレクション型、複合型
注
.NET 10 以降を対象とするアプリの場合、このセクションで説明するMicrosoft.AspNetCore.Components.DataAnnotations.Validation説明的なパッケージとアプローチの使用は推奨されなくなりました。
DataAnnotationsValidator コンポーネントの組み込みの検証機能を使用することをお勧めします。
Blazor では、組み込みの DataAnnotationsValidator によるデータ注釈を使用したフォーム入力の検証をサポートしています。 ただし、.NET 9 以前の DataAnnotationsValidator では、コレクション型または複合型のプロパティではないフォームにバインドされたモデルの最上位のプロパティのみが検証されます。
コレクション型と複合型のプロパティを含む、バインドされたモデルのオブジェクト グラフ全体を検証するには、.NET 9 以前のObjectGraphDataAnnotationsValidatorによって提供されるMicrosoft.AspNetCore.Components.DataAnnotations.Validationを使用します。
<EditForm ...>
<ObjectGraphDataAnnotationsValidator />
...
</EditForm>
[ValidateComplexType] でモデルのプロパティに注釈を付けます。 次のモデル クラスでは、ShipDescription クラスに、モデルがフォームにバインドされたときに検証する追加のデータ注釈が含まれています。
Starship.cs:
using System;
using System.ComponentModel.DataAnnotations;
public class Starship
{
...
[ValidateComplexType]
public ShipDescription ShipDescription { get; set; } = new();
...
}
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; }
}
別のアセンブリの検証モデルを使用する
ライブラリや.ClientのBlazor Web App プロジェクトなど、別のアセンブリで定義されたモデル検証の場合:
- ライブラリがプレーン クラス ライブラリ (
Microsoft.NET.Sdk.WebまたはMicrosoft.NET.Sdk.RazorSDK に基づいていない) の場合は、Microsoft.Extensions.ValidationNuGet パッケージのライブラリへのパッケージ参照を追加します。 このセクションで後述するプレーン クラス ライブラリには、追加の手順が必要です。 -
.Clientインスタンスを引数として受け取り、IServiceCollectionを呼び出すメソッドをライブラリまたはAddValidation プロジェクトに作成します。 - アプリで、メソッドと AddValidationの両方を呼び出します。
上記の方法では、両方のアセンブリから型が検証されます。
次の例では、AddValidationForTypesInClient メソッドは、.Client プロジェクトで定義されている型を使用して検証するために、Blazor Web Appの.Client プロジェクトに対して作成されます。
ServiceCollectionExtensions.cs ( .Client プロジェクト内):
namespace BlazorSample.Client.Extensions;
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddValidationForTypesInClient(
this IServiceCollection collection)
{
return collection.AddValidation();
}
}
サーバー プロジェクトの Program ファイルで、名前空間を追加し、 .Client プロジェクトのサービス コレクション拡張メソッド (AddValidationForTypesInClient) と AddValidationを呼び出します。
using BlazorSample.Client.Extensions;
...
builder.Services.AddValidationForTypesInClient();
builder.Services.AddValidation();
Microsoft.Extensions.Validation パッケージ (ValidatableTypeAttribute および SkipValidationAttribute) の新しい属性は、.NET 10 で試験段階として公開されます。 このパッケージは、フレームワーク間で検証機能用の新しい共有インフラストラクチャを提供することを目的としており、実験用の型を公開すると、フレームワークの使用をより適切にサポートするためにパブリック API の最終的な設計に対してより柔軟に対応できます。
Blazor アプリでは、生成された埋め込み属性を使用して型を使用できるようになります。
Microsoft.NET.Sdk.Web SDK (<Project Sdk="Microsoft.NET.Sdk.Web">) または Microsoft.NET.Sdk.Razor SDK (<Project Sdk="Microsoft.NET.Sdk.Razor">) を使用する RCL を使用する Web アプリ プロジェクトにRazor コンポーネント (.razor) が含まれている場合、フレームワークはプロジェクト内に内部属性 (Microsoft.Extensions.Validation.Embedded.ValidatableType、Microsoft.Extensions.Validation.Embedded.SkipValidation) を自動的に生成します。 これらの型は、実際の属性と互換性があり、試験段階としてマークされていません。 ほとんどの場合、開発者は、ソースを気にせずにクラスで [ValidatableType]/[SkipValidation] 属性を使用します。
ただし、上記のアプローチは、 Microsoft.NET.Sdk SDK (<Project Sdk="Microsoft.NET.Sdk">) を使用するプレーン クラス ライブラリでは実行できません。 プレーン クラス ライブラリで型を使用すると、コード分析の警告が表示されます。
ASP0029: 'Microsoft.Extensions.Validation.ValidatableTypeAttribute' は評価のみを目的としており、今後の更新プログラムで変更または削除される可能性があります。 この診断を抑制して続行します。
警告は、次のいずれかの方法を使用して抑制できます。
プロジェクト ファイル内の
<NoWarn>プロパティ:<PropertyGroup> <NoWarn>$(NoWarn);ASP0029</NoWarn> </PropertyGroup>属性が使用される
pragmaディレクティブ :#pragma warning disable ASP0029 [Microsoft.Extensions.Validation.ValidatableType] #pragma warning restore ASP0029EditorConfig ファイル (
.editorconfig) ルール:dotnet_diagnostic.ASP0029.severity = none
警告を抑制できない場合は、Web SDK と Razor SDK によって自動的に生成される埋め込み属性をライブラリに手動で作成します。
ValidatableTypeAttribute.cs:
namespace Microsoft.Extensions.Validation.Embedded
{
[AttributeUsage(AttributeTargets.Class)]
internal sealed class ValidatableTypeAttribute : Attribute
{
}
}
検証ソース ジェネレーターが型を検出して使用するには、正確な名前空間 (Microsoft.Extensions.Validation.Embedded) とクラス名 (ValidatableTypeAttribute) を使用します。 名前空間のグローバル using ステートメントは、 global using Microsoft.Extensions.Validation.Embedded; ステートメントを使用するか、ライブラリのプロジェクト ファイル内の <Using Include="Microsoft.Extensions.Validation.Embedded" /> 項目を使用して宣言できます。
どちらの方法が採用されている場合でも、今後コードを更新するための回避策が存在することを示します。 .NET 11 (2026 年 11 月) では、プレーン クラス ライブラリでの検証型の導入を容易にするフレームワークの更新が予定されています。
フォームの検証に基づいて送信ボタンを有効にする
フォームの検証に基づいて送信ボタンを有効または無効にするため、以下の例では次のことが行われています。
- 入力コンポーネントに関する記事の「
Starfleet Starship Database」セクションの前のStarship3フォーム ( コンポーネント) の簡略版が使用され、宇宙船の ID の値のみが受け取られます。Starshipの他のプロパティは、Starship型のインスタンスが作成されるときに、有効な既定値を受け取ります。 - コンポーネントを初期化するときに、フォームの EditContext を使用してモデルを割り当てます。
- コンテキストの OnFieldChanged コールバックでフォームを検証して、送信ボタンを有効または無効にします。
-
IDisposable を実装し、
Disposeメソッドでイベント ハンドラーの登録を解除します。 詳細については、ASP.NET Core Razor コンポーネントの破棄を参照してください。
注
EditForm.EditContext に割り当てるとき、EditForm.Model には EditForm を割り当てないでください。
Starship14.razor:
@page "/starship-14"
@implements IDisposable
@inject ILogger<Starship14> Logger
<EditForm EditContext="editContext" OnValidSubmit="Submit" FormName="Starship14">
<DataAnnotationsValidator />
<ValidationSummary />
<div>
<label>
Identifier:
<InputText @bind-Value="Model!.Id" />
</label>
</div>
<div>
<button type="submit" disabled="@formInvalid">Submit</button>
</div>
</EditForm>
@code {
private bool formInvalid = false;
private EditContext? editContext;
[SupplyParameterFromForm]
private Starship? Model { get; set; }
protected override void OnInitialized()
{
Model ??=
new()
{
Id = "NCC-1701",
Classification = "Exploration",
MaximumAccommodation = 150,
IsValidatedDesign = true,
ProductionDate = new DateTime(2245, 4, 11)
};
editContext = new(Model);
editContext.OnFieldChanged += HandleFieldChanged;
}
private void HandleFieldChanged(object? sender, FieldChangedEventArgs e)
{
if (editContext is not null)
{
formInvalid = !editContext.Validate();
StateHasChanged();
}
}
private void Submit() => Logger.LogInformation("Submit: Processing form");
public void Dispose()
{
if (editContext is not null)
{
editContext.OnFieldChanged -= HandleFieldChanged;
}
}
}
@page "/starship-14"
@implements IDisposable
@inject ILogger<Starship14> Logger
<EditForm EditContext="editContext" OnValidSubmit="Submit" FormName="Starship14">
<DataAnnotationsValidator />
<ValidationSummary />
<div>
<label>
Identifier:
<InputText @bind-Value="Model!.Id" />
</label>
</div>
<div>
<button type="submit" disabled="@formInvalid">Submit</button>
</div>
</EditForm>
@code {
private bool formInvalid = false;
private EditContext? editContext;
[SupplyParameterFromForm]
private Starship? Model { get; set; }
protected override void OnInitialized()
{
Model ??=
new()
{
Id = "NCC-1701",
Classification = "Exploration",
MaximumAccommodation = 150,
IsValidatedDesign = true,
ProductionDate = new DateTime(2245, 4, 11)
};
editContext = new(Model);
editContext.OnFieldChanged += HandleFieldChanged;
}
private void HandleFieldChanged(object? sender, FieldChangedEventArgs e)
{
if (editContext is not null)
{
formInvalid = !editContext.Validate();
StateHasChanged();
}
}
private void Submit() => Logger.LogInformation("Submit: Processing form");
public void Dispose()
{
if (editContext is not null)
{
editContext.OnFieldChanged -= HandleFieldChanged;
}
}
}
@page "/starship-14"
@implements IDisposable
@inject ILogger<Starship14> Logger
<EditForm EditContext="editContext" OnValidSubmit="Submit">
<DataAnnotationsValidator />
<ValidationSummary />
<div>
<label>
Identifier:
<InputText @bind-Value="Model!.Id" />
</label>
</div>
<div>
<button type="submit" disabled="@formInvalid">Submit</button>
</div>
</EditForm>
@code {
private bool formInvalid = false;
private EditContext? editContext;
private Starship? Model { get; set; }
protected override void OnInitialized()
{
Model ??=
new()
{
Id = "NCC-1701",
Classification = "Exploration",
MaximumAccommodation = 150,
IsValidatedDesign = true,
ProductionDate = new DateTime(2245, 4, 11)
};
editContext = new(Model);
editContext.OnFieldChanged += HandleFieldChanged;
}
private void HandleFieldChanged(object? sender, FieldChangedEventArgs e)
{
if (editContext is not null)
{
formInvalid = !editContext.Validate();
StateHasChanged();
}
}
private void Submit()
{
Logger.LogInformation("Submit called: Processing the form");
}
public void Dispose()
{
if (editContext is not null)
{
editContext.OnFieldChanged -= HandleFieldChanged;
}
}
}
フォームに有効な値が事前に読み込まれておらず、フォームの読み込みで Submit ボタンを無効にする場合は、formInvalid を true に設定します。
上記の方法の副作用として、ユーザーがいずれかのフィールドを操作した後、検証の概要 (ValidationSummary コンポーネント) に無効なフィールドが設定されます。 このシナリオには、次のいずれかの方法で対処します。
- フォームでは ValidationSummary コンポーネントを使用しないでください。
- 送信ボタンが選択された (たとえば、ValidationSummary メソッドで) ときに
Submitコンポーネントを表示します。
<EditForm ... EditContext="editContext" OnValidSubmit="Submit" ...>
<DataAnnotationsValidator />
<ValidationSummary style="@displaySummary" />
...
<button type="submit" disabled="@formInvalid">Submit</button>
</EditForm>
@code {
private string displaySummary = "display:none";
...
private void Submit()
{
displaySummary = "display:block";
}
}
DataAnnotationsValidator 検証の動作
DataAnnotationsValidator コンポーネントの検証順序と短絡動作は、System.ComponentModel.DataAnnotations.Validatorと同じです。
T型のインスタンスを検証するときに、次の規則が適用されます。
- 入れ子になったオブジェクトを再帰的に検証するなど、
Tのメンバー プロパティが検証されます。 -
Tの型レベルの属性が検証されます。 -
IValidatableObject.Validateメソッドは、実装
T場合に実行されます。
上記のいずれかの手順で検証エラーが発生した場合、残りの手順はスキップされます。
ASP.NET Core