.net模型校验和自定义校验
先学习回顾一下模型校验。
模型校验的结果关键词( 模型状态 、 重新运行验证 、 特性 验证 、 内置特性 )
1、模型状态
模型状态主要分为:模型绑定和模型验证。
模型绑定 的错误通常是数据转换错误。 例如,在一个整数字段中输入一个“x”。
模型验证 在模型绑定后发生,并报数据不符合业务逻辑的错误。 例如,在需要 1 到 5 之间评分的字段中输入 0或者6。
模型绑定和模型验证都在执行控制器操作或 Razor Pages 处理程序方法之前进行。 Web 应用负责检查
ModelState.IsValid
并做出相应响应。
Web 应用通常会重新显示包含错误消息的页面,如以下 controller 示例所示:
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 响应。
特性路由要求
[ApiController]
属性使属性路由成为要求。 例如:
C#复制
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
不能通过由
UseEndpoints
、
UseMvc
或
UseMvcWithDefaultRoute
定义的
传统路由
访问操作。
自动 HTTP 400 响应
[ApiController]
属性使模型验证错误自动触发 HTTP 400 响应。 因此,操作方法中不需要以下代码:
C#复制
if (!ModelState.IsValid)
return BadRequest(ModelState);
http:// ASP.NET Core MVC 使用 ModelStateInvalidFilter 操作筛选器来执行上述检查。
2、重新运行验证,手动二次校验
有时候数据发生变化,需要手动进行重复验证。 例如,你可能为属性计算一个值,并且希望将属性设置为新值后,再重新运行验证。 若要重新运行验证,请调用
ModelStateDictionary.ClearValidationState
来清除特定于模型的验证,然后再调用
TryValidateModel
对模型进行验证:
C#复制
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");
3、 添加特性验证(就是头上写个【xxx】就叫特性,java习惯叫注解)
通过 添加特性验证可以为模型的属性指定验证规则。 以下示例显示使用验证特性进行注释的模型类。
[ClassicMovie]
特性为自定义的验证特性,其他特性为内置的验证特性。
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; }
其中的=null!
现有项目禁用Null状态分析和变量注释,这意味着所有引用类型都可以为Null。从.NET6开始,默认情况下会为新项目启用它们,出发点是为了降低在线上出现抛出System.NullReferenceException的可能性。加上=null!,告诉编译器不用检查了,我这里校验过了。
4、内置特性
以下是一些内置验证特性:
- [ValidateNever] :指示应将某一属性或参数排除在验证外。
- [CreditCard] :验证属性是否有信用卡格式。 需要 jQuery Validation 附加方法 。
- [Compare] :验证模型中的两个属性是否匹配。
- [EmailAddress] :验证属性是否有电子邮件格式。
- [Phone] :验证属性是否有电话号码格式。
- [Range] :验证属性值是否在指定范围内。
- [RegularExpression] :验证属性值是否与指定的正则表达式匹配。
-
[Required]
:验证字段是否不为 null。 请参阅
s="nolink">[Required] 属性 ,获取关于该特性的行为的详细信息。
- [StringLength] :验证字符串属性值是否未超过指定长度限制。
- [Url] :验证属性是否有 URL 格式。
-
[Remote]
:通过调用服务器上的操作方法,验证客户端上的输入。 请参阅
ass="nolink">[Remote] 属性 ,获取关于该特性的行为的详细信息。
在 System.ComponentModel.DataAnnotations 命名空间中可找到验证特性的完整列表。
5、自定义设置错误消息
通过验证特性可以指定要为无效输入显示的错误消息。 例如:
C#复制
[StringLength(8, ErrorMessage = "Name length can't be more than 8.")]
在内部,特性使用用于字段名的某个占位符调用 String.Format ,有时还使用额外占位符。 例如:
C#复制
[StringLength(8, ErrorMessage = "{0} length must be between {2} and {1}.", MinimumLength = 6)]
应用于
Name
属性时,上述代码创建的错误消息将为“名称长度必须介于 6 到 8 之间”。
若要查找为特定特性的错误消息而传递给
String.Format
的参数,请参阅
DataAnnotations 源代码
。
6、在验证模型错误中使用 JSON
builder.Services.AddControllers(options =>
options.ModelMetadataDetailsProviders.Add(new SystemTextJsonValidationMetadataProvider());
也可以使用以下代码将验证配置为使用
NewtonsoftJsonValidationMetadataProvider
以在使用
http://Json.NET
时使用 JSON 属性名
builder.Services.AddControllers(options =>
options.ModelMetadataDetailsProviders.Add(new NewtonsoftJsonValidationMetadataProvider());
}).AddNewtonsoftJson();
8、不能为 null 的引用类型和 [Required] 特性
验证系统将不能是 null 的参数或绑定属性视为它们具有
[Required(AllowEmptyStrings = true)]
特性。(.net6自动启用了) 通过an class="nolink">启用 Nullable 上下文,MVC 将隐式开始对不可为 null 的属性或参数进行验证,就像它们已使用
[Required(AllowEmptyStrings = false)]
特性进行了特性化一样。 比如下列代码:
虽然什么都没写,但是请求不写这个参数,就会报错。
public class Person
public string Name { get; set; }
如果应用是使用
<Nullable>enable</Nullable>
构建的,则 JSON 或表单发布中缺少
Name
的值会导致验证错误。 使用可为 null 的引用类型来实现为
Name
属性指定 NULL 或缺少的值:
public class Person
public string? Name { get; set; }
如果不希望每个地方都设置引用类型的不为空的话
可以通过在
Program.cs
中配置
SuppressImplicitRequiredAttributeForNonNullableReferenceTypes
来禁用此行为:
builder.Services.AddControllers(
options => options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true);
或者项目.csporj文件中Nullable 配置,找到<Nullable>enable</Nullable>去掉。
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable><!--去掉Nullable开启的配置-->
</PropertyGroup>
7、[必需] 服务器上校验
在服务器上,如果模型的属性为 null,则认为缺少了所需值。 不能为 null 的字段始终有效,并且也不会显示
[Required]
属性的错误消息。这点就很蛋疼。经常404,但是就是查不出问题?
不能为 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。
8、[远程] 特性,自己写一个验证方法,用来验证前台输入
[Remote] 特性用来实现客户端验证,这种验证方式需要在服务器上调用方法,以确定字段输入是否有效。
例如,验证用户名是否已在使用。
若要实现远程验证:
-
创建可供 JavaScript 调用的操作方法。 jQuery Validation
remote
方法接受 JSON 响应:
-
true
表示输入数据有效。 -
false
、undefined
或null
表示输入无效。 显示默认错误消息。 - 任何其他字符串都表示输入无效。 将字符串显示为自定义错误消息。
以下是返回自定义错误消息的操作方法示例:
[AcceptVerbs("GET", "POST")]
public IActionResult VerifyEmail(string email)
if (!_userService.VerifyEmail(email))
return Json($"Email {email} is already in use.");
return Json(true);
在模型类中,使用指向验证操作方法的 [Remote] 特性注释属性,如下例所示:
C#复制
[Remote(action: "VerifyEmail", controller: "Users")] public string Email { get; set; } = null!;
为已禁用 JavaScript 的客户端实现 服务器端验证 。
例如验证姓名是否存在,使用附加字段AdditionalFields
通过
[Remote]
特性的
AdditionalFields
属性可以根据服务器上的数据验证字段组合。 例如,如果
User
模型具有
FirstName
和
LastName
属性,可能需要验证该名称对尚未被现有用户占用。 下面的示例演示如何使用
AdditionalFields
:,在验证firstName的时候把lastName的值一并带进去验证。
[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
运算符可简化稍后的重构过程。 此验证的操作方法必须接受
firstName
和
lastName
参数:
[AcceptVerbs("GET", "POST")]
public IActionResult VerifyName(string firstName, string lastName)
if (!_userService.VerifyName(firstName, lastName))
return Json($"A user named {firstName} {lastName} already exists.");