2015-09-16 30 views
5

Sto provando a seguire this post da Jimmy Bogard per implementare una pipeline del mediatore in modo da poter utilizzare i gestori di richieste pre/post per fare un po 'di lavoro. Dai commenti su quell'articolo vengo a questo github gist. Non capisco ancora come collegare tutto questo, quindi ecco il mio primo tentativo. Cordiali saluti - Sto usando Autofac per DI e Web Api 2. Seguendo CQRS, ecco una query.Gestione errori/eccezioni in un gasdotto mediatore utilizzando 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>(); 
    } 

Ecco l'attuale controller Web API 2 mostra la HttpGet.

[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); 
    } 
} 

Infine ecco il codice di risoluzione delle dipendenze:

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); 
} 

sto ottenendo in GetAccountRequestValidationHandler. Tuttavia, quando la validazione fallisce (è stato passato un id di 0), come faccio a lanciare un'eccezione o interrompere l'esecuzione della pipeline? Come posso restituire il .WithMessage?

risposta

5

Anche io ho un po 'di difficoltà. Sembra che ci sono due/tre opzioni:

Usando un pre-handler ...

1) è possibile errori di caricamento nella richiesta e avere il controllo del gestore principale per gli errori prima che elabora il comando/interrogazione

O

2) Avere il pre-handler un'eccezione. Sembra che ci sia un bel po 'di disaccordo intorno a questa pratica. Da una parte sembra di gestire il flusso di controllo con le eccezioni, ma il campo "pro" sostiene che il cliente dovrebbe essere responsabile per l'invio di un comando valido per cominciare. Vale a dire. Può inviare query ajax per confermare che un nome utente è disponibile prima di consentire all'utente di fare clic su "Crea account". In questo caso un'eccezione che infrange questa regola sarebbe dovuta a una condizione di competizione.

Inserire il gestore di convalida direttamente nella pipeline.

Credo che questo sia più sulla falsariga di ciò che @jbogard stava pensando. Attualmente non sto usando questo, ma ho abbozzato come potrebbe essere questo - ci sono probabilmente esempi migliori là fuori, e ovviamente esattamente come si desidera definire e gestire le cose possono variare. L'essenza di ciò è che, essendo parte della pipeline, il validation-runner può tornare al chiamante senza che il gestore principale venga mai chiamato.

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); 
    } 
} 

Ecco il codice per l'aggancio che con 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 

Spero che questo aiuti ....