背景

.NETCore下的模型验证相信绝大部分的.NET开发者或多或少的都用过,微软官方提供的模型验证相关的类位于System.ComponentModel.DataAnnotations命令空间下,在使用的时候只需要给属性添加不同的特性即可实现对应的模型验证。如下所示:

public class Movie
{
public int Id { get; set; } [Required]
[StringLength(100)]
public string Title { get; set; }
}

在WebApi中,当请求接口时,程序会自动对模型进行验证,如无法验证通过,则会直接终止后续的逻辑执行,并响应400状态码,响应内容如下所示:

{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "00-4b16460fc83d7b4daa4f10d939016982-f823eebede419a4a-00",
"errors": {
"aa": [
"The aa field is required."
]
}
}

当然,你也可以自定义响应的内容,这不是本文的重点。本文的重点是,.NETCore系统默认的模型验证功能并不够强大,仅支持在Controller的Action中使用,不支持非Controller中或者控制台程序的验证,且代码侵入性较强。

而FluentValidation(https://fluentvalidation.net/ )则是功能更为强大的模型验证框架,支持任何场景下的模型验证,且不侵入代码。

下面就来和笔者一起了解下FluentValidation的用法。

接入

FluentValidation支持一下平台:

  • .NET 4.6.1+
  • .NET Core 2.0+
  • .NET Standard 2.0+

各个平台的集成方式大同小异,本文仅讲解.NETCore3.1的集成方式。

首先,使用NuGet安装FluentValidation.AspNetCore依赖。

添加需要验证的模型类,如Student类,代码如下:

public class Student
{
public int Id { get; set; } public int Age { get; set; } public string Name { get; set; }
}

然后创建类StudentValidator,并集成类AbstractValidator,代码如下:

public class StudentValidator : AbstractValidator<Student>
{
public StudentValidator()
{
RuleFor(x => x.Age).InclusiveBetween(10, 50);
RuleFor(x => x.Name).NotEmpty().MaximumLength(5);
}
}

上述的验证类中,要求Age大于10且小于50,Name不为空,且长度小于5。

最后,还需要将验证类注册到服务中。修改Startup的ConfigureServices,部分代码如下:

services.AddControllers().AddFluentValidation(conf =>
{
conf.RegisterValidatorsFromAssemblyContaining<StudentValidator>();
conf.RunDefaultMvcValidationAfterFluentValidationExecutes = false;
});

上述代码中,RegisterValidatorsFromAssemblyContaining方法的作用是扫描StudentValidator类所在的程序集中的所有验证类,并注册到服务中。

RunDefaultMvcValidationAfterFluentValidationExecutes为false时,会屏蔽掉系统默认的模型验证,如需兼容系统默认的模型验证,将RunDefaultMvcValidationAfterFluentValidationExecutes的值改为true即可。此参数默认为true。

下面在Controller中,添加一个Action,代码如下:

[HttpPost]
public IActionResult Add([FromBody] Student student)
{
return Ok(student);
}

打开swagger,访问接口,响应如下所示:

{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "00-6331a76578228b4cb9044aa40f514bc9-89fd8547c1921340-00",
"errors": {
"Age": [
"'Age' 必须在 10 (包含)和 25 (包含)之间, 您输入了 0。"
],
"Name": [
"'Name' 必须小于或等于5个字符。您输入了6个字符。"
]
}
}

至此,在 ASP.NET Core中集成FluentValidation就完成了。但到现在为止,这和系统默认的模型验证并没有区别。 在文章的开头笔者也提到过,FluentValidation不仅支持Controller中对模型进行验证,下面的代码就是非Controller场景下的验证。

public class DemoService
{
private readonly IValidator<Student> _studentValidator; public DemoService(IValidator<Student> studentValidator)
{
_studentValidator = studentValidator;
} public bool Run(Student student)
{
var valid = _studentValidator.Validate(student);
return valid.IsValid;
}
}

在上述代码中,通过构造函数注入的方式,获取到了IValidator实例,在Run方法中只需要调用Validate方法,参数是需要验证的对象,返回的对象就包含了验证的是否通过以及不通过时,具体的错误信息。

基础用法

内置规则

FluentValidation内置了多个常用的验证器,下面简单介绍几个特别常用或容易出错的验证器。

NotNull 和 NotEmpty

NotNull是确保指定的属性不为null,NotEmpty则表示确保指定的属性不为null、空字符串或空白(值类型的默认值,比如int类型的默认值为0),如果int类型属性设置NotEmpty验证器,则当值为0时,验证是无法通过的。

NotEqual 和 Equal

NotEqual 和 Equal分别是不相等和相等验证器,可与指定的值或者指定的属性进行比较。

MaximumLength、MinimumLength和Length

MaximumLength为最大长度验证器,MinimumLength为最小长度验证器,而Length则是二者的结合,需要注意的是,这三种验证器仅对字符串有效,且不会验证null,当值为null时,则不对长度进行验证,所以使用长度验证器时,建议结合NotNull一起使用。

LessThan、LessThanOrEqualTo、GreaterThan、GreaterThanOrEqualTo

上述的几个验证器为比较验证器,仅适用于继承IComparable接口的属性,分别表示的是:小于、小于或等于、大于、大于或等于。

Matches

正则表达式验证器,用于确保指定的属性与给定的正则表达式匹配。

ExclusiveBetween和InclusiveBetween

示例代码如下:

RuleFor(x => x.Id).ExclusiveBetween(1,10);
RuleFor(x => x.Id).InclusiveBetween(1,10);

以上代码均表示输入的Id的值需要在1,10之间,而两者的区别是,InclusiveBetween验证器是包含头和尾的,而ExclusiveBetween是不包含的,例如当Id值为1时,ExclusiveBetween验证失败,但InclusiveBetween则验证成功。

覆盖验证器默认的错误提示

在文章的开头提到了,当验证Student的Age属性不通过时,提示信息是:'Age' 必须在 10 (包含)和 25 (包含)之间, 您输入了 0。

这个提示信息对于开发者来讲,定位问题已经很清晰了,但如果要在WebApi中讲验证的错误信息返回给前端,那么这个提示就会被用户看到,则此错误信息就不太友好,FluentValidation提供了多种覆盖错误提示的方式,下面就来一起看下。

占位符

我们可以将验证Age的代码改为如下所示:

RuleFor(x => x.Age).InclusiveBetween(10, 25).WithMessage("年龄必须在{From}到{To}之间");

当验证不通过时,输出的错误信息则为:年龄必须在10到25之间。

程序自动将{From}和{To}进行了替换。每个验证器的占位符都不一样,有关占位符的完整列表,请查看官方文档 https://docs.fluentvalidation.net/en/latest/built-in-validators.html。

覆盖属性名称

此方法是将属性的名称使用指定的字符串替换,如下所示:

RuleFor(x => x.Age).InclusiveBetween(10, 25).WithName("年龄");

当发生错误时,会自动将系统默认的错误提示信息中的"Age"替换为"年龄"

默认情况下,When或者Otherwise将应用于链式调用的所有前置的验证器,如果只希望条件引用于前面的第一个验证器,则必须使用ApplyConditionTo.CurrentValidator显示指定

 RuleFor(x => x.Age).GreaterThan(10).LessThan(20).When(x => x.Sex == 2,ApplyConditionTo.CurrentValidator);

上述的代码,如果不加ApplyConditionTo.CurrentValidator,则当Sex等于2时,则要求Age大于10且小于20。而Sex不等于2时,则不作任何验证。如果加上ApplyConditionTo.CurrentValidator,则Age大于10的验证跟Sex的值没有任何关系了,程序会始终验证Age是否大于10

带条件的验证规则

使用When方法可控制规则执行的条件。例如,国家的法定结婚年龄为女性20岁,则验证年龄属性时,只有当性别为女时,才对年龄大于等于20进行校验。

RuleFor(x => x.Age).GreaterThan(20).When(x => x.Sex == 2);

相反的,Unless表示的是当指定条件不满足时,才执行校验。

RuleFor(x => x.Age).GreaterThan(20).Unless(x => x.Sex == 2);

上述代码表示当Sex值不为2时,校验Age是否大于等于20

如果需要为多个验证规则指定相同的条件,可以调用When的顶级方法,而不是在规则末尾调用When方法。

When(x => x.Sex == 2, () =>
{
RuleFor(x => x.Name).Must(x => !x.EndsWith("国庆"));
RuleFor(x => x.Age).LessThan(30);
});

上述代码表示是,当Sex等于2时,Age需要小于30,并且名字不能以"国庆"结尾。

将Otherwise方法链接到When调用,表示When条件不满足时,执行的验证规则。

When(x => x.Sex == 2, () =>
{
RuleFor(x => x.Name).Must(x => x.EndsWith("国庆"));
RuleFor(x => x.Age).LessThan(30);
}).Otherwise(() =>
{
RuleFor(x => x.Age).LessThan(50);
});

上述代码中的Otherwise方法表示的是,当Sex不等于2时,则Age需要小于50

链式调用

当一个属性使用多个验证规则时,可将多个验证器链接在一起,比如,Student类的Name属性不能为空,并且,长度需要小于10,则对应的代码为:

public StudentValidator()
{
RuleFor(x =>x.Name).NotEmpty().MaximumLength(10);
}

CascadeMode

CascadeMode是一个枚举类型的属性,有两个选项:Continue和Stop

如果设置为Stop,则检测到失败的验证,则立即终止,不会继续执行剩余属性的验证。默认值为Continue

CascadeMode = CascadeMode.Stop;
RuleFor(x => x.Name).NotEmpty().MaximumLength(10);
RuleFor(x => x.NickName).NotEmpty().MaximumLength(10);

如上述代码所示,当Name值不满足要求时,则会停止对NickName的校验

依赖规则

默认情况下,FluentValidation 中的所有规则都是独立的,不能彼此影响。这是异步验证工作所必需的,也是必要的。但是,在某些情况下,您可能希望确保某些规则仅在另一个规则完成之后执行。您可以使用DependentRules它来做到这一点。

比如,只有身高超过130的儿童,才需要验证是否购票,则可以通过如下的代码实现:

RuleFor(x => x.Height).GreaterThan(130).DependentRules(() =>
{
RuleFor(x => x.HasTicket).NotEmpty();
});

高级用法

异步验证

在某些情况下,你可能希望定义异步规则,比如从数据库或者外部api判断。

public StudentValidator(IStudentService studentService)
{
_studentService = studentService;
RuleFor(x => x.Name).MustAsync(async (name, token) => await _studentService.CheckExist(name));
}

上述代码中,通过一个异步方法的返回值验证Name属性。

另外,如果在非Controller场景下使用,则必须调用ValidateAsync方法进行验证。

转换值

您可以在对属性值执行验证之前使用 Transform方法转换属性值。

RuleFor(x => x.Weight).Transform(x => int.TryParse(x, out int val)?(int?)val:null).GreaterThan(10);

上述代码先试图将string类型转换成int类型,如果转换成功则对转换后的值做大于验证。如果转换失败,则不做验证。

回调

如果验证失败,可以使用回调做一些操作。

RuleFor(x => x.Weight).NotEmpty().OnFailure(x =>
{
Console.WriteLine("验证失败");
});

预验证

如果需要每次调用验证器前运行特定代码,可以通过重写PreValidate方法来做到这一点。

public class StudentValidator : AbstractValidator<Student>
{
public StudentValidator()
{
RuleFor(x => x.Weight).NotEmpty();
} protected override bool PreValidate(ValidationContext<Student> context,ValidationResult result)
{
if (context.InstanceToValidate == null) return true;
result.Errors.Add(new ValidationFailure("", "实体不能为null"));
return false;
}
}

福禄ICH.架构出品

作者:福尔斯

2021年3月

客官,.NETCore无代码侵入的模型验证了解下的更多相关文章

  1. confd + Nacos | 无代码侵入的配置变更管理

    Java技术栈 www.javastack.cn 优秀的Java技术公众号 来文来自阿里中间件投稿 作者:风卿,Nacos Committer,阿里巴巴开发工程师 为什么要支持confd,老的应用配置 ...

  2. TERSUS无代码开发(笔记04)-CSS样式设置

    CSS样式设置 1.常用显示样式 大小尺寸 说明  间距边距 说明  各类颜色 说明  width 宽 margin 外边距         color  颜色        height 高 pad ...

  3. ASP.NET没有魔法——ASP.NET MVC 模型验证

    在前面的文章中介绍了用户的注册及登录功能,在注册用户时可以通过代码的形式限制用户名及密码的格式,如果不符合要求那么就无法完成操作,如下图: 该功能的原理是Identity基于的Entity Frame ...

  4. 从.Net到Java学习第六篇——SpringBoot+mongodb&Thymeleaf&模型验证

    SpringBoot系列目录 SpringBoot整合mongodb MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的.如果你没用过Mong ...

  5. Selenium自动化:有代码测试与无代码测试。这些你都懂了吗?

    大多数测试人员认为 Selenium是满足其测试自动化需求的自动化框架.作为全球测试人员使用的开放源框架, Selenium 无疑是测试人员适应日趋敏捷的公司的一种好方法.实际上, Selenium仍 ...

  6. 当asp.net core偶遇docker一(模型验证和Rabbitmq 一)

    比如我们有一些设计,依赖于某些软件,比如rabbitmq 当管理员功能,反复错误三五次之后,就发送一条消息到队列里去,我们又不希望对原先设计带来侵入式的改变业务 这个时候,我们就可以在模型验证里面加入 ...

  7. webapi - 模型验证

    本次要和大家分享的是webapi的模型验证,讲解的内容可能不单单是做验证,但都是围绕模型来说明的:首先来吐槽下,今天下午老板为自己买了套新办公家具,看起来挺好说明老板有钱,不好的是我们干技术的又成了搬 ...

  8. ASP.NET Core 中文文档 第四章 MVC(2.2)模型验证

    原文:Model Validation 作者:Rachel Appel 翻译:娄宇(Lyrics) 校对:孟帅洋(书缘) 在这篇文章中: 章节: 介绍模型验证 验证 Attribute 模型状态 处理 ...

  9. EMF学习,为了实现可扩展可自定义的模型验证 - 各种实现方法学习

    自: http://blog.csdn.net/javaman_chen/article/details/6057033 http://www.ibm.com/developerworks/cn/op ...

随机推荐

  1. [Golang]-6 超时处理、非阻塞通道操作、通道的关闭和遍历

    目录 超时处理 非阻塞通道操作 通道的关闭 通道遍历 超时处理 超时 对于一个连接外部资源,或者其它一些需要花费执行时间的操作的程序而言是很重要的. 得益于通道和 select,在 Go中实现超时操作 ...

  2. Docker应用场景和局限性

    Docker有哪些好的特性?作为一种新兴的虚拟化方式,Docker跟传统的虚拟化方式相比具有众多的优势.首先, Docker容器的启动可以在秒级实现,这相比传统的虚拟机方式要快得多.其次, Docke ...

  3. CentOS 6 修改/etc/security/limits.conf不生效办法

    我们使用CentOS系统,在部署新的服务经常会遇到 打开最大文件数限制 too many open files的警告,通常我们只需要修改/etc/security/limits.conf该文件,增加两 ...

  4. 1009E Intercity Travelling 【数学期望】

    题目:戳这里 题意:从0走到n,难度分别为a1~an,可以在任何地方休息,每次休息难度将重置为a1开始.求总难度的数学期望. 解题思路: 跟这题很像,利用期望的可加性,我们分析每个位置的状态,不管怎么 ...

  5. I ❤️ W3C : Secure Contexts

    I ️ W3C : Secure Contexts Secure Contexts W3C Candidate Recommendation, 15 September 2016 https://ww ...

  6. uname -a

    uname -a Linux shell command https://en.wikipedia.org/wiki/Uname#:~:text=uname $ uname # Darwin $ un ...

  7. 如何使用 iMovie 去除视频里面的声音

    如何使用 iMovie 去除视频里面的声音 视频去除背景音 iMovie https://www.apple.com/imovie/ https://books.apple.com/book/id14 ...

  8. asm FPU 寄存器

    TOP-- TOP++ 顶部 ST(0) ST(1) ST(2) ST(3) ST(4) ST(5) ST(6) ST(7) 底部 指令后的注释通常是执行后的结果 push section .data ...

  9. NGK生态商城即将上线官网,推动生态落地应用

    NGK生态商城即将上线官网,以推动生态落地应用.此举意味着NGK生态将跻身区块链顶尖之列,同时,NGK代币.NGK Dapp游戏 "呼叫河马" 以及NGK DeFi项目Baccar ...

  10. 教你玩转CSS border(边框)

    边框样式 边框样式属性指定要显示什么样的边界. border-style属性用来定义边框的样式 border-style的值 代码演示: <!DOCTYPE html> <html ...