相关文章推荐
讲道义的针织衫  ·  pyspark withcolumn ...·  1 年前    · 
帅呆的玉米  ·  @RequestBody ...·  1 年前    · 
神勇威武的小蝌蚪  ·  c# - Bug in ...·  1 年前    · 

查看或下载示例代码 如何下载 )。

模型状态表示两个子系统的错误:模型绑定和模型验证。 源自 模型绑定 的错误通常是数据转换错误。 例如,在一个整数字段中输入一个“x”。 模型验证在模型绑定后发生,并报告数据不符合业务规则的错误。 例如,在需要 1 到 5 之间评分的字段中输入 0。

模型绑定和模型验证都在执行控制器操作或 Razor Pages 处理程序方法之前进行。 Web 应用负责检查 ModelState.IsValid 并做出相应响应。 Web 应用通常会重新显示包含错误消息的页面,如以下 Razor Pages 示例所示:

public async Task<IActionResult> OnPostAsync() if (!ModelState.IsValid) return Page(); _context.Movies.Add(Movie); await _context.SaveChangesAsync(); return RedirectToPage("./Index");

对于具有控制器和视图的 ASP.NET Core MVC,以下示例演示如何在控制器操作内部检查 ModelState.IsValid

public async Task<IActionResult> Create(Movie movie) if (!ModelState.IsValid) return View(movie); _context.Movies.Add(movie); await _context.SaveChangesAsync(); return RedirectToAction(nameof(Index));

如果 Web API 控制器具有 [ApiController] 属性,则无需检查 ModelState.IsValid 。 在此情况下,如果模型状态无效,将返回包含错误详细信息的自动 HTTP 400 响应。 有关详细信息,请参阅 自动 HTTP 400 响应

重新运行验证

验证自动进行,但是可能需要手动进行重复验证。 例如,你可能为属性计算一个值,并且希望将属性设置为所计算的值后,再重新运行验证。 若要重新运行验证,请调用 ModelStateDictionary.ClearValidationState 来清除特定于模型的验证,然后再调用 TryValidateModel 对模型进行验证:

public async Task<IActionResult> OnPostTryValidateAsync() var modifiedReleaseDate = DateTime.Now.Date; Movie.ReleaseDate = modifiedReleaseDate; ModelState.ClearValidationState(nameof(Movie)); if (!TryValidateModel(Movie, nameof(Movie))) return Page(); _context.Movies.Add(Movie); await _context.SaveChangesAsync(); return RedirectToPage("./Index");

通过验证特性可以为模型属性指定验证规则。 示例应用中 的以下示例演示了一个使用验证属性进行批注的模型类。 属性 [ClassicMovie] 是自定义验证属性,其他属性是内置的。 [ClassicMovieWithClientValidator] 未显示,它表示实现自定义特性的另一种方法。

public class Movie public int Id { get; set; } [Required] [StringLength(100)] public string Title { get; set; } = null!; [ClassicMovie(1960)] [DataType(DataType.Date)] [Display(Name = "Release Date")] public DateTime ReleaseDate { get; set; } [Required] [StringLength(1000)] public string Description { get; set; } = null!; [Range(0, 999.99)] public decimal Price { get; set; } public Genre Genre { get; set; } public bool Preorder { get; set; }

以下是一些内置验证特性:

  • [ValidateNever] :指示应从验证中排除属性或参数。
  • [CreditCard] :验证属性是否具有信用卡格式。 需要 jQuery Validation 附加方法
  • [比较] :验证模型中的两个属性是否匹配。
  • [EmailAddress] :验证 属性是否具有电子邮件格式。
  • [电话] :验证 属性是否具有电话号码格式。
  • [Range] :验证属性值是否在指定范围内。
  • [RegularExpression] :验证属性值是否与指定的正则表达式匹配。
  • [必需] :验证字段是否不为 null。 请参阅 [Required] 属性 ,获取关于该特性的行为的详细信息。
  • [StringLength] :验证字符串属性值是否未超过指定的长度限制。
  • [Url] :验证 属性是否具有 URL 格式。
  • [远程] :通过在服务器上调用操作方法来验证客户端上的输入。 请参阅 [Remote] 属性 ,获取关于该特性的行为的详细信息。
  • 可以在 命名空间中找到 System.ComponentModel.DataAnnotations 验证属性的完整列表。

    通过验证特性可以指定要为无效输入显示的错误消息。 例如:

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

    在内部,特性使用用于字段名的某个占位符调用 String.Format,有时还使用额外占位符。 例如:

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

    应用于 Name 属性时,上述代码创建的错误消息将为“名称长度必须介于 6 到 8 之间”。

    若要了解特定属性的错误消息传递给 String.Format 哪些参数,请参阅 DataAnnotations 源代码

    在验证错误中使用 JSON 属性名称

    默认情况下,当发生验证错误时,模型验证会生成一个 ModelStateDictionary,其中属性名用作错误键。 某些应用(例如单页应用)受益于使用 JSON 属性名来处理 Web API 生成的验证错误。 以下代码将验证配置为使用 SystemTextJsonValidationMetadataProvider 以使用 JSON 属性名:

    using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(options => options.ModelMetadataDetailsProviders.Add(new SystemTextJsonValidationMetadataProvider()); var app = builder.Build(); app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.Run();

    以下代码将验证配置为使用 NewtonsoftJsonValidationMetadataProvider 以在使用 Json.NET 时使用 JSON 属性名:

    using Microsoft.AspNetCore.Mvc.NewtonsoftJson; var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(options => options.ModelMetadataDetailsProviders.Add(new NewtonsoftJsonValidationMetadataProvider()); }).AddNewtonsoftJson(); var app = builder.Build(); app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.Run();

    有关使用 camel-casing 的策略示例,请参阅 Program.cs GitHub 上的

    不可为 null 的引用类型和 [Required] 特性

    验证系统将不可为 null 的参数或绑定属性视为它们具有 [Required(AllowEmptyStrings = true)] 特性。 通过启用 Nullable 上下文,MVC 将隐式开始对不可为 null 的属性或参数进行验证,就像它们已使用 [Required(AllowEmptyStrings = false)] 特性进行了特性化一样。 考虑下列代码:

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

    如果应用是使用 <Nullable>enable</Nullable>生成的,则 ON 或表单帖子中 JS缺少 的值Name会导致验证错误。 使用可为 null 的引用类型来实现为 Name 属性指定 NULL 或缺少的值:

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

    可以通过在 Program.cs 中配置 SuppressImplicitRequiredAttributeForNonNullableReferenceTypes 来禁用此行为:

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

    [必需] 服务器上的验证

    在服务器上,如果属性为 null,则认为所需值缺失。 不可为 null 的字段始终有效,并且从不显示 [Required] 属性的错误消息。

    但是,不可为 null 的属性的模型绑定可能会失败,从而导致 The value '' is invalid 等错误消息。 若要为不可为 null 的类型的服务器端验证指定自定义错误消息,可使用以下选项:

  • 将字段设置为可以为 null(例如,decimal?而不是 decimal)。 Nullable<T> 值类型被视为标准的可以为 null 的类型。

  • 指定模型绑定要使用的默认错误消息,如以下示例所示:

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

    有关模型绑定错误(可以为其设置默认消息)的更多信息,请参阅 DefaultModelBindingMessageProvider

    [必需] 客户端上的验证

    在客户端上处理不可为 null 类型和字符串的方式与在服务器上不同。 在客户端上:

  • 只有在为值输入一个输入时,才认为该值存在。 因此,客户端验证处理不可为 null 类型的方式与处理可以为 null 类型的方式相同。
  • jQuery 验证必需方法将字符串字段中的空格视为有效输入。 如果只输入空格,服务器端验证会将必需的字符串字段视为无效。
  • 如前所述,将不可为 null 类型视为具有 [Required(AllowEmptyStrings = true)] 特性。 这意味着即使不应用 [Required(AllowEmptyStrings = true)] 特性,也可进行客户端验证。 但如果不使用该特性,将收到默认错误消息。 若要指定自定义错误消息,使用该特性。

    [远程] 特性

    [Remote] 属性实现客户端验证,该验证要求在服务器上调用 方法来确定字段输入是否有效。 例如,应用可能需要验证用户名是否已在使用。

    若要实现远程验证:

  • 创建可供 JavaScript 调用的操作方法。 jQuery 验证 远程 方法需要 JSON 响应:

  • true 表示输入数据有效。
  • falseundefinednull 表示输入无效。 显示默认错误消息。
  • 任何其他字符串都表示输入无效。 将字符串显示为自定义错误消息。
  • 以下是返回自定义错误消息的操作方法示例:

    [AcceptVerbs("GET", "POST")] public IActionResult VerifyEmail(string email) if (!_userService.VerifyEmail(email)) return Json($"Email {email} is already in use."); return Json(true);
  • 在模型类中,使用指向验证操作方法的 [Remote] 特性注释属性,如下例所示:

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

    还需要为禁用 JavaScript 的客户端实现服务器端验证

    通过 [Remote] 特性的 AdditionalFields 属性可以根据服务器上的数据验证字段组合。 例如,如果 User 模型具有 FirstNameLastName 属性,可能需要验证该名称对尚未被现有用户占用。 下面的示例演示如何使用 AdditionalFields

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

    AdditionalFields 可以显式设置为字符串“FirstName”和“LastName”,但使用 nameof 运算符可简化以后的重构。 此验证的操作方法必须接受 firstNamelastName 参数:

    [AcceptVerbs("GET", "POST")] public IActionResult VerifyName(string firstName, string lastName) if (!_userService.VerifyName(firstName, lastName)) return Json($"A user named {firstName} {lastName} already exists."); return Json(true);

    用户输入名字或姓氏时,JavaScript 会进行远程调用,查看该名称对是否已占用。

    若要验证两个或更多附加字段,可将其以逗号分隔列表形式提供。 例如,若要向模型中添加 MiddleName 属性,可按以下示例所示设置 [Remote] 特性:

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

    AdditionalFields 与所有属性参数一样,必须是常量表达式。 因此,请勿使用内插字符串或调用 Join 来初始化 AdditionalFields

    内置特性的替代特性

    如果需要并非由内置属性提供的验证,可以:

  • 创建自定义特性
  • 实现 IValidatableObject
  • 自定义特性

    对于内置验证特性无法处理的情况,可以创建自定义验证特性。 创建继承自 ValidationAttribute 的类,并替代 IsValid 方法。

    方法 IsValid 接受名为 value 的对象,这是要验证的输入。 重载还接受 ValidationContext 对象,该对象提供其他信息,例如模型绑定创建的模型实例。

    以下示例验证“经典”流派电影的发行日期是否不晚于指定年份[ClassicMovie] 属性:

  • 仅在服务器上运行。
  • 对于经典电影,验证发行日期:
  • public class ClassicMovieAttribute : ValidationAttribute public ClassicMovieAttribute(int year) => Year = year; public int Year { get; } public string GetErrorMessage() => $"Classic movies must have a release year no later than {Year}."; protected override ValidationResult? IsValid( object? value, ValidationContext validationContext) var movie = (Movie)validationContext.ObjectInstance; var releaseYear = ((DateTime)value!).Year; if (movie.Genre == Genre.Classic && releaseYear > Year) return new ValidationResult(GetErrorMessage()); return ValidationResult.Success;

    上述示例中的 movie 变量表示 Movie 对象,其中包含表单提交中的数据。 验证失败时,返回 ValidationResult 和错误消息。

    IValidatableObject

    上述示例只适用于 Movie 类型。 类级别验证的另一方式是在模型类中实现 IValidatableObject,如下例所示:

    public class ValidatableMovie : IValidatableObject private const int _classicYear = 1960; public int Id { get; set; } [Required] [StringLength(100)] public string Title { get; set; } = null!; [DataType(DataType.Date)] [Display(Name = "Release Date")] public DateTime ReleaseDate { get; set; } [Required] [StringLength(1000)] public string Description { get; set; } = null!; [Range(0, 999.99)] public decimal Price { get; set; } public Genre Genre { get; set; } public bool Preorder { get; set; } public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) if (Genre == Genre.Classic && ReleaseDate.Year > _classicYear) yield return new ValidationResult( $"Classic movies must have a release year no later than {_classicYear}.", new[] { nameof(ReleaseDate) });

    自定义验证

    以下代码演示如何在检查模型后添加模型错误:

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

    以下代码在控制器中实现验证测试:

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

    以下代码验证电话号码和电子邮件是否唯一:

    public async Task<IActionResult> OnPostAsync() // Attach Validation Error Message to the Model on validation failure. if (Contact.Name == Contact.ShortName) ModelState.AddModelError("Contact.ShortName", "Short name can't be the same as Name."); if (_context.Contact.Any(i => i.PhoneNumber == Contact.PhoneNumber)) ModelState.AddModelError("Contact.PhoneNumber", "The Phone number is already in use."); if (_context.Contact.Any(i => i.Email == Contact.Email)) ModelState.AddModelError("Contact.Email", "The Email is already in use."); if (!ModelState.IsValid || _context.Contact == null || Contact == null) // if model is invalid, return the page with the model state errors. return Page(); _context.Contact.Add(Contact); await _context.SaveChangesAsync(); return RedirectToPage("./Index");

    以下代码在控制器中实现验证测试:

    [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Create([Bind("Id,Name,ShortName,Email,PhoneNumber")] Contact contact) // Attach Validation Error Message to the Model on validation failure. if (contact.Name == contact.ShortName) ModelState.AddModelError(nameof(contact.ShortName), "Short name can't be the same as Name."); if (_context.Contact.Any(i => i.PhoneNumber == contact.PhoneNumber)) ModelState.AddModelError(nameof(contact.PhoneNumber), "The Phone number is already in use."); if (_context.Contact.Any(i => i.Email == contact.Email)) ModelState.AddModelError(nameof(contact.Email), "The Email is already in use."); if (ModelState.IsValid) _context.Add(contact); await _context.SaveChangesAsync(); return RedirectToAction(nameof(Index)); return View(contact);

    检查唯一的电话号码或电子邮件通常也通过 远程验证完成。

    ValidationResult

    请考虑以下自定义 ValidateNameAttribute

    public class ValidateNameAttribute : ValidationAttribute public ValidateNameAttribute() const string defaultErrorMessage = "Error with Name"; ErrorMessage ??= defaultErrorMessage; protected override ValidationResult? IsValid(object? value, ValidationContext validationContext) if (value == null || string.IsNullOrWhiteSpace(value.ToString())) return new ValidationResult("Name is required."); if (value.ToString()!.ToLower().Contains("zz")) return new ValidationResult( FormatErrorMessage(validationContext.DisplayName)); return ValidationResult.Success;

    在以下代码中,应用了自定义 [ValidateName] 属性:

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

    当模型包含 zz时,将返回新的 ValidationResult

    顶级节点验证

    顶级节点包括:

  • 控制器属性
  • 页处理程序参数
  • 页模型属性
  • 除了验证模型属性之外,还验证了模型绑定的顶级节点。 在 示例应用的以下示例中 VerifyPhone , 方法使用 RegularExpressionAttribute 来验证 phone 操作参数:

    [AcceptVerbs("GET", "POST")] public IActionResult VerifyPhone( [RegularExpression(@"^\d{3}-\d{3}-\d{4}$")] string phone) if (!ModelState.IsValid) return Json($"Phone {phone} has an invalid format. Format: ###-###-####"); return Json(true);

    顶级节点可以将 BindRequiredAttribute 与验证属性结合使用。 在 示例应用的以下示例中, CheckAge 方法指定 age 在提交表单时必须从查询字符串绑定 参数:

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

    在“检查年龄”页 (CheckAge.cshtml) ,有两种形式。 第一个表单将 99Age 值作为查询字符串参数提交:https://localhost:5001/Users/CheckAge?Age=99

    当提交查询字符串中格式设置正确的 age 参数时,表单将进行验证。

    “检查年限”页面上的第二个表单提交请求正文中的 Age 值,验证失败。 绑定失败,因为 age 参数必须来自查询字符串。

    最大错误数

    达到最大错误数(默认为 200)时,验证停止。 可以使用 Program.cs 中的以下代码配置该数字:

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

    最大递归次数

    ValidationVisitor 遍历所验证模型的对象图。 对于深度或无限递归的模型,验证可能会导致堆栈溢出。 MvcOptions.MaxValidationDepth 提供了一种在访问者递归超过配置的深度时提前停止验证的方法。 MvcOptions.MaxValidationDepth 的默认值为 32。

    如果模型图不需要验证,验证将自动短路(跳过)。 运行时为其跳过验证的对象包括基元集合(如 byte[]string[]Dictionary<string, string>)和不具有任何验证器的复杂对象图。

    客户端验证

    客户端验证将阻止提交,直到表单变为有效为止。 “提交”按钮运行 JavaScript:要么提交表单要么显示错误消息。

    表单上存在输入错误时,客户端验证会避免到服务器的不必要往返。 以下脚本引用 和 _ValidationScriptsPartial.cshtml_Layout.cshtml,支持客户端验证:

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

    jQuery Unobtrusive Validation 脚本是一个基于热门 jQuery Validation 插件构建的自定义 Microsoft 前端库。 如果没有 jQuery 非介入式验证,则必须在两个位置编码相同的验证逻辑:一次是在模型属性上的服务器端验证特性中,一次是在客户端脚本中。 标记帮助程序HTML 帮助程序则使用模型属性中的验证特性和类型元数据,呈现需要验证的表单元素的 HTML 5 data- 特性。 jQuery Unobtrusive Validation 分析 data- 特性并将逻辑传递给 jQuery Validation,从而将服务器端验证逻辑有效地“复制”到客户端。 可以使用标记帮助程序在客户端上显示验证错误,如下所示:

    <div class="form-group"> <label asp-for="Movie.ReleaseDate" class="control-label"></label> <input asp-for="Movie.ReleaseDate" class="form-control" /> <span asp-validation-for="Movie.ReleaseDate" class="text-danger"></span>

    上述标记帮助程序呈现以下 HTML:

    <div class="form-group">
        <label class="control-label" for="Movie_ReleaseDate">Release Date</label>
        <input class="form-control" type="date" data-val="true"
            data-val-required="The Release Date field is required."
            id="Movie_ReleaseDate" name="Movie.ReleaseDate" value="">
        <span class="text-danger field-validation-valid"
            data-valmsg-for="Movie.ReleaseDate" data-valmsg-replace="true"></span>
    

    请注意,HTML 输出中的 data- 特性与 Movie.ReleaseDate 属性的验证特性相对应。 data-val-required 特性包含在用户未填写上映日期字段时将显示的错误消息。 jQuery Unobtrusive Validation 将此值传递给 jQuery Validation required() 方法,该方法随后在随附的 <span> 元素中显示该消息。

    数据类型验证基于属性的 .NET 类型,除非该类型被 [DataType] 属性重写。 浏览器具有自己的默认错误消息,但是 jQuery 验证非介入式验证包可以替代这些消息。 [DataType] [ EmailAddress] 等属性和子类可用于指定错误消息。

    非介入式验证

    有关非介入式验证的信息,请参阅此 GitHub 问题

    向动态表单添加验证

    jQuery Unobtrusive Validation 会在当页面第一次加载时将验证逻辑和参数传递到 jQuery Validation。 因此,不会对动态生成的表单自动执行验证。 若要启用验证,指示 jQuery 非介入式验证在创建动态表单后立即对其进行分析。 例如,以下代码在通过 AJAX 添加的表单上设置客户端验证。

    $.get({
        url: "https://url/that/returns/a/form",
        dataType: "html",
        error: function(jqXHR, textStatus, errorThrown) {
            alert(textStatus + ": Couldn't add form. " + errorThrown);
        success: function(newFormHTML) {
            var container = document.getElementById("form-container");
            container.insertAdjacentHTML("beforeend", newFormHTML);
            var forms = container.getElementsByTagName("form");
            var newForm = forms[forms.length - 1];
            $.validator.unobtrusive.parse(newForm);
    

    $.validator.unobtrusive.parse() 方法采用 jQuery 选择器作为它的一个参数。 此方法指示 jQuery 非介入式验证分析该选择器内表单的 data- 属性。 这些特性的值随后会传递到 jQuery Validation 插件。

    向动态控件添加验证

    $.validator.unobtrusive.parse() 方法适用于整个表单,而不是 <input><select/> 等单个动态生成的控件。 若要重新分析表单,删除之前分析表单时添加的验证数据,如下例所示:

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

    自定义客户端验证

    自定义客户端验证是通过生成适用于自定义 jQuery Validation 适配器的 data- HTML 特性来完成的。 以下示例适配器代码是为本文前面部分介绍的 [ClassicMovie][ClassicMovieWithClientValidator] 特性编写的:

    $.validator.addMethod('classicmovie', function (value, element, params) { var genre = $(params[0]).val(), year = params[1], date = new Date(value); // The Classic genre has a value of '0'. if (genre && genre.length > 0 && genre[0] === '0') { // The release date for a Classic is valid if it's no greater than the given year. return date.getUTCFullYear() <= year; return true; $.validator.unobtrusive.adapters.add('classicmovie', ['year'], function (options) { var element = $(options.form).find('select#Movie_Genre')[0]; options.rules['classicmovie'] = [element, parseInt(options.params['year'])]; options.messages['classicmovie'] = options.message;

    有关如何编写适配器的信息,请参阅 jQuery Validation 文档

    给定字段的适配器的使用由 data- 特性触发,这些特性:

  • 将字段标记为正在验证 (data-val="true")。
  • 确定验证规则名称和错误消息文本(例如,data-val-rulename="Error message.")。
  • 提供验证器所需的任何其他参数(例如,data-val-rulename-param1="value")。
  • 以下示例显示了data-示例应用的ClassicMovie 属性的属性:

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

    如前所述,标记帮助程序HTML 帮助程序使用验证特性的信息呈现 data- 特性。 编写用于创建自定义 data- HTML 特性的代码有以下两种方式:

  • 创建派生自 AttributeAdapterBase<TAttribute> 的类和实现 IValidationAttributeAdapterProvider 的类,并在 DI 中注册特性及其适配器。 此方法遵循 单一责任原则 ,即与服务器相关的验证代码和与客户端相关的验证代码位于单独的类中。 适配器还具有一个优点,因为它已在 DI 中注册,因此可以根据需要使用 DI 中的其他服务。
  • ValidationAttribute 类中实现 IClientModelValidator。 如果特性既不进行任何服务器端验证,也不需要 DI 的任何服务,则此方法可能适用。
  • 用于客户端验证的 AttributeAdapter

    在 HTML 中呈现data-属性的此方法由ClassicMovie示例应用中的 属性使用。 若要使用此方法添加客户端验证:

  • 为自定义验证特性创建特性适配器类。 从 AttributeAdapterBase<TAttribute>派生类。 创建将 data- 特性添加到所呈现输出中的 AddValidation 方法,如下例所示:

    public class ClassicMovieAttributeAdapter : AttributeAdapterBase<ClassicMovieAttribute> public ClassicMovieAttributeAdapter( ClassicMovieAttribute attribute, IStringLocalizer? stringLocalizer) : base(attribute, stringLocalizer) public override void AddValidation(ClientModelValidationContext context) MergeAttribute(context.Attributes, "data-val", "true"); MergeAttribute(context.Attributes, "data-val-classicmovie", GetErrorMessage(context)); var year = Attribute.Year.ToString(CultureInfo.InvariantCulture); MergeAttribute(context.Attributes, "data-val-classicmovie-year", year); public override string GetErrorMessage(ModelValidationContextBase validationContext) => Attribute.GetErrorMessage();
  • 创建实现 IValidationAttributeAdapterProvider 的适配器提供程序类。 使用 GetAttributeAdapter 方法,将自定义属性传递给适配器的构造函数,如下例所示:

    public class CustomValidationAttributeAdapterProvider : IValidationAttributeAdapterProvider private readonly IValidationAttributeAdapterProvider baseProvider = new ValidationAttributeAdapterProvider(); public IAttributeAdapter? GetAttributeAdapter( ValidationAttribute attribute, IStringLocalizer? stringLocalizer) if (attribute is ClassicMovieAttribute classicMovieAttribute) return new ClassicMovieAttributeAdapter(classicMovieAttribute, stringLocalizer); return baseProvider.GetAttributeAdapter(attribute, stringLocalizer);
  • Program.cs 中为 DI 注册适配器提供程序:

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

    用于客户端验证的 IClientModelValidator

    在 HTML 中呈现data-属性的此方法由ClassicMovieWithClientValidator示例应用中的 属性使用。 若要使用此方法添加客户端验证:

  • 在自定义验证特性中,实现 IClientModelValidator 接口并创建 AddValidation 方法。 使用 AddValidation 方法,添加 data- 特性进行验证,如下例所示:

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

    禁用客户端验证

    以下代码禁用 Razor Pages 中的客户端验证:

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

    可禁用客户端验证的其他选项:

  • 注释掉所有文件中对 _ValidationScriptsPartial.cshtml 引用。
  • 删除 Pages\Shared_ValidationScriptsPartial.cshtml 文件的内容。
  • 上述方法不会阻止 ASP.NET CoreIdentityRazor类库的客户端验证。 有关详细信息,请参阅 ASP.NET Core 项目中的基架

    问题详细信息

    问题详细信息并不是描述 HTTP API 错误的唯一响应格式,但它们通常用于报告 HTTP API 的错误。

    问题详细信息服务实现 IProblemDetailsService 接口,该接口支持在 ASP.NET Core 中创建问题详细信息。 IServiceCollection 上的 AddProblemDetails 扩展方法注册默认 IProblemDetailsService 实现。

    在 ASP.NET Core 应用中,下列中间件会在调用 AddProblemDetails 时生成问题详细信息 HTTP 响应,除非 Accept 请求 HTTP 标头不包含注册的 IProblemDetailsWriter 支持的内容类型之一(默认:application/json):

  • ExceptionHandlerMiddleware:未定义自定义处理程序时生成问题详细信息响应。
  • StatusCodePagesMiddleware:默认生成问题详细信息响应。
  • DeveloperExceptionPageMiddleware:当 Accept 请求 HTTP 标头不包含 text/html 时,在开发中生成问题详细信息响应。
  • System.ComponentModel.DataAnnotations
  • 本文介绍如何在 ASP.NET Core MVC 或 Razor Pages 应用中验证用户输入。

    查看或下载示例代码如何下载)。

    模型状态表示两个子系统的错误:模型绑定和模型验证。 源自模型绑定的错误通常是数据转换错误。 例如,在一个整数字段中输入一个“x”。 模型验证在模型绑定后发生,并报告数据不符合业务规则的错误。 例如,在需要 1 到 5 之间评分的字段中输入 0。

    模型绑定和模型验证都在执行控制器操作或 Razor Pages 处理程序方法之前进行。 Web 应用负责检查 ModelState.IsValid 并做出相应响应。 Web 应用通常会重新显示包含错误消息的页面,如以下 Razor Pages 示例所示:

    public async Task<IActionResult> OnPostAsync() if (!ModelState.IsValid) return Page(); _context.Movies.Add(Movie); await _context.SaveChangesAsync(); return RedirectToPage("./Index");

    对于具有控制器和视图的 ASP.NET Core MVC,以下示例演示如何在控制器操作内部检查ModelState.IsValid

    public async Task<IActionResult> Create(Movie movie) if (!ModelState.IsValid) return View(movie); _context.Movies.Add(movie); await _context.SaveChangesAsync(); return RedirectToAction(nameof(Index));

    如果 Web API 控制器具有 [ApiController] 属性,则无需检查ModelState.IsValid。 在此情况下,如果模型状态无效,将返回包含错误详细信息的自动 HTTP 400 响应。 有关详细信息,请参阅自动 HTTP 400 响应

    重新运行验证

    验证自动进行,但是可能需要手动进行重复验证。 例如,你可能为属性计算一个值,并且希望将属性设置为所计算的值后,再重新运行验证。 若要重新运行验证,请调用 ModelStateDictionary.ClearValidationState 来清除特定于模型的验证,然后再调用 TryValidateModel 对模型进行验证:

    public async Task<IActionResult> OnPostTryValidateAsync() var modifiedReleaseDate = DateTime.Now.Date; Movie.ReleaseDate = modifiedReleaseDate; ModelState.ClearValidationState(nameof(Movie)); if (!TryValidateModel(Movie, nameof(Movie))) return Page(); _context.Movies.Add(Movie); await _context.SaveChangesAsync(); return RedirectToPage("./Index");

    通过验证特性可以为模型属性指定验证规则。 示例应用中的以下示例显示了一个使用验证属性进行批注的模型类。 属性 [ClassicMovie] 是自定义验证属性,其他属性是内置属性。 [ClassicMovieWithClientValidator] 未显示,它表示实现自定义特性的另一种方法。

    public class Movie public int Id { get; set; } [Required] [StringLength(100)] public string Title { get; set; } = null!; [ClassicMovie(1960)] [DataType(DataType.Date)] [Display(Name = "Release Date")] public DateTime ReleaseDate { get; set; } [Required] [StringLength(1000)] public string Description { get; set; } = null!; [Range(0, 999.99)] public decimal Price { get; set; } public Genre Genre { get; set; } public bool Preorder { get; set; }

    以下是一些内置验证特性:

  • [ValidateNever]:指示应从验证中排除属性或参数。
  • [CreditCard]:验证属性是否具有信用卡格式。 需要 jQuery Validation 附加方法
  • [比较]:验证模型中的两个属性是否匹配。
  • [EmailAddress]:验证 属性是否具有电子邮件格式。
  • [电话]:验证 属性是否具有电话号码格式。
  • [Range]:验证属性值是否在指定范围内。
  • [RegularExpression]:验证属性值是否与指定的正则表达式匹配。
  • [必需]:验证字段是否不为 null。 请参阅 [Required] 属性,获取关于该特性的行为的详细信息。
  • [StringLength]:验证字符串属性值是否未超过指定的长度限制。
  • [Url]:验证 属性是否具有 URL 格式。
  • [远程]:通过调用服务器上的操作方法验证客户端上的输入。 请参阅 [Remote] 属性,获取关于该特性的行为的详细信息。
  • 可以在 命名空间中找到 System.ComponentModel.DataAnnotations 验证属性的完整列表。

    通过验证特性可以指定要为无效输入显示的错误消息。 例如:

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

    在内部,特性使用用于字段名的某个占位符调用 String.Format,有时还使用额外占位符。 例如:

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

    应用于 Name 属性时,上述代码创建的错误消息将为“名称长度必须介于 6 到 8 之间”。

    若要了解特定属性的错误消息传递给 String.Format 哪些参数,请参阅 DataAnnotations 源代码

    不可为 null 的引用类型和 [必需] 属性

    验证系统将不可为 null 的参数或绑定属性视为它们具有 [Required(AllowEmptyStrings = true)] 特性。 通过 启用 Nullable 上下文,MVC 隐式开始验证 非泛型类型 或参数上的不可为 null 属性,就好像它们已使用 [Required(AllowEmptyStrings = true)] 特性进行特性化一样。 考虑下列代码:

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

    如果应用是使用 <Nullable>enable</Nullable>生成的,则 ON 或表单帖子中 JS缺少 的值Name会导致验证错误。 使用可为 null 的引用类型来实现为 Name 属性指定 NULL 或缺少的值:

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

    可以通过在 Program.cs 中配置 SuppressImplicitRequiredAttributeForNonNullableReferenceTypes 来禁用此行为:

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

    泛型类型和 [必需] 属性上的不可为 null 属性

    泛型类型上的不可为 null 属性必须在需要该类型时包含 [Required] 属性。 在以下代码中, TestRequired 不是必需的:

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

    在以下代码中, TestRequired 显式标记为必需:

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

    [必需] 服务器上的验证

    在服务器上,如果属性为 null,则认为所需值缺失。 不可为 null 的字段始终有效,并且从不显示 [Required] 属性的错误消息。

    但是,不可为 null 的属性的模型绑定可能会失败,从而导致 The value '' is invalid 等错误消息。 若要为不可为 null 的类型的服务器端验证指定自定义错误消息,可使用以下选项:

  • 将字段设置为可以为 null(例如,decimal?而不是 decimal)。 Nullable<T> 值类型被视为标准的可以为 null 的类型。

  • 指定模型绑定要使用的默认错误消息,如以下示例所示:

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

    有关模型绑定错误(可以为其设置默认消息)的更多信息,请参阅 DefaultModelBindingMessageProvider

    [必需] 客户端上的验证

    在客户端上处理不可为 null 类型和字符串的方式与在服务器上不同。 在客户端上:

  • 只有在为值输入一个输入时,才认为该值存在。 因此,客户端验证处理不可为 null 类型的方式与处理可以为 null 类型的方式相同。
  • jQuery 验证必需方法将字符串字段中的空格视为有效输入。 如果只输入空格,服务器端验证会将必需的字符串字段视为无效。
  • 如前所述,将不可为 null 类型视为具有 [Required(AllowEmptyStrings = true)] 特性。 这意味着即使不应用 [Required(AllowEmptyStrings = true)] 特性,也可进行客户端验证。 但如果不使用该特性,将收到默认错误消息。 若要指定自定义错误消息,使用该特性。

    [远程] 特性

    [Remote] 属性实现客户端验证,该验证要求在服务器上调用方法来确定字段输入是否有效。 例如,应用可能需要验证用户名是否已在使用。

    若要实现远程验证:

  • 创建可供 JavaScript 调用的操作方法。 jQuery Validation 远程 方法需要 JSON 响应:

  • true 表示输入数据有效。
  • falseundefinednull 表示输入无效。 显示默认错误消息。
  • 任何其他字符串都表示输入无效。 将字符串显示为自定义错误消息。
  • 以下是返回自定义错误消息的操作方法示例:

    [AcceptVerbs("GET", "POST")] public IActionResult VerifyEmail(string email) if (!_userService.VerifyEmail(email)) return Json($"Email {email} is already in use."); return Json(true);
  • 在模型类中,使用指向验证操作方法的 [Remote] 特性注释属性,如下例所示:

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

    通过 [Remote] 特性的 AdditionalFields 属性可以根据服务器上的数据验证字段组合。 例如,如果 User 模型具有 FirstNameLastName 属性,可能需要验证该名称对尚未被现有用户占用。 下面的示例演示如何使用 AdditionalFields

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

    AdditionalFields 可以显式设置为字符串“FirstName”和“LastName”,但使用 nameof 运算符可简化以后的重构。 此验证的操作方法必须接受 firstNamelastName 参数:

    [AcceptVerbs("GET", "POST")] public IActionResult VerifyName(string firstName, string lastName) if (!_userService.VerifyName(firstName, lastName)) return Json($"A user named {firstName} {lastName} already exists."); return Json(true);

    用户输入名字或姓氏时,JavaScript 会进行远程调用,查看该名称对是否已占用。

    若要验证两个或更多附加字段,可将其以逗号分隔列表形式提供。 例如,若要向模型中添加 MiddleName 属性,可按以下示例所示设置 [Remote] 特性:

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

    AdditionalFields 与所有属性参数一样,必须是常量表达式。 因此,请勿使用内插字符串或调用 Join 来初始化 AdditionalFields

    内置特性的替代特性

    如果需要并非由内置属性提供的验证,可以:

  • 创建自定义特性
  • 实现 IValidatableObject
  • 自定义特性

    对于内置验证特性无法处理的情况,可以创建自定义验证特性。 创建继承自 ValidationAttribute 的类,并替代 IsValid 方法。

    方法 IsValid 接受名为 value 的对象,这是要验证的输入。 重载还接受 ValidationContext 对象,该对象提供其他信息,例如模型绑定创建的模型实例。

    以下示例验证“经典”流派电影的发行日期是否不晚于指定年份[ClassicMovie] 属性:

  • 仅在服务器上运行。
  • 对于经典电影,验证发行日期:
  • public class ClassicMovieAttribute : ValidationAttribute public ClassicMovieAttribute(int year) => Year = year; public int Year { get; } public string GetErrorMessage() => $"Classic movies must have a release year no later than {Year}."; protected override ValidationResult? IsValid( object? value, ValidationContext validationContext) var movie = (Movie)validationContext.ObjectInstance; var releaseYear = ((DateTime)value!).Year; if (movie.Genre == Genre.Classic && releaseYear > Year) return new ValidationResult(GetErrorMessage()); return ValidationResult.Success;

    上述示例中的 movie 变量表示 Movie 对象,其中包含表单提交中的数据。 验证失败时,返回 ValidationResult 和错误消息。

    IValidatableObject

    上述示例只适用于 Movie 类型。 类级别验证的另一方式是在模型类中实现 IValidatableObject,如下例所示:

    public class ValidatableMovie : IValidatableObject private const int _classicYear = 1960; public int Id { get; set; } [Required] [StringLength(100)] public string Title { get; set; } = null!; [DataType(DataType.Date)] [Display(Name = "Release Date")] public DateTime ReleaseDate { get; set; } [Required] [StringLength(1000)] public string Description { get; set; } = null!; [Range(0, 999.99)] public decimal Price { get; set; } public Genre Genre { get; set; } public bool Preorder { get; set; } public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) if (Genre == Genre.Classic && ReleaseDate.Year > _classicYear) yield return new ValidationResult( $"Classic movies must have a release year no later than {_classicYear}.", new[] { nameof(ReleaseDate) });

    顶级节点验证

    顶级节点包括:

  • 控制器属性
  • 页处理程序参数
  • 页模型属性
  • 除了验证模型属性之外,还验证了模型绑定的顶级节点。 在 示例应用的以下示例中 VerifyPhone , 方法使用 RegularExpressionAttribute 来验证 phone 操作参数:

    [AcceptVerbs("GET", "POST")] public IActionResult VerifyPhone( [RegularExpression(@"^\d{3}-\d{3}-\d{4}$")] string phone) if (!ModelState.IsValid) return Json($"Phone {phone} has an invalid format. Format: ###-###-####"); return Json(true);

    顶级节点可以将 BindRequiredAttribute 与验证属性结合使用。 在 示例应用的以下示例中, CheckAge 方法指定 age 在提交表单时必须从查询字符串绑定 参数:

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

    在“检查年龄”页 (CheckAge.cshtml) ,有两种形式。 第一个表单将 99Age 值作为查询字符串参数提交:https://localhost:5001/Users/CheckAge?Age=99

    当提交查询字符串中格式设置正确的 age 参数时,表单将进行验证。

    “检查年限”页面上的第二个表单提交请求正文中的 Age 值,验证失败。 绑定失败,因为 age 参数必须来自查询字符串。

    最大错误数

    达到最大错误数(默认为 200)时,验证停止。 可以使用 Program.cs 中的以下代码配置该数字:

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

    最大递归次数

    ValidationVisitor 遍历所验证模型的对象图。 对于深度或无限递归的模型,验证可能会导致堆栈溢出。 MvcOptions.MaxValidationDepth 提供了一种在访问者递归超过配置的深度时提前停止验证的方法。 MvcOptions.MaxValidationDepth 的默认值为 32。

    如果模型图不需要验证,验证将自动短路(跳过)。 运行时为其跳过验证的对象包括基元集合(如 byte[]string[]Dictionary<string, string>)和不具有任何验证器的复杂对象图。

    客户端验证

    客户端验证将阻止提交,直到表单变为有效为止。 “提交”按钮运行 JavaScript:要么提交表单要么显示错误消息。

    表单上存在输入错误时,客户端验证会避免到服务器的不必要往返。 以下脚本引用 和 _ValidationScriptsPartial.cshtml_Layout.cshtml,支持客户端验证:

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

    jQuery Unobtrusive Validation 脚本是一个基于热门 jQuery Validation 插件构建的自定义 Microsoft 前端库。 如果没有 jQuery 非介入式验证,则必须在两个位置编码相同的验证逻辑:一次是在模型属性上的服务器端验证特性中,一次是在客户端脚本中。 标记帮助程序HTML 帮助程序则使用模型属性中的验证特性和类型元数据,呈现需要验证的表单元素的 HTML 5 data- 特性。 jQuery Unobtrusive Validation 分析 data- 特性并将逻辑传递给 jQuery Validation,从而将服务器端验证逻辑有效地“复制”到客户端。 可以使用标记帮助程序在客户端上显示验证错误,如下所示:

    <div class="form-group"> <label asp-for="Movie.ReleaseDate" class="control-label"></label> <input asp-for="Movie.ReleaseDate" class="form-control" /> <span asp-validation-for="Movie.ReleaseDate" class="text-danger"></span>

    上述标记帮助程序呈现以下 HTML:

    <div class="form-group">
        <label class="control-label" for="Movie_ReleaseDate">Release Date</label>
        <input class="form-control" type="date" data-val="true"
            data-val-required="The Release Date field is required."
            id="Movie_ReleaseDate" name="Movie.ReleaseDate" value="">
        <span class="text-danger field-validation-valid"
            data-valmsg-for="Movie.ReleaseDate" data-valmsg-replace="true"></span>
    

    请注意,HTML 输出中的 data- 特性与 Movie.ReleaseDate 属性的验证特性相对应。 data-val-required 特性包含在用户未填写上映日期字段时将显示的错误消息。 jQuery Unobtrusive Validation 将此值传递给 jQuery Validation required() 方法,该方法随后在随附的 <span> 元素中显示该消息。

    数据类型验证基于属性的 .NET 类型,除非该类型被 [DataType] 属性重写。 浏览器具有自己的默认错误消息,但是 jQuery 验证非介入式验证包可以替代这些消息。 [DataType] [ EmailAddress] 等属性和子类可用于指定错误消息。

    非介入式验证

    有关非介入式验证的信息,请参阅此 GitHub 问题

    向动态表单添加验证

    jQuery Unobtrusive Validation 会在当页面第一次加载时将验证逻辑和参数传递到 jQuery Validation。 因此,不会对动态生成的表单自动执行验证。 若要启用验证,指示 jQuery 非介入式验证在创建动态表单后立即对其进行分析。 例如,以下代码在通过 AJAX 添加的表单上设置客户端验证。

    $.get({
        url: "https://url/that/returns/a/form",
        dataType: "html",
        error: function(jqXHR, textStatus, errorThrown) {
            alert(textStatus + ": Couldn't add form. " + errorThrown);
        success: function(newFormHTML) {
            var container = document.getElementById("form-container");
            container.insertAdjacentHTML("beforeend", newFormHTML);
            var forms = container.getElementsByTagName("form");
            var newForm = forms[forms.length - 1];
            $.validator.unobtrusive.parse(newForm);
    

    $.validator.unobtrusive.parse() 方法采用 jQuery 选择器作为它的一个参数。 此方法指示 jQuery 非介入式验证分析该选择器内表单的 data- 属性。 这些特性的值随后会传递到 jQuery Validation 插件。

    向动态控件添加验证

    $.validator.unobtrusive.parse() 方法适用于整个表单,而不是 <input><select/> 等单个动态生成的控件。 若要重新分析表单,删除之前分析表单时添加的验证数据,如下例所示:

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

    自定义客户端验证

    自定义客户端验证是通过生成适用于自定义 jQuery Validation 适配器的 data- HTML 特性来完成的。 以下示例适配器代码是为本文前面部分介绍的 [ClassicMovie][ClassicMovieWithClientValidator] 特性编写的:

    $.validator.addMethod('classicmovie', function (value, element, params) { var genre = $(params[0]).val(), year = params[1], date = new Date(value); // The Classic genre has a value of '0'. if (genre && genre.length > 0 && genre[0] === '0') { // The release date for a Classic is valid if it's no greater than the given year. return date.getUTCFullYear() <= year; return true; $.validator.unobtrusive.adapters.add('classicmovie', ['year'], function (options) { var element = $(options.form).find('select#Movie_Genre')[0]; options.rules['classicmovie'] = [element, parseInt(options.params['year'])]; options.messages['classicmovie'] = options.message;

    有关如何编写适配器的信息,请参阅 jQuery Validation 文档

    给定字段的适配器的使用由 data- 特性触发,这些特性:

  • 将字段标记为正在验证 (data-val="true")。
  • 确定验证规则名称和错误消息文本(例如,data-val-rulename="Error message.")。
  • 提供验证器所需的任何其他参数(例如,data-val-rulename-param1="value")。
  • 以下示例显示了data-示例应用的ClassicMovie 属性的属性:

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

    如前所述,标记帮助程序HTML 帮助程序使用验证特性的信息呈现 data- 特性。 编写用于创建自定义 data- HTML 特性的代码有以下两种方式:

  • 创建派生自 AttributeAdapterBase<TAttribute> 的类和实现 IValidationAttributeAdapterProvider 的类,并在 DI 中注册特性及其适配器。 此方法遵循 单一责任原则 ,即与服务器相关的验证代码和与客户端相关的验证代码位于单独的类中。 适配器还具有一个优点,因为它已在 DI 中注册,因此可以根据需要使用 DI 中的其他服务。
  • ValidationAttribute 类中实现 IClientModelValidator。 如果特性既不进行任何服务器端验证,也不需要 DI 的任何服务,则此方法可能适用。
  • 用于客户端验证的 AttributeAdapter

    在 HTML 中呈现data-属性的此方法由ClassicMovie示例应用中的 属性使用。 若要使用此方法添加客户端验证:

  • 为自定义验证特性创建特性适配器类。 从 AttributeAdapterBase<TAttribute>派生类。 创建将 data- 特性添加到所呈现输出中的 AddValidation 方法,如下例所示:

    public class ClassicMovieAttributeAdapter : AttributeAdapterBase<ClassicMovieAttribute> public ClassicMovieAttributeAdapter( ClassicMovieAttribute attribute, IStringLocalizer? stringLocalizer) : base(attribute, stringLocalizer) public override void AddValidation(ClientModelValidationContext context) MergeAttribute(context.Attributes, "data-val", "true"); MergeAttribute(context.Attributes, "data-val-classicmovie", GetErrorMessage(context)); var year = Attribute.Year.ToString(CultureInfo.InvariantCulture); MergeAttribute(context.Attributes, "data-val-classicmovie-year", year); public override string GetErrorMessage(ModelValidationContextBase validationContext) => Attribute.GetErrorMessage();
  • 创建实现 IValidationAttributeAdapterProvider 的适配器提供程序类。 使用 GetAttributeAdapter 方法,将自定义属性传递给适配器的构造函数,如下例所示:

    public class CustomValidationAttributeAdapterProvider : IValidationAttributeAdapterProvider private readonly IValidationAttributeAdapterProvider baseProvider = new ValidationAttributeAdapterProvider(); public IAttributeAdapter? GetAttributeAdapter( ValidationAttribute attribute, IStringLocalizer? stringLocalizer) if (attribute is ClassicMovieAttribute classicMovieAttribute) return new ClassicMovieAttributeAdapter(classicMovieAttribute, stringLocalizer); return baseProvider.GetAttributeAdapter(attribute, stringLocalizer);
  • Program.cs 中为 DI 注册适配器提供程序:

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

    用于客户端验证的 IClientModelValidator

    在 HTML 中呈现data-属性的此方法由ClassicMovieWithClientValidator示例应用中的 属性使用。 若要使用此方法添加客户端验证:

  • 在自定义验证特性中,实现 IClientModelValidator 接口并创建 AddValidation 方法。 使用 AddValidation 方法,添加 data- 特性进行验证,如下例所示:

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

    禁用客户端验证

    以下代码禁用 Razor Pages 中的客户端验证:

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

    可禁用客户端验证的其他选项:

  • 注释掉所有文件中对 _ValidationScriptsPartial.cshtml 引用。
  • 删除 Pages\Shared_ValidationScriptsPartial.cshtml 文件的内容。
  • 上述方法不会阻止 ASP.NET CoreIdentityRazor类库的客户端验证。 有关详细信息,请参阅 ASP.NET Core 项目中的基架

  • System.ComponentModel.DataAnnotations
  • 本文介绍如何在 ASP.NET Core MVC 或 Razor Pages 应用中验证用户输入。

    查看或下载示例代码如何下载)。

    模型状态表示两个子系统的错误:模型绑定和模型验证。 源自模型绑定的错误通常是数据转换错误。 例如,在一个整数字段中输入一个“x”。 模型验证在模型绑定后发生,并报告数据不符合业务规则的错误。 例如,在需要 1 到 5 之间评分的字段中输入 0。

    模型绑定和模型验证都在执行控制器操作或 Razor Pages 处理程序方法之前进行。 Web 应用负责检查 ModelState.IsValid 并做出相应响应。 Web 应用通常会重新显示带有错误消息的页面:

    public async Task<IActionResult> OnPostAsync() if (!ModelState.IsValid) return Page(); _context.Movies.Add(Movie); await _context.SaveChangesAsync(); return RedirectToPage("./Index");

    如果 Web API 控制器具有 [ApiController] 属性,则无需检查ModelState.IsValid。 在此情况下,如果模型状态无效,将返回包含错误详细信息的自动 HTTP 400 响应。 有关详细信息,请参阅自动 HTTP 400 响应

    重新运行验证

    验证自动进行,但是可能需要手动进行重复验证。 例如,你可能为属性计算一个值,并且希望将属性设置为所计算的值后,再重新运行验证。 若要重新运行验证,请调用 ModelStateDictionary.ClearValidationState 来清除特定于模型的验证,然后再调用 TryValidateModel 对模型进行验证:

    public async Task<IActionResult> OnPostTryValidateAsync() var modifiedReleaseDate = DateTime.Now.Date; Movie.ReleaseDate = modifiedReleaseDate; ModelState.ClearValidationState(nameof(Movie)); if (!TryValidateModel(Movie, nameof(Movie))) return Page(); _context.Movies.Add(Movie); await _context.SaveChangesAsync(); return RedirectToPage("./Index");

    通过验证特性可以为模型属性指定验证规则。 示例应用中的以下示例显示了一个使用验证属性进行批注的模型类。 属性 [ClassicMovie] 是自定义验证属性,其他属性是内置属性。 [ClassicMovieWithClientValidator] 未显示,它表示实现自定义特性的另一种方法。

    public class Movie public int Id { get; set; } [Required] [StringLength(100)] public string Title { get; set; } [ClassicMovie(1960)] [DataType(DataType.Date)] [Display(Name = "Release Date")] public DateTime ReleaseDate { get; set; } [Required] [StringLength(1000)] public string Description { get; set; } [Range(0, 999.99)] public decimal Price { get; set; } public Genre Genre { get; set; } public bool Preorder { get; set; }

    以下是一些内置验证特性:

  • [ValidateNever]:指示应从验证中排除属性或参数。
  • [CreditCard]:验证属性是否具有信用卡格式。 需要 jQuery Validation 附加方法
  • [比较]:验证模型中的两个属性是否匹配。
  • [EmailAddress]:验证 属性是否具有电子邮件格式。
  • [电话]:验证 属性是否具有电话号码格式。
  • [Range]:验证属性值是否在指定范围内。
  • [RegularExpression]:验证属性值是否与指定的正则表达式匹配。
  • [必需]:验证字段是否不为 null。 请参阅 [Required] 属性,获取关于该特性的行为的详细信息。
  • [StringLength]:验证字符串属性值是否未超过指定的长度限制。
  • [Url]:验证 属性是否具有 URL 格式。
  • [远程]:通过调用服务器上的操作方法验证客户端上的输入。 请参阅 [Remote] 属性,获取关于该特性的行为的详细信息。
  • 可以在 命名空间中找到 System.ComponentModel.DataAnnotations 验证属性的完整列表。

    通过验证特性可以指定要为无效输入显示的错误消息。 例如:

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

    在内部,特性使用用于字段名的某个占位符调用 String.Format,有时还使用额外占位符。 例如:

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

    应用于 Name 属性时,上述代码创建的错误消息将为“名称长度必须介于 6 到 8 之间”。

    若要了解特定属性的错误消息传递给 String.Format 哪些参数,请参阅 DataAnnotations 源代码

    不可为 null 的引用类型和 [Required] 特性

    验证系统将不可为 null 的参数或绑定属性视为它们具有 [Required(AllowEmptyStrings = true)] 特性。 通过启用 Nullable 上下文,MVC 将隐式开始对不可为 null 的属性或参数进行验证,就像它们已使用 [Required(AllowEmptyStrings = true)] 特性进行了特性化一样。 考虑下列代码:

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

    如果应用是使用 <Nullable>enable</Nullable>生成的,则 ON 或表单帖子中 JS缺少 的值Name会导致验证错误。 使用可为 null 的引用类型来实现为 Name 属性指定 NULL 或缺少的值:

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

    可以通过在 Startup.ConfigureServices 中配置 SuppressImplicitRequiredAttributeForNonNullableReferenceTypes 来禁用此行为:

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

    [必需] 服务器上的验证

    在服务器上,如果属性为 null,则认为所需值缺失。 不可为 null 的字段始终有效,并且从不显示 [Required] 属性的错误消息。

    但是,不可为 null 的属性的模型绑定可能会失败,从而导致 The value '' is invalid 等错误消息。 若要为不可为 null 的类型的服务器端验证指定自定义错误消息,可使用以下选项:

  • 将字段设置为可以为 null(例如,decimal?而不是 decimal)。 Nullable<T> 值类型被视为标准的可以为 null 的类型。

  • 指定模型绑定要使用的默认错误消息,如以下示例所示:

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

    有关模型绑定错误(可以为其设置默认消息)的更多信息,请参阅 DefaultModelBindingMessageProvider

    [必需] 客户端上的验证

    在客户端上处理不可为 null 类型和字符串的方式与在服务器上不同。 在客户端上:

  • 只有在为值输入一个输入时,才认为该值存在。 因此,客户端验证处理不可为 null 类型的方式与处理可以为 null 类型的方式相同。
  • jQuery 验证必需方法将字符串字段中的空格视为有效输入。 如果只输入空格,服务器端验证会将必需的字符串字段视为无效。
  • 如前所述,将不可为 null 类型视为具有 [Required(AllowEmptyStrings = true)] 特性。 这意味着即使不应用 [Required(AllowEmptyStrings = true)] 特性,也可进行客户端验证。 但如果不使用该特性,将收到默认错误消息。 若要指定自定义错误消息,使用该特性。

    [远程] 特性

    [Remote] 属性实现客户端验证,该验证要求在服务器上调用 方法来确定字段输入是否有效。 例如,应用可能需要验证用户名是否已在使用。

    若要实现远程验证:

  • 创建可供 JavaScript 调用的操作方法。 jQuery 验证 远程 方法需要 JSON 响应:

  • true 表示输入数据有效。
  • falseundefinednull 表示输入无效。 显示默认错误消息。
  • 任何其他字符串都表示输入无效。 将字符串显示为自定义错误消息。
  • 以下是返回自定义错误消息的操作方法示例:

    [AcceptVerbs("GET", "POST")] public IActionResult VerifyEmail(string email) if (!_userService.VerifyEmail(email)) return Json($"Email {email} is already in use."); return Json(true);
  • 在模型类中,使用指向验证操作方法的 [Remote] 特性注释属性,如下例所示:

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

    通过 [Remote] 特性的 AdditionalFields 属性可以根据服务器上的数据验证字段组合。 例如,如果 User 模型具有 FirstNameLastName 属性,可能需要验证该名称对尚未被现有用户占用。 下面的示例演示如何使用 AdditionalFields

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

    AdditionalFields 可以显式设置为字符串“FirstName”和“LastName”,但使用 nameof 运算符可简化以后的重构。 此验证的操作方法必须接受 firstNamelastName 参数:

    [AcceptVerbs("GET", "POST")] public IActionResult VerifyName(string firstName, string lastName) if (!_userService.VerifyName(firstName, lastName)) return Json($"A user named {firstName} {lastName} already exists."); return Json(true);

    用户输入名字或姓氏时,JavaScript 会进行远程调用,查看该名称对是否已占用。

    若要验证两个或更多附加字段,可将其以逗号分隔列表形式提供。 例如,若要向模型中添加 MiddleName 属性,可按以下示例所示设置 [Remote] 特性:

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

    AdditionalFields 与所有属性参数一样,必须是常量表达式。 因此,请勿使用内插字符串或调用 Join 来初始化 AdditionalFields

    内置特性的替代特性

    如果需要并非由内置属性提供的验证,可以:

  • 创建自定义特性
  • 实现 IValidatableObject
  • 自定义特性

    对于内置验证特性无法处理的情况,可以创建自定义验证特性。 创建继承自 ValidationAttribute 的类,并替代 IsValid 方法。

    方法 IsValid 接受名为 的对象,该值是要验证的输入。 重载还接受 ValidationContext 对象,该对象提供其他信息,例如模型绑定创建的模型实例。

    以下示例验证“经典”流派电影的发行日期是否不晚于指定年份[ClassicMovie] 属性:

  • 仅在服务器上运行。
  • 对于经典电影,验证发行日期:
  • public class ClassicMovieAttribute : ValidationAttribute public ClassicMovieAttribute(int year) Year = year; public int Year { get; } public string GetErrorMessage() => $"Classic movies must have a release year no later than {Year}."; protected override ValidationResult IsValid(object value, ValidationContext validationContext) var movie = (Movie)validationContext.ObjectInstance; var releaseYear = ((DateTime)value).Year; if (movie.Genre == Genre.Classic && releaseYear > Year) return new ValidationResult(GetErrorMessage()); return ValidationResult.Success;

    上述示例中的 movie 变量表示 Movie 对象,其中包含表单提交中的数据。 验证失败时,返回 ValidationResult 和错误消息。

    IValidatableObject

    上述示例只适用于 Movie 类型。 类级别验证的另一方式是在模型类中实现 IValidatableObject,如下例所示:

    public class ValidatableMovie : IValidatableObject private const int _classicYear = 1960; public int Id { get; set; } [Required] [StringLength(100)] public string Title { get; set; } [DataType(DataType.Date)] [Display(Name = "Release Date")] public DateTime ReleaseDate { get; set; } [Required] [StringLength(1000)] public string Description { get; set; } [Range(0, 999.99)] public decimal Price { get; set; } public Genre Genre { get; set; } public bool Preorder { get; set; } public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) if (Genre == Genre.Classic && ReleaseDate.Year > _classicYear) yield return new ValidationResult( $"Classic movies must have a release year no later than {_classicYear}.", new[] { nameof(ReleaseDate) });

    顶级节点验证

    顶级节点包括:

  • 控制器属性
  • 页处理程序参数
  • 页模型属性
  • 除了验证模型属性之外,还验证了模型绑定的顶级节点。 在 示例应用中的以下示例中 VerifyPhone , 方法使用 RegularExpressionAttribute 来验证 phone 操作参数:

    [AcceptVerbs("GET", "POST")] public IActionResult VerifyPhone( [RegularExpression(@"^\d{3}-\d{3}-\d{4}$")] string phone) if (!ModelState.IsValid) return Json($"Phone {phone} has an invalid format. Format: ###-###-####"); return Json(true);

    顶级节点可以将 BindRequiredAttribute 与验证属性结合使用。 在 以下示例应用中CheckAge 方法指定 age 在提交表单时必须从查询字符串绑定 参数:

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

    在“检查年龄”页 (CheckAge.cshtml) 中,有两种形式。 第一个表单将 99Age 值作为查询字符串参数提交:https://localhost:5001/Users/CheckAge?Age=99

    当提交查询字符串中格式设置正确的 age 参数时,表单将进行验证。

    “检查年限”页面上的第二个表单提交请求正文中的 Age 值,验证失败。 绑定失败,因为 age 参数必须来自查询字符串。

    最大错误数

    达到最大错误数(默认为 200)时,验证停止。 可以使用 Startup.ConfigureServices 中的以下代码配置该数字:

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

    最大递归次数

    ValidationVisitor 遍历所验证模型的对象图。 对于深度或无限递归的模型,验证可能会导致堆栈溢出。 MvcOptions.MaxValidationDepth 提供了一种在访问者递归超过配置的深度时提前停止验证的方法。 MvcOptions.MaxValidationDepth 的默认值为 32。

    如果模型图不需要验证,验证将自动短路(跳过)。 运行时为其跳过验证的对象包括基元集合(如 byte[]string[]Dictionary<string, string>)和不具有任何验证器的复杂对象图。

    客户端验证

    客户端验证将阻止提交,直到表单变为有效为止。 “提交”按钮运行 JavaScript:要么提交表单要么显示错误消息。

    表单上存在输入错误时,客户端验证会避免到服务器的不必要往返。 以下脚本引用 和 _ValidationScriptsPartial.cshtml_Layout.cshtml,支持客户端验证:

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

    jQuery Unobtrusive Validation 脚本是一个基于热门 jQuery Validation 插件构建的自定义 Microsoft 前端库。 如果没有 jQuery 非介入式验证,则必须在两个位置编码相同的验证逻辑:一次是在模型属性上的服务器端验证特性中,一次是在客户端脚本中。 标记帮助程序HTML 帮助程序则使用模型属性中的验证特性和类型元数据,呈现需要验证的表单元素的 HTML 5 data- 特性。 jQuery Unobtrusive Validation 分析 data- 特性并将逻辑传递给 jQuery Validation,从而将服务器端验证逻辑有效地“复制”到客户端。 可以使用标记帮助程序在客户端上显示验证错误,如下所示:

    <div class="form-group"> <label asp-for="Movie.ReleaseDate" class="control-label"></label> <input asp-for="Movie.ReleaseDate" class="form-control" /> <span asp-validation-for="Movie.ReleaseDate" class="text-danger"></span>

    上述标记帮助程序呈现以下 HTML:

    <div class="form-group">
        <label class="control-label" for="Movie_ReleaseDate">Release Date</label>
        <input class="form-control" type="date" data-val="true"
            data-val-required="The Release Date field is required."
            id="Movie_ReleaseDate" name="Movie.ReleaseDate" value="">
        <span class="text-danger field-validation-valid"
            data-valmsg-for="Movie.ReleaseDate" data-valmsg-replace="true"></span>
    

    请注意,HTML 输出中的 data- 特性与 Movie.ReleaseDate 属性的验证特性相对应。 data-val-required 特性包含在用户未填写上映日期字段时将显示的错误消息。 jQuery Unobtrusive Validation 将此值传递给 jQuery Validation required() 方法,该方法随后在随附的 <span> 元素中显示该消息。

    数据类型验证基于属性的 .NET 类型,除非该类型被 [DataType] 属性重写。 浏览器具有自己的默认错误消息,但是 jQuery 验证非介入式验证包可以替代这些消息。 [DataType] [ EmailAddress] 等属性和子类可用于指定错误消息。

    非介入式验证

    有关非介入式验证的信息,请参阅此 GitHub 问题

    向动态表单添加验证

    jQuery Unobtrusive Validation 会在当页面第一次加载时将验证逻辑和参数传递到 jQuery Validation。 因此,不会对动态生成的表单自动执行验证。 若要启用验证,指示 jQuery 非介入式验证在创建动态表单后立即对其进行分析。 例如,以下代码在通过 AJAX 添加的表单上设置客户端验证。

    $.get({
        url: "https://url/that/returns/a/form",
        dataType: "html",
        error: function(jqXHR, textStatus, errorThrown) {
            alert(textStatus + ": Couldn't add form. " + errorThrown);
        success: function(newFormHTML) {
            var container = document.getElementById("form-container");
            container.insertAdjacentHTML("beforeend", newFormHTML);
            var forms = container.getElementsByTagName("form");
            var newForm = forms[forms.length - 1];
            $.validator.unobtrusive.parse(newForm);
    

    $.validator.unobtrusive.parse() 方法采用 jQuery 选择器作为它的一个参数。 此方法指示 jQuery 非介入式验证分析该选择器内表单的 data- 属性。 这些特性的值随后会传递到 jQuery Validation 插件。

    向动态控件添加验证

    $.validator.unobtrusive.parse() 方法适用于整个表单,而不是 <input><select/> 等单个动态生成的控件。 若要重新分析表单,删除之前分析表单时添加的验证数据,如下例所示:

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

    自定义客户端验证

    自定义客户端验证是通过生成适用于自定义 jQuery Validation 适配器的 data- HTML 特性来完成的。 以下示例适配器代码是为本文前面部分介绍的 [ClassicMovie][ClassicMovieWithClientValidator] 特性编写的:

    $.validator.addMethod('classicmovie', function (value, element, params) { var genre = $(params[0]).val(), year = params[1], date = new Date(value); // The Classic genre has a value of '0'. if (genre && genre.length > 0 && genre[0] === '0') { // The release date for a Classic is valid if it's no greater than the given year. return date.getUTCFullYear() <= year; return true; $.validator.unobtrusive.adapters.add('classicmovie', ['year'], function (options) { var element = $(options.form).find('select#Movie_Genre')[0]; options.rules['classicmovie'] = [element, parseInt(options.params['year'])]; options.messages['classicmovie'] = options.message;

    有关如何编写适配器的信息,请参阅 jQuery Validation 文档

    给定字段的适配器的使用由 data- 特性触发,这些特性:

  • 将字段标记为正在验证 (data-val="true")。
  • 确定验证规则名称和错误消息文本(例如,data-val-rulename="Error message.")。
  • 提供验证器所需的任何其他参数(例如,data-val-rulename-param1="value")。
  • 以下示例显示了data-示例应用的ClassicMovie 属性的属性:

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

    如前所述,标记帮助程序HTML 帮助程序使用验证特性的信息呈现 data- 特性。 编写用于创建自定义 data- HTML 特性的代码有以下两种方式:

  • 创建派生自 AttributeAdapterBase<TAttribute> 的类和实现 IValidationAttributeAdapterProvider 的类,并在 DI 中注册特性及其适配器。 此方法遵循 单一责任原则 ,即与服务器相关的验证代码和与客户端相关的验证代码位于单独的类中。 适配器还具有一个优点,因为它已在 DI 中注册,因此可以根据需要使用 DI 中的其他服务。
  • ValidationAttribute 类中实现 IClientModelValidator。 如果特性既不进行任何服务器端验证,也不需要 DI 的任何服务,则此方法可能适用。
  • 用于客户端验证的 AttributeAdapter

    在 HTML 中呈现data-属性的此方法由ClassicMovie示例应用中的 属性使用。 若要使用此方法添加客户端验证:

  • 为自定义验证特性创建特性适配器类。 从 AttributeAdapterBase<TAttribute>派生类。 创建将 data- 特性添加到所呈现输出中的 AddValidation 方法,如下例所示:

    public class ClassicMovieAttributeAdapter : AttributeAdapterBase<ClassicMovieAttribute> public ClassicMovieAttributeAdapter(ClassicMovieAttribute attribute, IStringLocalizer stringLocalizer) : base(attribute, stringLocalizer) public override void AddValidation(ClientModelValidationContext context) MergeAttribute(context.Attributes, "data-val", "true"); MergeAttribute(context.Attributes, "data-val-classicmovie", GetErrorMessage(context)); var year = Attribute.Year.ToString(CultureInfo.InvariantCulture); MergeAttribute(context.Attributes, "data-val-classicmovie-year", year); public override string GetErrorMessage(ModelValidationContextBase validationContext) => Attribute.GetErrorMessage();
  • 创建实现 IValidationAttributeAdapterProvider 的适配器提供程序类。 使用 GetAttributeAdapter 方法,将自定义属性传递给适配器的构造函数,如下例所示:

    public class CustomValidationAttributeAdapterProvider : IValidationAttributeAdapterProvider private readonly IValidationAttributeAdapterProvider baseProvider = new ValidationAttributeAdapterProvider(); public IAttributeAdapter GetAttributeAdapter(ValidationAttribute attribute, IStringLocalizer stringLocalizer) if (attribute is ClassicMovieAttribute classicMovieAttribute) return new ClassicMovieAttributeAdapter(classicMovieAttribute, stringLocalizer); return baseProvider.GetAttributeAdapter(attribute, stringLocalizer);
  • Startup.ConfigureServices 中为 DI 注册适配器提供程序:

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

    用于客户端验证的 IClientModelValidator

    在 HTML 中呈现data-属性的此方法由ClassicMovieWithClientValidator示例应用中的 属性使用。 若要使用此方法添加客户端验证:

  • 在自定义验证特性中,实现 IClientModelValidator 接口并创建 AddValidation 方法。 使用 AddValidation 方法,添加 data- 特性进行验证,如下例所示:

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

    禁用客户端验证

    以下代码禁用 Razor Pages 中的客户端验证:

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

    可禁用客户端验证的其他选项:

  • 注释掉所有文件中对 _ValidationScriptsPartial.cshtml 引用。
  • 删除 Pages\Shared_ValidationScriptsPartial.cshtml 文件的内容。
  • 上述方法不会阻止 ASP.NET CoreIdentityRazor类库的客户端验证。 有关详细信息,请参阅 ASP.NET Core 项目中的基架

  • System.ComponentModel.DataAnnotations
  •