流利的验证将业务验证与验证验证划分
我正在使用 ASP、CQRS + MediatR 和流畅的验证。我想实现用户角色验证,但不想将其与业务逻辑验证混合在一起。您知道如何实施吗?
我的意思是必须针对特定请求执行特定的验证器。
有些东西告诉我解决方案在于 IEnumerable< IValidator>
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators) => _validators = validators;
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
if (_validators.Any())
{
var context = new ValidationContext<TRequest>(request);
var validationResults = await Task.WhenAll(_validators.Select(v => v.ValidateAsync(context, cancellationToken)));
var failures = validationResults.SelectMany(r => r.Errors).Where(f => f != null).ToArray();
if (failures.Any())
{
var errors = failures
.Select(x => new Error(x.ErrorMessage, x.ErrorCode))
.ToArray();
throw new ValidationException(errors);
}
}
return await next();
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
我看到你的担心,我也发现自己处于这种情况。我想将验证器与处理程序分开,同时将它们保留在域/业务项目中。另外,我不想仅仅为了处理错误请求或任何其他自定义业务异常而引发异常。
你有正确的想法
为此,您需要设置一个调解器管道,因此对于每个命令您都可以找到适当的验证器,验证并决定是否执行该命令或返回失败的结果。
首先,创建一个
ICommand
接口(虽然不是必需的,但我就是这样做的),如下所示:并且,
ICommandHandler
如下所示:这样我们只能对命令应用验证。您不是继承
IRequest
和IRequestHandler
,而是从ICommand
和ICommandHandler
继承。现在,按照我们之前商定的那样,为中介者创建一个 ValidationBehaviour。
这段代码很简单,构造函数中的所有验证器除外,因为您从程序集中注册了所有验证器,以便 DI 容器注入它们。
它等待所有验证来验证异步(因为我的验证主要需要调用数据库本身,例如获取用户角色等)。
然后检查错误并返回错误(这里我创建了一个 DTO 来包装我的错误和值以获得一致的结果)。
如果没有错误,只需让处理程序完成它的工作
return wait next();
现在,您必须注册此管道行为和所有验证器。
我使用 autofac,因此我可以通过以下方式轻松完成:
如果您使用 Microsoft DI,则可以:
示例用法:
我的通用 DTO 包装器
示例命令:
它的验证器:
这样您始终会获得结果对象,您可以检查
error 是否为 null
或!IsSuccess
,然后创建自定义Controller 库中的HandleResult(result)
方法可以打开异常以返回BadReuqestObjectResult(result)
或ForbiddenObjectResult(结果)
。如果您更喜欢抛出、捕获和处理管道中的异常,或者您不想使用非异步实现,请阅读此 https://code-maze.com/cqrs-mediatr-fluidation/
这样,您的所有验证都与处理程序相距甚远,同时保持结果一致。
I see your concern, I also found myself in this situation. I wanted to separate my validators from handlers while also keeping them in the domain/business project. Also I didn't want to throw exceptions just to handle bad request or any other custom business exception.
You have the right idea by
For this, you need to set up a mediator pipeline, so for every Command you can find the appropriate the appropriate validator, validate and decide whether to execute the command or return a failed result.
First, create an interface(although not necessary but it is how I did it) of
ICommand
like this:And,
ICommandHandler
like:This way we can only apply validation to commands. Instead of iheriting
IRequest<MyOutputDTO>
andIRequestHandler<MyCommand, MyOutputDTO>
you inherit fromICommand
andICommandHandler
.Now create a
ValidationBehaviour
for the mediator as we agreed before.This code simply, excepts all the validators in the constructor, because you register all your validator from assembly for your DI container to inject them.
It waits for all validations to validate async(because my validations mostly require calls to db itself such as getting user roles etc).
Then check for errors and return the error(here I have created a DTO to wrap my error and value to get consistent results).
If there were no errors simply let the handler do it's work
return await next();
Now you have to register this pipeline behavior and all the validators.
I use autofac so I can do it easily by
If you use Microsoft DI, you can:
Example usage:
My generic DTO Wrapper
A sample Command:
Validator for it:
This way you always get a result object, you can check if
error is null
or!IsSuccess
and then create a customHandleResult(result)
method in your Controller base which can switch on the exception to returnBadReuqestObjectResult(result)
orForbiddenObjectResult(result)
.If you prefer to throw, catch and handle the exceptions in the pipeline or you wan't non-async implementation, read this https://code-maze.com/cqrs-mediatr-fluentvalidation/
This way all your validations are very far from your handler while maintaining consistent results.
我认为你最初的做法是正确的。当您说要将身份验证验证与其他业务验证分开时,您的意思是返回像 403 和 401 这样的 http 错误吗?
如果是这种情况,请尝试使用 和 接口标记身份验证验证以识别它们,并且不要立即运行所有验证。首先在集合中搜索使用该接口的验证,如果失败,则发送一个自定义异常,您可以在 IActionFilter 中标识该异常以设置所需的结果。这段代码并没有完全做到这一点,但你可以提出一个想法。
I think that your initial approach its right. When you say that you want to keep the auth validations apart from the other business validation, do you mean like returning a http error like 403 and 401 right?
If thats the case try marking the auth validations with and interface to identify they, and do not run all the validations at once. Search first in the collection for a validation with that interface, and if it fails send a custom exception that you can identity in a IActionFilter to set the wanted result. This code does not do that exactly but you can make an idea.