【问题标题】:Handling errors/exceptions in a mediator pipeline using CQRS?使用 CQRS 处理调解器管道中的错误/异常?
【发布时间】:2015-12-13 10:29:51
【问题描述】:

我正在尝试遵循 Jimmy Bogard 的 this post 来实现中介管道,以便我可以使用前/后请求处理程序来完成一些工作。从那篇文章的 cmets 我来到这个github gist。我还不太明白如何将所有这些联系起来,所以这是我的第一次尝试。仅供参考 - 我将 Autofac 用于 DI 和 Web Api 2。在 CQRS 之后,这是一个查询。

public class GetAccountRequest : IAsyncRequest<GetAccountResponse>
{
    public int Id { get; set; }
}

//try using fluent validation
public class GetAccountRequestValidationHandler 
    : AbstractValidator<GetAccountRequest>, IAsyncPreRequestHandler<GetAccountRequest>
{
    public GetAccountRequestValidationHandler() {
        RuleFor(m => m.Id).GreaterThan(0).WithMessage("Please specify an id.");
    }

    public Task Handle(GetAccountRequest request) {
        Debug.WriteLine("GetAccountPreProcessor Handler");   
        return Task.FromResult(true);
    }
}

public class GetAccountResponse
{
    public int AccountId { get; set; }
    public string Name { get; set; }
    public string AccountNumber { get; set; }
    public string Nickname { get; set; }
    public string PhoneNumber { get; set; }
    public List<OrderAckNotification> OrderAckNotifications { get; set; }

    public class OrderAckNotification {
        public int Id { get; set; }
        public bool IsDefault { get; set; }
        public string Description { get; set; }
        public string Type { get; set; }
    }
}

GetAccountRequestHandler:

public class GetAccountRequestHandler 
    : IAsyncRequestHandler<GetAccountRequest, GetAccountResponse>
{
    private readonly IRedStripeDbContext _dbContext;

    public GetAccountRequestHandler(IRedStripeDbContext redStripeDbContext)
    {
        _dbContext = redStripeDbContext;
    }

    public async Task<GetAccountResponse> Handle(GetAccountRequest message)
    {
        //some mapping code here.. omitted for brevity
        Mapper.AssertConfigurationIsValid();

        return await _dbContext.Accounts.Where(a => a.AccountId == message.Id)
            .ProjectToSingleOrDefaultAsync<GetAccountResponse>();
    }

这是显示 HttpGet 的当前 web api 2 控制器。

[RoutePrefix("api/Accounts")]
public class AccountsController : ApiController
{
    private readonly IMediator _mediator;

    public AccountsController(IMediator mediator)
    {
        _mediator = mediator;
    }

    // GET: api/Accounts/2
    [Route("{id:int}")]
    [HttpGet]
    public async Task<IHttpActionResult> GetById([FromUri] GetAccountRequest request)
    {
        var model = await _mediator.SendAsync<GetAccountResponse>(request);

        return Ok(model);
    }
}

最后是依赖解析代码:

public void Configuration(IAppBuilder app)
{
    var config = new HttpConfiguration();

    ConfigureDependencyInjection(app, config);

    WebApiConfig.Register(config);
    app.UseWebApi(config);
}

private static void ConfigureDependencyInjection(IAppBuilder app, 
    HttpConfiguration config)
{
    var builder = new ContainerBuilder();
    builder.RegisterSource(new ContravariantRegistrationSource());
    builder.RegisterAssemblyTypes(typeof(IMediator).Assembly).AsImplementedInterfaces();

    builder.Register<SingleInstanceFactory>(ctx =>
    {
        var c = ctx.Resolve<IComponentContext>();
        return t => c.Resolve(t);
    });

    builder.Register<MultiInstanceFactory>(ctx =>
    {
        var c = ctx.Resolve<IComponentContext>();
        return t => (IEnumerable<object>)c.Resolve(
            typeof(IEnumerable<>).MakeGenericType(t));
    });

    //register all pre handlers
    builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
        .As(type => type.GetInterfaces()
            .Where(t => t.IsClosedTypeOf(typeof(IAsyncPreRequestHandler<>))));

    //register all post handlers
    builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
        .As(type => type.GetInterfaces()
            .Where(t => t.IsClosedTypeOf(typeof(IAsyncPostRequestHandler<,>))));


    //register all handlers
    builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
        .As(type => type.GetInterfaces()
            .Where(t => t.IsClosedTypeOf(typeof(IAsyncRequestHandler<,>)))
            .Select(t => new KeyedService("asyncRequestHandler", t)));

    //register pipeline decorator
    builder.RegisterGenericDecorator(typeof(AsyncMediatorPipeline<,>), 
        typeof(IAsyncRequestHandler<,>), "asyncRequestHandler");

    // Register Web API controller in executing assembly.
    builder.RegisterApiControllers(Assembly.GetExecutingAssembly()).InstancePerRequest();

    //register RedStripeDbContext
    builder.RegisterType<RedStripeDbContext>().As<IRedStripeDbContext>()
        .InstancePerRequest();

    builder.RegisterType<AutofacServiceLocator>().AsImplementedInterfaces();
    var container = builder.Build();

    config.DependencyResolver = new AutofacWebApiDependencyResolver(container);

    // This should be the first middleware added to the IAppBuilder.
    app.UseAutofacMiddleware(container);

    // Make sure the Autofac lifetime scope is passed to Web API.
    app.UseAutofacWebApi(config);
}

我正在进入 GetAccountRequestValidationHandler。但是,当验证失败(通过了 0 的 id)时,如何抛出异常或停止管道的执行?如何返回 .WithMessage?

【问题讨论】:

    标签: c# asp.net-web-api2 autofac cqrs mediatr


    【解决方案1】:

    我也为此苦苦挣扎。似乎有两个/三个选项:

    使用预处理器...

    1) 您可以将错误加载到请求中,并让主处理程序在处理命令/查询之前检查错误

    2) 让预处理器抛出异常。似乎围绕这种做法存在相当多的分歧。一方面,它感觉就像管理带有异常的控制流,但“专业”阵营认为客户端应该负责发送一个有效的命令。 IE。在让用户单击“创建帐户”之前,它可以发送 ajax 查询以确认用户名可用。在这种情况下,违反此规则的异常是由于竞争条件造成的。

    将验证处理程序直接放入管道中。

    我相信这更符合@jbogard 的想法。 我目前没有使用它,但我已经勾勒出它的样子——可能有更好的例子,当然你想要如何定义和处理事情可能会有所不同。它的要点是,作为管道的一部分,validation-runner 可以返回到调用者,而无需调用主处理程序。

    public class AsyncValidationPipeline<TRequest, TResponse> : IAsyncRequestHandler<TRequest, TResponse>
        where TRequest : IAsyncRequest<TResponse>
    {
        private IAsyncRequestHandler<TRequest, TResponse> _inner;
        private IValidator<TRequest>[] _validators;
    
        public AsyncValidationPipeline(IAsyncRequestHandler<TRequest, TResponse> inner,
            IValidator<TRequest>[] validators)
        {
            _inner = inner;
            _validators = validators;
        }
        public Task<TResponse> Handle(TRequest message)
        {
            List<string> errors = new List<string>();
            if (_validators != null && _validators.Any())
            {
                errors = _validators.Where(v => v.Fails(message))
                    .Select(v => v.ErrorMessage);
            }
    
            if (errors.Any())
            {
                throw new ValidationException(errors);
            }
            return _inner.Handle(message);
        }
    }
    

    这是与 AutoFac 连接的代码:

                //register all pre handlers
                builder.RegisterAssemblyTypes(assembliesToScan)
                    .AsClosedTypesOf(typeof(IAsyncPreRequestHandler<>));
    
                //register all post handlers
                builder.RegisterAssemblyTypes(assembliesToScan)
                    .AsClosedTypesOf(typeof(IAsyncPostRequestHandler<,>));
    
                const string handlerKey = "async-service-handlers";
                const string pipelineKey = "async-service-pipelines";
    
                // Request/Response for Query
    
                builder.RegisterAssemblyTypes(assembliesToScan)
                    .AsKeyedClosedTypesOf(typeof(IAsyncRequestHandler<,>), handlerKey)
                    ;
    
                // Decorate All Services with our Pipeline
                //builder.RegisterGenericDecorator(typeof(MediatorPipeline<,>), typeof(IRequestHandler<,>), fromKey: "service-handlers", toKey: "pipeline-handlers");
               builder.RegisterGenericDecorator(typeof(AsyncMediatorPipeline<,>), typeof(IAsyncRequestHandler<,>), fromKey: handlerKey, toKey: pipelineKey);
    
                // Decorate All Pipelines with our Validator
               builder.RegisterGenericDecorator(typeof(AsyncValidationHandler<,>), typeof(IAsyncRequestHandler<,>), fromKey: pipelineKey);//, toKey: "async-validator-handlers");
    
               // Add as many pipelines as you want, but the to/from keys must be kept in order and unique
    

    希望这会有所帮助....

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-11-08
      • 2019-06-22
      • 2019-04-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多