12

ASP.NET Core 数据验证:FluentValidation | Beck's Blog

 4 years ago
source link: http://beckjin.com/2019/12/01/aspnet-fluent-validation/?
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

ASP.NET Core 数据验证:FluentValidation

发表于 2019-12-01

| 分类于 ASP.NET Core

| 阅读次数: 252

在 .NET 项目开发中,作为一个比较规范的接口,数据合法性验证是不可或缺的,FluentValidation 是一个目前比较受欢迎的数据验证库,它支持参数定义与验证规则分离,这点在目前很多框架下还是比较重要的,特别是基于接口定义语言自动生成的代码(如:gRPCThrift),使用上与 MVC 中提供的数据验证(System.ComponentModel.DataAnnotations 命名空间中提供的各种数据验证 Attribute,如:RequiredRegularExpressionRange) 的最大区别是 MVC 中验证规则是通过在属性上标记特定的 Attribute。当然还有其他的数据验证方式,甚至也可以完全自己实现,所以在实际项目中选择适合的即可。

下面通过一个简单例子来说明 FluentValidation 的使用,更多请看 FluentValidation 官网介绍,本文关键的部分是介绍如何在项目中优雅并简单的整合这个验证库。

FluentValidation 使用

  1. NuGet 安装 FluentValidation.AspNetCore (Consol/Web Application 均可);

  2. 定义请求对象

    1
    2
    3
    4
    5
    6
    public class TestRequest
    {
    public string Name { get; set; }

    public List<string> Ids { get; set; }
    }
  3. 定义验证对象:

    1
    2
    3
    4
    5
    6
    7
    8
    public class TestRequestValidator : AbstractValidator<TestRequest>
    {
    public TestRequestValidator()
    {
    RuleFor(_ => _.Name).NotEmpty();
    RuleFor(_ => _.Ids).Must(_ => _ != null && _.Count > 0).WithMessage("Ids 不能为空");
    }
    }
  4. 实现数据验证

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 创建一个请求对象,未给属性赋值
    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 DynamicProxyAspectCoreDora.InterceptionAspect Injector 等,还有一些框架自身已具有拦截器功能,那就可以直接在拦截器内实现数据验证。

这里将使用 Castle DynamicProxy 来介绍整合方法,不过在这之前我们需要先对 FluentValidation 的使用进行封装,提供 InitializeIsValid 两个方法。使用上我们一般会在程序集中定义所有请求对象的 Validator,所以先通过 Initialize 将程序集内的 Validator 初始化到内存中,然后通过请求对象的扩展方法 IsValid 对数据合法性校验,不合法时返回第一个错误信息,具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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.DynamicProxyIInterceptor 接口,以下是部分代码,在方法体执行之前,先通过请求对象的扩展方法 IsValid 进行数据合法性验证,不通过则直接返回错误,合法则继续往下执行,完整代码请 查看这里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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,避免了接口中单独的一些数据合法性验证代码,使我们更关注业务功能的实现。

如果对你有帮助就好

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK