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 invalid 等错误消息。However, 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 的类型来处理可以为 null<T> 值类型。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 Validate 远程方法要求 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] 特性注释属性,如下例所示: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] 特性的 AdditionalFields 属性可以根据服务器上的数据验证字段组合。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(LastName))]
public string FirstName { get; set; }
[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(FirstName))]
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. 此验证的操作方法必须接受 first name 和 last name 参数: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($"A user named {firstName} {lastName} already exists.");
    }

    return Json(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 属性,可按以下示例所示设置 [Remote] 特性: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.

以下示例验证“经典”流派电影的发行日期是否不晚于指定年份 。The following example validates that the release date for a movie in the Classic genre isn't later than a specified year. [ClassicMovie2] 特性首先检查流派,只有流派为“经典”时才会继续检查 。The [ClassicMovie2] attribute checks the genre first and continues only if it's 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. 第一个表单提交 Age 的值 99 作为查询字符串: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 Validate 插件的自定义 Microsoft 前端库。The jQuery Unobtrusive Validation script is a custom Microsoft front-end library that builds on the popular jQuery Validate plugin. 如果没有 jQuery 非介入式验证,则必须在两个位置编码相同的验证逻辑:一次是在模型属性上的服务器端验证特性中,一次是在客户端脚本中。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 帮助程序则使用模型属性中的验证特性和类型元数据,呈现需要验证的表单元素的 HTML 5 data- 特性。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 Validate,从而将服务器端验证逻辑有效地“复制”到客户端。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- 特性与 ReleaseDate 属性的验证特性相对应。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 Validate required() 方法,该方法随后在随附的 <span> 元素中显示该消息。jQuery Unobtrusive Validation passes this value to the jQuery Validate required() method, which then displays that message in the accompanying <span> element.

如果 [DataType] 特性未替代属性的 .NET 类型,则数据类型验证基于该类型。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 Validate。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 Validate 插件。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 Validate 适配器的 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 Validate 文档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.

用于客户端验证的 AttributeAdapterAttributeAdapter 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 classicMovieAttribute)
            {
                return new ClassicMovieAttributeAdapter(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

本文介绍如何在 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 invalid 等错误消息。However, 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 的类型来处理可以为 null<T> 值类型。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 Validate 远程方法要求 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] 特性注释属性,如下例所示: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] 特性的 AdditionalFields 属性可以根据服务器上的数据验证字段组合。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(LastName))]
public string FirstName { get; set; }
[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(FirstName))]
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. 此验证的操作方法必须接受 first name 和 last name 参数: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($"A user named {firstName} {lastName} already exists.");
    }

    return Json(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 属性,可按以下示例所示设置 [Remote] 特性: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.

以下示例验证“经典”流派电影的发行日期是否不晚于指定年份 。The following example validates that the release date for a movie in the Classic genre isn't later than a specified year. [ClassicMovie2] 特性首先检查流派,只有流派为“经典”时才会继续检查 。The [ClassicMovie2] attribute checks the genre first and continues only if it's 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. 第一个表单提交 Age 的值 99 作为查询字符串: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 Validate 插件的自定义 Microsoft 前端库。The jQuery Unobtrusive Validation script is a custom Microsoft front-end library that builds on the popular jQuery Validate plugin. 如果没有 jQuery 非介入式验证,则必须在两个位置编码相同的验证逻辑:一次是在模型属性上的服务器端验证特性中,一次是在客户端脚本中。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 帮助程序则使用模型属性中的验证特性和类型元数据,呈现需要验证的表单元素的 HTML 5 data- 特性。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 Validate,从而将服务器端验证逻辑有效地“复制”到客户端。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- 特性与 ReleaseDate 属性的验证特性相对应。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 Validate required() 方法,该方法随后在随附的 <span> 元素中显示该消息。jQuery Unobtrusive Validation passes this value to the jQuery Validate required() method, which then displays that message in the accompanying <span> element.

如果 [DataType] 特性未替代属性的 .NET 类型,则数据类型验证基于该类型。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 Validate。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 Validate 插件。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 Validate 适配器的 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 Validate 文档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.

用于客户端验证的 AttributeAdapterAttributeAdapter 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 classicMovieAttribute)
            {
                return new ClassicMovieAttributeAdapter(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