问题
I have a Mediator application using Simple Injector for command handler registration, and injection and handlers is all setup and working perfectly.
class DoWashingCommandHandler : IRequestHandler<DoWashingCommand,object>
{
IBus bus;
public DoWashingCommandHandler(IBus bus) { this.bus = bus; }
Task<object> Handle(DoWashingCommand command)
{
this.bus.Send(new OtheCommand());
}
}
I have a need for 2 registrations of a IBus
implementations.
The first can be of any lifetime, the second has a background thread so i initially thought it would need to be a singleton but on review i believe it could also be of any lifetime and just have a static worker thread class within it (this would be important for scope):
// register as non-singleton to allow scope usage
// keep static worker thread as if it were Singleton
class DispatchOnBackgroundThread : IBus
{
static Worker = new Worker();
public Task<object> Send(object command)
{
Worker.Post(command);
}
public void Start(Container container, CancelationToken stoppingToken)
{
Worker.Start(container,stoppingToken);
}
class Worker
{
public void Post(object command) { /* snip */ }
public void Start(Container container, CancelationToken stoppingToken)
{ /* snip */ }
public void Thread()
{
/* loop */
var item = ReadFromQueue();
// get command handler type
// get command handler instance from container
// if instantiated instance has IBus dependency in this
// section then it must have used DispatchInThread as the
// concrete implementation for IBus (including if the handler
// calls container.GetInstance<IBus>()
handler.Handle(item.Request, cancellationToken);
}
// anything outside this Thread should use
// DispatchOnBackgroundThread for IBus
}
}
Then the registrations would be as follows (not sure how to avoid the double registration issue of IBus):
// i need to be able to register two types
container.Register<IBus,DispatchOnBackgroundThread>();
container.Register<IBus,DispatchInThread>();
// this would return any IBus references with DispatchOnBackgroundThread
var handler = this.container.GetInstance(requestHandlerType);
using(SomeSope.BeingScope(container))
{
// this would return any IBus references with DispatchInThread
var handler = this.container.GetInstance(requestHandlerType);
// and if handler or other code referenced container and called
// GetInstance, and IBus dependencies would be returned as
// DispatchInThread whilst in this scope
}
// this would return any IBus references with DispatchOnBackgroundThread
var other = this.container.GetInstance(requestHandlerType);
I think and in summary, this is a mix of Context-based injection and custom scope.
How can I achieve the above or is this a terrible code smell?
To give further context if required I need the above switchable resolved types in order to implement the solution of another question https://stackoverflow.com/a/61782405/915839
DI code is removed in above link, but i am very much using SimpleInjector in the actual implementation
回答1:
I'm not sure I fully understand your use case, and what it is that leads to this, but what you can do is create a wrapper IBus
implementation that forwards the call to the correct bus, while changing the forwarded bus implementation while running on a background thread.
This wrapper might look as follows:
class SwitchableBus : IBus
{
private readonly DispatchInCallingThread defaultBus;
private readonly DispatchOnBackgroundThread backgroundBus;
public SwitchableBus(
DispatchInCallingThread defaultBus, DispatchOnBackgroundThread backgroundBus)
{
this.defaultBus = defaultBus;
this.backgroundBus = backgroundBus;
this.Bus = defaultBus;
}
public IBus Bus { get; private set; }
public void SwitchToBackgroundBus() => this.Bus = this.backgroundBus;
public Task<object> Send(object command) => this.Bus.Send(command);
}
With this wrapper, you can use the following registrations:
container.Register<IBus, SwitchableBus>(Lifestyle.Scoped);
container.Register<SwitchableBus>(Lifestyle.Scoped);
container.Register<DispatchInCallingThread>(Lifestyle.Scoped);
container.Register<DispatchOnBackgroundThread>(Lifestyle.Scoped);
This allows you to have DispatchInCallingThread
used in the graph as follows:
using(SomeSope.BeingScope(container))
{
var handler = this.container.GetInstance(requestHandlerType);
handler.Handle(request);
}
In other words, by default the DispatchInCallingThread
is used.
And DispatchOnBackgroundThread
can be used by the graph as follows:
using(SomeSope.BeingScope(container))
{
container.GetInstance<SwitchableBus>().SwitchToBackgroundBus();
var handler = this.container.GetInstance(requestHandlerType);
handler.Handle(request);
}
Concequence of this is, however, that you should always resolve within an active Scope. But that would be a good idea anyway, because it is likely there will be Scoped
dependencies in a graph anyway. Simple Injector does not allow resolving a graph with Scoped dependencies outside the context of an active scope.
来源:https://stackoverflow.com/questions/61783276/simple-injector-dynamic-context-based-injection-at-runtime-between-two-registrat