在 MediatR 管道上使用多个 FluentValidator
Using multiple FluentValidators on MediatR pipeline
我有这样的 MediatR 管道行为:
public class FailFastRequestBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
private readonly IEnumerable<IValidator> _validators;
public FailFastRequestBehavior(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}
public Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
var failures = _validators
.Select(async v => await v.ValidateAsync(request))
.SelectMany(result => result.Result.Errors)
.Where(f => f != null);
return failures.Any()
? Errors(failures)
: next();
}
...
}
MediatR 命令如下:
public class MyUseCase
{
public class Command : IRequest<CommandResponse>
{
...
}
public class Validator : AbstractValidator<Command>
{
...
}
public class Handler<T>: IRequestHandler<T, CommandResponse>
{
...
}
}
验证器在 Startup.cs
上注册如下:
AssemblyScanner
.FindValidatorsInAssembly(Assembly.GetAssembly(typeof(MyUseCase)))
.ForEach(result =>
services.AddScoped(result.InterfaceType, result.ValidatorType));
这对 MyUseCase.Validator
很有效,它被注入管道并执行,验证 MyUseCase.Command
。
但它是一个大型应用程序,许多命令具有共同的属性,即每个订单操作都会收到一个 OrderId
并且我必须检查 Id 是否有效,实体是否存在于数据库中,是否经过身份验证用户是被修改订单的拥有者等
所以我尝试创建以下接口和验证器:
public interface IOrder
{
string OrderId { get; set; }
}
public class IOrderValidator : AbstractValidator<IOrder>
{
public IOrderValidator()
{
CascadeMode = CascadeMode.StopOnFirstFailure;
RuleFor(x => x.OrderId)
.Rule1()
.Rule2()
.Rule3()
.RuleN()
}
}
最后我将命令更改为:
public class MyUseCase
{
public class Command : IRequest<CommandResponse>: IOrder
{
...
}
public class Validator : AbstractValidator<Command>
{
...
}
public class Handler<T>: IRequestHandler<T, CommandResponse>
{
...
}
}
问题是 IOrderValidator
没有注入到管道中,只有 MyUseCase.Validator
是。
我是不是遗漏了什么,或者甚至可以在管道中注入多个验证器?
服务解析取决于您使用的 DI 容器。看来你使用的是内置的 .NET Core 容器,它无法解析逆变接口。
请考虑 Simple Injector,因为它知道如何处理逆变。此示例代码将解析您需要的所有验证器:
[Fact]
public void Test()
{
var container = new SimpleInjector.Container();
container.Collection.Append<IValidator<IOrder>, OrderValidator>();
container.Collection.Append<IValidator<Command>, CommandValidator>();
var validators = container.GetAllInstances<IValidator<Command>>();
validators.Should().HaveCount(2);
}
或者,您必须显式注册验证器参数化的所有命令,它们必须适用于:
[Fact]
public void Test()
{
var provider = new ServiceCollection()
.AddTransient(typeof(IValidator<Command>), typeof(OrderValidator))
.AddTransient(typeof(IValidator<Command>), typeof(CommandValidator))
.BuildServiceProvider();
var validators = provider.GetServices<IValidator<Command>>();
validators.Should().HaveCount(2);
}
注意在 Simple Injector 和 .NET Core DI 容器的情况下 IOrder
和 Command
之间的区别 OrderValidator
注册:
container.Collection.Append<IValidator<IOrder>, OrderValidator>();
servcies.AddTransient(typeof(IValidator<Command>), typeof(OrderValidator))
假设定义了以下 类 和接口:
interface IOrder
{
}
class Command : IRequest<CommandResponse>, IOrder
{
}
class CommandResponse
{
}
class OrderValidator : AbstractValidator<IOrder>
{
}
class CommandValidator : AbstractValidator<Command>
{
}
我有这样的 MediatR 管道行为:
public class FailFastRequestBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
private readonly IEnumerable<IValidator> _validators;
public FailFastRequestBehavior(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}
public Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
var failures = _validators
.Select(async v => await v.ValidateAsync(request))
.SelectMany(result => result.Result.Errors)
.Where(f => f != null);
return failures.Any()
? Errors(failures)
: next();
}
...
}
MediatR 命令如下:
public class MyUseCase
{
public class Command : IRequest<CommandResponse>
{
...
}
public class Validator : AbstractValidator<Command>
{
...
}
public class Handler<T>: IRequestHandler<T, CommandResponse>
{
...
}
}
验证器在 Startup.cs
上注册如下:
AssemblyScanner
.FindValidatorsInAssembly(Assembly.GetAssembly(typeof(MyUseCase)))
.ForEach(result =>
services.AddScoped(result.InterfaceType, result.ValidatorType));
这对 MyUseCase.Validator
很有效,它被注入管道并执行,验证 MyUseCase.Command
。
但它是一个大型应用程序,许多命令具有共同的属性,即每个订单操作都会收到一个 OrderId
并且我必须检查 Id 是否有效,实体是否存在于数据库中,是否经过身份验证用户是被修改订单的拥有者等
所以我尝试创建以下接口和验证器:
public interface IOrder
{
string OrderId { get; set; }
}
public class IOrderValidator : AbstractValidator<IOrder>
{
public IOrderValidator()
{
CascadeMode = CascadeMode.StopOnFirstFailure;
RuleFor(x => x.OrderId)
.Rule1()
.Rule2()
.Rule3()
.RuleN()
}
}
最后我将命令更改为:
public class MyUseCase
{
public class Command : IRequest<CommandResponse>: IOrder
{
...
}
public class Validator : AbstractValidator<Command>
{
...
}
public class Handler<T>: IRequestHandler<T, CommandResponse>
{
...
}
}
问题是 IOrderValidator
没有注入到管道中,只有 MyUseCase.Validator
是。
我是不是遗漏了什么,或者甚至可以在管道中注入多个验证器?
服务解析取决于您使用的 DI 容器。看来你使用的是内置的 .NET Core 容器,它无法解析逆变接口。
请考虑 Simple Injector,因为它知道如何处理逆变。此示例代码将解析您需要的所有验证器:
[Fact]
public void Test()
{
var container = new SimpleInjector.Container();
container.Collection.Append<IValidator<IOrder>, OrderValidator>();
container.Collection.Append<IValidator<Command>, CommandValidator>();
var validators = container.GetAllInstances<IValidator<Command>>();
validators.Should().HaveCount(2);
}
或者,您必须显式注册验证器参数化的所有命令,它们必须适用于:
[Fact]
public void Test()
{
var provider = new ServiceCollection()
.AddTransient(typeof(IValidator<Command>), typeof(OrderValidator))
.AddTransient(typeof(IValidator<Command>), typeof(CommandValidator))
.BuildServiceProvider();
var validators = provider.GetServices<IValidator<Command>>();
validators.Should().HaveCount(2);
}
注意在 Simple Injector 和 .NET Core DI 容器的情况下 IOrder
和 Command
之间的区别 OrderValidator
注册:
container.Collection.Append<IValidator<IOrder>, OrderValidator>();
servcies.AddTransient(typeof(IValidator<Command>), typeof(OrderValidator))
假设定义了以下 类 和接口:
interface IOrder
{
}
class Command : IRequest<CommandResponse>, IOrder
{
}
class CommandResponse
{
}
class OrderValidator : AbstractValidator<IOrder>
{
}
class CommandValidator : AbstractValidator<Command>
{
}