ASP.NET Core Blazor 静的 SSR におけるクライアント側フォーム検証

この記事では、Blazorが静的サーバー側レンダリング (静的 SSR) を使用するフォームにライブ クライアント側の検証を追加する方法について説明します。 ユーザーがフィールドを編集し、フォーム全体を検証してから送信すると、ブラウザーによって個々のフィールドが検証されます。 クライアント側のチェックに合格すると、フォームが送信され、サーバー上で再び検証されます。

対話型レンダリング モードを使用するフォームでは、この記事で説明する静的 SSR クライアント側検証機能は使用されません。 フィールドごとの検証とフォーム全体の送信検証の両方が、フォームのEditContextを介して.NETで実行されます。

クライアント側の検証のしくみ

静的 SSR フォームに DataAnnotationsValidator コンポーネントが含まれている場合、 Blazor フォームの検証規則がページにレンダリングされ、フォームが送信される前に JavaScript を使用して適用されます。 ユーザーは、サーバーへのラウンド トリップなしで検証エラーを確認します。

次の条件が満たされると、クライアント側の検証が自動的にアクティブになります。

  • フォームのホスティング コンポーネントは、静的 SSR を使用します (コンポーネントに @rendermode ディレクティブは適用されません)。
  • フォームには、 DataAnnotationsValidator コンポーネントが含まれています。
  • フォームのモデルでは、 System.ComponentModel.DataAnnotations 検証属性が使用されます。

組み込みの一連の検証属性の場合、JavaScript の構成、追加のパッケージ、またはサービスの登録は必要ありません。 「 カスタム クライアント側の検証規則 」セクションでは、カスタム検証属性のサポートを追加する方法について説明します。

Important

クライアント側の検証は、権限のある検証パスではなく、ユーザー エクスペリエンスの向上です。 これは、ブラウザーの JavaScript の実行を無効または変更することでバイパスできます。 サーバー側の検証は、フォームがポストされた後も実行され、権限が保持されます。 データの整合性を保護するためにクライアント側の検証に依存しないでください。

サポートされている検証属性

次の System.ComponentModel.DataAnnotations 属性はクライアント側で適用され、サーバー側のデータ注釈の動作と一致します。

カスタム ValidationAttribute派生属性など、この一覧に表示されない検証属性は、既定ではクライアント側では適用されません。 フォームが送信された後も、サーバー側の実行が続行されます。 カスタム属性のクライアント側ルールを指定するには、「カスタム クライアント側の検証規則 」セクションを参照してください。

検証のタイミング

フィールドの値がコミットされると、フィールドが検証されます。 テキスト入力 (<input> 要素) の場合、これはフィールドがフォーカスを失ったときに発生します。 チェック ボックスとドロップダウン リストは、選択直後に検証されます。

フィールドに検証エラーが表示された後、またはフォームが少なくとも 1 回送信された後は、すべてのキーストロークでフィールドが再検証され、修正がすぐに反映されます。

フォームを送信すると、すべての追跡フィールドが検証されます。 フィールドが無効な場合、送信はブロックされ、フォーカスは最初の無効なフィールドに移動します。

検証メッセージ、ローカライズ、およびアクセシビリティ

クライアント側の検証では、 ValidationMessage<TValue> を使用して個々のフィールドのメッセージを表示し、 ValidationSummary を使用してフォーム全体のメッセージを表示します。これは、対話型検証でユーザーにメッセージを報告するのと同じ方法です。

検証のローカライズが構成されている場合、エラー メッセージはページのレンダリング時にサーバーにローカライズされるため、クライアント側の検証では、サーバー側のエクスペリエンスと同じローカライズされた文字列が表示されます。 ローカライズには Microsoft.Extensions.Validationが必要です。 詳細については、「ASP.NET Coreでの検証」を参照してください。

入力要素と検証メッセージ コンテナーの ARIA 属性は Blazor によって自動的に管理されるため、支援技術は追加の構成なしで検証エラーをアナウンスします。

検証状態の CSS クラス

クライアント側検証エンジンは、次の表に示すように、 Blazorの対話型検証と同じ CSS クラスを適用します。

要素 クラス
入力 validまたはinvalid、ユーザーがフィールドを編集した後にmodified
検証メッセージ validation-message
検証の概要 validation-summary-errors または validation-summary-valid

クライアント側の検証ではブラウザーの Constraint Validation API も呼び出すため、標準の CSS 擬似クラス :valid と :invalid は、各入力欄の現在の検証状態を反映します。

クライアント側の検証をオプトアウトする

この機能は、複数のレベルで無効にすることができます。 サーバー側の検証は、このセクションのオプションの影響を受けません。

1 つのフォームをオプトアウトする

DataAnnotationsValidator コンポーネントの DisableClientValidation パラメーターを true に設定します。

<DataAnnotationsValidator DisableClientValidation="true" />

アプリ全体をオプトアウトする

Program ファイルにRazor コンポーネント サービスが登録されている場合に、RazorComponentsServiceOptionsにDisableClientValidationを設定します。

builder.Services.AddRazorComponents(options =>
{
    options.DisableClientValidation = true;
});

グローバル オプションが優先されます。 trueに設定されている場合、フォームはクライアント側の検証規則を出力しません。

1 つの送信ボタンをオプトアウトする

ボタンで標準の HTML formnovalidate 属性 を使用します。 フォームはクライアント側のチェックなしで投稿され、サーバー側の検証は投稿後も実行されます。

<button type="submit" formnovalidate>Save draft</button>

これは、送信を成功させるために完全に有効なフォームを必要としない "下書きの保存" または "戻る" ボタンを実装するために使用できます。

カスタム クライアント側の検証規則

カスタム検証属性は引き続きサーバー上で実行されますが、既定ではクライアント側の実装はありません。 ブラウザーでカスタム ルールを適用するには、.NET属性からルールを出力し、一致する JavaScript バリデーターを登録する 2 つの手順が必要です。 サーバー側の実装は引き続き権限を持ちます。

.NETからルールを出力する

検証属性に IClientValidationRuleProvider を実装し、1 つ以上の ClientValidationRule インスタンスを返します。 ルールの Name はクライアント側の検証コントロールを識別し、 Parameters は検証コントロールに必要な値を提供します。

次のStartsWithAttributeは、IsValidでサーバー側を検証し、prefix パラメーターを使用してstartswithクライアント側の規則を提供します。

using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Components.Forms;

namespace BlazorSample.Validation;

// A custom validation attribute that also contributes a client-side rule for static SSR
// forms. The rule name ('startswith') must match the validator registered on the client
// with Blazor.formValidation.addValidator.
public sealed class StartsWithAttribute : ValidationAttribute, IClientValidationRuleProvider
{
    private readonly string prefix;

    public StartsWithAttribute(string prefix)
    {
        this.prefix = prefix;
        ErrorMessage = $"The value must start with '{prefix}'.";
    }

    protected override ValidationResult? IsValid(object? value,
        ValidationContext validationContext)
    {
        // Leave empty values to RequiredAttribute, matching the client-side validator.
        if (value is not string text || text.Length == 0)
        {
            return ValidationResult.Success;
        }

        if (!text.StartsWith(prefix, StringComparison.Ordinal))
        {
            return new ValidationResult(ErrorMessage, [validationContext.MemberName!]);
        }

        return ValidationResult.Success;
    }

    public IEnumerable<ClientValidationRule> GetClientValidationRules()
    {
        yield return new ClientValidationRule(
            "startswith",
            new Dictionary<string, string> { ["prefix"] = prefix });
    }
}

通常の方法でモデルに属性を適用します。

using System.ComponentModel.DataAnnotations;

namespace BlazorSample.Validation;

// Model for the static SSR client-side validation sample. The StartsWith attribute
// contributes a client-side rule enforced in the browser before the form is submitted.
public class ShipModel
{
    [Required]
    [StartsWith("NCC-")]
    public string? Registry { get; set; }
}

JavaScript バリデーターを登録する

JavaScript で Blazor.formValidation.addValidator(name, validator) を呼び出して、ルール名を検証機能に関連付けます。 nameは、大文字と小文字を含め、ClientValidationRule.Nameと完全に一致する必要があります。 登録はアプリ全体で行われ、同じ名前をもう一度登録すると、前の検証コントロールが置き換えられます。

Warning

出力されたルール名に対して JavaScript 検証コントロールが登録されていない場合、ルールはブラウザーでスキップされます。 フォームが投稿されると、サーバー側の検証は引き続き実行されます。

アプリのスタートアップ コードからカスタム 検証コントロールを 1 回登録します。 次の表に示すように、 Blazor の開始方法に基づいて登録場所を選択します。

Blazor スタートアップ。 登録場所
自動起動 (既定) blazor.web.js の直後のスクリプト
手動 Blazor.start() Blazor.start() によって返される継続処理
いずれかのスタートアップ スタイル JavaScript 初期化子の afterWebStarted コールバック

JavaScript 初期化子は、どちらのスタートアップ スタイルでも動作します。 アプリの wwwroot フォルダー内の {ASSEMBLY NAME}.lib.module.js という名前のファイル内:

// A JavaScript initializer works with automatic or manual Blazor startup. The validation service
// is available when afterWebStarted runs, even if the current page contains no validated form.
export function afterWebStarted(blazor) {
  blazor.formValidation.addValidator('startswith', (context) => {
    const value = context.value;

    // An empty value is valid. Use [Required] to require a value.
    if (!value) {
      return { success: true };
    }

    return { success: value.startsWith(context.params.prefix) };
  });
}

自動起動では、アプリ固有の検証スクリプトを blazor.web.jsした直後に読み込むことができます。

<script src="@Assets["_framework/blazor.web.js"]"></script>
<script src="@Assets["js/custom-validation.js"]"></script>

2 番目のスクリプトは、 Blazor.formValidation.addValidator を直接呼び出すことができます。

手動スタートアップを使用して、アプリ スクリプトで登録を定義します。

function registerCustomValidators(blazor) {
  blazor.formValidation.addValidator('startswith', (context) => {
    const value = context.value;

    if (!value) {
      return { success: true };
    }

    return { success: value.startsWith(context.params.prefix) };
  });
}

// Automatic startup initializes formValidation before this script runs.
// Manual startup calls registerCustomValidators after Blazor.start completes.
if (Blazor.formValidation) {
  registerCustomValidators(Blazor);
}

自動スタートアップが無効になっているスクリプトを読み込み、 Blazor.start() が完了した後に検証コントロールを登録します。

<script src="@Assets["_framework/blazor.web.js"]" autostart="false"></script>
<script src="@Assets["js/custom-validation.js"]"></script>
<script>
    Blazor.start().then(() => {
        registerCustomValidators(Blazor);
    });
</script>

Important

ページまたはフォーム コンポーネントからではなく、アプリのスタートアップ コードから検証コントロールを登録します。 コンポーネント スクリプトは Blazor 開始前に実行でき、拡張ナビゲーションによって追加されたスクリプトは実行されません。 静的 SSR コンポーネントには対話型の.NET ランタイムがないため、IJSRuntimeを使用することもできません。

JavaScript バリデーター関数を記述する

検証コントロールは、次の表に示すように、次のメンバーを持つコンテキスト オブジェクトを受け取ります。

メンバー Description
value フィールドの現在の値を文字列として使用するか、値がない場合に null/undefined します。
element 検証された input、 select、または textarea 要素。
params ルールの Parameters は文字列ディクショナリとして使用されます。

検証コントロールは、値が有効な場合に { success: true } を返す必要があります。 ルールのサーバー指定メッセージを使用する { success: false } を返すか、 { success: false, message: '...' } を返して、その呼び出しのメッセージをオーバーライドできます。

通常、空の値は、 required以外の規則で有効として扱う必要があります。省略可能なフィールドは空のままで、指定された値を検証します。

大文字と小文字の区別、正規化、空の値の処理など、.NETと JavaScript に同じルール セマンティクスを実装します。 実装が異なる場合、ブラウザーと権限のあるサーバー側の検証によって異なる結果が生成される可能性があります。

フォームをオンデマンドで検証する

Blazor.formValidation API では、次の表に示すように、オンデマンドで検証するための JavaScript メソッドも公開されています。

Method Description
validateField(element) 1 つのフィールド要素を検証し、エラー表示を更新します。 有効な場合は true を返します。
validateForm(form) フォーム内のすべての追跡フィールドを検証します。 すべてのフィールドが有効な場合に true を返します。

制限事項

  • クライアント側の規則は、サーバー側の検証に含まれるフィールドに対してのみ出力されます。 Microsoft.Extensions.Validationを使用しない場合、最上位レベルのモデル プロパティのみが検証されます。 入れ子になったオブジェクトとコレクションを検証するには、アプリが AddValidation を呼び出し、モデルを検出する必要があります。 詳細については、「ASP.NET Coreでの検証」を参照してください。 この制限は、構成ミスによって権限のあるサーバーの検証が欠落するバグを防ぐために役立つ意図的な機能であることに注意してください。
  • 後で ストリーミング レンダリング 更新によって既存のフォームに追加された入力は、クライアント側の検証を受け取りません。 1つのストリーミング バッチで配信されるフォームは、通常どおり処理されます。
  • サポートされている検証属性に記載されている属性にのみ、クライアント側の実装が組み込まれています。 たとえば、数値以外のオペランド型を持つ RangeAttribute は、サーバーでのみ適用されます。 その他の属性には、 カスタムのクライアント側検証規則が必要です。
  • カスタム JavaScript バリデーターは同期的です。 ネットワーク呼び出しまたはその他の非同期作業を必要とするルールは、サーバー上で実行するか、対話型レンダリング モードで非同期検証を使用する必要があります。 詳細については、「高度なフォーム検証 ASP.NET Core Blazor参照してください。