如何基于MediatR和FluentValidation实现仅触发命令的通用ValidationBehavior
背景与需求
在CQRS架构中,已通过自定义接口区分命令(ICommand)与查询(IQuery),希望ValidationBehavior仅在处理继承自IRequest<TResponse>的ICommand<TResponse>时触发,而非对所有IRequest<TResponse>请求生效。
自定义CQRS接口实现
用于区分命令和查询的接口定义如下:
public interface ICommand<TResponse> : IRequest<ErrorOr<TResponse>> { } public interface IQuery<TResponse> : IRequest<ErrorOr<TResponse>> { }
对应的处理器接口:
public interface ICommandHandler<TCommand, TResponse> : IRequestHandler<TCommand, ErrorOr<TResponse>> where TCommand : ICommand<TResponse> { } public interface IQueryHandler<TQuery, TResponse> : IRequestHandler<TQuery, ErrorOr<TResponse>> where TQuery : IQuery<TResponse> { }
标准验证行为实现(可正常工作)
最初的通用实现会对所有IRequest<TResponse>触发,通过检查是否存在对应验证器来决定是否执行验证:
public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IRequest<TResponse> where TResponse : IErrorOr { private readonly IValidator<TRequest>? _validator; public ValidationBehavior(IValidator<TRequest>? validator = null) { _validator = validator; } public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken) { if(_validator is null) { return await next(); } var validationResult = await _validator.ValidateAsync(request); if (validationResult.IsValid) { return await next(); } var validationErrors = validationResult.Errors .ConvertAll(error => Errors.Wands.NotValid( error.PropertyName, error.ErrorMessage)); return (dynamic)validationErrors; } }
修改后的验证行为(无法工作)
尝试将泛型约束改为仅针对ICommand<TResponse>,修改后ValidationBehavior完全失效:
public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, ErrorOr<TResponse>> where TRequest : ICommand<TResponse> { private readonly IValidator<TRequest> _validator; public ValidationBehavior(IValidator<TRequest> validator) { _validator = validator; } public async Task<ErrorOr<TResponse>> Handle(TRequest request, RequestHandlerDelegate<ErrorOr<TResponse>> next, CancellationToken cancellationToken) { var validationResult = await _validator.ValidateAsync(request); if (validationResult.IsValid) { return await next(); } var validationErrors = validationResult.Errors .ConvertAll(error => Errors.Wands.NotValid( error.PropertyName, error.ErrorMessage)); return validationErrors; } }
失效原因分析
MediatR管道匹配逻辑问题:
MediatR的管道行为匹配依赖泛型参数的精确匹配。ICommand<TResponse>继承自IRequest<ErrorOr<TResponse>>,当发送命令请求时,MediatR识别的是IRequest<ErrorOr<TResponse>>类型的请求,而修改后的管道行为约束为TRequest : ICommand<TResponse>,导致MediatR无法正确推断并匹配到该管道。依赖注入失败风险:
修改后的构造函数要求必须注入IValidator<TRequest>(非可空),如果某个ICommand未实现对应的验证器,DI容器会抛出异常,导致管道无法被实例化。
解决方案
方案1:通用管道内过滤命令类型
保留通用管道的泛型约束,在Handle方法中判断请求是否为ICommand类型,仅对命令执行验证:
public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IRequest<TResponse> where TResponse : IErrorOr { private readonly IValidator<TRequest>? _validator; public ValidationBehavior(IValidator<TRequest>? validator = null) { _validator = validator; } public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken) { // 仅对实现了ICommand<>的请求执行验证 var isCommand = typeof(TRequest).GetInterfaces() .Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICommand<>)); if (!isCommand || _validator is null) { return await next(); } var validationResult = await _validator.ValidateAsync(request); if (validationResult.IsValid) { return await next(); } var validationErrors = validationResult.Errors .ConvertAll(error => Errors.Wands.NotValid( error.PropertyName, error.ErrorMessage)); return (dynamic)validationErrors; } }
方案2:针对ICommand的管道行为(调整后可工作)
调整管道的泛型定义和注入逻辑,确保MediatR能正确匹配,同时允许验证器可选注入:
public class CommandValidationBehavior<TCommand, TResponse> : IPipelineBehavior<TCommand, ErrorOr<TResponse>> where TCommand : ICommand<TResponse> { private readonly IValidator<TCommand>? _validator; // 改为可选注入,避免无验证器时DI失败 public CommandValidationBehavior(IValidator<TCommand>? validator = null) { _validator = validator; } public async Task<ErrorOr<TResponse>> Handle(TCommand request, RequestHandlerDelegate<ErrorOr<TResponse>> next, CancellationToken cancellationToken) { if (_validator is null) { return await next(); } var validationResult = await _validator.ValidateAsync(request); if (validationResult.IsValid) { return await next(); } var validationErrors = validationResult.Errors .ConvertAll(error => Errors.Wands.NotValid( error.PropertyName, error.ErrorMessage)); return validationErrors; } }
在DI容器中注册该管道行为:
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(CommandValidationBehavior<,>));
关键注意事项
- 确保所有需要验证的
ICommand都实现了对应的IValidator<TCommand>,DI容器能正确解析。 - 若存在无需验证的
ICommand,必须将IValidator设为可选注入(使用?和默认null),避免DI异常。
内容的提问来源于stack exchange,提问作者Maks0s

