Reputation: 780
I'm trying to pass correlationId
that I would receive through controller to my IEventDispatcher
and I want this object that holds correlationId
to be disposed soon as EventHandler
is done with it.
Also, I'm listening to ServiceBus
queue
and topic
and I have instance of IEventDispatcher
there as well and I'd like to apply same logic there.
I inject EventDispatcher
in Controller where I set internal Context
which I hope to have access to in EventHandler
but not by passing context
directly but accessing context
through DI.
public class CommandController : ControllerBase
{
private readonly IEventDispatcher eventDispatcher;
public CommandController(IEventDispatcher eventDispatcher)
{
this.eventDispatcher = eventDispatcher;
}
[HttpPost]
public async Task<IActionResult> Post([FromBody]TAction action)
{
try
{
// I want this context to be disposed after handler
// that is called inside of eventDispatcher is done with execution
var context = new HttpRequestContext(HttpContext);
await eventDispatcher.Dispatch<TAction>((TAction)action, context);
return Ok();
} catch (Exception e)
{
return BadRequest((new BadExceptionResult { Error = e.Message }));
}
}
}
I use dotnet core 3.1 web api, and I have SimpleInjector setup like this:
class ServiceSetup
private Container container = new Container();
public void ConfigureServices(IServiceCollection services)
{
container.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();
// here I set RequestContextRegistrator
// and then I set this context inside EventHandler to set context
// and hope to have it available in handler
container.Register<IRequestContextRegistrator, RequestContextRegistrator>(
Lifestyle.Scoped);
container.Register(() => container.GetInstance<IRequestContextRegistrator>().Get(),
Lifestyle.Scoped);
// ...
// EventHandlers are registered just before EventDispatcher as EventDispatcher is
// is depending on eventHandlers to be registered before eventDispatcher can send
// a request to them
var eventDispatcher = new EventDispatcher(container);
services.AddSingleton<IEventDispatcher>(eventDispatcher);
// ...
}
public void Configure(IApplicationBuilder app)
{
app.UseSimpleInjector(container);
// ...
container.Verify();
}
}
RequestContextRegistrator : IRequestContextRegistrator
internal class RequestContextRegistrator : IRequestContextRegistrator
{
private readonly IContext context = new Context();
public IContext RegisterContext(IContext context)
{
context.CorrelationId = new Guid().ToString();
return context;
}
public Context Get()
{
return new Context()
{
CorrelationId = context.CorrelationId
};
}
}
Here it is how EventDispatcher
kind of looks like
public class EventDispatcher : IEventDispatcher
{
Container container;
public EventDispatcher(Container container)
{
this.container = container;
}
public async Task Dispatch<TAction>(TAction action, IContext context)
{
using (AsyncScopedLifestyle.BeginScope(container))
{
// this is registered in `ConfigureServices` before
var handler = container.GetInstance(IEventHandler<TAction>);
}
}
}
As you can see I use using (AsyncScopedLifestyle.BeginScope(container))
but in handler's constructor I never get the Context
object injected, it's always null
.
Upvotes: 0
Views: 475
Reputation: 172606
You can store the IContext
inside a Scoped components (such as your RequestContextRegistrator
) at the start of your scope. For instance:
public async Task Dispatch<TAction>(TAction action, IContext context)
{
using (AsyncScopedLifestyle.BeginScope(container))
{
container.GetInstance<IContextProvider>().SetContext(context);
var handler = container.GetInstance<IEventHandler<TAction>>();
await handler.Handle(action);
}
}
Your handlers can now be injected with the IContextProvider
to get access to the IContext
:
public class OrderShippedHandler : IEventHandler<OrderShipped>
{
private readonly IContextProvider provider;
public OrderShippedHandler(IContextProvider provider) => this.provider = provider;
public async Task Handle(OrderShipped e)
{
IContext context = this.provider.Context;
}
}
Don't forget to register the IContextProvider
implemenation as scoped.
This way of storing state inside object graphs is called the Closure Composition Model.
Upvotes: 1