You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于MediatR和FluentValidation实现仅触发命令的通用ValidationBehavior

CQRS架构下仅对ICommand触发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;
    }
}

失效原因分析

  1. MediatR管道匹配逻辑问题:
    MediatR的管道行为匹配依赖泛型参数的精确匹配。ICommand<TResponse>继承自IRequest<ErrorOr<TResponse>>,当发送命令请求时,MediatR识别的是IRequest<ErrorOr<TResponse>>类型的请求,而修改后的管道行为约束为TRequest : ICommand<TResponse>,导致MediatR无法正确推断并匹配到该管道。

  2. 依赖注入失败风险:
    修改后的构造函数要求必须注入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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 01:43:11