ASP.NET Core MVC 和 Razor Pages 中的模型驗證

本文說明如何在 ASP.NET Core MVC 或 Razor Pages 應用程式中驗證使用者輸入。

檢視或下載範例程式碼 (如何下載)。

模型狀態

模型狀態代表來自兩個子系統的錯誤:模型繫結和模型驗證。 源自模型繫結的錯誤通常是資料轉換錯誤。 例如,在整數欄位中輸入「x」。 模型驗證會發生在模型繫結之後,並報告資料不符合商務規則的錯誤。 例如,在預期 1 到 5 的評等欄位中輸入 0。

模型繫結和模型繫結會在執行控制器動作或 Razor Pages 處理常式方法之前進行。 針對 Web 應用程式,此應用程式的責任為檢查 ModelState.IsValid 並做出適當回應。 Web 應用程式通常會以錯誤訊息重新顯示頁面,如下列 Razor Pages 範例所示:

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],其會顯示替代方式來實作自訂屬性。

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

內建屬性

下列是部分內建驗證屬性:

您可以在 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 屬性時,透過上述程式碼建立的錯誤訊息會是 「名稱長度必須介於 6 和 8 之間」。

若要找出哪些參數傳遞給 String.Format 以取得特定屬性的錯誤訊息,請參閱 DataAnnotations 原始程式碼

在驗證錯誤中使用 JSON 屬性名稱

根據預設,當驗證錯誤發生時,模型驗證會產生屬性名稱做為錯誤索引鍵的 ModelStateDictionary。 某些應用程式 (例如單頁應用程式) 受益於使用 JSON 屬性名稱來取得 Web API 所產生的驗證錯誤。 下列程式碼會設定驗證以透過 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 的參考型別和 [必要] 屬性

驗證系統會將不可為 Null 的參數或繫結屬性視為具有 [Required(AllowEmptyStrings = true)] 屬性。 MVC 會啟用 Nullable 內容,隱含地開始驗證不可為 Null 的屬性或參數,就像其已使用 [Required(AllowEmptyStrings = true)] 屬性一樣。 請考慮下列程式碼:

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.cs 中設定 SuppressImplicitRequiredAttributeForNonNullableReferenceTypes,即可停用此行為:

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

[必要] 在伺服器上執行驗證

在伺服器上,如果必要的值屬性為 Null,則會視為遺失。 不可為 Null 的欄位一律有效,且一律不會顯示 [Required] 屬性的錯誤訊息。

不過,不可為 Null 屬性的模型繫結可能會失敗,並產生一則錯誤訊息,例如 The value '' is invalid。 若要針對不可為 Null 型別的伺服器端驗證指定自訂錯誤訊息,您可以使用下列選項:

  • 將欄位設成可為 Null (例如 decimal?,而不是 decimal)。 可為 Null<T> 實值型別會視為如同標準的可為 Null 型別。

  • 指定模型繫結要使用的預設錯誤訊息,如下列範例所示:

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

    如需您可以為其設定預設訊息之模型繫結錯誤的詳細資訊,請參閱 DefaultModelBindingMessageProvider

[必要] 在用戶端上執行驗證

不可為 Null 的型別和字串,會在用戶端上以不同於在伺服器上的方式處理。 在用戶端上:

  • 只有在為值的輸入進行輸入後,才會將該值視為存在。 因此,用戶端驗證處理非 Null 型別的方式,會與可為 Null 型別的方式相同。
  • 字串欄位中的空白字元,會以 jQuery 驗證必要方法視為有效的輸入。 如果僅輸入空白字元,則伺服器端驗證會將必要的字串欄位視為無效。

如先前所述,不可為 Null 的型別會視為具有 [Required(AllowEmptyStrings = true)] 屬性。 這表示即使您不套用 [Required(AllowEmptyStrings = true)] 屬性,也可以取得用戶端驗證。 但是,如果您不使用該屬性,就會出現預設的錯誤訊息。 若要指定自訂錯誤訊息,請使用該屬性。

[遠端] 屬性

[Remote] 屬性會實作用戶端驗證,該驗證需要呼叫伺服器上的方法來判斷欄位輸入是否有效。 例如,應用程式可能需要驗證使用者名稱是否已經在使用中。

實作遠端驗證:

  1. 為 JavaScript 建立動作方法來呼叫。 JQuery 驗證遠端方法會預期 JSON 回應:

    • true 表示輸入資料有效。
    • falseundefinednull 表示輸入無效。 顯示預設錯誤訊息。
    • 任何其他字串都表示輸入無效。 將字串顯示為自訂錯誤訊息。

    自訂錯誤訊息的動作方法範例如下:

    [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] 屬性 (Attribute) 來標註屬性 (Property),如下列範例所示:

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

伺服器端驗證也必須針對已停用 JavaScript 的用戶端進行實作。

其他欄位

[Remote] 屬性 (Attribute) 的 AdditionalFields 屬性 (Property) 可讓您針對伺服器上的資料來驗證欄位組合。 例如,如果 User 模型具 FirstNameLastName 屬性,建議您確認沒有任何現有使用者已具有該組名稱。 下列範例顯示如何使用 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 屬性 (Property) 新增至模型,請設定 [Remote] 屬性 (Attribute),如下列範例所示:

[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 型別。 類別層級驗證的另一個選項,是在模型類別中實作 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)中,有兩種形式。 第一個表單會提交 Age99 做為查詢字串參數:https://localhost:5001/Users/CheckAge?Age=99

從查詢字串提交正確格式化的 age 時,即會驗證表單。

[檢查年齡] 頁面上的第二個表單會在要求本文中提交 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 低調驗證 (jQuery Unobtrusive Validation) 指令碼是建置在熱門 jQuery 驗證外掛程式上的自訂 Microsoft 前端程式庫。 若沒有 jQuery 低調驗證,您就必須在兩個地方撰寫相同的驗證邏輯程式碼:一次在模型屬性 (Property) 上的伺服器端驗證屬性 (Attribute),另一次在用戶端指令碼中。 反之,標記協助程式HTML 協助程式使用模型屬性 (Property) 中的驗證屬性 (Attribute) 和類型中繼資料,來轉譯需要驗證之表單項目的 HTML 5 data- 屬性 (Attribute)。 jQuery 低調驗證會剖析 data- 屬性並將邏輯傳遞至 jQuery 驗證,以有效地將伺服器端驗證邏輯「複製」到用戶端。 您可以使用標記協助程式來顯示用戶端的驗證錯誤,如下所示:

<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- 屬性 (attribute) 會對應至 Movie.ReleaseDate 屬性 (property) 的驗證屬性 (attribute)。 data-val-required 屬性包含使用者未填入發行日期欄位時所要顯示的錯誤訊息。 jQuery 低調驗證會將此值傳遞至 jQuery 驗證的 required() 方法,然後在隨附的 <span> 項目中顯示該訊息。

資料型別驗證是根據屬性的 .NET 型別,除非是由 [DataType] 屬性覆寫。 瀏覽器具有自己的預設錯誤訊息,但 jQuery 驗證低調驗證套件可以覆寫這些訊息。 [DataType] 屬性和子類別 (例如 [EmailAddress]) 可讓您指定錯誤訊息。

低調驗證

如需不顯眼驗證的資訊,請參閱此 GitHub 問題

將驗證新增至動態表單

jQuery 低調驗證會在第一次載入頁面時,將驗證邏輯和傳遞參數至 jQuery 驗證。 因此,驗證不會在動態產生的表單上自動運作。 若要啟用驗證,請指示 jQuery 低調驗證在建立動態表單之後立即進行剖析。 例如,下列程式碼會在透過 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() 方法接受 jQuery 選取器的一個引數。 此方法會指示 jQuery 低調驗證在該選取器內剖析表單的 data- 屬性。 然後,這些屬性的值會傳遞至 jQuery 驗證外掛程式。

將驗證新增至動態控制項

$.validator.unobtrusive.parse() 方法適用於整個表單,而非動態產生的個別控制項 (例如 <input><select/>)。 若要重新剖析表單,請移除先前剖析表單時新增的驗證資料,如下列範例所示:

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

自訂用戶端驗證

自訂用戶端驗證是透過產生使用自訂 jQuery 驗證配接器的 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 驗證文件

用於指定欄位之配接器的使用,是由 data- 屬性觸發,因此會:

  • 將欄位加上旗標為待驗證 (data-val="true")。
  • 識別驗證規則名稱和錯誤訊息文字 (例如 data-val-rulename="Error message.")。
  • 提供驗證程式需要的任何其他參數 (例如 data-val-rulename-param1="value")。

下列範例顯示範例應用程式ClassicMovie 屬性的 data- 屬性:

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

如前文所述,標記協助程式HTML 協助程式 使用來自驗證屬性的資訊來轉譯 data- 屬性。 有二個選項可撰寫用於建立自訂 data- HTML 屬性的程式碼:

用戶端驗證的屬性配接器

這種在 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 專案中的 Scaffold Identity

問題詳細資料

問題詳細資料 並不是描述 HTTP API 錯誤的唯一回應格式,不過,它們通常會用來報告 HTTP API 的錯誤。

問題詳細資料服務會實作 IProblemDetailsService 介面 (其支援在 ASP.NET Core 中建立問題詳細資料)。 IServiceCollection 上的 AddProblemDetails 擴充方法會註冊預設的 IProblemDetailsService 實作。

在 ASP.NET Core 應用程式中,下列中介軟體會在呼叫 AddProblemDetails 時產生問題詳細資料 HTTP 回應,除非 Accept 要求 HTTP 標頭不包含註冊的 IProblemDetailsWriter 所支援的其中一個內容類型 (預設值:application/json):

其他資源

本文說明如何在 ASP.NET Core MVC 或 Razor Pages 應用程式中驗證使用者輸入。

檢視或下載範例程式碼 (如何下載)。

模型狀態

模型狀態代表來自兩個子系統的錯誤:模型繫結和模型驗證。 源自模型繫結的錯誤通常是資料轉換錯誤。 例如,在整數欄位中輸入「x」。 模型驗證會發生在模型繫結之後,並報告資料不符合商務規則的錯誤。 例如,在預期 1 到 5 的評等欄位中輸入 0。

模型繫結和模型繫結會在執行控制器動作或 Razor Pages 處理常式方法之前進行。 針對 Web 應用程式,此應用程式的責任為檢查 ModelState.IsValid 並做出適當回應。 Web 應用程式通常會以錯誤訊息重新顯示頁面,如下列 Razor Pages 範例所示:

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],其會顯示替代方式來實作自訂屬性。

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

內建屬性

下列是部分內建驗證屬性:

您可以在 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 屬性時,透過上述程式碼建立的錯誤訊息會是 「名稱長度必須介於 6 和 8 之間」。

若要找出哪些參數傳遞給 String.Format 以取得特定屬性的錯誤訊息,請參閱 DataAnnotations 原始程式碼

不可為 Null 的參考型別和 [必要] 屬性

驗證系統會將不可為 Null 的參數或繫結屬性視為具有 [Required(AllowEmptyStrings = true)] 屬性。 MVC 會啟用 Nullable 內容,隱含地開始驗證不可為 Null 的屬性非泛型型別或參數,就像其已使用 [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.cs 中設定 SuppressImplicitRequiredAttributeForNonNullableReferenceTypes,即可停用此行為:

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

[必要] 在伺服器上執行驗證

在伺服器上,如果必要的值屬性為 Null,則會視為遺失。 不可為 Null 的欄位一律有效,且一律不會顯示 [Required] 屬性的錯誤訊息。

不過,不可為 Null 屬性的模型繫結可能會失敗,並產生一則錯誤訊息,例如 The value '' is invalid。 若要針對不可為 Null 型別的伺服器端驗證指定自訂錯誤訊息,您可以使用下列選項:

  • 將欄位設成可為 Null (例如 decimal?,而不是 decimal)。 可為 Null<T> 實值型別會視為如同標準的可為 Null 型別。

  • 指定模型繫結要使用的預設錯誤訊息,如下列範例所示:

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

    如需您可以為其設定預設訊息之模型繫結錯誤的詳細資訊,請參閱 DefaultModelBindingMessageProvider

[必要] 在用戶端上執行驗證

不可為 Null 的型別和字串,會在用戶端上以不同於在伺服器上的方式處理。 在用戶端上:

  • 只有在為值的輸入進行輸入後,才會將該值視為存在。 因此,用戶端驗證處理非 Null 型別的方式,會與可為 Null 型別的方式相同。
  • 字串欄位中的空白字元,會以 jQuery 驗證必要方法視為有效的輸入。 如果僅輸入空白字元,則伺服器端驗證會將必要的字串欄位視為無效。

如先前所述,不可為 Null 的型別會視為具有 [Required(AllowEmptyStrings = true)] 屬性。 這表示即使您不套用 [Required(AllowEmptyStrings = true)] 屬性,也可以取得用戶端驗證。 但是,如果您不使用該屬性,就會出現預設的錯誤訊息。 若要指定自訂錯誤訊息,請使用該屬性。

[遠端] 屬性

[Remote] 屬性會實作用戶端驗證,該驗證需要呼叫伺服器上的方法來判斷欄位輸入是否有效。 例如,應用程式可能需要驗證使用者名稱是否已經在使用中。

實作遠端驗證:

  1. 為 JavaScript 建立動作方法來呼叫。 JQuery 驗證遠端方法會預期 JSON 回應:

    • true 表示輸入資料有效。
    • falseundefinednull 表示輸入無效。 顯示預設錯誤訊息。
    • 任何其他字串都表示輸入無效。 將字串顯示為自訂錯誤訊息。

    自訂錯誤訊息的動作方法範例如下:

    [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] 屬性 (Attribute) 來標註屬性 (Property),如下列範例所示:

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

其他欄位

[Remote] 屬性 (Attribute) 的 AdditionalFields 屬性 (Property) 可讓您針對伺服器上的資料來驗證欄位組合。 例如,如果 User 模型具 FirstNameLastName 屬性,建議您確認沒有任何現有使用者已具有該組名稱。 下列範例顯示如何使用 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 屬性 (Property) 新增至模型,請設定 [Remote] 屬性 (Attribute),如下列範例所示:

[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 型別。 類別層級驗證的另一個選項,是在模型類別中實作 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)中,有兩種形式。 第一個表單會提交 Age99 做為查詢字串參數:https://localhost:5001/Users/CheckAge?Age=99

從查詢字串提交正確格式化的 age 時,即會驗證表單。

[檢查年齡] 頁面上的第二個表單會在要求本文中提交 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 低調驗證 (jQuery Unobtrusive Validation) 指令碼是建置在熱門 jQuery 驗證外掛程式上的自訂 Microsoft 前端程式庫。 若沒有 jQuery 低調驗證,您就必須在兩個地方撰寫相同的驗證邏輯程式碼:一次在模型屬性 (Property) 上的伺服器端驗證屬性 (Attribute),另一次在用戶端指令碼中。 反之,標記協助程式HTML 協助程式使用模型屬性 (Property) 中的驗證屬性 (Attribute) 和類型中繼資料,來轉譯需要驗證之表單項目的 HTML 5 data- 屬性 (Attribute)。 jQuery 低調驗證會剖析 data- 屬性並將邏輯傳遞至 jQuery 驗證,以有效地將伺服器端驗證邏輯「複製」到用戶端。 您可以使用標記協助程式來顯示用戶端的驗證錯誤,如下所示:

<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- 屬性 (attribute) 會對應至 Movie.ReleaseDate 屬性 (property) 的驗證屬性 (attribute)。 data-val-required 屬性包含使用者未填入發行日期欄位時所要顯示的錯誤訊息。 jQuery 低調驗證會將此值傳遞至 jQuery 驗證的 required() 方法,然後在隨附的 <span> 項目中顯示該訊息。

資料型別驗證是根據屬性的 .NET 型別,除非是由 [DataType] 屬性覆寫。 瀏覽器具有自己的預設錯誤訊息,但 jQuery 驗證低調驗證套件可以覆寫這些訊息。 [DataType] 屬性和子類別 (例如 [EmailAddress]) 可讓您指定錯誤訊息。

低調驗證

如需不顯眼驗證的資訊,請參閱此 GitHub 問題

將驗證新增至動態表單

jQuery 低調驗證會在第一次載入頁面時,將驗證邏輯和傳遞參數至 jQuery 驗證。 因此,驗證不會在動態產生的表單上自動運作。 若要啟用驗證,請指示 jQuery 低調驗證在建立動態表單之後立即進行剖析。 例如,下列程式碼會在透過 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() 方法接受 jQuery 選取器的一個引數。 此方法會指示 jQuery 低調驗證在該選取器內剖析表單的 data- 屬性。 然後,這些屬性的值會傳遞至 jQuery 驗證外掛程式。

將驗證新增至動態控制項

$.validator.unobtrusive.parse() 方法適用於整個表單,而非動態產生的個別控制項 (例如 <input><select/>)。 若要重新剖析表單,請移除先前剖析表單時新增的驗證資料,如下列範例所示:

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

自訂用戶端驗證

自訂用戶端驗證是透過產生使用自訂 jQuery 驗證配接器的 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 驗證文件

用於指定欄位之配接器的使用,是由 data- 屬性觸發,因此會:

  • 將欄位加上旗標為待驗證 (data-val="true")。
  • 識別驗證規則名稱和錯誤訊息文字 (例如 data-val-rulename="Error message.")。
  • 提供驗證程式需要的任何其他參數 (例如 data-val-rulename-param1="value")。

下列範例顯示範例應用程式ClassicMovie 屬性的 data- 屬性:

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

如前文所述,標記協助程式HTML 協助程式 使用來自驗證屬性的資訊來轉譯 data- 屬性。 有二個選項可撰寫用於建立自訂 data- HTML 屬性的程式碼:

用戶端驗證的屬性配接器

這種在 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 專案中的 Scaffold Identity

其他資源

本文說明如何在 ASP.NET Core MVC 或 Razor Pages 應用程式中驗證使用者輸入。

檢視或下載範例程式碼 (如何下載)。

模型狀態

模型狀態代表來自兩個子系統的錯誤:模型繫結和模型驗證。 源自模型繫結的錯誤通常是資料轉換錯誤。 例如,在整數欄位中輸入「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],其會顯示替代方式來實作自訂屬性。

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

內建屬性

下列是部分內建驗證屬性:

您可以在 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 屬性時,透過上述程式碼建立的錯誤訊息會是 「名稱長度必須介於 6 和 8 之間」。

若要找出哪些參數傳遞給 String.Format 以取得特定屬性的錯誤訊息,請參閱 DataAnnotations 原始程式碼

不可為 Null 的參考型別和 [必要] 屬性

驗證系統會將不可為 Null 的參數或繫結屬性視為具有 [Required(AllowEmptyStrings = true)] 屬性。 MVC 會啟用 Nullable 內容,隱含地開始驗證不可為 Null 的屬性或參數,就像其已使用 [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; }
}

Startup.ConfigureServices 中設定 SuppressImplicitRequiredAttributeForNonNullableReferenceTypes,即可停用此行為:

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

[必要] 在伺服器上執行驗證

在伺服器上,如果必要的值屬性為 Null,則會視為遺失。 不可為 Null 的欄位一律有效,且一律不會顯示 [Required] 屬性的錯誤訊息。

不過,不可為 Null 屬性的模型繫結可能會失敗,並產生一則錯誤訊息,例如 The value '' is invalid。 若要針對不可為 Null 型別的伺服器端驗證指定自訂錯誤訊息,您可以使用下列選項:

  • 將欄位設成可為 Null (例如 decimal?,而不是 decimal)。 可為 Null<T> 實值型別會視為如同標準的可為 Null 型別。

  • 指定模型繫結要使用的預設錯誤訊息,如下列範例所示:

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

    如需您可以為其設定預設訊息之模型繫結錯誤的詳細資訊,請參閱 DefaultModelBindingMessageProvider

[必要] 在用戶端上執行驗證

不可為 Null 的型別和字串,會在用戶端上以不同於在伺服器上的方式處理。 在用戶端上:

  • 只有在為值的輸入進行輸入後,才會將該值視為存在。 因此,用戶端驗證處理非 Null 型別的方式,會與可為 Null 型別的方式相同。
  • 字串欄位中的空白字元,會以 jQuery 驗證必要方法視為有效的輸入。 如果僅輸入空白字元,則伺服器端驗證會將必要的字串欄位視為無效。

如先前所述,不可為 Null 的型別會視為具有 [Required(AllowEmptyStrings = true)] 屬性。 這表示即使您不套用 [Required(AllowEmptyStrings = true)] 屬性,也可以取得用戶端驗證。 但是,如果您不使用該屬性,就會出現預設的錯誤訊息。 若要指定自訂錯誤訊息,請使用該屬性。

[遠端] 屬性

[Remote] 屬性會實作用戶端驗證,該驗證需要呼叫伺服器上的方法來判斷欄位輸入是否有效。 例如,應用程式可能需要驗證使用者名稱是否已經在使用中。

實作遠端驗證:

  1. 為 JavaScript 建立動作方法來呼叫。 JQuery 驗證遠端方法會預期 JSON 回應:

    • true 表示輸入資料有效。
    • falseundefinednull 表示輸入無效。 顯示預設錯誤訊息。
    • 任何其他字串都表示輸入無效。 將字串顯示為自訂錯誤訊息。

    自訂錯誤訊息的動作方法範例如下:

    [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] 屬性 (Attribute) 來標註屬性 (Property),如下列範例所示:

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

其他欄位

[Remote] 屬性 (Attribute) 的 AdditionalFields 屬性 (Property) 可讓您針對伺服器上的資料來驗證欄位組合。 例如,如果 User 模型具 FirstNameLastName 屬性,建議您確認沒有任何現有使用者已具有該組名稱。 下列範例顯示如何使用 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 屬性 (Property) 新增至模型,請設定 [Remote] 屬性 (Attribute),如下列範例所示:

[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 型別。 類別層級驗證的另一個選項,是在模型類別中實作 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)中,有兩種形式。 第一個表單會提交 Age99 做為查詢字串參數:https://localhost:5001/Users/CheckAge?Age=99

從查詢字串提交正確格式化的 age 時,即會驗證表單。

[檢查年齡] 頁面上的第二個表單會在要求本文中提交 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 低調驗證 (jQuery Unobtrusive Validation) 指令碼是建置在熱門 jQuery 驗證外掛程式上的自訂 Microsoft 前端程式庫。 若沒有 jQuery 低調驗證,您就必須在兩個地方撰寫相同的驗證邏輯程式碼:一次在模型屬性 (Property) 上的伺服器端驗證屬性 (Attribute),另一次在用戶端指令碼中。 反之,標記協助程式HTML 協助程式使用模型屬性 (Property) 中的驗證屬性 (Attribute) 和類型中繼資料,來轉譯需要驗證之表單項目的 HTML 5 data- 屬性 (Attribute)。 jQuery 低調驗證會剖析 data- 屬性並將邏輯傳遞至 jQuery 驗證,以有效地將伺服器端驗證邏輯「複製」到用戶端。 您可以使用標記協助程式來顯示用戶端的驗證錯誤,如下所示:

<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- 屬性 (attribute) 會對應至 Movie.ReleaseDate 屬性 (property) 的驗證屬性 (attribute)。 data-val-required 屬性包含使用者未填入發行日期欄位時所要顯示的錯誤訊息。 jQuery 低調驗證會將此值傳遞至 jQuery 驗證的 required() 方法,然後在隨附的 <span> 項目中顯示該訊息。

資料型別驗證是根據屬性的 .NET 型別,除非是由 [DataType] 屬性覆寫。 瀏覽器具有自己的預設錯誤訊息,但 jQuery 驗證低調驗證套件可以覆寫這些訊息。 [DataType] 屬性和子類別 (例如 [EmailAddress]) 可讓您指定錯誤訊息。

低調驗證

如需不顯眼驗證的資訊,請參閱此 GitHub 問題

將驗證新增至動態表單

jQuery 低調驗證會在第一次載入頁面時,將驗證邏輯和傳遞參數至 jQuery 驗證。 因此,驗證不會在動態產生的表單上自動運作。 若要啟用驗證,請指示 jQuery 低調驗證在建立動態表單之後立即進行剖析。 例如,下列程式碼會在透過 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() 方法接受 jQuery 選取器的一個引數。 此方法會指示 jQuery 低調驗證在該選取器內剖析表單的 data- 屬性。 然後,這些屬性的值會傳遞至 jQuery 驗證外掛程式。

將驗證新增至動態控制項

$.validator.unobtrusive.parse() 方法適用於整個表單,而非動態產生的個別控制項 (例如 <input><select/>)。 若要重新剖析表單,請移除先前剖析表單時新增的驗證資料,如下列範例所示:

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

自訂用戶端驗證

自訂用戶端驗證是透過產生使用自訂 jQuery 驗證配接器的 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 驗證文件

用於指定欄位之配接器的使用,是由 data- 屬性觸發,因此會:

  • 將欄位加上旗標為待驗證 (data-val="true")。
  • 識別驗證規則名稱和錯誤訊息文字 (例如 data-val-rulename="Error message.")。
  • 提供驗證程式需要的任何其他參數 (例如 data-val-rulename-param1="value")。

下列範例顯示範例應用程式ClassicMovie 屬性的 data- 屬性:

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

如前文所述,標記協助程式HTML 協助程式 使用來自驗證屬性的資訊來轉譯 data- 屬性。 有二個選項可撰寫用於建立自訂 data- HTML 屬性的程式碼:

用戶端驗證的屬性配接器

這種在 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 專案中的 Scaffold Identity

其他資源