背景

.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. CF1465-D. Grime Zoo

    CF1465-D. Grime Zoo 题意: 一个长度为n,由\(0,1,?\)这三个字符构成的字符串,字符串中\(01\)子串贡献\(x\)值,\(10\)的子串贡献\(y\)值,现在让你把\(? ...

  2. CF1463-B. Find The Array

    题意: 给出一个由n个数组成的数组a,这个数组的元素和为S,要求你找出一个由n个数字组成数组b,这个数组满足: 数组中的每个数可以将他两边的数字整除或者被他两边的数字整除 数组b中每个位置的数字减去数 ...

  3. SOHO 程序员

    SOHO 程序员:从事程序开发.维护的家居办公人员. 一.自由程序员 SOHO程序员代表一种自由.弹性而新型的工作方式.SOHO,代表一种新经济.新概念. 是一些热爱软件开发的一族. SOHO程序员 ...

  4. mssql 2005安装

    SQL Server 2005详细安装过程及配置   说明:个人感觉SQL Server 2005是目前所有的SQL Server版本当中最好用的一个版本了,原因就是这个版本比起其它版本来说要安装简单 ...

  5. Ubuntu 18.04 + pip3 install virtualenvwrapper 报错 ERROR: virtualenvwrapper could not find virtualenv in your path

    接上片... 问题 virtualenvwrapper装好后, 发现使用mkvirtualenv XX时, 又找不到virtualenv了... apt install python3-virtual ...

  6. TensorFlow+restore读取模型

    # 注意和前一或二篇Lenet训练并验证的文章从`y_conv = tf.nn.softmax(fc2)`起的不同 # 部分函数请参照前后2篇文章 import tensorflow as tf im ...

  7. React Security Best Practices All In One

    React Security Best Practices All In One Default XSS Protection with Data Binding Dangerous URLs Ren ...

  8. iOS effect & swiper delete components

    iOS effect & swiper delete components mint-ui & cell-swipe https://elemefe.github.io/mint-ui ...

  9. how to create one single-file Web Component just using the HTML, CSS, JavaScript

    how to create one single-file Web Component just using the HTML, CSS, JavaScript web components html ...

  10. 同步vscode的setting.json和extensions

    vc 详情 $ npm i -g vscode-config $ vc config --token <your github token> $ vc config --id <yo ...