客官,.NETCore无代码侵入的模型验证了解下

.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#p-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<Student>,代码如下:

 

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#p-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<Student>实例,在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月

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/304101.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

敏捷个人:提供更多文档下载,并转载一篇敏捷个人读书笔记

这两周一直忙着OpenExpressApp的自动化测试支持了&#xff0c;对于敏捷个人最近在思考作为新手如何学习的问题&#xff0c;后期我会写篇blog与大家分享一下。在敏捷个人项目中我发布了敏捷个人&#xff0d;认识自我&#xff0c;管理自我.pdf&#xff0c;有很多朋友之前看过&…

大数据揭秘:低学历者发财的概率有多大?结果很吃惊

先看两幅图&#xff1a;Table 1: Mean Earnings by Highest Degree Earned, $: 2009 (SAUS, table 232)Table 2: Unemployment Rates by Educational Attainment图一是美国社会收入和最高学历的关系&#xff0c;图二是美国社会失业率和受教育程度的关系&#xff0c;数据来自SAU…

多个cpp文件生成so_boostpython:从多个.cpp文件创建一个模块(.so)

我开始在C中编写一些我想在Python代码中调用的模块。为此&#xff0c;我使用boostpython。随着代码的增长&#xff0c;我决定将其分成几个.cpp文件。现在&#xff0c;我有了这样的东西&#xff1a;食品.cpp#include "Bar.hpp"#include "Baz.hpp"#include u…

SSH远程终端连接数问题

系统 linux &#xff08;Debian&#xff09; 存在问题&#xff1a;SSH终端连接数最大为10个 解决方案&#xff1a; 1) 修改/etc/ssh/sshd_config中#MaxStartups 10:30:60&#xff0c;将其改为MaxStartups 1000 2) 重启SSH服务&#xff0c;/etc/init.d/ssh restar…

Win10 Terminal + WSL 2 安装配置指南

自从 Windows Terminal 正式发布后就再没有用过 Windows 系统自带的终端了。主要是 Terminal 简洁且灵活&#xff0c;更重要的是支持特殊字体&#xff0c;通过一些简单的配置可以使得终端看起来更舒适养眼。自从 Win 10 有了 Linux 子系统&#xff08;WSL&#xff09;&#xff…

如何快速解剖数据背后隐藏的信息

1946年2月16日&#xff0c;是一个值得纪念的日子。在这一天&#xff0c;人类历史上真正意义上的第一台电子计算机诞生了&#xff0c;此后计算机便随着科技的发展以强大的生命力飞速发展着。而作为用来定义计算机程序的形式语言——编程语言也紧跟计算机其后蓬勃发展&#xff0c…

mysql改密码脚本_mysql密码修改脚本

网上搜索&#xff1a; mysql密码修改工具 title 护卫神MySQL密码修改工具 echo off color 0a ECHO ┏━━━━━━━━━━┥ 护卫神www.huweishen.com ┝━━━━━━━━━┓ ECHO ┃ 提示: ┃ E…

来,Consul 服务发现入个门(一看就会的那种)

前言在微服务架构中&#xff0c;对于一个系统&#xff0c;会划分出多个微服务&#xff0c;而且都是独立开发、独立部署&#xff0c;最后聚合在一起形成一个系统提供服务。当服务数量增多时&#xff0c;这些小服务怎么管理&#xff1f;调用方又怎么能确定服务的IP和端口&#xf…

深入理解alias, alias_method和alias_method_chain

对于alias, alias_method, alias_method_chain的深入理解是有益的&#xff0c;因为rails3的源码里很多地方使用了alias_method_chain的魔法。 有人评论说alias_method_chain使用的过多不好&#xff0c;具体怎么不好&#xff0c;是后话了&#xff0c;这篇文章集中在理解这3个方法…

mysql数据库的安装和配置文件_MySQL 数据库安装与配置详解

目录一、概述MySQL 版本&#xff1a;5.7.17客户端工具&#xff1a;NavicatforMySQL** 二、MySQL 安装**安装条件&#xff1a;如果 Windows Server 2003 在安装.net framework4.0 安装过程中报错&#xff1a; net framework 4.0 安装时提示产生阻滞问题:运行安装程序前&#xff…

Magicodes.IE Excel合并行数据导入教程

说明Magicodes.IE.Excel目前已支持合并行单元格导入&#xff0c;如本篇教程所示。安装包Magicodes.IE.ExcelInstall-PackageMagicodes.IE.Excel添加Dto参考示例代码如下所示&#xff1a;public class MergeRowsImportDto {[ImporterHeader(Name "学号")]public long…

2010.7.27 OnDraw与OnPaint有什么区别

引用&#xff1a;http://wenku.baidu.com/view/bc9b1c661ed9ad51f01df2ab.html OnPaint是WM_PAINT消息的消息处理函数&#xff0c;在OnPaint中调用OnDraw&#xff0c;一般来说&#xff0c;用户自己的绘图代码应放在OnDraw中。 OnPaint()是CWnd的类成员&#xff0c;负责响应WM_P…

D轮融资1亿美金,6亿美金估值,3位计算机学霸如何带领海归团队创造业内神话?!

顺为资本在创始合伙人雷军及许达来的带领下成功领投了51Talk、丁香园、爱奇艺、一起作业等超级公司有近20家公司估值超过10亿美元如今顺为资本为何愿意投资这家公司&#xff1f;酷家乐6年破6亿美金的公司估值17年全年营收超3亿有着设计师300万这家以家居云设计为核心的创业公司…

linux下mysql案例_Linux下安装MySQL多实例

环境说明&#xff1a;Centos 6.6 64位mysql 使用最新版本5.7.16版本这里安装两个MySQL实例&#xff0c;分别使用3306/3307端口号目录结构&#xff1a;/data/mysql/mysql3306/data/mysql/mysql3306/data/data/mysql/mysql3307/log/data/mysql/mysql3306/tmp执行命令&#xff1a;…

Navicat

作为Oracle, MySQL, Sqlite, PostgreSQL的统一客户端&#xff0c;Navicat无疑是最方便简洁的&#xff0c;而且界面非常友好。导入导出支持的格式也很全。 在连Oracle的时候&#xff0c;会报错说字符集不支持。只要手工指定OCI就好&#xff0c;方法如下&#xff1a; 在OCI libra…

分布式链路追踪框架的基本实现原理

目录分布式追踪分布式系统分布式追踪分布式追踪有什么用呢什么是分布式追踪Dapper分布式追踪系统的实现跟踪树和 spanJaeger 和 OpenTracingOpenTracing 数据模型Span 格式TraceOpenTracingJaeger 结构SpanOpenTracing API分布式追踪什么是分布式追踪分布式系统当我们使用 Goog…

mysql外键约束创建及删除_MySQL中的外键的创建,约束和删除

一、外键的创建语法一&#xff1a;后续添加方法alter table 表名 add constraint 约束名 foreign key(当前表中约束的字段) references 主表表名(要约束的字段名);alter table student add constraint fk_class_student foreign key(cls_id) class(cls_id) on update cascade o…

不爱读书怎么办?用这个新奇的方法,熟知137亿年来的地球通史

今天&#xff0c;小木为模友们挑选的小荐货&#xff1a;《地球通史墙书》第一次接触《地球通史》墙书的时候&#xff0c;虽然“地球通史”的概念非常吸引我&#xff0c;但“墙书”的叫法怎么听都像是一个噱头&#xff0c;所以小木一直有些莫名的排斥&#xff0c;直到在小天把试…

测试驱动开发简介

本文节选自《测试驱动的面向对象软件开发》 第1章第4节“测试驱动开发简介” TDD核心的循环是&#xff1a;写一个测试&#xff1b;写一些代码让测试通过&#xff1b;重构代码&#xff0c;使被测试特征的实现尽可能简单。重复这个过程&#xff0c;如图1-1所示。 图1-1基本TDD循环…

在 .NET 中使用 Flurl 高效处理Http请求

简介官方介绍&#xff0c;Flurl是一个现代的&#xff0c;流利的&#xff0c;支持异步的&#xff0c;可测试的&#xff0c;可移植的&#xff0c;URL增强和Http客户端组件。Url构建现在有一个登录的接口&#xff0c;地址如下&#xff1a;https://www.some-api.com/login?nameLee…