ASP.NET Core MVC 和 Razor Pages 中的模型驗證Model validation in ASP.NET Core MVC and Razor Pages

本文說明如何在 ASP.NET Core MVC 或 Razor Pages 應用程式中驗證使用者輸入。This article explains how to validate user input in an ASP.NET Core MVC or Razor Pages app.

檢視或下載範例程式碼 (如何下載)。View or download sample code (how to download).

模型狀態Model state

模型狀態代表來自兩個子系統的錯誤:模型繫結和模型驗證。Model state represents errors that come from two subsystems: model binding and model validation. 源自模型繫結的錯誤通常是資料轉換錯誤 (例如在必須輸入整數的欄位中輸入 "x")。Errors that originate from model binding are generally data conversion errors (for example, an "x" is entered in a field that expects an integer). 模型驗證發生於模型繫結之後,並會在資料不符合商務規則時回報錯誤 (例如在必須輸入 1 到 5 之間評等的欄位中輸入 0)。Model validation occurs after model binding and reports errors where the data doesn't conform to business rules (for example, a 0 is entered in a field that expects a rating between 1 and 5).

模型繫結和驗證會在執行控制器動作或 Razor Pages 處理常式方法之前進行。Both model binding and validation occur before the execution of a controller action or a Razor Pages handler method. 針對 Web 應用程式,此應用程式的責任為檢查 ModelState.IsValid 並做出適當回應。For web apps, it's the app's responsibility to inspect ModelState.IsValid and react appropriately. Web 應用程式通常會重新顯示頁面,並出現錯誤訊息:Web apps typically redisplay the page with an error message:

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

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

    return RedirectToPage("./Index");
}

如果 Web API 控制器具有 [ApiController] 屬性,則該控制器就不需要檢查 ModelState.IsValidWeb API controllers don't have to check ModelState.IsValid if they have the [ApiController] attribute. 在此情況下,當模型狀態無效時,會自動傳回 HTTP 400 回應並包含問題的詳細資料。In that case, an automatic HTTP 400 response containing issue details is returned when model state is invalid. 如需詳細資訊,請參閱自動 HTTP 400 回應For more information, see Automatic HTTP 400 responses.

重新執行驗證Rerun validation

驗證會自動進行,但建議您以手動方式重複進行。Validation is automatic, but you might want to repeat it manually. 例如,您可能會計算屬性的值,並在將屬性設定為計算的值之後重新執行驗證。For example, you might compute a value for a property and want to rerun validation after setting the property to the computed value. 若要重新執行驗證,請呼叫 TryValidateModel 方法,如下所示:To rerun validation, call the TryValidateModel method, as shown here:

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

TryValidateModel(movie);

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

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

return View(movie);

驗證屬性Validation attributes

驗證屬性可讓您指定模型屬性的驗證規則。Validation attributes let you specify validation rules for model properties. 下列來自範例應用程式的範例,顯示以驗證屬性標註的模型類別。The following example from the sample app shows a model class that is annotated with validation attributes. [ClassicMovie] 屬性是自訂驗證屬性,其他的則是內建。The [ClassicMovie] attribute is a custom validation attribute and the others are built-in. (未顯示的是 [ClassicMovie2],其會顯示替代方式來實作自訂屬性。)(Not shown is [ClassicMovie2], which shows an alternative way to implement a custom attribute.)

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

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

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

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

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

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

    public bool Preorder { get; set; }
}

內建屬性Built-in attributes

下列是部分內建驗證屬性:Here are some of the built-in validation attributes:

  • [CreditCard]:驗證屬性是否具有信用卡格式。[CreditCard]: Validates that the property has a credit card format.
  • [Compare]:驗證模型比對中的兩個屬性。[Compare]: Validates that two properties in a model match.
  • [EmailAddress]:驗證屬性是否具有電子郵件格式。[EmailAddress]: Validates that the property has an email format.
  • [Phone]:驗證屬性是否具有電話號碼格式。[Phone]: Validates that the property has a telephone number format.
  • [Range]:驗證屬性值是否落在指定的範圍內。[Range]: Validates that the property value falls within a specified range.
  • [RegularExpression]:驗證屬性值是否符合指定的規則運算式。[RegularExpression]: Validates that the property value matches a specified regular expression.
  • [Required]:驗證欄位不是 Null。[Required]: Validates that the field is not null. 請參閱 [必要] 屬性以取得此屬性行為的詳細資料。See [Required] attribute for details about this attribute's behavior.
  • [StringLength]:驗證字串屬性值未超過指定的長度上限。[StringLength]: Validates that a string property value doesn't exceed a specified length limit.
  • [Url]:驗證屬性是否具有 URL 格式。[Url]: Validates that the property has a URL format.
  • [Remote]:藉由在伺服器上呼叫動作方法來驗證用戶端上的輸入。[Remote]: Validates input on the client by calling an action method on the server. 請參閱 [遠端] 屬性以取得此屬性行為的詳細資料。See [Remote] attribute for details about this attribute's behavior.

您可以在 System.ComponentModel.DataAnnotations 命名空間中找到驗證屬性的完整清單。A complete list of validation attributes can be found in the System.ComponentModel.DataAnnotations namespace.

錯誤訊息Error messages

驗證屬性可讓您指定要顯示的無效輸入錯誤訊息。Validation attributes let you specify the error message to be displayed for invalid input. 例如:For example:

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

就內部而言,屬性會使用欄位名稱的預留位置呼叫 String.Format,有時候也會使用其他預留位置。Internally, the attributes call String.Format with a placeholder for the field name and sometimes additional placeholders. 例如:For example:

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

套用至 Name 屬性時,透過上述程式碼建立的錯誤訊息會是 「名稱長度必須介於 6 和 8 之間」。When applied to a Name property, the error message created by the preceding code would be "Name length must be between 6 and 8.".

若要找出哪些參數傳遞給 String.Format 以取得特定屬性的錯誤訊息,請參閱 DataAnnotations 原始程式碼To find out which parameters are passed to String.Format for a particular attribute's error message, see the DataAnnotations source code.

[必要] 屬性[Required] attribute

根據預設,驗證系統會將不可為 Null 的參數或屬性視為具有 [Required] 屬性。By default, the validation system treats non-nullable parameters or properties as if they had a [Required] attribute. 例如 decimalint實值型別不可為 Null。Value types such as decimal and int are non-nullable.

[必要] 在伺服器上執行驗證[Required] validation on the server

在伺服器上,如果必要的值屬性為 Null,則會視為遺失。On the server, a required value is considered missing if the property is null. 不可為 Null 的欄位一律有效,且一律不會顯示 [必要] 屬性的錯誤訊息。A non-nullable field is always valid, and the [Required] attribute's error message is never displayed.

不過,不可為 Null 屬性的模型繫結可能會失敗,並產生一則錯誤訊息,例如 The value '' is invalidHowever, model binding for a non-nullable property may fail, resulting in an error message such as The value '' is invalid. 若要針對不可為 Null 型別的伺服器端驗證指定自訂錯誤訊息,您可以使用下列選項:To specify a custom error message for server-side validation of non-nullable types, you have the following options:

  • 將欄位設成可為 Null (例如 decimal?,而不是 decimal)。Make the field nullable (for example, decimal? instead of decimal). 可為 Null<T> 實值型別會視為如同標準的可為 Null 型別。Nullable<T> value types are treated like standard nullable types.

  • 指定模型繫結要使用的預設錯誤訊息,如下列範例所示:Specify the default error message to be used by model binding, as shown in the following example:

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

    如需您可以為其設定預設訊息之模型繫結錯誤的詳細資訊,請參閱 DefaultModelBindingMessageProviderFor more information about model binding errors that you can set default messages for, see DefaultModelBindingMessageProvider.

[必要] 在用戶端上執行驗證[Required] validation on the client

不可為 Null 的型別和字串,會在用戶端上以不同於在伺服器上的方式處理。Non-nullable types and strings are handled differently on the client compared to the server. 在用戶端上:On the client:

  • 只有在為值的輸入進行輸入後,才會將該值視為存在。A value is considered present only if input is entered for it. 因此,用戶端驗證處理非 Null 型別的方式,會與可為 Null 型別的方式相同。Therefore, client-side validation handles non-nullable types the same as nullable types.
  • 字串欄位中的空白字元,會以 jQuery 驗證必要方法視為有效的輸入。Whitespace in a string field is considered valid input by the jQuery Validation required method. 如果僅輸入空白字元,則伺服器端驗證會將必要的字串欄位視為無效。Server-side validation considers a required string field invalid if only whitespace is entered.

如先前所述,不可為 Null 的型別會視為具有 [Required] 屬性。As noted earlier, non-nullable types are treated as though they had a [Required] attribute. 這表示即使您不套用 [Required] 屬性,也可以取得用戶端驗證。That means you get client-side validation even if you don't apply the [Required] attribute. 但是,如果您不使用該屬性,就會出現預設的錯誤訊息。But if you don't use the attribute, you get a default error message. 若要指定自訂錯誤訊息,請使用該屬性。To specify a custom error message, use the attribute.

[遠端] 屬性[Remote] attribute

[Remote] 屬性會實作用戶端驗證,該驗證需要呼叫伺服器上的方法來判斷欄位輸入是否有效。The [Remote] attribute implements client-side validation that requires calling a method on the server to determine whether field input is valid. 例如,應用程式可能需要驗證使用者名稱是否已經在使用中。For example, the app may need to verify whether a user name is already in use.

實作遠端驗證:To implement remote validation:

  1. 為 JavaScript 建立動作方法來呼叫。Create an action method for JavaScript to call. JQuery 驗證遠端方法會預期 JSON 回應:The jQuery Validate remote method expects a JSON response:

    • "true" 表示輸入資料有效。"true" means the input data is valid.
    • "false"undefinednull 表示輸入無效。"false", undefined, or null means the input is invalid. 顯示預設錯誤訊息。Display the default error message.
    • 任何其他字串都表示輸入無效。Any other string means the input is invalid. 將字串顯示為自訂錯誤訊息。Display the string as a custom error message.

    自訂錯誤訊息的動作方法範例如下:Here's an example of an action method that returns a custom error message:

    [AcceptVerbs("Get", "Post")]
    public IActionResult VerifyEmail(string email)
    {
        if (!_userRepository.VerifyEmail(email))
        {
            return Json($"Email {email} is already in use.");
        }
    
        return Json(true);
    }
    
  2. 在模型類別中,使用指向驗證動作方法的 [Remote] 屬性 (Attribute) 來標註屬性 (Property),如下列範例所示:In the model class, annotate the property with a [Remote] attribute that points to the validation action method, as shown in the following example:

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

    [Remote] 屬性位於 Microsoft.AspNetCore.Mvc 命名空間。The [Remote] attribute is in the Microsoft.AspNetCore.Mvc namespace. 如不使用 Microsoft.AspNetCore.AppMicrosoft.AspNetCore.All 中繼套件,請安裝 Microsoft.AspNetCore.Mvc.ViewFeatures NuGet 套件。Install the Microsoft.AspNetCore.Mvc.ViewFeatures NuGet package if you're not using the Microsoft.AspNetCore.App or Microsoft.AspNetCore.All metapackage.

其他欄位Additional fields

[Remote] 屬性 (Attribute) 的 AdditionalFields 屬性 (Property) 可讓您針對伺服器上的資料來驗證欄位組合。The AdditionalFields property of the [Remote] attribute lets you validate combinations of fields against data on the server. 例如,如果 User 模型具 FirstNameLastName 屬性,建議您確認沒有任何現有使用者已具有該組名稱。For example, if the User model had FirstName and LastName properties, you might want to verify that no existing users already have that pair of names. 下列範例顯示如何使用 AdditionalFieldsThe following example shows how to use AdditionalFields:

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

AdditionalFields 可能已明確設定為 "FirstName""LastName" 字串,但使用 nameof 運算子可簡化稍後的重構。AdditionalFields could be set explicitly to the strings "FirstName" and "LastName", but using the nameof operator simplifies later refactoring. 這項驗證的動作方法必須接受名字和姓氏引數:The action method for this validation must accept both first name and last name arguments:

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

    return Json(data: true);
}

當使用者輸入名字或姓氏時,JavaScript 會進行遠端呼叫以查看該組名稱是否已在使用中。When the user enters a first or last name, JavaScript makes a remote call to see if that pair of names has been taken.

若要驗證兩個或多個其他欄位,請以逗號分隔清單來提供這些欄位。To validate two or more additional fields, provide them as a comma-delimited list. 例如,若要將 MiddleName 屬性 (Property) 新增至模型,請設定 [Remote] 屬性 (Attribute),如下列範例所示:For example, to add a MiddleName property to the model, set the [Remote] attribute as shown in the following example:

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

如同所有屬性引數,AdditionalFields 必須是常數運算式。AdditionalFields, like all attribute arguments, must be a constant expression. 因此,請勿使用內插字串或呼叫 Join 來初始化 AdditionalFieldsTherefore, don't use an interpolated string or call Join to initialize AdditionalFields.

內建屬性的替代項目Alternatives to built-in attributes

如果您需要內建屬性未提供的驗證,您可以:If you need validation not provided by built-in attributes, you can:

自訂屬性Custom attributes

在內建驗證屬性無法處理的情況下,您可以建立自訂驗證屬性。For scenarios that the built-in validation attributes don't handle, you can create custom validation attributes. 建立繼承自 ValidationAttribute 的類別,並覆寫 IsValid 方法。Create a class that inherits from ValidationAttribute, and override the IsValid method.

IsValid 方法會接受名為 value 的物件,這是要驗證的輸入。The IsValid method accepts an object named value, which is the input to be validated. 多載也會接受 ValidationContext 物件,該物件會提供其他資訊,例如模型繫結所建立的模型執行個體。An overload also accepts a ValidationContext object, which provides additional information, such as the model instance created by model binding.

下列範例會驗證 Classic 內容類型中電影的發行日期,不晚於指定的年份。The following example validates that the release date for a movie in the Classic genre isn't later than a specified year. [ClassicMovie2] 屬性會先檢查內容類型,如果是 Classic 才會繼續。The [ClassicMovie2] attribute checks the genre first and continues only if it's Classic. 針對識別為 Classic 的電影,會檢查發行日期,以確定其不晚於傳遞至屬性建構函式的限制。)For movies identified as classics, it checks the release date to make sure it's not later than the limit passed to the attribute constructor.)

public class ClassicMovieAttribute : ValidationAttribute
{
    private int _year;

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

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

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

        return ValidationResult.Success;
    }

    public int Year => _year;

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

上述範例中的 movie 變數代表 Movie 物件,該物件包含表單送出中的資料。The movie variable in the preceding example represents a Movie object that contains the data from the form submission. IsValid 方法會檢查日期與內容類型。The IsValid method checks the date and genre. 驗證成功時,IsValid 會傳回 ValidationResult.Success 程式碼。Upon successful validation, IsValid returns a ValidationResult.Success code. 驗證失敗時,會傳回 ValidationResult 和錯誤訊息。When validation fails, a ValidationResult with an error message is returned.

IValidatableObjectIValidatableObject

上述範例僅適用於 Movie 型別。The preceding example works only with Movie types. 類別層級驗證的另一個選項,是在模型類別中實作 IValidatableObject,如下列範例所示:Another option for class-level validation is to implement IValidatableObject in the model class, as shown in the following example:

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

    public int Id { get; set; }

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

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

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

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

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

    public bool Preorder { get; set; }

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

最上層節點驗證Top-level node validation

最上層節點包括:Top-level nodes include:

  • 動作參數Action parameters
  • 控制器屬性Controller properties
  • 頁面處理常式參數Page handler parameters
  • 頁面模型屬性Page model properties

除了驗證模型屬性,也會驗證模型所繫結的最上層節點。Model-bound top-level nodes are validated in addition to validating model properties. 在來自範例應用程式的下列範例中,VerifyPhone 方法會使用 RegularExpressionAttribute 來驗證 phone 動作參數:In the following example from the sample app, the VerifyPhone method uses the RegularExpressionAttribute to validate the phone action parameter:

[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 和驗證屬性。Top-level nodes can use BindRequiredAttribute with validation attributes. 在來自範例應用程式的下列範例中,CheckAge 方法會指定提交表單時必須從查詢字串繫結 age 參數:In the following example from the sample app, the CheckAge method specifies that the age parameter must be bound from the query string when the form is submitted:

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

在 [檢查年齡] 頁面 (CheckAge.cshtml) 中,有兩個表單。In the Check Age page (CheckAge.cshtml), there are two forms. 第一個表單會提交 Age99 作為查詢字串:https://localhost:5001/Users/CheckAge?Age=99The first form submits an Age value of 99 as a query string: https://localhost:5001/Users/CheckAge?Age=99.

從查詢字串提交正確格式化的 age 時,即會驗證表單。When a properly formatted age parameter from the query string is submitted, the form validates.

[檢查年齡] 頁面上的第二個表單會在要求本文中提交 Age 值,而且驗證會失敗。The second form on the Check Age page submits the Age value in the body of the request, and validation fails. 由於 age 參數必須來自查詢字串,因此繫結會失敗。Binding fails because the age parameter must come from a query string.

執行 CompatibilityVersion.Version_2_1 或更新版本時,根據預設會啟用最上層節點驗證。When running with CompatibilityVersion.Version_2_1 or later, top-level node validation is enabled by default. 否則,會停用最上層節點驗證。Otherwise, top-level node validation is disabled. 預設選項可以藉由設定 (Startup.ConfigureServices) 中的 AllowValidatingTopLevelNodes 屬性來覆寫,如下所示:The default option can be overridden by setting the AllowValidatingTopLevelNodes property in (Startup.ConfigureServices), as shown here:

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

最大錯誤數Maximum errors

達到最大錯誤數 (預設為 200) 時,就會停止驗證。Validation stops when the maximum number of errors is reached (200 by default). 您可以在 Startup.ConfigureServices 中使用下列程式碼來設定此數目:You can configure this number with the following code in Startup.ConfigureServices:

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

最大遞迴Maximum recursion

ValidationVisitor 會周遊所正驗證之模型的物件圖形。ValidationVisitor traverses the object graph of the model being validated. 針對非常深或無限遞迴的模型,驗證可能會導致堆疊溢位。For models that are very deep or are infinitely recursive, validation may result in stack overflow. MvcOptions.MaxValidationDepth 可用來在訪客遞迴超過設定的深度時,提早停止驗證。MvcOptions.MaxValidationDepth provides a way to stop validation early if the visitor recursion exceeds a configured depth. 執行 CompatibilityVersion.Version_2_2 或更新版本時,MvcOptions.MaxValidationDepth 的預設值是 200。The default value of MvcOptions.MaxValidationDepth is 200 when running with CompatibilityVersion.Version_2_2 or later. 針對舊版,值為 Null,表示沒有深度條件約束。For earlier versions, the value is null, which means no depth constraint.

自動最少運算Automatic short-circuit

如果模型圖形不需要驗證,則驗證會自動進行最少運算 (略過)。Validation is automatically short-circuited (skipped) if the model graph doesn't require validation. 執行階段會略過驗證的物件,包含基本集合 (例如 byte[]string[]Dictionary<string, string>) 和沒有任何驗證程式的複雜物件圖形。Objects that the runtime skips validation for include collections of primitives (such as byte[], string[], Dictionary<string, string>) and complex object graphs that don't have any validators.

停用驗證Disable validation

停用驗證:To disable validation:

  1. 建立不會將任何欄位標記為無效的 IObjectModelValidator 實作。Create an implementation of IObjectModelValidator that doesn't mark any fields as invalid.

    public class NullObjectModelValidator : IObjectModelValidator
    {
        public void Validate(
            ActionContext actionContext,
            ValidationStateDictionary validationState,
            string prefix,
            object model)
        {
        }
    }
    
  2. 將下列程式碼新增至 Startup.ConfigureServices,以取代相依性插入容器中的預設 IObjectModelValidator 實作。Add the following code to Startup.ConfigureServices to replace the default IObjectModelValidator implementation in the dependency injection container.

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

您仍可能會看到來自模型繫結的模型狀態錯誤。You might still see model state errors that originate from model binding.

用戶端驗證Client-side validation

用戶端驗證可避免送出無效的表單。Client-side validation prevents submission until the form is valid. [送出] 按鈕會執行 JavaScript 以送出表單或顯示錯誤訊息。The Submit button runs JavaScript that either submits the form or displays error messages.

當表單上存在輸入錯誤時,用戶端驗證可避免伺服器上不必要的來回往返。Client-side validation avoids an unnecessary round trip to the server when there are input errors on a form. _Layout.cshtml_ValidationScriptsPartial.cshtml 中的下列指令碼參考支援用戶端驗證:The following script references in _Layout.cshtml and _ValidationScriptsPartial.cshtml support client-side validation:

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

jQuery 低調驗證 (jQuery Unobtrusive Validation) 指令碼是建置在熱門 jQuery 驗證 外掛程式上的自訂 Microsoft 前端程式庫。The jQuery Unobtrusive Validation script is a custom Microsoft front-end library that builds on the popular jQuery Validate plugin. 若沒有 jQuery 低調驗證,您就必須在兩個地方撰寫相同的驗證邏輯程式碼:一次在模型屬性 (Property) 上的伺服器端驗證屬性 (Attribute),另一次在用戶端指令碼中。Without jQuery Unobtrusive Validation, you would have to code the same validation logic in two places: once in the server-side validation attributes on model properties, and then again in client-side scripts. 反之,標記協助程式HTML 協助程式使用模型屬性 (Property) 中的驗證屬性 (Attribute) 和類型中繼資料,來轉譯需要驗證之表單項目的 HTML 5 data- 屬性 (Attribute)。Instead, Tag Helpers and HTML helpers use the validation attributes and type metadata from model properties to render HTML 5 data- attributes for the form elements that need validation. jQuery 低調驗證會剖析 data- 屬性並將邏輯傳遞至 jQuery 驗證,以有效地將伺服器端驗證邏輯「複製」到用戶端。jQuery Unobtrusive Validation parses the data- attributes and passes the logic to jQuery Validate, effectively "copying" the server-side validation logic to the client. 您可以使用標記協助程式來顯示用戶端的驗證錯誤,如下所示:You can display validation errors on the client using tag helpers as shown here:

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

上述標記協助程式會轉譯下列 HTML。The preceding tag helpers render the following HTML.

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

請注意,HTML 輸出中的 data- 屬性 (attribute) 會對應至 ReleaseDate 屬性 (property) 的驗證屬性 (attribute)。Notice that the data- attributes in the HTML output correspond to the validation attributes for the ReleaseDate property. data-val-required 屬性包含使用者未填入發行日期欄位時所要顯示的錯誤訊息。The data-val-required attribute contains an error message to display if the user doesn't fill in the release date field. jQuery 低調驗證會將此值傳遞至 jQuery 驗證的 required() 方法,然後在隨附的 <span> 項目中顯示該訊息。jQuery Unobtrusive Validation passes this value to the jQuery Validate required() method, which then displays that message in the accompanying <span> element.

資料型別驗證是根據屬性的 .NET 型別,除非是由 [DataType] 屬性覆寫。Data type validation is based on the .NET type of a property, unless that is overridden by a [DataType] attribute. 瀏覽器具有自己的預設錯誤訊息,但 jQuery 驗證低調驗證套件可以覆寫這些訊息。Browsers have their own default error messages, but the jQuery Validation Unobtrusive Validation package can override those messages. [DataType] 屬性和子類別 (例如 [EmailAddress]) 可讓您指定錯誤訊息。[DataType] attributes and subclasses such as [EmailAddress] let you specify the error message.

將驗證新增至動態表單Add Validation to Dynamic Forms

jQuery 低調驗證會在第一次載入頁面時將驗證邏輯和傳遞參數至 jQuery 驗證。jQuery Unobtrusive Validation passes validation logic and parameters to jQuery Validate when the page first loads. 因此,驗證不會在動態產生的表單上自動運作。Therefore, validation doesn't work automatically on dynamically generated forms. 若要啟用驗證,請指示 jQuery 低調驗證在建立動態表單之後立即進行剖析。To enable validation, tell jQuery Unobtrusive Validation to parse the dynamic form immediately after you create it. 例如,下列程式碼會在透過 AJAX 新增的表單上設定用戶端驗證。For example, the following code sets up client-side validation on a form added via 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 選取器的一個引數。The $.validator.unobtrusive.parse() method accepts a jQuery selector for its one argument. 此方法會指示 jQuery 低調驗證在該選取器內剖析表單的 data- 屬性。This method tells jQuery Unobtrusive Validation to parse the data- attributes of forms within that selector. 然後,這些屬性的值會傳遞至 jQuery 驗證外掛程式。The values of those attributes are then passed to the jQuery Validate plugin.

將驗證新增至動態控制項Add Validation to Dynamic Controls

$.validator.unobtrusive.parse() 方法適用於整個表單,而非動態產生的個別控制項 (例如 <input><select/>)。The $.validator.unobtrusive.parse() method works on an entire form, not on individual dynamically generated controls, such as <input> and <select/>. 若要重新剖析表單,請移除先前剖析表單時新增的驗證資料,如下列範例所示:To reparse the form, remove the validation data that was added when the form was parsed earlier, as shown in the following example:

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

自訂用戶端驗證Custom client-side validation

自訂用戶端驗證是透過產生使用自訂 jQuery 驗證配接器的 data- HTML 屬性來進行。Custom client-side validation is done by generating data- HTML attributes that work with a custom jQuery Validate adapter. 下列配接器程式碼範例,是針對本文稍早介紹的 ClassicMovieClassicMovie2 屬性所撰寫:The following sample adapter code was written for the ClassicMovie and ClassicMovie2 attributes that were introduced earlier in this article:

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

        return true;
    });

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

如需如何撰寫配接器的資訊,請參閱 jQuery 驗證文件For information about how to write adapters, see the jQuery Validate documentation.

用於指定欄位之配接器的使用,是由 data- 屬性觸發,因此會:The use of an adapter for a given field is triggered by data- attributes that:

  • 將欄位加上旗標為待驗證 (data-val="true")。Flag the field as being subject to validation (data-val="true").
  • 識別驗證規則名稱和錯誤訊息文字 (例如 data-val-rulename="Error message.")。Identify a validation rule name and error message text (for example, data-val-rulename="Error message.").
  • 提供驗證程式需要的任何其他參數 (例如 data-val-rulename-parm1="value")。Provide any additional parameters the validator needs (for example, data-val-rulename-parm1="value").

下列範例顯示範例應用程式之 ClassicMovie 屬性的 data- 屬性:The following example shows the data- attributes for the sample app's ClassicMovie attribute:

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

如前文所述,標記協助程式HTML 協助程式 使用來自驗證屬性的資訊來轉譯 data- 屬性。As noted earlier, Tag Helpers and HTML helpers use information from validation attributes to render data- attributes. 有二個選項可撰寫用於建立自訂 data- HTML 屬性的程式碼:There are two options for writing code that results in the creation of custom data- HTML attributes:

  • 建立衍生自 AttributeAdapterBase<TAttribute> 的類別,以及可實作 IValidationAttributeAdapterProvider 的類別,並在 DI 中註冊您的屬性及其配接器。Create a class that derives from AttributeAdapterBase<TAttribute> and a class that implements IValidationAttributeAdapterProvider, and register your attribute and its adapter in DI. 此方法遵循單一職責原則,其中伺服器相關與用戶端相關的驗證程式碼位於不同類別中。This method follows the single responsibility principal in that server-related and client-related validation code is in separate classes. 配接器也具有優點,由於其在 DI 中註冊,因此可以使用 DI 中的其他服務 (如需要)。The adapter also has the advantage that since it is registered in DI, other services in DI are available to it if needed.
  • 在您的 ValidationAttribute 類別中實作 IClientModelValidatorImplement IClientModelValidator in your ValidationAttribute class. 如果屬性不執行任何伺服器端驗證,且不需要任何來自 DI 的服務,則此方法可能適合。This method might be appropriate if the attribute doesn't do any server-side validation and doesn't need any services from DI.

用戶端驗證的屬性配接器AttributeAdapter for client-side validation

這種在 HTML 中轉譯 data- 屬性的方法,由範例應用程式中的 ClassicMovie 屬性使用。This method of rendering data- attributes in HTML is used by the ClassicMovie attribute in the sample app. 使用此方法來新增用戶端驗證:To add client validation by using this method:

  1. 建立自訂驗證屬性的屬性配接器類別。Create an attribute adapter class for the custom validation attribute. AttributeAdapterBase<T> 衍生類別。Derive the class from AttributeAdapterBase<T>. 建立會將 data- 屬性新增至轉譯輸出的 AddValidation 方法,如下列範例所示:Create an AddValidation method that adds data- attributes to the rendered output, as shown in this example:

    public class ClassicMovieAttributeAdapter : AttributeAdapterBase<ClassicMovieAttribute>
    {
        private int _year;
    
        public ClassicMovieAttributeAdapter(ClassicMovieAttribute attribute, IStringLocalizer stringLocalizer) : base (attribute, stringLocalizer)
        {
            _year = attribute.Year;
        }
        public override void AddValidation(ClientModelValidationContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }
    
            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-classicmovie", GetErrorMessage(context));
    
            var year = Attribute.Year.ToString(CultureInfo.InvariantCulture);
            MergeAttribute(context.Attributes, "data-val-classicmovie-year", year);
        }
        public override string GetErrorMessage(ModelValidationContextBase validationContext)
        {
            return Attribute.GetErrorMessage();
        }
    }
    
  2. 建立實作 IValidationAttributeAdapterProvider 的配接器提供者類別。Create an adapter provider class that implements IValidationAttributeAdapterProvider. GetAttributeAdapter 方法中將自訂屬性傳遞至配接器的建構函式,如下列範例所示:In the GetAttributeAdapter method pass in the custom attribute to the adapter's constructor, as shown in this example:

    public class CustomValidationAttributeAdapterProvider :
        IValidationAttributeAdapterProvider
    {
        IValidationAttributeAdapterProvider baseProvider =
            new ValidationAttributeAdapterProvider();
        public IAttributeAdapter GetAttributeAdapter(ValidationAttribute attribute,
            IStringLocalizer stringLocalizer)
        {
            if (attribute is ClassicMovieAttribute)
            {
                return new ClassicMovieAttributeAdapter(
                    attribute as ClassicMovieAttribute, stringLocalizer);
            }
            else
            {
                return baseProvider.GetAttributeAdapter(attribute, stringLocalizer);
            }
        }
    }
    
  3. Startup.ConfigureServices 中註冊 DI 的配接器提供者:Register the adapter provider for DI in Startup.ConfigureServices:

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

用戶端驗證的 IClientModelValidatorIClientModelValidator for client-side validation

這種在 HTML 中轉譯 data- 屬性的方法,由範例應用程式中的 ClassicMovie2 屬性使用。This method of rendering data- attributes in HTML is used by the ClassicMovie2 attribute in the sample app. 使用此方法來新增用戶端驗證:To add client validation by using this method:

  • 在自訂驗證屬性中,實作 IClientModelValidator 介面並建立 AddValidation 方法。In the custom validation attribute, implement the IClientModelValidator interface and create an AddValidation method. AddValidation 方法中,為驗證新增 data- 屬性,如下列範例所示:In the AddValidation method, add data- attributes for validation, as shown in the following example:

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

停用用戶端驗證Disable client-side validation

下列程式碼會停用 MVC 檢視中的用戶端驗證:The following code disables client validation in MVC views:

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

以及在 Razor Pages 中:And in Razor Pages:

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

停用用戶端驗證的另一個選項,是將 .cshtml 檔案中對 _ValidationScriptsPartial 的參考標記為註解。Another option for disabling client validation is to comment out the reference to _ValidationScriptsPartial in your .cshtml file.

其他資源Additional resources