Simple Injector dynamic context-based injection at runtime between two registrations

巧了我就是萌 提交于 2020-05-17 06:02:08

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!