Note
これは、この記事の最新バージョンではありません。 現在のリリースについては、 この記事の .NET 10 バージョンを参照してください。
Warning
このバージョンの ASP.NET Core はサポート対象から除外されました。 詳細については、 .NET および .NET Core サポート ポリシーを参照してください。 現在のリリースについては、 この記事の .NET 10 バージョンを参照してください。
この記事では、再利用可能な検証コンポーネントとリモート検証について説明します。 データ注釈、直接EditContext検証、メッセージ表示、スタイル設定、状態、送信動作など、一般的なフォーム検証 API については、フォームの検証 ASP.NET Core Blazor参照してください。
Blazor API と最小 API によって共有されるモデルベースの検証規則については、ASP.NET Coreの検証を参照してください。
静的サーバー側レンダリング (静的 SSR) でのブラウザー検証については、静的 SSR のクライアント側フォーム検証 ASP.NET Core Blazor参照してください。
バリデーター コンポーネントをビルドする
検証コンポーネントは、フォームの EditContext と ValidationMessageStoreを使用する検証をカプセル化します。 これは、同じ検証動作が複数のフォームで使用される場合や、モデルの検証属性ではなくサービスからエラーが発生した場合に便利です。
コンポーネント:
- フォームの
EditContextをカスケード パラメーターとして受け取ります。 - エラー用のメッセージ ストアを作成します。
- 検証が要求されたときに古いフォーム エラーをクリアします。
- フィールドが変更されたときに、フィールドの古いエラーをクリアします。
- エラーを表示およびクリアするためのメソッドを公開します。
- 破棄されると、イベント ハンドラーの登録を解除します。
CustomValidation.razor:
@implements IDisposable
@code {
[CascadingParameter]
private EditContext? CurrentEditContext { get; set; }
private ValidationMessageStore messages = default!;
protected override void OnInitialized()
{
if (CurrentEditContext is null)
{
throw new InvalidOperationException(
"CustomValidation requires a cascading EditContext.");
}
messages = new ValidationMessageStore(CurrentEditContext);
CurrentEditContext.OnValidationRequested +=
HandleValidationRequested;
CurrentEditContext.OnFieldChanged += HandleFieldChanged;
}
public void DisplayErrors(IDictionary<string, string[]> errors)
{
foreach (var error in errors)
{
messages.Add(
CurrentEditContext!.Field(error.Key),
error.Value);
}
CurrentEditContext!.NotifyValidationStateChanged();
}
public void ClearErrors()
{
messages.Clear();
CurrentEditContext!.NotifyValidationStateChanged();
}
private void HandleValidationRequested(
object? sender, ValidationRequestedEventArgs e) =>
ClearErrors();
private void HandleFieldChanged(
object? sender, FieldChangedEventArgs e)
{
messages.Clear(e.FieldIdentifier);
CurrentEditContext!.NotifyValidationStateChanged();
}
public void Dispose()
{
if (CurrentEditContext is not null)
{
CurrentEditContext.OnValidationRequested -=
HandleValidationRequested;
CurrentEditContext.OnFieldChanged -= HandleFieldChanged;
}
}
}
フォームまたはサービスにエラーが表示される場合は、コンポーネントを EditForm 内に配置し、コンポーネント参照をキャプチャします。
<EditForm Model="Model" OnValidSubmit="Submit">
<DataAnnotationsValidator />
<CustomValidation @ref="customValidation" />
<ValidationSummary />
...
</EditForm>
@code {
private CustomValidation? customValidation;
}
コンポーネントは、 DataAnnotationsValidatorと共に使用できます。 各検証コントロールには、同じ EditContextに関連付けられた独自のメッセージ ストアがあり、両方の検証コントロールからのメッセージを ValidationMessage および ValidationSummary 表示します。
外部エラーを受け入れる代わりに検証コントロール コンポーネント内にビジネス ルールを実装するには、 HandleValidationRequested または HandleFieldChanged からルールを実行し、そのメッセージを messagesに追加します。 フォーム コンポーネントでこれを直接実行する小さな例については、フォームの検証 ASP.NET Core Blazor参照してください。
非同期検証を追加する
同じコンポーネント パターンで非同期処理がサポートされます。
-
OnValidationRequestedハンドラーで、e.AddAsyncValidatorを呼び出してフォーム レベルの作業を登録します。EditFormは、OnValidSubmitまたはOnInvalidSubmitを呼び出す前に待機します。 -
OnFieldChangedハンドラーで、EditContext.RegisterAsyncFieldValidatorを呼び出して、そのフィールドの検証を開始します。 同じフィールドに対して別の検証を開始すると、前の操作が優先され、取り消されます。
フォーム レベルの非同期検証の場合:
private void HandleValidationRequested(
object? sender, ValidationRequestedEventArgs e) =>
e.AddAsyncValidator(ValidateAsync);
private async Task ValidateAsync(CancellationToken cancellationToken)
{
var field = CurrentEditContext!.Field(nameof(Model.Username));
messages.Clear(field);
var available = await Http.GetFromJsonAsync<bool>(
$"api/usernames/available?value={Uri.EscapeDataString(Model.Username)}",
cancellationToken);
if (!available)
{
messages.Add(field, "The username is already taken.");
}
CurrentEditContext.NotifyValidationStateChanged();
}
フィールド レベルの非同期検証の場合:
private void HandleFieldChanged(
object? sender, FieldChangedEventArgs e)
{
CurrentEditContext!.RegisterAsyncFieldValidator(
e.FieldIdentifier,
token => ValidateFieldAsync(e.FieldIdentifier, token));
}
指定されたキャンセル トークンを I/O に渡します。 操作を開始する前に前のメッセージをクリアし、例外の後に部分的な結果を発行しないようにし、メッセージの更新後に NotifyValidationStateChanged を呼び出します。
操作が置き換えられたか、検証パスが取り消されたために取り消された操作は破棄されます。 その他の例外により、フィールドまたはフォームがエラー状態になります。 保留中の状態とエラー状態を表示する方法については、フォームの検証 ASP.NET Core Blazor参照してください。
フォーム レベルとフィールドごとの非同期検証を組み合わせた完全なコンポーネントについては、次のサンプルを参照してください。
@implements IDisposable
@inject HttpClient Http
@* A validator component that runs asynchronous validation, both when the whole form is
validated on submit and per field as the user edits the Username field. *@
@code {
[CascadingParameter]
private EditContext? CurrentEditContext { get; set; }
[Parameter, EditorRequired]
public RegistrationModel Model { get; set; } = default!;
private ValidationMessageStore? messages;
protected override void OnInitialized()
{
ArgumentNullException.ThrowIfNull(CurrentEditContext);
messages = new ValidationMessageStore(CurrentEditContext);
CurrentEditContext.OnValidationRequested += OnValidationRequested;
CurrentEditContext.OnFieldChanged += OnFieldChanged;
}
// Registers asynchronous work for the whole form. EditForm awaits it before invoking
// OnValidSubmit.
private void OnValidationRequested(
object? sender, ValidationRequestedEventArgs e) =>
e.AddAsyncValidator(ValidateUsernameAsync);
// Registers asynchronous work for a single field. A new registration supersedes and
// cancels any validation already in flight for the field.
private void OnFieldChanged(object? sender, FieldChangedEventArgs e)
{
if (e.FieldIdentifier.FieldName != nameof(RegistrationModel.Username))
{
return;
}
CurrentEditContext!.RegisterAsyncFieldValidator(
e.FieldIdentifier,
token => CheckAsync(e.FieldIdentifier, token));
}
private Task ValidateUsernameAsync(CancellationToken token) =>
CheckAsync(CurrentEditContext!.Field(nameof(Model.Username)), token);
private async Task CheckAsync(FieldIdentifier field, CancellationToken token)
{
messages!.Clear(field);
var available = await Http.GetFromJsonAsync<bool>(
$"api/usernames/available?value={Uri.EscapeDataString(Model.Username)}",
token);
if (!available)
{
messages.Add(field, "The username is already taken.");
}
CurrentEditContext!.NotifyValidationStateChanged();
}
public void Dispose()
{
if (CurrentEditContext is not null)
{
CurrentEditContext.OnValidationRequested -= OnValidationRequested;
CurrentEditContext.OnFieldChanged -= OnFieldChanged;
}
}
}
モデルの非同期検証属性については、「ASP.NET Coreでの検証」を参照してください。
検証コントロール コンポーネント コードは、コンポーネントが実行される場所で実行されます。 Interactive WebAssembly ではブラウザーで実行され、対話型サーバーの回線を介してサーバー上で実行されます。
静的 SSR では、検証コントロール コンポーネント コードはフォームポスト中にサーバー上で実行され、要求間のライブ.NETフィールド検証は提供されません。
対話型 WebAssembly からのリモート検証
リモート検証では、対話型 WebAssembly コンポーネントからサーバー エンドポイントにフォーム データが送信され、返されたフィールド エラーがフォームの EditContextに追加されます。 これは、ルールでプライベート サーバー データ、外部サービス、またはブラウザーで実行すべきではないその他のロジックが必要な場合に便利です。
フォームは次のとおりです。
- データ注釈の検証をローカルで実行します。
-
OnValidSubmitからエンドポイントにローカルで有効な入力を送信します。 - フィールドキー検証エラーをサーバーから受信します。
- 検証コンポーネントを使用して、フォームにリモート エラーを追加します。
OnValidSubmit は、ローカル検証が成功したことを意味します。 リモート検証も成功した後にのみ、モデルを処理または保存します。
Important
プライベート検証データやビジネス ルールをブラウザーに送信しないでください。 クライアント側の検証はバイパスできるため、サーバーはすべての要求を個別に検証する必要があります。
次の使用例は、フォームが送信されたときにリモートで検証します。 フィールドごとのライブ リモート チェックの場合は、[非同期検証の 追加] の非同期フィールド検証パターンを使用します。
WebAssembly フォームがプリレンダリングされている場合は、そのクライアント側サービスもプリレンダリング中に使用できる必要があります。 使用可能な方法については、「Prerender ASP.NET Core Razor コンポーネント」を参照してください。
最小限の API を使用して検証する
ハンドラーを実行する前に、サーバー プロジェクトの AddValidation を呼び出して、サポートされているエンドポイント パラメーターを検証します。
生成された検証メタデータに使用されるMicrosoft.Extensions.Validation API は、.NET 10 では試験段階です。 詳細については、「ASP.NET Coreでの検証」を参照してください。
モデルが.Client プロジェクトで宣言されている場合は、「ASP.NET Coreの検証」の説明に従って、生成された検証メタデータを両方のプロジェクトに登録します。
エンドポイントはプライベート ビジネス ルールを追加し、モデル メンバー名でキー指定されたエラーを返します。
app.MapPost("/api/starships/validate", (StarshipModel model) =>
{
Dictionary<string, string[]> errors = [];
if (model.Classification == "Defense" &&
string.IsNullOrWhiteSpace(model.Description))
{
errors[nameof(model.Description)] =
["A defense ship requires a description."];
}
if (errors.Count > 0)
{
return Results.ValidationProblem(errors);
}
return Results.NoContent();
});
app.MapPost("/api/starships/validate", (StarshipModel model) =>
{
Dictionary<string, string[]> errors = [];
if (model.Classification == "Defense" &&
string.IsNullOrWhiteSpace(model.Description))
{
errors[nameof(model.Description)] =
["A defense ship requires a description."];
}
if (errors.Count > 0)
{
return Results.ValidationProblem(errors);
}
return Results.NoContent();
});
自動検証では、ハンドラーを実行する前に無効なデータ注釈が拒否されます。
ValidationProblemは、フィールドキー付きメッセージを含むerrorsプロパティを持つ400 Bad Requestを返します。 検証が成功すると、 204 No Contentが返されます。
API コントローラーを使用して検証する
ホスト Blazor WebAssembly ソリューションで、共有モデルを Shared プロジェクトに配置し、 Server プロジェクトの API コントローラーを使用して検証します。
[ApiController]属性は、アクションを実行する前に無効なデータ注釈を自動的に拒否します。
[ApiController]
[Route("api/starships/validate")]
public class StarshipValidationController : ControllerBase
{
[HttpPost]
public IActionResult Validate(StarshipModel model)
{
if (model.Classification == "Defense" &&
string.IsNullOrWhiteSpace(model.Description))
{
ModelState.AddModelError(
nameof(model.Description),
"A defense ship requires a description.");
}
if (!ModelState.IsValid)
{
return ValidationProblem(ModelState);
}
return NoContent();
}
}
サーバー プロジェクトにコントローラーを登録してマップします。 コントローラーは、検証が失敗したときにValidationProblemDetails応答と共に400 Bad Requestを返し、成功すると204 No Contentします。
エンドポイントを呼び出してエラーを表示する
WebAssembly プロジェクトの HttpClient をアプリのベース アドレスに登録します。
builder.Services.AddScoped(sp =>
new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddScoped(sp =>
new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
[検証コンポーネントのビルド] のCustomValidation コンポーネントを次の形式で配置します。
@page "/"
@using System.Net
@using System.Net.Http.Json
@using BlazorWebAppRemoteValidation.Client.Models
@inject HttpClient Http
<PageTitle>Remote validation</PageTitle>
<h1>Remote validation</h1>
<p>Local validation runs before the form calls the remote endpoint. The endpoint validates the model again and applies a private business rule that requires a description for defense ships.</p>
<EditForm Model="Model" OnValidSubmit="Submit">
<DataAnnotationsValidator />
<CustomValidation @ref="remoteErrors" />
<ValidationSummary />
<p>
<label>
Identifier:
<InputText id="identifier" @bind-Value="Model.Identifier" />
</label>
<ValidationMessage For="() => Model.Identifier" />
</p>
<p>
<label>
Classification:
<InputSelect id="classification" @bind-Value="Model.Classification">
<option value="">Select...</option>
<option value="Exploration">Exploration</option>
<option value="Defense">Defense</option>
</InputSelect>
</label>
<ValidationMessage For="() => Model.Classification" />
</p>
<p>
<label>
Description:
<InputText id="description" @bind-Value="Model.Description" />
</label>
<ValidationMessage For="() => Model.Description" />
</p>
<button id="submit" type="submit">Submit</button>
</EditForm>
@if (accepted)
{
<p id="accepted" role="status">The server accepted the form.</p>
}
@code {
private StarshipModel Model { get; } = new();
private CustomValidation? remoteErrors;
private bool accepted;
private async Task Submit()
{
accepted = false;
using var response = await Http.PostAsJsonAsync(
"api/starships/validate", Model);
if (response.IsSuccessStatusCode)
{
accepted = true;
return;
}
if (response.StatusCode == HttpStatusCode.BadRequest)
{
var problem = await response.Content
.ReadFromJsonAsync<ValidationProblemResponse>()
?? throw new InvalidOperationException(
"The validation response didn't contain a response body.");
remoteErrors!.DisplayErrors(problem.Errors);
return;
}
response.EnsureSuccessStatusCode();
}
private sealed record ValidationProblemResponse(
Dictionary<string, string[]> Errors);
}
@using System.Net
@using System.Net.Http.Json
@inject HttpClient Http
<EditForm Model="Model" OnValidSubmit="Submit">
<DataAnnotationsValidator />
<CustomValidation @ref="remoteErrors" />
<ValidationSummary />
...
</EditForm>
@code {
private StarshipModel Model { get; } = new StarshipModel();
private CustomValidation? remoteErrors;
private async Task Submit()
{
using var response = await Http.PostAsJsonAsync(
"api/starships/validate", Model);
if (response.IsSuccessStatusCode)
{
// Process or save the model.
return;
}
if (response.StatusCode == HttpStatusCode.BadRequest)
{
var problem = await response.Content
.ReadFromJsonAsync<ValidationProblemResponse>();
if (problem is not null)
{
remoteErrors!.DisplayErrors(problem.Errors);
}
return;
}
response.EnsureSuccessStatusCode();
}
private sealed class ValidationProblemResponse
{
public Dictionary<string, string[]> Errors { get; set; } =
new Dictionary<string, string[]>();
}
}
検証コントロール コンポーネントは、そのフィールドが変更されたときにリモート フィールド エラーをクリアするため、ユーザーは値を修正して再度送信できます。 アプリケーションのセキュリティ要件に従ってエンドポイントを保護する。認証と承認は、この検証例の範囲外です。
完全なリモート検証サンプルには、ホスト エンドポイント、共有モデル、アセンブリ間検証登録、検証コンポーネント、対話型 WebAssembly フォームが含まれます。
.NET 10 のリモート検証サンプルでは、認証とサーバー側プロキシを使用する Interactive Auto アプリの同じ検証フローを示します。
関連するコンテンツ
ASP.NET Core