ASP.NET Core MVC および Razor Pages でのモデルの検証

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

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

モデルの状態

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

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

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

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

    return RedirectToPage("./Index");
}

コントローラーとビューを含む ASP.NET Core MVC の場合、次の例は、コントローラー アクションの内部を確認ModelState.IsValidする方法を示しています。

public async Task<IActionResult> Create(Movie movie)
{
    if (!ModelState.IsValid)
    {
        return View(movie);
    }

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

    return RedirectToAction(nameof(Index));
}

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

検証を再実行する

検証は自動的に行われますが、手動で繰り返したい場合があります。 たとえば、プロパティの値を計算し、プロパティを計算値に設定した後で検証を再実行するような場合です。 検証を再実行するには、ModelStateDictionary.ClearValidationState を呼び出して検証対象のモデルに固有の検証をクリアし、その後に TryValidateModel と入力します。

public async Task<IActionResult> OnPostTryValidateAsync()
{
    var modifiedReleaseDate = DateTime.Now.Date;
    Movie.ReleaseDate = modifiedReleaseDate;

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

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

    return RedirectToPage("./Index");
}

検証属性

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

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

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

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

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

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

    public Genre Genre { get; set; }

    public bool Preorder { get; set; }
}

組み込みの属性

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

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

検証システムでは null 非許容型のパラメーターまたはバインド プロパティは [Required(AllowEmptyStrings = true)] 属性を持つものとして処理されます。 コンテキストをNullable有効にすると、MVC は、非ジェネリック型またはパラメーターに対して null 非許容プロパティの検証を暗黙的に開始します。これは、属性に属性が設定[Required(AllowEmptyStrings = true)]されているかのように行われます。 次のコードがあるとします。

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

アプリがビルド<Nullable>enable</Nullable>された場合、ON またはフォーム投稿のJS値Nameが不足すると、検証エラーが発生します。 null 値を許容する参照型を使用して、Name プロパティに null 値または欠損値を指定できるようにします。

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

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

builder.Services.AddControllers(
    options => options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true);

ジェネリック型と [必須] 属性の null 非許容プロパティ

ジェネリック型の null 非許容プロパティには、型が必要な場合に [Required] 属性を含める必要があります。 次のコードでは、 TestRequired 必須ではありません。

public class WeatherForecast<T>
{
    public string TestRequired { get; set; } = null!;
    public T? Inner { get; set; }
}

次のコードでは、 TestRequired 明示的に必須としてマークされています。

using System.ComponentModel.DataAnnotations;

public class WeatherForecast<T>
{
    [Required]
    public string TestRequired { get; set; } = null!;
    public T? Inner { get; set; }
}

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

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

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

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

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

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

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

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

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

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

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

[Remote] 属性

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

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

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

    • 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; } = null!;
    

追加フィールド

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

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

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

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; } = null!;

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

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

    [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アクション パラメーターをRegularExpressionAttributephone検証するために使用します。

[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) に達すると、検証は停止します。 この数は、Program.cs の次のコードを使用して構成します。

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

builder.Services.AddSingleton
    <IValidationAttributeAdapterProvider, CustomValidationAttributeAdapterProvider>();

最大再帰

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

自動省略

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

クライアント側の検証

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

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

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.3/jquery.validate.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-unobtrusive/3.2.12/jquery.validate.unobtrusive.js"></script>

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

<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 Validation に渡されます。 したがって、動的に生成されるフォームでは、検証は自動的には機能しません。 検証を有効にするには、作成直後に動的フォームを解析するよう、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 Validation プラグインに渡されます。

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

$.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 Validation アダプターで動作する data- HTML 属性を生成することによって行われます。 次のサンプルのアダプター コードは、この記事で前に導入した [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 Validation のドキュメントをご覧ください。

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

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

次の例は、data-サンプル アプリの属性のClassicMovie属性を示しています。

<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 に登録します。 このメソッドは、サーバー関連の検証コードとクライアント関連の検証コードが別々のクラスにあるという 1 つの責任原則 に従います。 また、アダプターは 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. Program.cs でアダプター プロバイダーを DI に登録します。

    builder.Services.AddRazorPages()
        .AddMvcOptions(options =>
        {
            options.MaxModelValidationErrors = 50;
            options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
                _ => "The field is required.");
        });
    
    builder.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 static bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
        {
            if (attributes.ContainsKey(key))
            {
                return false;
            }
    
            attributes.Add(key, value);
            return true;
        }
    }
    

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

次のコードにより、Razor Pages のクライアント検証が無効になります。

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

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

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

上記の方法では、ASP.NET Core クラス ライブラリのクライアント側のIdentityRazor検証が妨げられません。 詳細については、「ASP.NET Core プロジェクトの Identity のスキャフォールディング」を参照してください。

その他のリソース

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

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

モデルの状態

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

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

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

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

    return RedirectToPage("./Index");
}

コントローラーとビューを含む ASP.NET Core MVC の場合、次の例は、コントローラー アクションの内部を確認ModelState.IsValidする方法を示しています。

public async Task<IActionResult> Create(Movie movie)
{
    if (!ModelState.IsValid)
    {
        return View(movie);
    }

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

    return RedirectToAction(nameof(Index));
}

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

検証を再実行する

検証は自動的に行われますが、手動で繰り返したい場合があります。 たとえば、プロパティの値を計算し、プロパティを計算値に設定した後で検証を再実行するような場合です。 検証を再実行するには、ModelStateDictionary.ClearValidationState を呼び出して検証対象のモデルに固有の検証をクリアし、その後に TryValidateModel と入力します。

public async Task<IActionResult> OnPostTryValidateAsync()
{
    var modifiedReleaseDate = DateTime.Now.Date;
    Movie.ReleaseDate = modifiedReleaseDate;

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

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

    return RedirectToPage("./Index");
}

検証属性

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

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

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

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

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

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

    public Genre Genre { get; set; }

    public bool Preorder { get; set; }
}

組み込みの属性

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

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

検証エラーで ON プロパティ名を使用 JSする

既定では、検証エラーが発生すると、モデル検証によって、エラー キーとしてプロパティ名が生成 ModelStateDictionary されます。 シングル ページ アプリなどの一部のアプリでは、Web API から生成された検証エラーに ON プロパティ名を使用 JSするとメリットがあります。 次のコードでは、ON プロパティ名を使用 SystemTextJsonValidationMetadataProvider するように検証を JS構成します。

using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers(options =>
{
    options.ModelMetadataDetailsProviders.Add(new SystemTextJsonValidationMetadataProvider());
});

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

次のコードでは、Json.NET を使用 NewtonsoftJsonValidationMetadataProvider するときに ON プロパティ名を使用 JSするように検証 構成します。

using Microsoft.AspNetCore.Mvc.NewtonsoftJson;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers(options =>
{
    options.ModelMetadataDetailsProviders.Add(new NewtonsoftJsonValidationMetadataProvider());
}).AddNewtonsoftJson();

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

キャメル ケースを使用するポリシーの例については、GitHub を参照してくださいProgram.cs

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

検証システムでは null 非許容型のパラメーターまたはバインド プロパティは [Required(AllowEmptyStrings = true)] 属性を持つものとして処理されます。 Nullable コンテキストを有効にすることで、MVC は [Required(AllowEmptyStrings = true)] 属性が付けられているかのように、null 非許容型のプロパティまたはパラメーターの検証を暗黙的に開始します。 次のコードがあるとします。

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

アプリがビルド<Nullable>enable</Nullable>された場合、ON またはフォーム投稿のJS値Nameが不足すると、検証エラーが発生します。 null 値を許容する参照型を使用して、Name プロパティに null 値または欠損値を指定できるようにします。

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

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

builder.Services.AddControllers(
    options => options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true);

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

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

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

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

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

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

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

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

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

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

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

[Remote] 属性

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

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

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

    • 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; } = null!;
    

追加フィールド

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

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

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

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; } = null!;

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

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

    [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アクション パラメーターをRegularExpressionAttributephone検証するために使用します。

[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) に達すると、検証は停止します。 この数は、Program.cs の次のコードを使用して構成します。

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

builder.Services.AddSingleton
    <IValidationAttributeAdapterProvider, CustomValidationAttributeAdapterProvider>();

最大再帰

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

自動省略

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

クライアント側の検証

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

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

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.3/jquery.validate.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-unobtrusive/3.2.12/jquery.validate.unobtrusive.js"></script>

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

<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 Validation に渡されます。 したがって、動的に生成されるフォームでは、検証は自動的には機能しません。 検証を有効にするには、作成直後に動的フォームを解析するよう、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 Validation プラグインに渡されます。

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

$.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 Validation アダプターで動作する data- HTML 属性を生成することによって行われます。 次のサンプルのアダプター コードは、この記事で前に導入した [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 Validation のドキュメントをご覧ください。

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

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

次の例は、data-サンプル アプリの属性のClassicMovie属性を示しています。

<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 に登録します。 このメソッドは、サーバー関連の検証コードとクライアント関連の検証コードが別々のクラスにあるという 1 つの責任原則 に従います。 また、アダプターは 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. Program.cs でアダプター プロバイダーを DI に登録します。

    builder.Services.AddRazorPages()
        .AddMvcOptions(options =>
        {
            options.MaxModelValidationErrors = 50;
            options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
                _ => "The field is required.");
        });
    
    builder.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 static bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
        {
            if (attributes.ContainsKey(key))
            {
                return false;
            }
    
            attributes.Add(key, value);
            return true;
        }
    }
    

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

次のコードにより、Razor Pages のクライアント検証が無効になります。

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

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

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

上記の方法では、ASP.NET Core クラス ライブラリのクライアント側検証IdentityRazorが妨げられません。 詳細については、「ASP.NET Core プロジェクトの Identity のスキャフォールディング」を参照してください。

その他のリソース

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

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

モデルの状態

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

モデル バインドとモデル検証はどちらも、コントローラー アクションまたは Razor Pages ハンドラー メソッドの実行前に行われます。 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 応答」を参照してください。

検証を再実行する

検証は自動的に行われますが、手動で繰り返したい場合があります。 たとえば、プロパティの値を計算し、プロパティを計算値に設定した後で検証を再実行するような場合です。 検証を再実行するには、ModelStateDictionary.ClearValidationState を呼び出して検証対象のモデルに固有の検証をクリアし、その後に TryValidateModel と入力します。

public async Task<IActionResult> OnPostTryValidateAsync()
{
    var modifiedReleaseDate = DateTime.Now.Date;
    Movie.ReleaseDate = modifiedReleaseDate;

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

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

    return RedirectToPage("./Index");
}

検証属性

検証属性を使用して、モデルのプロパティの検証規則を指定できます。 サンプル アプリの次の例は、検証属性で注釈が付けられたモデル クラスを示しています。 属性は [ClassicMovie] カスタム検証属性であり、他の属性は組み込まれています。 [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]: プロパティまたはパラメーターを検証から除外する必要があることを示します。
  • [CreditCard]: プロパティにクレジット カード形式があることを検証します。 JQuery Validation の追加メソッドが必要です。
  • [比較]: モデル内の 2 つのプロパティが一致することを検証します。
  • [EmailAddress]: プロパティに電子メール形式があることを検証します。
  • [Phone]: プロパティに電話番号の形式があることを検証します。
  • [範囲]: プロパティ値が指定した範囲内にあることを検証します。
  • [RegularExpression]: プロパティ値が指定した正規表現と一致することを検証します。
  • [必須]: フィールドが 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(AllowEmptyStrings = true)] 属性を持つものとして処理されます。 Nullable コンテキストを有効にすることで、MVC は [Required(AllowEmptyStrings = true)] 属性が付けられているかのように、null 非許容型のプロパティまたはパラメーターの検証を暗黙的に開始します。 次のコードがあるとします。

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

アプリがビルド<Nullable>enable</Nullable>された場合、ON またはフォーム投稿のJS値Nameが不足すると、検証エラーが発生します。 null 値を許容する参照型を使用して、Name プロパティに null 値または欠損値を指定できるようにします。

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? を使用)。 許容<T> 値型は、標準の 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(AllowEmptyStrings = true)] 属性を持つものとして処理されます。 つまり、[Required(AllowEmptyStrings = true)] 属性を適用していない場合でも、クライアント側の検証が行われます。 ただし、属性を使用しない場合は、既定のエラー メッセージが表示されます。 カスタム エラー メッセージを指定するには、属性を使用します。

[Remote] 属性

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

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

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

    • 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] 属性の 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アクション パラメーターをRegularExpressionAttributephone検証するために使用します。

[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> など) や、検証コントロールを何も持たない複雑なオブジェクト グラフなどがあります。

クライアント側の検証

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

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

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

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

<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 Validation に渡されます。 したがって、動的に生成されるフォームでは、検証は自動的には機能しません。 検証を有効にするには、作成直後に動的フォームを解析するよう、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 Validation プラグインに渡されます。

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

$.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 Validation アダプターで動作する data- HTML 属性を生成することによって行われます。 次のサンプルのアダプター コードは、この記事で前に導入した [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 Validation のドキュメントをご覧ください。

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

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

次の例は、data-サンプル アプリClassicMovieの属性の属性を示しています。

<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 に登録します。 このメソッドは、サーバー関連の検証コードとクライアント関連の検証コードが別々のクラスにあるという 1 つの責任原則 に従います。 また、アダプターは 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;
        }
    }
    

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

次のコードにより、Razor Pages のクライアント検証が無効になります。

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

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

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

上記の方法では、ASP.NET Core クラス ライブラリのクライアント側のIdentityRazor検証が妨げられません。 詳細については、「ASP.NET Core プロジェクトの Identity のスキャフォールディング」を参照してください。

その他のリソース