MVC とページ ASP.NET Core でのモデルの検証 Razor

作成者: Kirk Larkin

この記事では、ASP.NET Core MVC またはページアプリでユーザー入力を検証する方法について説明し Razor ます。

サンプル コードを表示またはダウンロードします (ダウンロード方法)。

モデルの状態

モデルの状態では、モデル バインドとモデル検証の 2 つのサブシステムで発生したエラーが表されます。 モデルバインドから発生するエラーは、通常、データ変換エラーです。 たとえば、"x" は整数フィールドに入力されます。 モデルの検証は、モデル バインド後に行われ、データがビジネス ルールに準拠していないエラーを報告します。 たとえば、1 から 5 の評価を想定したフィールドに 0 が入力されたとします。

モデルバインドとモデル検証は、どちらもコントローラーアクションまたはページハンドラーメソッドの実行前に行わ Razor れます。 Web アプリでは、ModelState.IsValid を調べて適切に対処するのはアプリの責任です。 通常、Web アプリではエラー メッセージを含むページを再表示します。

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    _context.Movies.Add(Movie);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

Web API コントローラーでは、[ApiController] 属性が設定されている場合は、ModelState.IsValid を確認する必要はありません。 その場合、モデルが無効な状態のときは、エラーの詳細を含む HTTP 400 応答が自動的に返されます。 詳細については、「自動的な HTTP 400 応答」を参照してください。

検証を再実行する

検証は自動的に行われますが、手動で繰り返したい場合があります。 たとえば、プロパティの値を計算し、プロパティを計算値に設定した後で検証を再実行するような場合です。 検証を再実行するには、次に示すように TryValidateModel メソッドを呼び出します。

Movie.ReleaseDate = modifiedReleaseDate;

if (!TryValidateModel(Movie, nameof(Movie)))
{
    return Page();
}

_context.Movies.Add(Movie);
await _context.SaveChangesAsync();

return RedirectToPage("./Index");

検証属性

検証属性を使用して、モデルのプロパティの検証規則を指定できます。 サンプル アプリの次の例では、検証属性で注釈されたモデル クラスが示されています。 [ClassicMovie] 属性はカスタム検証属性であり、その他は組み込み属性です。 [ClassicMovieWithClientValidator] は表示されません。 [ClassicMovieWithClientValidator] は、カスタム属性を実装するもう 1 つの方法を示しています。

public class Movie
{
    public int Id { get; set; }

    [Required]
    [StringLength(100)]
    public string Title { get; set; }

    [ClassicMovie(1960)]
    [DataType(DataType.Date)]
    [Display(Name = "Release Date")]
    public DateTime ReleaseDate { get; set; }

    [Required]
    [StringLength(1000)]
    public string Description { get; set; }

    [Range(0, 999.99)]
    public decimal Price { get; set; }

    public Genre Genre { get; set; }

    public bool Preorder { get; set; }
}

組み込みの属性

組み込み検証属性の一部を次に示します。

  • [ValidateNever]: は、 ValidateNeverAttribute プロパティまたはパラメーターを検証から除外する必要があることを示します。
  • [CreditCard]: プロパティにクレジットカード形式があることを検証します。 JQuery 検証の追加のメソッドが必要です。
  • [Compare]: モデル内の2つのプロパティが一致することを検証します。
  • [EmailAddress]: プロパティが電子メール形式であることを検証します。
  • [Phone]: プロパティに電話番号の書式が設定されていることを検証します。
  • [Range]: プロパティ値が指定した範囲内にあることを検証します。
  • [RegularExpression]: プロパティ値が指定した正規表現に一致することを検証します。
  • [Required]: フィールドが null でないことを検証します。 この属性の動作の詳細については、「 [Required] 属性」を参照してください。
  • [StringLength]: 文字列プロパティの値が、指定された長さの制限を超えていないことを検証します。
  • [Url]: プロパティに URL 形式があることを検証します。
  • [Remote]: サーバーでアクションメソッドを呼び出すことによって、クライアントの入力を検証します。 この属性の動作の詳細については、「 [Remote] 属性」を参照してください。

検証属性の完全な一覧については、System.ComponentModel.DataAnnotations 名前空間で確認できます。

エラー メッセージ

検証属性では、無効な入力に対して表示されるエラー メッセージを指定できます。 次に例を示します。

[StringLength(8, ErrorMessage = "Name length can't be more than 8.")]

内部的には、属性ではフィールド名のプレースホルダーを指定して String.Format が呼び出され、場合によっては追加のプレースホルダーが指定されます。 次に例を示します。

[StringLength(8, ErrorMessage = "{0} length must be between {2} and {1}.", MinimumLength = 6)]

Name プロパティに適用すると、上記のコードによって作成されるエラー メッセージは "Name length must be between 6 and 8" になります。

特定の属性のエラー メッセージに対して String.Format に渡されるパラメーターを確認するには、DataAnnotations のソース コードをご覧ください。

Null 非許容の参照型と [Required] 属性

検証システムは、属性を持っているかのように、null 非許容パラメーターまたはバインドされたプロパティを扱い [Required] ます。 コンテキスト を有効 Nullable にすると、MVCは、null 非許容プロパティまたはパラメーターが 属性で属性付けされている場合と同様に、暗黙的に検証を開始 [Required] します。 次のコードがあるとします。

public class Person
{
    public string Name { get; set; }
}

アプリが を使用してビルドされた場合、JSON またはフォームのポストに の値が見つからないと <Nullable>enable</Nullable> Name 、検証エラーが発生します。 null 許容参照型を使用して、 プロパティに null 値または欠損値を指定 Name できます。

public class Person
{
    public string? Name { get; set; }
}

. Startup.ConfigureServicesSuppressImplicitRequiredAttributeForNonNullableReferenceTypes を次のように構成することで、この動作を無効にできます。

services.AddControllers(options => options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true);

サーバーでの [Required] の検証

サーバーでは、プロパティが null の場合、必須の値が不足しているものと見なされます。 null 非許容型フィールドは常に有効であり、[Required] 属性のエラー メッセージが表示されることはありません。

ただし、null 非許容型プロパティに対するモデル バインドは失敗する場合があり、The value '' is invalid などのエラー メッセージが表示されます。 null 非許容型のサーバー側検証に対してカスタム エラー メッセージを指定するには、次のオプションがあります。

  • フィールドを null 許容型にします (たとえば、decimal の代わりに decimal? を使用)。 Null <T> 許容value 型は、標準の null 許容型と同様に処理されます。

  • 次の例に示すように、モデル バインドで使用される既定のエラー メッセージを指定します。

    services.AddRazorPages()
        .AddMvcOptions(options =>
        {
            options.MaxModelValidationErrors = 50;
            options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
                _ => "The field is required.");
        });
    
    services.AddSingleton<IValidationAttributeAdapterProvider,
        CustomValidationAttributeAdapterProvider>();
    

    既定のメッセージを設定できるモデル バインド エラーに関して詳しくは、DefaultModelBindingMessageProvider をご覧ください。

クライアントでの [Required] の検証

クライアントでの null 非許容型と文字列の処理は、サーバーとは異なります。 クライアント側 :

  • 値は、入力が行われた場合にのみ存在するものと見なされます。 そのため、クライアント側の検証では、null 非許容型は null 許容型と同じように処理されます。
  • 文字列フィールド内の空白文字は、jQuery Validation の required メソッドでは有効な入力と見なされます。 サーバー側の検証では、空白文字のみが入力された場合は、必須文字列フィールドが無効であるものと見なされます。

前述のように、null 非許容型は [Required] 属性を持つものとして処理されます。 つまり、[Required] 属性を適用していない場合でも、クライアント側の検証が行われます。 ただし、属性を使用しない場合は、既定のエラー メッセージが表示されます。 カスタム エラー メッセージを指定するには、属性を使用します。

[Remote] 属性

[Remote] 属性では、フィールド入力が有効かどうかを判断するためにサーバーでメソッドを呼び出す必要があるクライアント側検証が実装されます。 たとえば、アプリでは、ユーザー名が既に使用されているかどうかを確認することが必要な場合があります。

リモート検証を実装するには:

  1. JavaScript で呼び出すアクション メソッドを作成します。 jQuery 検証リモート メソッドでは 、JSON 応答が必要です。

    • true は、入力データが有効であることを意味します。
    • falseundefined、または null は、入力が有効ではないことを意味します。 既定のエラー メッセージを表示します。
    • その他の文字列は、入力が無効であることを意味します。 カスタム エラー メッセージとして文字列を表示します。

    カスタム エラー メッセージを返すアクション メソッドの例を次に示します。

    [AcceptVerbs("GET", "POST")]
    public IActionResult VerifyEmail(string email)
    {
        if (!_userService.VerifyEmail(email))
        {
            return Json($"Email {email} is already in use.");
        }
    
        return Json(true);
    }
    
  2. 次の例に示すように、モデル クラスで、検証アクション メソッドを指し示す [Remote] 属性を使用してプロパティに注釈を付けます。

    [Remote(action: "VerifyEmail", controller: "Users")]
    public string Email { get; set; }
    

    [Remote] 属性は Microsoft.AspNetCore.Mvc 名前空間にあります。

追加フィールド

[Remote] 属性の AdditionalFields プロパティでは、サーバー上のデータに対してフィールドの組み合わせを検証できます。 たとえば、User モデルに FirstName プロパティと LastName プロパティがある場合、その名前のペアを使用する既存ユーザーがいないことを確認したいことがあります。 AdditionalFields を使用する方法を次の例に示します。

[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(LastName))]
[Display(Name = "First Name")]
public string FirstName { get; set; }

[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(FirstName))]
[Display(Name = "Last Name")]
public string LastName { get; set; }

AdditionalFields を文字列 FirstName および LastName に明示的に設定することもできますが、nameof 演算子を使用すると、後のリファクタリングが容易になります。 この検証のアクション メソッドは、firstNamelastName の両方の引数を受け入れる必要があります。

[AcceptVerbs("GET", "POST")]
public IActionResult VerifyName(string firstName, string lastName)
{
    if (!_userService.VerifyName(firstName, lastName))
    {
        return Json($"A user named {firstName} {lastName} already exists.");
    }

    return Json(true);
}

ユーザーが名または姓を入力すると、JavaScript はリモート呼び出しを行って、その名前のペアが取得されているかどうかを確認します。

複数の追加フィールドを検証するには、それらをコンマ区切りのリストとして提供します。 たとえば、MiddleName プロパティをモデルに追加するには、[Remote] 属性を次の例のように設定します。

[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(FirstName) + "," + nameof(LastName))]
public string MiddleName { get; set; }

他の属性引数と同じように、AdditionalFields も定数式である必要があります。 したがって、補間文字列を使用したり、Join を呼び出して AdditionalFields を初期化したりしないでください。

組み込み属性に代わる方法

組み込み属性によって提供されない検証が必要な場合は、次のようにできます。

カスタム属性

組み込みの検証属性で処理されないシナリオの場合は、カスタム検証属性を作成できます。 ValidationAttribute を継承するクラスを作成し、IsValid メソッドをオーバーライドします。

IsValid メソッドは、value という名前のオブジェクトを受け取ります。これは、検証対象の入力です。 オーバーロードは ValidationContext オブジェクトも受け取ります。これは、モデル バインドによって作成されたモデル インスタンスなどの追加情報を提供します。

次の例では、Classic ジャンルの映画の公開日が指定した年より後ではないことを検証します。 [ClassicMovie] 属性:

  • サーバー上でのみ実行されます。
  • クラシック映画の場合は、公開日が検証されます。
public class ClassicMovieAttribute : ValidationAttribute
{
    public ClassicMovieAttribute(int year)
    {
        Year = year;
    }

    public int Year { get; }

    public string GetErrorMessage() =>
        $"Classic movies must have a release year no later than {Year}.";

    protected override ValidationResult IsValid(object value,
        ValidationContext validationContext)
    {
        var movie = (Movie)validationContext.ObjectInstance;
        var releaseYear = ((DateTime)value).Year;

        if (movie.Genre == Genre.Classic && releaseYear > Year)
        {
            return new ValidationResult(GetErrorMessage());
        }

        return ValidationResult.Success;
    }
}

前の例の movie 変数は、フォーム送信からのデータを格納している Movie オブジェクトを表します。 検証に失敗すると、エラー メッセージを含む ValidationResult が返されます。

IValidatableObject

前の例は、Movie 型でのみ動作します。 クラス レベルの検証に対するもう 1 つのオプションは、次の例に示すように、IValidatableObject をモデル クラスに実装することです。

public class ValidatableMovie : IValidatableObject
{
    private const int _classicYear = 1960;

    public int Id { get; set; }

    [Required]
    [StringLength(100)]
    public string Title { get; set; }

    [DataType(DataType.Date)]
    [Display(Name = "Release Date")]
    public DateTime ReleaseDate { get; set; }

    [Required]
    [StringLength(1000)]
    public string Description { get; set; }

    [Range(0, 999.99)]
    public decimal Price { get; set; }

    public Genre Genre { get; set; }

    public bool Preorder { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (Genre == Genre.Classic && ReleaseDate.Year > _classicYear)
        {
            yield return new ValidationResult(
                $"Classic movies must have a release year no later than {_classicYear}.",
                new[] { nameof(ReleaseDate) });
        }
    }
}

最上位ノードの検証

最上位ノードには次が含まれています。

  • アクションのパラメーター
  • コントローラーのプロパティ
  • ページ ハンドラーのパラメーター
  • ページ モデルのプロパティ

モデルのパラメーター検証に加え、モデルが関連付けられた最上位ノードが検証されます。 サンプル アプリからの次の例の VerifyPhone メソッドでは、RegularExpressionAttribute を使用して phone アクションパラメーターが検証されています。

[AcceptVerbs("GET", "POST")]
public IActionResult VerifyPhone(
    [RegularExpression(@"^\d{3}-\d{3}-\d{4}$")] string phone)
{
    if (!ModelState.IsValid)
    {
        return Json($"Phone {phone} has an invalid format. Format: ###-###-####");
    }

    return Json(true);
}

最上位ノードでは、検証属性と共に BindRequiredAttribute を使用できます。 サンプル アプリからの次の例では、CheckAge メソッドによって、フォームの送信時、クエリ文字列から age パラメーターを関連付ける必要があることが指定されます。

[HttpPost]
public IActionResult CheckAge([BindRequired, FromQuery] int age)
{

[年齢確認] ページ (CheckAge.cshtml) には 2 つのフォームがあります。 最初のフォームでは、Age の値 99 がクエリ文字列パラメーター https://localhost:5001/Users/CheckAge?Age=99 として送信されます。

クエリ文字列の正しく書式設定された age パラメーターが送信されると、フォームの有効性が確認されます。

[年齢確認] ページの 2 番目のフォームでは、要求本文で Age 値が送信され、検証は不合格となります。 age パラメーターはクエリ文字列で渡される必要があるため、バインドが失敗します。

最大エラー数

エラーの最大数 (既定では 200) に達すると、検証は停止します。 この数は、Startup.ConfigureServices の次のコードを使用して構成します。

services.AddRazorPages()
    .AddMvcOptions(options =>
    {
        options.MaxModelValidationErrors = 50;
        options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
            _ => "The field is required.");
    });

services.AddSingleton<IValidationAttributeAdapterProvider,
    CustomValidationAttributeAdapterProvider>();

最大再帰

ValidationVisitor では、検証対象のモデルのオブジェクト グラフが走査されます。 深いモデルまたは無限に再帰するモデルでは、検証でスタック オーバーフローが発生する可能性があります。 MvcOptions.MaxValidationDepth では、ビジターの再帰が構成されている深さを超えた場合、早い段階で検証を停止する方法が提供されています。 MvcOptions.MaxValidationDepth の既定値は 32 です。

自動省略

モデル グラフの検証が必要ない場合、検証は自動的に省略 (スキップ) されます。 ランタイムで検証がスキップされるオブジェクトとしては、プリミティブのコレクション (byte[]string[]Dictionary<string, string> など) や、検証コントロールを何も持たない複雑なオブジェクト グラフなどがあります。

検証を無効にする

検証を無効にするには:

  1. どのフィールドも無効としてマークしない IObjectModelValidator の実装を作成します。

    public class NullObjectModelValidator : IObjectModelValidator
    {
        public void Validate(ActionContext actionContext,
            ValidationStateDictionary validationState, string prefix, object model)
        {
    
        }
    }
    
  2. 依存関係挿入コンテナーで、次のコードを Startup.ConfigureServices に追加し、既定の IObjectModelValidator の実装を置き換えます。

    services.AddSingleton<IObjectModelValidator, NullObjectModelValidator>();
    

その場合でも、モデル バインドから発生するモデル状態エラーが表示される可能性があります。

クライアント側の検証

クライアント側検証は、フォームが有効になるまで送信を許可しません。 送信ボタンをクリックすると、フォームの送信またはエラー メッセージの表示を行う JavaScript が実行されます。

クライアント側の検証を使用すると、フォームに入力エラーがある場合に、サーバーへの不要なラウンドトリップを回避できます。 _Layout.cshtml および _ValidationScriptsPartial.cshtml の次のスクリプト参照では、クライアント側の検証がサポートされています。

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.1/jquery.validate.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-unobtrusive/3.2.11/jquery.validate.unobtrusive.min.js"></script>

jQuery Unobtrusive Validationスクリプトは、一般的なjQuery検証プラグインを基に構築されたカスタム Microsoft フロントエンド ライブラリです。 jQuery Unobtrusive Validation を使用しないと、同じ検証ロジックを 2 か所でコーディングする必要があります。1 つはモデル プロパティでのサーバー側検証属性で、もう 1 つはクライアント側スクリプトです。 代わりに、タグ ヘルパーおよび HTML ヘルパーでは、モデル プロパティの検証属性と型メタデータを使用して、検証の必要なフォーム要素に対する HTML 5 の data- 属性がレンダリングされます。 jQuery Unobtrusive Validation は属性を解析し、そのロジックを jQuery 検証に渡し、サーバー側の検証ロジックを実質的にクライアントに "コピー data- " します。 次に示すように、タグ ヘルパーを使用して、クライアントで検証エラーを表示できます。

<div class="form-group">
    <label asp-for="Movie.ReleaseDate" class="control-label"></label>
    <input asp-for="Movie.ReleaseDate" class="form-control" />
    <span asp-validation-for="Movie.ReleaseDate" class="text-danger"></span>
</div>

上記のタグ ヘルパーでは、次の HTML がレンダリングされます。

<div class="form-group">
    <label class="control-label" for="Movie_ReleaseDate">Release Date</label>
    <input class="form-control" type="date" data-val="true"
        data-val-required="The Release Date field is required."
        id="Movie_ReleaseDate" name="Movie.ReleaseDate" value="">
    <span class="text-danger field-validation-valid"
        data-valmsg-for="Movie.ReleaseDate" data-valmsg-replace="true"></span>
</div>

HTML 出力の data- 属性が、Movie.ReleaseDate プロパティの検証属性に対応していることに注意してください。 data-val-required 属性には、ユーザーが公開日フィールドを入力していない場合に表示されるエラー メッセージが含まれています。 jQuery Unobtrusive Validation は、この値を jQuery Validation required() メソッドに渡し、そのメッセージを付随する要素に表示 <span> します。

[DataType] 属性によってオーバーライドされていない限り、データ型の検証はプロパティの .NET 型に基づいて行われます。 ブラウザーには独自の既定のエラー メッセージがありますが、jQuery Validation Unobtrusive Validation パッケージでそれらのメッセージをオーバーライドできます。 [DataType] 属性と [EmailAddress] などのサブクラスを使用して、エラー メッセージを指定できます。

控えめな検証

控えめな検証については、こちらの GitHub のイシューをご覧ください。

動的なフォームに検証を追加する

jQuery Unobtrusive Validation は、ページが最初に読み込まれるときに、検証ロジックとパラメーターを jQuery 検証に渡します。 したがって、動的に生成されるフォームでは、検証は自動的には機能しません。 検証を有効にするには、作成直後に動的フォームを解析するよう、jQuery Unobtrusive Validation に指示します。 たとえば、次のコードでは、AJAX によって追加されるフォームでクライアント側検証が設定されます。

$.get({
    url: "https://url/that/returns/a/form",
    dataType: "html",
    error: function(jqXHR, textStatus, errorThrown) {
        alert(textStatus + ": Couldn't add form. " + errorThrown);
    },
    success: function(newFormHTML) {
        var container = document.getElementById("form-container");
        container.insertAdjacentHTML("beforeend", newFormHTML);
        var forms = container.getElementsByTagName("form");
        var newForm = forms[forms.length - 1];
        $.validator.unobtrusive.parse(newForm);
    }
})

$.validator.unobtrusive.parse() メソッドには、その引数の 1 つで jQuery セレクターを指定します。 このメソッドは、そのセレクター内のフォームの data- 属性を解析するよう jQuery Unobtrusive Validation に指示します。 これらの属性の値は、jQuery 検証プラグインに渡されます。

動的なコントロールに検証を追加する

$.validator.unobtrusive.parse() メソッドは、<input><select/> などの動的に生成される個々のコントロールではなく、フォーム全体に対して動作します。 フォームを再解析するには、次の例に示すように、フォームが前に解析されたときに追加された検証データを削除します。

$.get({
    url: "https://url/that/returns/a/control",
    dataType: "html",
    error: function(jqXHR, textStatus, errorThrown) {
        alert(textStatus + ": Couldn't add control. " + errorThrown);
    },
    success: function(newInputHTML) {
        var form = document.getElementById("my-form");
        form.insertAdjacentHTML("beforeend", newInputHTML);
        $(form).removeData("validator")    // Added by jQuery Validation
               .removeData("unobtrusiveValidation");   // Added by jQuery Unobtrusive Validation
        $.validator.unobtrusive.parse(form);
    }
})

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

カスタム クライアント側の検証は、カスタム jQuery 検証アダプターで動作する HTML 属性 data- を生成することで行われます。 次のサンプルのアダプター コードは、この記事で前に導入した [ClassicMovie] および [ClassicMovieWithClientValidator] 属性用に記述されたものです。

$.validator.addMethod('classicmovie', function (value, element, params) {
    var genre = $(params[0]).val(), year = params[1], date = new Date(value);

    // The Classic genre has a value of '0'.
    if (genre && genre.length > 0 && genre[0] === '0') {
        // The release date for a Classic is valid if it's no greater than the given year.
        return date.getUTCFullYear() <= year;
    }

    return true;
});

$.validator.unobtrusive.adapters.add('classicmovie', ['year'], function (options) {
    var element = $(options.form).find('select#Movie_Genre')[0];

    options.rules['classicmovie'] = [element, parseInt(options.params['year'])];
    options.messages['classicmovie'] = options.message;
});

アダプターの書き込み方法については、jQuery 検証に関する ドキュメントを参照してください

特定のフィールドに対するアダプターの使用は、次のような data- 属性によってトリガーされます。

  • 検証対象としてフィールドにフラグを設定します (data-val="true")。
  • 検証規則名とエラー メッセージ テキストを示します (例: data-val-rulename="Error message.")。
  • 検証コントロールで必要なその他のパラメーターを提供します (例: data-val-rulename-param1="value")。

次の例では、サンプル アプリの ClassicMovie 属性に対する data- 属性を示します。

<input class="form-control" type="date"
    data-val="true"
    data-val-classicmovie="Classic movies must have a release year no later than 1960."
    data-val-classicmovie-year="1960"
    data-val-required="The Release Date field is required."
    id="Movie_ReleaseDate" name="Movie.ReleaseDate" value="">

前に説明したように、タグ ヘルパーHTML ヘルパーでは、検証属性からの情報を使用して data- 属性がレンダリングされます。 カスタム data- HTML 属性が作成されるようになるコードを記述するには、2 つのオプションがあります。

  • AttributeAdapterBase<TAttribute> から派生するクラスと IValidationAttributeAdapterProvider を実装するクラスを作成し、属性とそのアダプターを DI に登録します。 この方法では単一責任の原則に従って、サーバー関連の検証コードとクライアント関連の検証コードは別のクラスになります。 アダプターには、DI に登録されるため、必要な場合に DI 内の他のサービスがそれを使用できるという利点もあります。
  • ValidationAttribute クラスで IClientModelValidator を実装します。 この方法は、属性でサーバー側の検証が何も行われず、DI からのサービスが必要ない場合に、適している可能性があります。

クライアント側検証用の AttributeAdapter

HTML に data- 属性をレンダリングするこの方法は、サンプル アプリの ClassicMovie 属性で使用されています。 この方法を使用してクライアント検証を追加するには、次のようにします。

  1. カスタム検証属性の属性アダプター クラスを作成します。 AttributeAdapterBase<TAttribute>からクラスを派生させます。 次の例で示すように、レンダリングされた出力に data- 属性を追加する AddValidation メソッドを作成します。

    public class ClassicMovieAttributeAdapter : AttributeAdapterBase<ClassicMovieAttribute>
    {
        public ClassicMovieAttributeAdapter(ClassicMovieAttribute attribute,
            IStringLocalizer stringLocalizer)
            : base(attribute, stringLocalizer)
        {
    
        }
    
        public override void AddValidation(ClientModelValidationContext context)
        {
            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-classicmovie", GetErrorMessage(context));
    
            var year = Attribute.Year.ToString(CultureInfo.InvariantCulture);
            MergeAttribute(context.Attributes, "data-val-classicmovie-year", year);
        }
    
        public override string GetErrorMessage(ModelValidationContextBase validationContext) =>
            Attribute.GetErrorMessage();
    }
    
  2. IValidationAttributeAdapterProvider を実装するアダプター プロバイダー クラスを作成します。 次の例に示すように、GetAttributeAdapter メソッドで、カスタム属性をアダプターのコンストラクターに渡します。

    public class CustomValidationAttributeAdapterProvider : IValidationAttributeAdapterProvider
    {
        private readonly IValidationAttributeAdapterProvider baseProvider =
            new ValidationAttributeAdapterProvider();
    
        public IAttributeAdapter GetAttributeAdapter(ValidationAttribute attribute,
            IStringLocalizer stringLocalizer)
        {
            if (attribute is ClassicMovieAttribute classicMovieAttribute)
            {
                return new ClassicMovieAttributeAdapter(classicMovieAttribute, stringLocalizer);
            }
    
            return baseProvider.GetAttributeAdapter(attribute, stringLocalizer);
        }
    }
    
  3. Startup.ConfigureServices でアダプター プロバイダーを DI に登録します。

    services.AddRazorPages()
        .AddMvcOptions(options =>
        {
            options.MaxModelValidationErrors = 50;
            options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
                _ => "The field is required.");
        });
    
    services.AddSingleton<IValidationAttributeAdapterProvider,
        CustomValidationAttributeAdapterProvider>();
    

クライアント側検証用の IClientModelValidator

HTML に data- 属性をレンダリングするこの方法は、サンプル アプリの ClassicMovieWithClientValidator 属性で使用されています。 この方法を使用してクライアント検証を追加するには、次のようにします。

  • カスタム検証属性で、IClientModelValidator インターフェイスを実装し、AddValidation メソッドを作成します。 次の例で示すように、AddValidation メソッドで、検証用の data- 属性を追加します。

    public class ClassicMovieWithClientValidatorAttribute :
        ValidationAttribute, IClientModelValidator
    {
        public ClassicMovieWithClientValidatorAttribute(int year)
        {
            Year = year;
        }
    
        public int Year { get; }
    
        public void AddValidation(ClientModelValidationContext context)
        {
            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-classicmovie", GetErrorMessage());
    
            var year = Year.ToString(CultureInfo.InvariantCulture);
            MergeAttribute(context.Attributes, "data-val-classicmovie-year", year);
        }
    
        public string GetErrorMessage() =>
            $"Classic movies must have a release year no later than {Year}.";
    
        protected override ValidationResult IsValid(object value,
            ValidationContext validationContext)
        {
            var movie = (Movie)validationContext.ObjectInstance;
            var releaseYear = ((DateTime)value).Year;
    
            if (movie.Genre == Genre.Classic && releaseYear > Year)
            {
                return new ValidationResult(GetErrorMessage());
            }
    
            return ValidationResult.Success;
        }
    
        private bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
        {
            if (attributes.ContainsKey(key))
            {
                return false;
            }
    
            attributes.Add(key, value);
            return true;
        }
    }
    

クライアント側検証を無効にする

次のコードは、Pages でのクライアント検証を無効 Razor にします。

services.AddRazorPages()
    .AddViewOptions(options =>
    {
        options.HtmlHelperOptions.ClientValidationEnabled = false;
    });

クライアント側検証を無効にするその他のオプション:

  • すべての .cshtml ファイル内の _ValidationScriptsPartial への参照をコメントアウトします。
  • Pages\Shared_ValidationScriptsPartial.cshtml ファイルの内容を削除します。

前の方法では、クライアント側でクラス ライブラリが検証される ASP.NET Core Identity Razor のを防ぐ必要があります。 詳細については、「IdentityASP.NET Core プロジェクトでのスキャフォールディング」を参照してください。

その他のリソース

この記事では、Core MVC または Pages アプリでユーザー入力 ASP.NET 検証する方法 Razor について説明します。

サンプル コードを表示またはダウンロードします (ダウンロード方法)。

モデルの状態

モデルの状態では、モデル バインドとモデル検証の 2 つのサブシステムで発生したエラーが表されます。 モデル バインドで発生するエラーは、一般に、データ変換エラーです (たとえば、整数が必要なフィールドに "x" が入力された場合)。 モデル検証は、モデル バインドの後で行われて、データがビジネス ルールに従っていないエラーが報告されます (たとえば、1 から 5 までのレーティングが必要なフィールドに 0 が入力された場合)。

モデル バインドと検証はどちらも、コントローラー アクションまたは Pages ハンドラー メソッドの実行 Razor 前に行われます。 Web アプリでは、ModelState.IsValid を調べて適切に対処するのはアプリの責任です。 通常、Web アプリではエラー メッセージを含むページを再表示します。

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    _context.Movie.Add(Movie);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

Web API コントローラーでは、[ApiController] 属性が設定されている場合は、ModelState.IsValid を確認する必要はありません。 その場合、モデルが無効な状態のときは、エラーの詳細を含む HTTP 400 応答が自動的に返されます。 詳細については、「自動的な HTTP 400 応答」を参照してください。

検証を再実行する

検証は自動的に行われますが、手動で繰り返したい場合があります。 たとえば、プロパティの値を計算し、プロパティを計算値に設定した後で検証を再実行するような場合です。 検証を再実行するには、次に示すように TryValidateModel メソッドを呼び出します。

var movie = new Movie
{
    Title = title,
    Genre = genre,
    ReleaseDate = modifiedReleaseDate,
    Description = description,
    Price = price,
    Preorder = preorder,
};

TryValidateModel(movie);

if (ModelState.IsValid)
{
    _context.AddMovie(movie);
    _context.SaveChanges();

    return RedirectToAction(actionName: nameof(Index));
}

return View(movie);

検証属性

検証属性を使用して、モデルのプロパティの検証規則を指定できます。 サンプル アプリの次の例では、検証属性で注釈されたモデル クラスが示されています。 [ClassicMovie] 属性はカスタム検証属性であり、その他は組み込み属性です。 [ClassicMovie2] は示されていませんが、カスタム属性を実装するもう 1 つの方法を示します。

public class Movie
{
    public int Id { get; set; }

    [Required]
    [StringLength(100)]
    public string Title { get; set; }

    [ClassicMovie(1960)]
    [DataType(DataType.Date)]
    public DateTime ReleaseDate { get; set; }

    [Required]
    [StringLength(1000)]
    public string Description { get; set; }

    [Range(0, 999.99)]
    public decimal Price { get; set; }

    [Required]
    public Genre Genre { get; set; }

    public bool Preorder { get; set; }
}

組み込みの属性

組み込みの検証属性には次のものがあります。

  • [CreditCard]: プロパティがクレジット カード形式を持っている場合に検証します。
  • [Compare]: モデル内の 2 つのプロパティが一致すると検証します。 たとえば、Register.cshtml.cs ファイルは [Compare] を使用して、入力された 2 つのパスワードが一致していることを検証します。 ス Identity キャフォールディングをクリックすると、Register コードが表示されます。
  • [EmailAddress]: プロパティが電子メール形式であることを検証します。
  • [Phone]: プロパティに電話番号の書式が設定されていることを検証します。
  • [Range]: プロパティ値が指定した範囲内にあることを検証します。
  • [RegularExpression]: プロパティ値が指定した正規表現に一致することを検証します。
  • [Required]: フィールドが null でないことを検証します。 この属性の動作の詳細については、「 [Required] 属性」を参照してください。
  • [StringLength]: 文字列プロパティの値が、指定された長さの制限を超えていないことを検証します。
  • [Url]: プロパティに URL 形式があることを検証します。
  • [Remote]: サーバーでアクションメソッドを呼び出すことによって、クライアントの入力を検証します。 この属性の動作の詳細については、「 [Remote] 属性」を参照してください。

クライアント側の検証で [RegularExpression] 属性を使用する場合、regex はクライアントの JavaScript で実行されます。 これは、ECMAScript 一致の動作が使用されることを意味します。 詳細については、次を参照してください。この GitHub の問題します。

検証属性の完全な一覧については、System.ComponentModel.DataAnnotations 名前空間で確認できます。

エラー メッセージ

検証属性では、無効な入力に対して表示されるエラー メッセージを指定できます。 次に例を示します。

[StringLength(8, ErrorMessage = "Name length can't be more than 8.")]

内部的には、属性ではフィールド名のプレースホルダーを指定して String.Format が呼び出され、場合によっては追加のプレースホルダーが指定されます。 次に例を示します。

[StringLength(8, ErrorMessage = "{0} length must be between {2} and {1}.", MinimumLength = 6)]

Name プロパティに適用すると、上記のコードによって作成されるエラー メッセージは "Name length must be between 6 and 8" になります。

特定の属性のエラー メッセージに対して String.Format に渡されるパラメーターを確認するには、DataAnnotations のソース コードをご覧ください。

[Required] 属性

既定では、検証システムでは null 非許容型のパラメーターまたはプロパティは [Required] 属性を持つものとして処理されます。 decimalint などの値の型は null 非許容型です。

サーバーでの [Required] の検証

サーバーでは、プロパティが null の場合、必須の値が不足しているものと見なされます。 null 非許容型フィールドは常に有効であり、[Required] 属性のエラー メッセージが表示されることはありません。

ただし、null 非許容型プロパティに対するモデル バインドは失敗する場合があり、The value '' is invalid などのエラー メッセージが表示されます。 null 非許容型のサーバー側検証に対してカスタム エラー メッセージを指定するには、次のオプションがあります。

  • フィールドを null 許容型にします (たとえば、decimal の代わりに decimal? を使用)。 Null <T> 値を許容値型は、標準の null 許容型と同様に扱われます。

  • 次の例に示すように、モデル バインドで使用される既定のエラー メッセージを指定します。

    services.AddMvc(options => 
        {
            options.MaxModelValidationErrors = 50;
            options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
                (_) => "The field is required.");
        })
        .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
    services.AddSingleton
        <IValidationAttributeAdapterProvider, 
         CustomValidationAttributeAdapterProvider>();
    

    既定のメッセージを設定できるモデル バインド エラーに関して詳しくは、DefaultModelBindingMessageProvider をご覧ください。

クライアントでの [Required] の検証

クライアントでの null 非許容型と文字列の処理は、サーバーとは異なります。 クライアント側 :

  • 値は、入力が行われた場合にのみ存在するものと見なされます。 そのため、クライアント側の検証では、null 非許容型は null 許容型と同じように処理されます。
  • 文字列フィールド内の空白文字は、jQuery Validation の required メソッドでは有効な入力と見なされます。 サーバー側の検証では、空白文字のみが入力された場合は、必須文字列フィールドが無効であるものと見なされます。

前述のように、null 非許容型は [Required] 属性を持つものとして処理されます。 つまり、[Required] 属性を適用していない場合でも、クライアント側の検証が行われます。 ただし、属性を使用しない場合は、既定のエラー メッセージが表示されます。 カスタム エラー メッセージを指定するには、属性を使用します。

[Remote] 属性

[Remote] 属性では、フィールド入力が有効かどうかを判断するためにサーバーでメソッドを呼び出す必要があるクライアント側検証が実装されます。 たとえば、アプリでは、ユーザー名が既に使用されているかどうかを確認することが必要な場合があります。

リモート検証を実装するには:

  1. JavaScript で呼び出すアクション メソッドを作成します。 JQuery Validate の remote メソッドでは、JSON の応答が必要です。

    • "true" は、入力データが有効であることを意味します。
    • "false"undefined、または null は、入力が有効ではないことを意味します。 既定のエラー メッセージを表示します。
    • その他の文字列は、入力が無効であることを意味します。 カスタム エラー メッセージとして文字列を表示します。

    カスタム エラー メッセージを返すアクション メソッドの例を次に示します。

    [AcceptVerbs("Get", "Post")]
    public IActionResult VerifyEmail(string email)
    {
        if (!_userRepository.VerifyEmail(email))
        {
            return Json($"Email {email} is already in use.");
        }
    
        return Json(true);
    }
    
  2. 次の例に示すように、モデル クラスで、検証アクション メソッドを指し示す [Remote] 属性を使用してプロパティに注釈を付けます。

    [Remote(action: "VerifyEmail", controller: "Users")]
    public string Email { get; set; }
    

    [Remote] 属性は Microsoft.AspNetCore.Mvc 名前空間にあります。 Microsoft.AspNetCore.App または Microsoft.AspNetCore.All メタパッケージを使用していない場合は、Microsoft.AspNetCore.Mvc.ViewFeatures NuGet パッケージをインストールします。

追加フィールド

[Remote] 属性の AdditionalFields プロパティでは、サーバー上のデータに対してフィールドの組み合わせを検証できます。 たとえば、User モデルに FirstName プロパティと LastName プロパティがある場合、その名前のペアを使用する既存ユーザーがいないことを確認したいことがあります。 AdditionalFields を使用する方法を次の例に示します。

[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(LastName))]
public string FirstName { get; set; }
[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(FirstName))]
public string LastName { get; set; }

AdditionalFields を文字列 "FirstName" および "LastName" に明示的に設定することもできますが、nameof 演算子を使用すると、後のリファクタリングが容易になります。 この検証のアクション メソッドは、名と姓の両方を引数として受け取る必要があります。

[AcceptVerbs("Get", "Post")]
public IActionResult VerifyName(string firstName, string lastName)
{
    if (!_userRepository.VerifyName(firstName, lastName))
    {
        return Json($"A user named {firstName} {lastName} already exists.");
    }

    return Json(true);
}

ユーザーが名または姓を入力すると、JavaScript はリモート呼び出しを行って、その名前のペアが取得されているかどうかを確認します。

複数の追加フィールドを検証するには、それらをコンマ区切りのリストとして提供します。 たとえば、MiddleName プロパティをモデルに追加するには、[Remote] 属性を次の例のように設定します。

[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(FirstName) + "," + nameof(LastName))]
public string MiddleName { get; set; }

他の属性引数と同じように、AdditionalFields も定数式である必要があります。 したがって、補間文字列を使用したり、Join を呼び出して AdditionalFields を初期化したりしないでください。

組み込み属性に代わる方法

組み込み属性によって提供されない検証が必要な場合は、次のようにできます。

カスタム属性

組み込みの検証属性で処理されないシナリオの場合は、カスタム検証属性を作成できます。 ValidationAttribute を継承するクラスを作成し、IsValid メソッドをオーバーライドします。

IsValid メソッドは、value という名前のオブジェクトを受け取ります。これは、検証対象の入力です。 オーバーロードは ValidationContext オブジェクトも受け取ります。これは、モデル バインドによって作成されたモデル インスタンスなどの追加情報を提供します。

次の例では、Classic ジャンルの映画の公開日が指定した年より後ではないことを検証します。 [ClassicMovie2] 属性では最初にジャンルがチェックされ、Classic である場合にのみ続行されます。 クラシックとして識別された映画については、公開日が属性のコンストラクターに渡された制限より後ではないことがチェックされます。

public class ClassicMovieAttribute : ValidationAttribute
{
    private int _year;

    public ClassicMovieAttribute(int year)
    {
        _year = year;
    }

    protected override ValidationResult IsValid(
        object value, ValidationContext validationContext)
    {
        var movie = (Movie)validationContext.ObjectInstance;
        var releaseYear = ((DateTime)value).Year;

        if (movie.Genre == Genre.Classic && releaseYear > _year)
        {
            return new ValidationResult(GetErrorMessage());
        }

        return ValidationResult.Success;
    }

    public int Year => _year;

    public string GetErrorMessage()
    {
        return $"Classic movies must have a release year no later than {_year}.";
    }
}

前の例の movie 変数は、フォーム送信からのデータを格納している Movie オブジェクトを表します。 IsValid メソッドでは、日付とジャンルがチェックされます。 検証に成功すると、IsValid によって ValidationResult.Success コードが返されます。 検証に失敗すると、エラー メッセージを含む ValidationResult が返されます。

IValidatableObject

前の例は、Movie 型でのみ動作します。 クラス レベルの検証に対するもう 1 つのオプションは、次の例に示すように、IValidatableObject をモデル クラスに実装することです。

public class MovieIValidatable : IValidatableObject
{
    private const int _classicYear = 1960;

    public int Id { get; set; }

    [Required]
    [StringLength(100)]
    public string Title { get; set; }

    [Required]
    public DateTime ReleaseDate { get; set; }

    [Required]
    [StringLength(1000)]
    public string Description { get; set; }

    [Range(0, 999.99)]
    public decimal Price { get; set; }

    [Required]
    public Genre Genre { get; set; }

    public bool Preorder { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (Genre == Genre.Classic && ReleaseDate.Year > _classicYear)
        {
            yield return new ValidationResult(
                $"Classic movies must have a release year earlier than {_classicYear}.",
                new[] { "ReleaseDate" });
        }
    }
}

最上位ノードの検証

最上位ノードには次が含まれています。

  • アクションのパラメーター
  • コントローラーのプロパティ
  • ページ ハンドラーのパラメーター
  • ページ モデルのプロパティ

モデルのパラメーター検証に加え、モデルが関連付けられた最上位ノードが検証されます。 サンプル アプリからの次の例の VerifyPhone メソッドでは、RegularExpressionAttribute を使用して phone アクションパラメーターが検証されています。

[AcceptVerbs("Get", "Post")]
public IActionResult VerifyPhone(
    [RegularExpression(@"^\d{3}-\d{3}-\d{4}$")] string phone)
{
    if (!ModelState.IsValid)
    {
        return Json($"Phone {phone} has an invalid format. Format: ###-###-####");
    }

    return Json(true);
}

最上位ノードでは、検証属性と共に BindRequiredAttribute を使用できます。 サンプル アプリからの次の例では、CheckAge メソッドによって、フォームの送信時、クエリ文字列から age パラメーターを関連付ける必要があることが指定されます。

[HttpPost]
public IActionResult CheckAge(
    [BindRequired, FromQuery] int age)
{

[年齢確認] ページ (CheckAge.cshtml) には 2 つのフォームがあります。 最初のフォームでは、Age の値 99 がクエリ文字列 https://localhost:5001/Users/CheckAge?Age=99 として送信されます。

クエリ文字列の正しく書式設定された age パラメーターが送信されると、フォームの有効性が確認されます。

[年齢確認] ページの 2 番目のフォームでは、要求本文で Age 値が送信され、検証は不合格となります。 age パラメーターはクエリ文字列で渡される必要があるため、バインドが失敗します。

CompatibilityVersion.Version_2_1 以降で実行すると、最上位ノードの検証が既定で有効になります。 それ以外の場合、最上位ノードの検証は無効です。 次に示すように、Startup.ConfigureServicesAllowValidatingTopLevelNodes プロパティを設定することにより、既定のオプションをオーバーライドできます。

services.AddMvc(options => 
    {
        options.MaxModelValidationErrors = 50;
        options.AllowValidatingTopLevelNodes = false;
    })
    .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

最大エラー数

エラーの最大数 (既定では 200) に達すると、検証は停止します。 この数は、Startup.ConfigureServices の次のコードを使用して構成します。

services.AddMvc(options => 
    {
        options.MaxModelValidationErrors = 50;
        options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
            (_) => "The field is required.");
    })
    .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddSingleton
    <IValidationAttributeAdapterProvider, 
     CustomValidationAttributeAdapterProvider>();

最大再帰

ValidationVisitor では、検証対象のモデルのオブジェクト グラフが走査されます。 非常に深いモデルまたは無限に再帰するモデルでは、検証でスタック オーバーフローが発生する可能性があります。 MvcOptions.MaxValidationDepth では、ビジターの再帰が構成されている深さを超えた場合、早い段階で検証を停止する方法が提供されています。 CompatibilityVersion.Version_2_2 以降で実行したときの MvcOptions.MaxValidationDepth の既定値は 32 です。 それより前のバージョンでは、値は null であり、深さの制約がないことを意味します。

自動省略

モデル グラフの検証が必要ない場合、検証は自動的に省略 (スキップ) されます。 ランタイムで検証がスキップされるオブジェクトとしては、プリミティブのコレクション (byte[]string[]Dictionary<string, string> など) や、検証コントロールを何も持たない複雑なオブジェクト グラフなどがあります。

検証を無効にする

検証を無効にするには:

  1. どのフィールドも無効としてマークしない IObjectModelValidator の実装を作成します。

    public class NullObjectModelValidator : IObjectModelValidator
    {
        public void Validate(
            ActionContext actionContext,
            ValidationStateDictionary validationState,
            string prefix,
            object model)
        {
        }
    }
    
  2. 依存関係挿入コンテナーで、次のコードを Startup.ConfigureServices に追加し、既定の IObjectModelValidator の実装を置き換えます。

    // There is only one `IObjectModelValidator` object,
    // so AddSingleton replaces the default one.
    services.AddSingleton<IObjectModelValidator>(new NullObjectModelValidator());
    

その場合でも、モデル バインドから発生するモデル状態エラーが表示される可能性があります。

クライアント側の検証

クライアント側検証は、フォームが有効になるまで送信を許可しません。 送信ボタンをクリックすると、フォームの送信またはエラー メッセージの表示を行う JavaScript が実行されます。

クライアント側の検証を使用すると、フォームに入力エラーがある場合に、サーバーへの不要なラウンドトリップを回避できます。 _Layout.cshtml および _ValidationScriptsPartial.cshtml の次のスクリプト参照では、クライアント側の検証がサポートされています。

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.17.0/jquery.validate.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-unobtrusive/3.2.11/jquery.validate.unobtrusive.min.js"></script>

jQuery Unobtrusive Validation スクリプトは、人気のある jQuery Validate プラグインを基に作成された Microsoft のカスタム フロントエンド ライブラリです。 jQuery Unobtrusive Validation を使用しないと、同じ検証ロジックを 2 か所でコーディングする必要があります。1 つはモデル プロパティでのサーバー側検証属性で、もう 1 つはクライアント側スクリプトです。 代わりに、タグ ヘルパーおよび HTML ヘルパーでは、モデル プロパティの検証属性と型メタデータを使用して、検証の必要なフォーム要素に対する HTML 5 の data- 属性がレンダリングされます。 jQuery Unobtrusive Validation では、data- 属性が解析され、ロジックが jQuery Validate に渡されて、サーバー側検証ロジックがクライアントに実質的に "コピー" されます。 次に示すように、タグ ヘルパーを使用して、クライアントで検証エラーを表示できます。

<div class="form-group">
    <label asp-for="ReleaseDate" class="col-md-2 control-label"></label>
    <div class="col-md-10">
        <input asp-for="ReleaseDate" class="form-control" />
        <span asp-validation-for="ReleaseDate" class="text-danger"></span>
    </div>
</div>

上記のタグ ヘルパーでは、次の HTML がレンダリングされます。

<form action="/Movies/Create" method="post">
    <div class="form-horizontal">
        <h4>Movie</h4>
        <div class="text-danger"></div>
        <div class="form-group">
            <label class="col-md-2 control-label" for="ReleaseDate">ReleaseDate</label>
            <div class="col-md-10">
                <input class="form-control" type="datetime"
                data-val="true" data-val-required="The ReleaseDate field is required."
                id="ReleaseDate" name="ReleaseDate" value="">
                <span class="text-danger field-validation-valid"
                data-valmsg-for="ReleaseDate" data-valmsg-replace="true"></span>
            </div>
        </div>
    </div>
</form>

HTML 出力の data- 属性が、ReleaseDate プロパティの検証属性に対応していることに注意してください。 data-val-required 属性には、ユーザーが公開日フィールドを入力していない場合に表示されるエラー メッセージが含まれています。 jQuery の控えめな検証は、この値を jQuery Validate required () メソッドに渡します。このメソッドは、付随する要素にそのメッセージを表示し <span> ます。

[DataType] 属性によってオーバーライドされていない限り、データ型の検証はプロパティの .NET 型に基づいて行われます。 ブラウザーには独自の既定のエラー メッセージがありますが、jQuery Validation Unobtrusive Validation パッケージでそれらのメッセージをオーバーライドできます。 [DataType] 属性と [EmailAddress] などのサブクラスを使用して、エラー メッセージを指定できます。

動的なフォームに検証を追加する

ページが初めて読み込まれるときに、jQuery Unobtrusive Validation によって検証ロジックとパラメーターが jQuery Validate に渡されます。 したがって、動的に生成されるフォームでは、検証は自動的には機能しません。 検証を有効にするには、作成直後に動的フォームを解析するよう、jQuery Unobtrusive Validation に指示します。 たとえば、次のコードでは、AJAX によって追加されるフォームでクライアント側検証が設定されます。

$.get({
    url: "https://url/that/returns/a/form",
    dataType: "html",
    error: function(jqXHR, textStatus, errorThrown) {
        alert(textStatus + ": Couldn't add form. " + errorThrown);
    },
    success: function(newFormHTML) {
        var container = document.getElementById("form-container");
        container.insertAdjacentHTML("beforeend", newFormHTML);
        var forms = container.getElementsByTagName("form");
        var newForm = forms[forms.length - 1];
        $.validator.unobtrusive.parse(newForm);
    }
})

$.validator.unobtrusive.parse() メソッドには、その引数の 1 つで jQuery セレクターを指定します。 このメソッドは、そのセレクター内のフォームの data- 属性を解析するよう jQuery Unobtrusive Validation に指示します。 その後、これらの属性の値は、jQuery Validate プラグインに渡されます。

動的なコントロールに検証を追加する

$.validator.unobtrusive.parse() メソッドは、<input><select/> などの動的に生成される個々のコントロールではなく、フォーム全体に対して動作します。 フォームを再解析するには、次の例に示すように、フォームが前に解析されたときに追加された検証データを削除します。

$.get({
    url: "https://url/that/returns/a/control",
    dataType: "html",
    error: function(jqXHR, textStatus, errorThrown) {
        alert(textStatus + ": Couldn't add control. " + errorThrown);
    },
    success: function(newInputHTML) {
        var form = document.getElementById("my-form");
        form.insertAdjacentHTML("beforeend", newInputHTML);
        $(form).removeData("validator")    // Added by jQuery Validate
               .removeData("unobtrusiveValidation");   // Added by jQuery Unobtrusive Validation
        $.validator.unobtrusive.parse(form);
    }
})

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

カスタム クライアント側検証は、カスタム jQuery Validate アダプターで動作する data- HTML 属性を生成することによって行われます。 次のサンプルのアダプター コードは、この記事で前に導入した ClassicMovie および ClassicMovie2 属性用に記述されたものです。

$.validator.addMethod('classicmovie',
    function (value, element, params) {
        // Get element value. Classic genre has value '0'.
        var genre = $(params[0]).val(),
            year = params[1],
            date = new Date(value);
        if (genre && genre.length > 0 && genre[0] === '0') {
            // Since this is a classic movie, invalid if release date is after given year.
            return date.getUTCFullYear() <= year;
        }

        return true;
    });

$.validator.unobtrusive.adapters.add('classicmovie',
    ['year'],
    function (options) {
        var element = $(options.form).find('select#Genre')[0];
        options.rules['classicmovie'] = [element, parseInt(options.params['year'])];
        options.messages['classicmovie'] = options.message;
    });

アダプターの作成方法については、jQuery Validate のドキュメントをご覧ください。

特定のフィールドに対するアダプターの使用は、次のような data- 属性によってトリガーされます。

  • 検証対象としてフィールドにフラグを設定します (data-val="true")。
  • 検証規則名とエラー メッセージ テキストを示します (例: data-val-rulename="Error message.")。
  • 検証コントロールで必要なその他のパラメーターを提供します (例: data-val-rulename-parm1="value")。

次の例では、サンプル アプリの ClassicMovie 属性に対する data- 属性を示します。

<input class="form-control" type="datetime"
    data-val="true"
    data-val-classicmovie1="Classic movies must have a release year earlier than 1960."
    data-val-classicmovie1-year="1960"
    data-val-required="The ReleaseDate field is required."
    id="ReleaseDate" name="ReleaseDate" value="">

前に説明したように、タグ ヘルパーHTML ヘルパーでは、検証属性からの情報を使用して data- 属性がレンダリングされます。 カスタム data- HTML 属性が作成されるようになるコードを記述するには、2 つのオプションがあります。

  • AttributeAdapterBase<TAttribute> から派生するクラスと IValidationAttributeAdapterProvider を実装するクラスを作成し、属性とそのアダプターを DI に登録します。 この方法では単一責任の原則に従って、サーバー関連の検証コードとクライアント関連の検証コードは別のクラスになります。 アダプターには、DI に登録されるため、必要な場合に DI 内の他のサービスがそれを使用できるという利点もあります。
  • ValidationAttribute クラスで IClientModelValidator を実装します。 この方法は、属性でサーバー側の検証が何も行われず、DI からのサービスが必要ない場合に、適している可能性があります。

クライアント側検証用の AttributeAdapter

HTML に data- 属性をレンダリングするこの方法は、サンプル アプリの ClassicMovie 属性で使用されています。 この方法を使用してクライアント検証を追加するには、次のようにします。

  1. カスタム検証属性の属性アダプター クラスを作成します。 AttributeAdapterBase<TAttribute>からクラスを派生させます。 次の例で示すように、レンダリングされた出力に data- 属性を追加する AddValidation メソッドを作成します。

    public class ClassicMovieAttributeAdapter : AttributeAdapterBase<ClassicMovieAttribute>
    {
        private int _year;
    
        public ClassicMovieAttributeAdapter(ClassicMovieAttribute attribute, IStringLocalizer stringLocalizer) : base (attribute, stringLocalizer)
        {
            _year = attribute.Year;
        }
        public override void AddValidation(ClientModelValidationContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }
    
            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-classicmovie", GetErrorMessage(context));
    
            var year = Attribute.Year.ToString(CultureInfo.InvariantCulture);
            MergeAttribute(context.Attributes, "data-val-classicmovie-year", year);
        }
        public override string GetErrorMessage(ModelValidationContextBase validationContext)
        {
            return Attribute.GetErrorMessage();
        }
    }
    
  2. IValidationAttributeAdapterProvider を実装するアダプター プロバイダー クラスを作成します。 次の例に示すように、GetAttributeAdapter メソッドで、カスタム属性をアダプターのコンストラクターに渡します。

    public class CustomValidationAttributeAdapterProvider :
        IValidationAttributeAdapterProvider
    {
        IValidationAttributeAdapterProvider baseProvider =
            new ValidationAttributeAdapterProvider();
        public IAttributeAdapter GetAttributeAdapter(ValidationAttribute attribute,
            IStringLocalizer stringLocalizer)
        {
            if (attribute is ClassicMovieAttribute classicMovieAttribute)
            {
                return new ClassicMovieAttributeAdapter(classicMovieAttribute, stringLocalizer);
            }
            else
            {
                return baseProvider.GetAttributeAdapter(attribute, stringLocalizer);
            }
        }
    }
    
  3. Startup.ConfigureServices でアダプター プロバイダーを DI に登録します。

    services.AddMvc(options => 
        {
            options.MaxModelValidationErrors = 50;
            options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
                (_) => "The field is required.");
        })
        .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
    services.AddSingleton
        <IValidationAttributeAdapterProvider, 
         CustomValidationAttributeAdapterProvider>();
    

クライアント側検証用の IClientModelValidator

HTML に data- 属性をレンダリングするこの方法は、サンプル アプリの ClassicMovie2 属性で使用されています。 この方法を使用してクライアント検証を追加するには、次のようにします。

  • カスタム検証属性で、IClientModelValidator インターフェイスを実装し、AddValidation メソッドを作成します。 次の例で示すように、AddValidation メソッドで、検証用の data- 属性を追加します。

    
    public class ClassicMovie2Attribute : ValidationAttribute, IClientModelValidator
    {
        private int _year;
    
        public ClassicMovie2Attribute(int year)
        {
            _year = year;
        }
    
        protected override ValidationResult IsValid(
            object value, ValidationContext validationContext)
        {
            var movie = (Movie)validationContext.ObjectInstance;
            var releaseYear = ((DateTime)value).Year;
    
            if (movie.Genre == Genre.Classic && releaseYear > _year)
            {
                return new ValidationResult(GetErrorMessage());
            }
    
            return ValidationResult.Success;
        }
    
        public void AddValidation(ClientModelValidationContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }
    
            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-classicmovie", GetErrorMessage());
    
            var year = _year.ToString(CultureInfo.InvariantCulture);
            MergeAttribute(context.Attributes, "data-val-classicmovie-year", year);
        }
        private bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
        {
            if (attributes.ContainsKey(key))
            {
                return false;
            }
    
            attributes.Add(key, value);
            return true;
        }
        protected string GetErrorMessage()
        {
            return $"Classic movies must have a release year no later than {_year} [from attribute 2].";
        }
    }
    

クライアント側検証を無効にする

次のコードでは、MVC ビューのクライアント検証が無効になります。

services.AddMvc().AddViewOptions(options =>
{
    if (_env.IsDevelopment())
    {
        options.HtmlHelperOptions.ClientValidationEnabled = false;
    }
});

ページ内 Razor :

services.Configure<HtmlHelperOptions>(o => o.ClientValidationEnabled = false);

クライアント検証を無効にするもう 1 つのオプションは、.cshtml ファイルで _ValidationScriptsPartial への参照をコメントにすることです。

その他のリソース