【问题标题】:Using Fluent Results inside a MediatR validation pipeline to return a Result<TResponse>在 MediatR 验证管道中使用 Fluent Results 以返回 Result<TResponse>
【发布时间】:2020-07-12 11:53:08
【问题描述】:

首先,我将Fluent ResultsMediatrFluent Validation 结合使用

我最初关注this article,但我没有重新发明轮子,而是开始在我的 Fluent 验证管道中使用 FluentResults。基本上来自我的 CQRS 查询的所有响应都包含在 Result 对象中,这避免了将异常作为错误处理方法。

但是,我无法让我的管道发挥出色:

public class ValidationPipeline<TRequest, TResponse>
    : IPipelineBehavior<TRequest, TResponse>
    where TResponse : class
    where TRequest : IRequest<TResponse>
{
    private readonly IValidator<TRequest> _compositeValidator;

    public ValidationPipeline(IValidator<TRequest> compositeValidator)
    {
        _compositeValidator = compositeValidator;
    }

    public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
    {
        var result = await _compositeValidator.ValidateAsync(request, cancellationToken);

        if (!result.IsValid)
        {
            Error error = new Error();
            var responseType = typeof(TResponse);

            foreach (var validationFailure in result.Errors)
            {
                Log.Warning($"{responseType} - {validationFailure.ErrorMessage}");
                error.Reasons.Add(new Error(validationFailure.ErrorMessage));
            }
            // This always returns null instead of a Result with errors in it. 
            var f = Result.Fail(error) as TResponse;
            return f;

        }

        return await next();
    }
}

我还必须以某种方式将 Result 对象转换回 TResponse,其中 TResponse 始终是 Result

非常感谢任何建议!

编辑:

Autofac 集成

    protected override void Load(ContainerBuilder builder)
    {
        var assembly = Assembly.GetExecutingAssembly();

        // MediatR
        builder.AddMediatR(assembly);
        // Register the Command's Validators (Validators based on FluentValidation library)
        builder.RegisterAssemblyTypes(assembly)
            .Where(t => t.IsClosedTypeOf(typeof(IValidator<>)))
            .AsImplementedInterfaces();
        // Register all the Command classes (they implement IRequestHandler) in assembly holding the Commands
        builder.RegisterAssemblyTypes(assembly)
            .AsClosedTypesOf(typeof(IRequestHandler<,>));
        // Register Behavior Pipeline
        builder.RegisterGeneric(typeof(ValidationPipeline<,>)).As(typeof(IPipelineBehavior<,>));

    }

【问题讨论】:

  • 这是否编译并运行?但只是一直返回 f 为空?
  • 如果你用具体类 Result 替换 TResponse 的使用(既然你说 Result 将始终用于 Result,会发生什么?
  • @AnnL。感谢您的评论,我确实尝试过,当我将“类”更改为“结果”时,管道停止被调用。我用可能相关的 AutoFac 配置更新了帖子
  • 这很有趣。我想知道:这听起来像 TResponse 并不是真正的 Result 对象,或者它可能没有意识到它是。能否调试并在转换为TResponse 的行处下断点,以便在运行时检查TResponse 的具体类型是什么?
  • @AnnL。好建议,我试过了,TResponse 是一个带有传递 T 参数的 Result 对象。我添加了一个答案,解释了我是如何找到解决方法的。

标签: c# asp.net-core cqrs fluentvalidation mediatr


【解决方案1】:

你应该改变

其中 TResponse : 类

其中 TResponse : 结果

并确保您的所有请求都是 IRequest

其中 T 是您要返回的实际响应。

【讨论】:

  • 正是我的想法!
  • 感谢您的回答,我确实尝试过,当我将“类”更改为“结果”时,管道停止被调用。
  • @JasonLandbridge 你也改变了你的处理程序吗?
  • @dariogriffo,是的,我试过了,但它最终打破了 MediatR 的工作方式。我需要 2 种不同的 TResponse 类型才能使其按建议工作。 TResponse 可以是 Result 或 CustomClass,我需要它只是 CustomClass 才能使其工作,但我不能同时返回 Result,我需要让它工作。
【解决方案2】:

我正在编辑我的帖子,但它变得太长了,我认为它几乎可以解决我的问题。

我已经能够将问题隔离到以下行:

 var f = Result.Fail(error).ToResult<CustomClass>() as TResponse;
 return f;

如果我硬引用传递给 Result 的类,则转换按预期工作,一切正常。现在问题变成了,我怎样才能获得一个可以从 TResponse 传递到 .ToResult&lt;T&gt;() 的类引用?

我一直在寻找答案,但似乎根本不可能在运行时检索编译时类引用,而我在编译时需要它。

我还尝试实例化结果对象的副本,并在添加验证错误后将其返回。像这样:

 var resultType = typeof(TResponse).GetGenericArguments()[0];
 var invalidResponseType = typeof(ValidateableResponse<>).MakeGenericType(resultType);
 var f = Activator.CreateInstance(invalidResponseType, null) as TResponse;
 return f;

这可行,但 Result 对象有一个封闭的构造函数,因此我留下了一个例外。我在 FluentResult 的 GitHub 上留下了一个 issue,也许可以改一下。

现在我有一个解决方法,我让我的处理程序从 BaseHandler 继承

基本处理程序:

public class BaseHandler
{
    public Result Validate<TQuery, TValidator>(TQuery request) where TValidator : AbstractValidator<TQuery>
    {
        var validator = (TValidator)Activator.CreateInstance(typeof(TValidator));
        var result = validator.Validate(request);
        return CreateResult(result);
    }

    public async Task<Result> ValidateAsync<TQuery, TValidator>(TQuery request) where TValidator : AbstractValidator<TQuery>
    {
        var validator = (TValidator)Activator.CreateInstance(typeof(TValidator));
        var result = await validator.ValidateAsync(request);
        return CreateResult(result);
    }

    private Result CreateResult(ValidationResult result)
    {
        if (!result.IsValid)
        {
            if (result.Errors.Count == 1)
            {
                string msg = $"Validation Failure: {result.Errors.First().ErrorMessage}";
                return Result.Fail(msg);
            }

            Error error = new Error("Validation Failure");

            foreach (var validationFailure in result.Errors)
            {
                Log.Warning($"{validationFailure.ErrorMessage}");
                error.Reasons.Add(new Error(validationFailure.ErrorMessage));
            }
            return Result.Fail(error);
        }
        return Result.Ok();
    }
}

用法示例:

  // At the top of my handle function
  var result = await ValidateAsync<GetItemByIdQuery, GetItemByIdQueryValidator>(request);
  if (result.IsFailed) return result;

这也有效,唯一的缺点是我必须在每个 Handle 函数的顶部添加它才能启用验证。

目前还好,我会等着看FluentResult包是否可以更新,这样我就可以尝试之前的建议了。

感谢大家的建议!

【讨论】:

    【解决方案3】:

    如果我理解你的问题,我想我已经做了你想做的同样的事情。以下是我对这个问题的解决方案。

        public class ValidationPipeline<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
        where TResponse : ResultBase<TResponse>, new() where TRequest : IRequest<TResponse>
    {
        private readonly IEnumerable<IValidator<TRequest>> _validators;
    
        public ValidationPipeline(IEnumerable<IValidator<TRequest>> validators)
        {
            _validators = validators ?? throw new ArgumentNullException(nameof(validators));
        }
    
        public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken,
            RequestHandlerDelegate<TResponse> next)
        {
            var validationFailures = _validators.Select(validator => validator.Validate(request))
                .SelectMany(validationResult => validationResult.Errors)
                .Where(validationFailure => validationFailure != null).ToList();
    
            if (validationFailures.Any())
            {
                var responseType = typeof(TResponse);
                TResponse invalidResponse;
                if (responseType.IsGenericType)
                {
                    var resultType = responseType.GetGenericArguments()[0];
                    var invalidResponseType = typeof(Result<>).MakeGenericType(resultType);
    
                    invalidResponse = Activator.CreateInstance(invalidResponseType, null) as TResponse;
                }
                else
                {
                    invalidResponse = new TResponse();
                }
    
                invalidResponse.WithErrors(validationFailures.Select(s => s.ErrorMessage));
                return invalidResponse;
            }
    
            return await next();
        }
    }
    

    这将处理您使用 Result 或 Result 的情况。

    【讨论】:

      【解决方案4】:

      我遇到了类似的问题。此外,一些处理程序返回Result,而其他处理程序返回Result&lt;ABC&gt;Result&lt;XYZ&gt;。通用行为无法处理此问题。

      两种解决方法:

      1. 不是返回Result&lt;&gt;,而是抛出异常并隐蔽到管道中的Result(捕获或全局错误句柄)。

      2. 像这样分别注册每个处理程序和管道

        services.AddTransient( typeof(IPipelineBehavior&lt;MyCommand,Result&lt;MyCommandResult&gt;&gt;), typeof(MyBehavior&lt;MyCommand, MyCommandResult&gt;));

        但这很麻烦,必须自动化。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-05-23
        • 2017-10-19
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多