ASP.NET Core 数据验证:FluentValidation
在 .NET 项目开发中,作为一个比较规范的接口,数据合法性验证是不可或缺的,FluentValidation 是一个目前比较受欢迎的数据验证库,它支持参数定义与验证规则分离,这点在目前很多框架下还是比较重要的,特别是基于接口定义语言自动生成的代码(如:gRPC
、Thrift
),使用上与 MVC 中提供的数据验证(System.ComponentModel.DataAnnotations
命名空间中提供的各种数据验证 Attribute,如:Required
、RegularExpression
、Range
等) 的最大区别是 MVC 中验证规则是通过在属性上标记特定的 Attribute。当然还有其他的数据验证方式,甚至也可以完全自己实现,所以在实际项目中选择适合的即可。
下面通过一个简单例子来说明 FluentValidation 的使用,更多请看 FluentValidation 官网介绍,本文关键的部分是介绍如何在项目中优雅并简单的整合这个验证库。
FluentValidation 使用
NuGet 安装
FluentValidation.AspNetCore
(Consol/Web Application 均可);-
定义请求对象
public class TestRequest { public string Name { get; set; } public List<string> Ids { get; set; } }
-
定义验证对象:
public class TestRequestValidator : AbstractValidator<TestRequest> { public TestRequestValidator() { RuleFor(_ => _.Name).NotEmpty(); RuleFor(_ => _.Ids).Must(_ => _ != null && _.Count > 0).WithMessage("Ids 不能为空"); } }
-
实现数据验证
// 创建一个请求对象,未给属性赋值 var testRequest = new TestRequest(); var validator = new TestRequestValidator(); var result = validator.Validate(testRequest); if (!result.IsValid) { foreach (var error in result.Errors) { Console.WriteLine($"{error.PropertyName}:{error.ErrorMessage}"); } }
主要步骤是创建一个基于请求对象的 Validator
,在 Validator
中通过 RuleFor
定义一些规则,然后基于验证规则对请求对象的属性值进行校验,如何不合法则通过 Errors 属性返回,一般情况下我们会把这个错误信息返回给接口调用方。
AOP 整合 FluentValidation
通过上面的例子介绍,如果每个接口内都创建当前请求对象的 Validator
,然后判断数据是否合法,肯定疯掉。所以我们一般也不会这么玩,这种事情当然是交给 AOP ,如果不了解 AOP 可以 点击这里 。AOP 只是一个概念 ,在 .NET Core 中 AOP 的实现可选择:ActionFilter(MVC)
、Castle DynamicProxy
、AspectCore
、Dora.Interception
、Aspect Injector
等,还有一些框架自身已具有拦截器功能,那就可以直接在拦截器内实现数据验证。
这里将使用 Castle DynamicProxy
来介绍整合方法,不过在这之前我们需要先对 FluentValidation 的使用进行封装,提供 Initialize
、IsValid
两个方法。使用上我们一般会在程序集中定义所有请求对象的 Validator
,所以先通过 Initialize
将程序集内的 Validator
初始化到内存中,然后通过请求对象的扩展方法 IsValid
对数据合法性校验,不合法时返回第一个错误信息,具体代码如下:
public static class ValidatorExtension
{
private static readonly object Locker = new object();
private static ConcurrentDictionary<string, IValidator> _cacheValidators;
public static void Initialize(Assembly assembly)
{
lock (Locker)
{
if (_cacheValidators == null)
{
_cacheValidators = new ConcurrentDictionary<string, IValidator>();
var results = AssemblyScanner.FindValidatorsInAssembly(assembly);
foreach (var result in results)
{
var modelType = result.InterfaceType.GenericTypeArguments[0];
_cacheValidators.TryAdd(modelType.FullName, (IValidator)Activator.CreateInstance(result.ValidatorType));
}
}
}
}
public static bool IsValid<T>(this T request, out string msg) where T : class
{
msg = string.Empty;
if (_cacheValidators == null || !_cacheValidators.TryGetValue(request.GetType().FullName, out var validator))
return true;
var result = validator.Validate(request);
if (!result.IsValid)
{
// 返回第一个错误信息
msg = result.Errors[0].ErrorMessage;
return false;
}
return true;
}
}
项目中安装 Castle.Windsor
NuGet 包,实现 Castle.DynamicProxy
的 IInterceptor
接口,以下是部分代码,在方法体执行之前,先通过请求对象的扩展方法 IsValid
进行数据合法性验证,不通过则直接返回错误,合法则继续往下执行,完整代码请 查看这里
public void Intercept(IInvocation invocation)
{
var request = invocation.Arguments[0];
var isValid = request.IsValid(out var message);
if (!isValid)
{
var resultType = invocation.Method.ReturnType.GenericTypeArguments[0];
invocation.ReturnValue = GetParamsErrorValueAsync((dynamic)Activator.CreateInstance(resultType), message);
return;
}
invocation.Proceed();
invocation.ReturnValue = GetReturnValueAsync((dynamic)invocation.ReturnValue);
}
以上就实现了在拦截器中整合 FluentValidation,避免了接口中单独的一些数据合法性验证代码,使我们更关注业务功能的实现。
参考链接
原文地址:https://www.cnblogs.com/lonelyxmas/p/12928491.html
- WordPress自定义栏目运用实例Ⅰ:添加文章来源
- ZooKeeper 笔记(5) ACL(Access Control List)访问控制列表
- 算法:支持重复元素的二分查找
- .NET 异常处理的动作策略(Action Policy)
- 如何在mac本上安装android sdk
- Fityk-曲线拟合工具
- maven: 打包可运行的jar包(java application)及依赖项处理
- Effective java 第2版 - 笔记(01) 单例(Singleton)的枚举(enum)实现
- 恶心的0.5四舍五入问题
- 乐视金融更名 相关域名引关注
- weblogic.nodemanager.common.ConfigException: Native version is enabled but nodemanager native librar
- hadoop 2.6伪分布安装
- ssh 免密码设置失败原因总结
- C++服务器开发之基于对象的编程风格
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Redis 5.0.8 主从搭建
- Linux 修改网卡名称及mac地址
- MySQL Galera Cluster全解析 Part 10 grastate.dat文件详解
- MySQL MHA部署 Part 7 MHA手动切换测试
- 使用sysbench进行压测 Part2 sysbench语法介绍
- mysqlbinlog命令详解 Part 1-实验环境准备
- mysqlbinlog命令详解 Part 2 -MySQL 事件类型
- mysqlbinlog命令详解 Part 4 -查看行事件具体SQL语句
- mysqlbinlog命令详解 Part 5 通过位置和时间查看日志
- mysqlbinlog命令详解 Part 6 读取远程MySQL服务器日志
- mysqlbinlog命令详解 Part 7 备份二进制日志文件
- mysqlbinlog命令详解 Part 8 指定 Server ID
- mysqlbinlog命令详解 Part 9 MySQL备份策略
- mysqlbinlog命令详解 Part 10 恢复MySQL
- mysqldump命令详解 Part 2- 建立触发器 事件