您现在的位置是:网站首页> 编程资料编程资料

ASP.NET Core 6.0 基于模型验证的数据验证功能_实用技巧_

2023-05-24 289人已围观

简介 ASP.NET Core 6.0 基于模型验证的数据验证功能_实用技巧_

1 前言

在程序中,需要进行数据验证的场景经常存在,且数据验证是有必要的。前端进行数据验证,主要是为了减少服务器请求压力,和提高用户体验;后端进行数据验证,主要是为了保证数据的正确性,保证系统的健壮性。

本文描述的数据验证方案,是基于官方的模型验证(Model validation),也是笔者近期面试过程中才得知的方式【之前个人混淆了:模型验证(Model validation)和 EF 模型配置的数据注释(Data annotation)方式】。

注:MVC 和 API 的模型验证有些许差异,本文主要描述的是 API 下的模型验证。

1.1 数据验证的场景

比较传统的验证方式如下:

public string TraditionValidation(TestModel model) { if (string.IsNullOrEmpty(model.Name)) { return "名字不能为空!"; } if (model.Name.Length > 10) { return "名字长度不能超过10!"; } return "验证通过!"; }

在函数中,对模型的各个属性分别做验证。

虽然函数能与模型配合重复使用,但是确实不够优雅。

官方提供了模型验证(Model validation)的方式,下面将会基于这种方式,提出相应的解决方案。

1.2 本文的脉络

先大概介绍一下模型验证(Model validation)的使用,随后提出两种自定义方案。

最后会大概解读一下 AspNetCore 这一块相关的源码。

2 模型验证

2.1 介绍

官方提供的模型验证(Model validation)的方式,是通过在模型属性上添加验证特性(Validation attributes),配置验证规则以及相应的错误信息(ErrorMessage)。当验证不通过时,将会返回验证不通过的错误信息。

其中,除了内置的验证特性,用户也可以自定义验证特性(本文不展开),具体请自行查看自定义特性一节。

在 MVC 中,需要通过如下代码来调用(在 action 中):

if (!ModelState.IsValid) { return View(movie); }

在 API 中,只要控制器拥有 [ApiController] 特性,如果模型验证不通过,将自动返回包含错误信息的 HTTP400 相应,详细请参阅自动 HTTP 400 响应。

2.2 基本使用

(1)自定义模型

如下代码中,[Required] 表示该属性为必须,ErrorMessage = "" 为该验证特性验证不通过时,返回的验证信息。

public class TestModel { [Required(ErrorMessage = "名字不能为空!")] [StringLength(10, ErrorMessage = "名字长度不能超过10个字符!")] public string? Name { get; set; } [Phone(ErrorMessage = "手机格式错误!")] public string? Phone { get; set; } }

(2)控制器代码

控制器上有 [ApiController] 特性即可触发:

[ApiController] [Route("[controller]/[action]")] public class TestController : ControllerBase { [HttpPost] public TestModel ModelValidation(TestModel model) { return model; } }

(3)测试

输入不合法的数据,格式如下:

{ "name": "string string", "email": "111" }

输出信息如下:

{
  "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "traceId": "00-4d4df1b3618a97a6c50d5fe45884543d-81ac2a79523fd282-00",
  "errors": {
    "Name": [
      "名字长度不能超过10个字符!"
    ],
    "Email": [
      "邮箱格式错误!"
    ]
  }
}

2.3 内置特性

官方列出的一些内置特性如:

[ValidateNever]:指示属性或参数应从验证中排除。

[CreditCard]:验证属性是否具有信用卡格式。

[Compare]:验证模型中的两个属性是否匹配。

[EmailAddress]:验证属性是否具有电子邮件格式。

[Phone]:验证属性是否具有电话号码格式。

[Range]:验证属性值是否在指定的范围内。

[RegularExpression]:验证属性值是否与指定的正则表达式匹配。

[Required]:验证字段是否不为 null。

[StringLength]:验证字符串属性值是否不超过指定长度限制。

[URL]:验证属性是否具有 URL 格式。

[Remote]:通过在服务器上调用操作方法来验证客户端上的输入。

可以在命名空间中找到 System.ComponentModel.DataAnnotations 验证属性的完整列表。

3 自定义数据验证

3.1 介绍

由于官方模型验证返回的格式与我们程序实际需要的格式有差异,所以这一部分主要是替换模型验证的返回格式,使用的实际上还是模型验证的能力。

3.2 前置准备

准备一个统一返回格式:

public class ApiResult { public int Code { get; set; } public string? Msg { get; set; } public object? Data { get; set; } }

当数据验证不通过时:

Code 为 400,表示请求数据存在问题。

Msg 默认为:数据验证不通过!用于前端提示。

Data 为错误信息明细,用于前端提示。

如:

{ "code": 400, "msg": "数据验证不通过!", "data": [ "名字长度不能超过10个字符!", "邮箱格式错误!" ] }

3.3 方案1:替换工厂

替换 ApiBehaviorOptions 中默认定义的 InvalidModelStateResponseFactory,在 Program.cs 中:

builder.Services.Configure(options => { options.InvalidModelStateResponseFactory = actionContext => { //获取验证失败的模型字段 var errors = actionContext.ModelState .Where(s => s.Value != null && s.Value.ValidationState == ModelValidationState.Invalid) .SelectMany(s => s.Value!.Errors.ToList()) .Select(e => e.ErrorMessage) .ToList(); // 统一返回格式 var result = new ApiResult() { Code = StatusCodes.Status400BadRequest, Msg = "数据验证不通过!", Data = errors }; return new BadRequestObjectResult(result); }; });

3.4 方案2:自定义过滤器

(1)自定义过滤器

public class DataValidationFilter : IActionFilter { public void OnActionExecuting(ActionExecutingContext context) { // 如果其他过滤器已经设置了结果,则跳过验证 if (context.Result != null) return; // 如果验证通过,跳过后面的动作 if (context.ModelState.IsValid) return; // 获取失败的验证信息列表 var errors = context.ModelState .Where(s => s.Value != null && s.Value.ValidationState == ModelValidationState.Invalid) .SelectMany(s => s.Value!.Errors.ToList()) .Select(e => e.ErrorMessage) .ToArray(); // 统一返回格式 var result = new ApiResult() { Code = StatusCodes.Status400BadRequest, Msg = "数据验证不通过!", Data = errors }; // 设置结果 context.Result = new B
                
                

-六神源码网