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

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

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

モデルの状態

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

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

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 の追加メソッドが必要です。
  • [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 のソース コードをご覧ください。

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

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

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 を使って JSON プロパティ名を使用するように検証を構成しています。

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> でビルドされた場合、JSON またはフォーム ポストで Name の値がない場合、検証エラーになります。 [Required(AllowEmptyStrings = true)] 属性が暗黙的であるため、これは矛盾しているように見えるかもしれませんが、空の文字列が既定で null に変換されるため、これは予期される動作です。 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? を使用)。 Nullable<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 Validation の remote メソッドでは、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; } = null!;
    

JavaScript を無効にしたクライアントには、サーバー側の検証も実装する必要があります。

追加フィールド

[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) });
        }
    }
}

カスタム検証

次のコードは、モデルを調べた後にモデル エラーを追加する方法を示しています。

if (Contact.Name == Contact.ShortName)
{
    ModelState.AddModelError("Contact.ShortName", 
                             "Short name can't be the same as Name.");
}

次のコードは、コントローラーで検証テストを実装します。

if (contact.Name == contact.ShortName)
{
    ModelState.AddModelError(nameof(contact.ShortName),
                             "Short name can't be the same as Name.");
}

次のコードは、電話番号とメールアドレスが一意であることを確認します。

public async Task<IActionResult> OnPostAsync()
{
    // Attach Validation Error Message to the Model on validation failure.          

    if (Contact.Name == Contact.ShortName)
    {
        ModelState.AddModelError("Contact.ShortName", 
                                 "Short name can't be the same as Name.");
    }

    if (_context.Contact.Any(i => i.PhoneNumber == Contact.PhoneNumber))
    {
        ModelState.AddModelError("Contact.PhoneNumber",
                                  "The Phone number is already in use.");
    }
    if (_context.Contact.Any(i => i.Email == Contact.Email))
    {
        ModelState.AddModelError("Contact.Email", "The Email is already in use.");
    }

    if (!ModelState.IsValid || _context.Contact == null || Contact == null)
    {
        // if model is invalid, return the page with the model state errors.
        return Page();
    }
    _context.Contact.Add(Contact);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

次のコードは、コントローラーで検証テストを実装します。

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Id,Name,ShortName,Email,PhoneNumber")] Contact contact)
{
    // Attach Validation Error Message to the Model on validation failure.
    if (contact.Name == contact.ShortName)
    {
        ModelState.AddModelError(nameof(contact.ShortName),
                                 "Short name can't be the same as Name.");
    }

    if (_context.Contact.Any(i => i.PhoneNumber == contact.PhoneNumber))
    {
        ModelState.AddModelError(nameof(contact.PhoneNumber),
                                  "The Phone number is already in use.");
    }
    if (_context.Contact.Any(i => i.Email == contact.Email))
    {
        ModelState.AddModelError(nameof(contact.Email), "The Email is already in use.");
    }

    if (ModelState.IsValid)
    {
        _context.Add(contact);
        await _context.SaveChangesAsync();
        return RedirectToAction(nameof(Index));
    }
    return View(contact);
}

一意の電話番号またはメールアドレスの確認は、通常、リモート検証でも行われます。

ValidationResult

次のカスタム ValidateNameAttribute について考えてみましょう。

public class ValidateNameAttribute : ValidationAttribute
{
    public ValidateNameAttribute()
    {
        const string defaultErrorMessage = "Error with Name";
        ErrorMessage ??= defaultErrorMessage;
    }

    protected override ValidationResult? IsValid(object? value,
                                         ValidationContext validationContext)
    {
        if (value == null || string.IsNullOrWhiteSpace(value.ToString()))
        {
            return new ValidationResult("Name is required.");
        }

        if (value.ToString()!.ToLower().Contains("zz"))
        {

            return new ValidationResult(
                        FormatErrorMessage(validationContext.DisplayName));
        }

        return ValidationResult.Success;
    }
}

次のコードでは、カスタム [ValidateName] 属性が適用されます。

public class Contact
{
    public Guid Id { get; set; }

    [ValidateName(ErrorMessage = "Name must not contain `zz`")] 
    public string? Name { get; set; }
    public string? Email { get; set; }
    public string? PhoneNumber { get; set; }
}

モデルに zz が含まれている場合、新しい ValidationResult が返されます。

最上位ノードの検証

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

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

モデルのパラメーター検証に加え、モデルが関連付けられた最上位ノードが検証されます。 サンプル アプリからの次の例の 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) に達すると、検証は停止します。 この数は、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 に登録します。 この方法では単一責任の原則に従って、サーバー関連の検証コードとクライアント関連の検証コードは別のクラスになります。 アダプターには、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;
    });

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

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

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

問題の詳細

問題の詳細は、HTTP API エラーを記述する唯一の応答形式ではありませんが、一般的に HTTP API のエラーを報告するために使用されます。

問題の詳細サービスは、IProblemDetailsService インターフェイスを実装し、これにより、ASP.NET Core での問題の詳細の作成がサポートされます。 IServiceCollectionAddProblemDetails 拡張メソッドは、既定の IProblemDetailsService 実装を登録します。

ASP.NET Core アプリでは、次のミドルウェアによって、AddProblemDetails が呼び出されたときに問題の詳細 HTTP 応答が生成されます。ただし、Accept 要求 HTTP ヘッダーに、登録された IProblemDetailsWriter (既定値: application/json) によってサポートされるいずれかのコンテンツ タイプが含まれていない場合を除きます。

その他のリソース

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

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

モデルの状態

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

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

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 の追加メソッドが必要です。
  • [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(AllowEmptyStrings = true)] 属性を持つものとして処理されます。 Nullable コンテキストを有効にすることで、MVC は [Required(AllowEmptyStrings = true)] 属性が付けられているかのように、非ジェネリック型のプロパティまたはパラメーターの検証を暗黙的に開始します。 次のコードがあるとします。

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

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

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

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

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

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

ジェネリック型の 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? を使用)。 Nullable<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 Validation の remote メソッドでは、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; } = 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 メソッドでは、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) に達すると、検証は停止します。 この数は、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 に登録します。 この方法では単一責任の原則に従って、サーバー関連の検証コードとクライアント関連の検証コードは別のクラスになります。 アダプターには、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;
    });

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

  • すべての .cshtml ファイル内の _ValidationScriptsPartial への参照をコメントアウトします。
  • 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 の追加メソッドが必要です。
  • [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(AllowEmptyStrings = true)] 属性を持つものとして処理されます。 Nullable コンテキストを有効にすることで、MVC は [Required(AllowEmptyStrings = true)] 属性が付けられているかのように、null 非許容型のプロパティまたはパラメーターの検証を暗黙的に開始します。 次のコードがあるとします。

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

アプリが <Nullable>enable</Nullable> でビルドされた場合、JSON またはフォーム ポストで 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? を使用)。 Nullable<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 Validation の remote メソッドでは、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] 属性の 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> など) や、検証コントロールを何も持たない複雑なオブジェクト グラフなどがあります。

クライアント側の検証

クライアント側検証は、フォームが有効になるまで送信を許可しません。 送信ボタンをクリックすると、フォームの送信またはエラー メッセージの表示を行う 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 に登録します。 この方法では単一責任の原則に従って、サーバー関連の検証コードとクライアント関連の検証コードは別のクラスになります。 アダプターには、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;
    });

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

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

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

その他のリソース