How to change dependency registration at run time

2019-04-30 01:33发布

问题:

I'm using the Simple Injector IoC framework, and I would like to be able to change the dependency registration at run time. For example, I have two implementations, A and B, of interface I. Implementation A is registered at app start, but depending on some flag which can change during runtime, I would like to switch the implementation. We are currently doing this the OnActionExecuting event of our BaseController, which all of our controllers inherit from. Here is the sample code of what I am trying to do.

protected override void OnActionExecuting(
    ActionExecutingContext filterContext)
{
    if (IsRuntimeFlag)
    {
        // check current implementation type and 
        // change implementation to A
    }
    else
    {
        // check current implementation type and 
        // change implementation to B
    }

    base.OnActionExecuting(filterContext);
}

Thanks in advance for your help.

回答1:

In case IsRuntimeFlag is a configuration value (thus cannot change during the lifetime of the application), you can make the registration as follows:

if (IsRuntimeFlag)
{
    container.Register<I, A>();
}
else
{
    container.Register<I, B>();
}

or equally:

container.Register(typeof(I), IsRuntimeFlag ? typeof(A) : typeof(B));

In case the value can change during the lifetime of the application a proxy or composite that deals with dispatching to the right instance is the right solution:

public sealed class RuntimeFlagIComposite : I
{
    private readonly A a;
    private readonly B b;

    public RuntimeFlagIComposite(A a, B b) {
        this.a = a;
        this.b = b;
    }

    void I.Method() => this.Instance.Method();

    private I Instance => IsRuntimeFlag ? this.a : this.b;
}

Because the composite directly depends on A and B, you can simply register it as follows:

container.Register<I, RuntimeFlagIComposite>();

When A or B have a different lifetime than transient (a new instance for each request), you should also register them. For instance:

container.Register<A>(Lifestyle.Singleton);
container.Register<B>(Lifestyle.Scoped);

You can also let your composite depend on the I abstraction itself instead of the concrete A and B implementations:

public class RuntimeFlagIComposite : I
{
    private I a;
    private I b;

    public RuntimeFlagIComposite(I a, I b)
    {
        this.a = a;
        this.b = b;
    }
}

Depending on the I abstraction makes this class more flexible and more testable. It does mean however, that you need to register it a little bit different:

container.Register<I, RuntimeFlagIComposite>();

container.RegisterConditional<I, A>(c => c.Consumer.Target.Name == "a");
container.RegisterConditional<I, B>(c => c.Consumer.Target.Name == "b");