Simple Injector: How can I skip verification of an object in the container

一笑奈何 提交于 2019-12-13 02:48:45

问题


I'm using Simple Injector to inject dependencies into my objects via Constructor Injection.

For a particular set of objects (all derived from a common abstract base class) I inject a factory instead of the concrete object so that I can determine at run-time which derived instance should be injected. The factory is registered with the container as a singleton, as are the derived instances.

After registering all of my objects with the DI Container I call the Container.Verify() method to verify my configuration. The problem is, the implementation of this method creates an instance of every type registered with the container. These derived instances are expensive to create and their creation has side-effects (they come from legacy code that is being updated to use DI). Long-term I'll get rid of the side-effects but short term it's not feasable.

How can I tell the container not to verify these derived instances?

I want to keep the Verify() call (at the very least for debug builds) but can't accept these instances being instantiated by the Verify() call. Furthermore, they need to be registered with a Singleton Lifestyle so I can't just not register them with the container.

One solution I came up with is to not register the derived objects with the container (thereby making them non-verifiable), and then have the (Singleton Lifestyle) Factory implementation cache the first instance it creates. It works, but it's dirty, and if someone explicitly requests an instance of the derived type somewhere else (unlikely) a new instance would be created (because the default Lifestyle is Transient).

Here's an example of the class structure I have:

public class AbstractBase { }
public class Derived1 : AbstractBase { }
public class Derived2 : AbstractBase { }
public class Derived3 : AbstractBase { }

public class MyFactory
{
    private readonly Func<Derived1> _factory1;
    private readonly Func<Derived2> _factory2;
    private readonly Func<Derived3> _factory3;

    public MyFactory(Func<Derived1> factory1, Func<Derived2> factory2, Func<Derived3> factory3)
    {
        _factory1 = factory1;
        _factory2 = factory2;
        _factory3 = factory3;
    }

    public AbstractBase Create()
    {
        if (AppSettings.ProductType == ProductType.Type1)
            return _factory1();

        if (AppSettings.ProductType == ProductType.Type2)
            return _factory2();

        if (AppSettings.ProductType == ProductType.Type3)
            return _factory3();

        throw new NotSupportedException();
    }
}

And the registrations with the DI Container:

Container container = new Container();
container.RegisterSingle<Derived1>();
container.RegisterSingle<Derived2>();
container.RegisterSingle<Derived3>();
container.RegisterSingle<Func<Derived1>>(() => container.GetInstance<Derived1>());
container.RegisterSingle<Func<Derived2>>(() => container.GetInstance<Derived2>());
container.RegisterSingle<Func<Derived3>>(() => container.GetInstance<Derived3>());
container.RegisterSingle<MyFactory>();

container.Verify(); // <-- will instantiate new instances of Derived1, Derived2, and Derived3. Not desired.

回答1:


From your question I understand that you are very well aware of the downsides of your current approach, and you are currently dealing with some legacy code that can't be changed in one single iteration. But since others will be reading this as well, I'd like to make my usual note that injection constructors should be simple, fast and reliable.

With that out of the way, to answer question: No, there is no way to mark registration to be skipped for registration in Simple Injector.

All InstanceProducer instances that are created for a container prior to calling Verify() will get verified (unless they are garbage collected before). Typically, InstanceProducer instances are created implicitly for you when you call a Register overload, but you can also create new InstanceProducer instances by calling Lifestyle.CreateProducer. These producers will not be part of any object graph (which means that Simple Injector will not use them to auto-wire other types), and they act as root types. Still however, the container keeps track of those producers and verification will apply to them as well.

So the trick here is to trigger the creation of new InstanceProducer instances after the verification process. You can do that either by calling Lifestyle.CreateProducer, or you can do this by resolving an unregistered concrete type, as you are doing in your example. Downside of course of resolving an unregistered type is that it gets resolved as transient by default. There are two ways around this. Either you can cache the instance yourself or you can instruct the container to create that particular type as singleton.

Doing the caching yourself might look like this:

var lazy1 = new Lazy<Derived1>(container.GetInstance<Derived1>);
container.RegisterSingle<Func<Derived1>>(() => lazy1.Value);

Caching it yourself however has the downside that that you blind the diagnostic system and make it impossible to spot any Lifestyle mismatches for you. So a better option is to override the lifestyle selection behavior, which looks like:

// Custom lifestyle selection behavior
public class AbstractBaseDerivativesAsSingleton : ILifestyleSelectionBehavior {
    public Lifestyle SelectLifestyle(Type serviceType, Type implementationType) {
        typeof(AbstractBase).IsAssignableFrom(implementationType)
            ? Lifestyle.Singleton
            : Lifestyle.Transient;
    }
}

// Usage
var container = new Container();
container.Options.LifestyleSelectionBehavior =
    new AbstractBaseDerivativesAsSingleton();

container.RegisterSingle<Func<Derived1>>(() => container.GetInstance<Derived1>());

Another way to solve this is by creating InstanceProducers yourself using the Lifestyle.CreateProducer call. Since you want singletons, you will have to call Lifestyle.Singleton.CreateProducer. Those producers need to be created after the call to verify, so you will still need to use Lazy as delaying mechanism:

// This factory should be part of your composition root, 
// because it now depends on the container.
public class MyFactory : IMyFactory
{
    private readonly Container container;
    private readonly Dictionary<ProductType, Lazy<InstanceProducer>> producers;

    public MyFactory(Container container) {
        this.container = container;
        this.producers = new Dictionary<ProductType, Lazy<InstanceProducer>>
        {
            {ProductType.Type1, new Lazy<InstanceProducer>(this.CreateProducer<Derived1>)},
            {ProductType.Type2, new Lazy<InstanceProducer>(this.CreateProducer<Derived2>)},
            {ProductType.Type3, new Lazy<InstanceProducer>(this.CreateProducer<Derived3>)}
        };
    }

    public AbstractBase Create() {
        return (AbstractBase)this.producers[AppSettings.ProductType].GetInstance()
    }

    private InstanceProducer CreateProducer<T>() where T : AbstractBase {
        Lifestyle.Singleton.CreateProducer<AbstractBase, T>(this.container);
    }
}

Also consider changing your factory to a mediator or proxy. Factories are often not the right abstraction, because they often just increase complexity. Instead you can make a proxy that takes the same interface and delegates the call to the real instance (where you still use factory-like behavior in the background):

public class AbstractBaseAppSettingsSwitchProxy : AbstractBase
{
    private readonly IMyFactory factory;
    public AbstractBaseAppSettingsSwitchProxy(IMyFactory factory) {
        this.factory = factory;
    }

    public override void SomeFunction() {
        this.factory.Create().SomeFunction();
    }
}

With this proxy, you can hide from any consumer the fact that there are multiple possible AbstractBase implementations. The consumer can simply communicate with AbstractBase as if there always is exactly one. This can make your application code cleaner, consumer code simpler, and make consumers easier to test.

If a proxy is not an option, you can still consider the mediator pattern. A mediator works about the same as the proxy above, but with the difference that it gets its own interface. So it's much like the factory (with its own interface), but with the difference that the mediator is responsible for calling the delegated objects. It will not return an instance to the consumer.

I'm aware that these solutions might not be applicable to you, because of the structure of the AbstractBase. If you have a fat base class with some methods virtual and others not, it might be quite hard to do this. These solutions typically only work well in a well designed (SOLID) system. But that's actually how it all works. My experience is that without SOLID code, everything gets cumbersome. One of our primary jobs as software developers is to lower total cost of ownership of our software and one of the best ways of doing this is by applying the SOLID principles to our software.

One last note though. It seems to me that you are reading some configuration value inside your factory. If this value is defined in the application's configuration file, the value can only be changed by restarting the application (which is something that IIS does for you automatically). If this is such configuration value, you actually don't need all this non-sense at all. You can simply make the following registrations:

Container container = new Container();

container.RegisterSingle(typeof(AbstractBase, GetConfiguredAbstractBaseType()));

private static Type GetConfiguredAbstractBaseType() {
    switch (AppSettings.ProductType) {
        case ProductType.Type1: return typeof(Derived1);
        case ProductType.Type2: return typeof(Derived2);
        case ProductType.Type3: return typeof(Derived3);
        default: throw new NotSupportedException();
    }
}

Of course this brings us back again to the initial problem of not being able to verify. But we can again solve this with a proxy:

public class LazyAbstractBaseProxy : AbstractBase
{
    private readonly Lazy<AbstractBase> lazy;
    public LazyAbstractBaseProxy(Lazy<AbstractBase> lazy) {
        this.lazy = lazy;
    }

    public override void SomeFunction() {
        this.lazy.Value.SomeFunction();
    }
}

Type type = GetConfiguredAbstractBaseType();
var lazy = new Lazy<InstanceProducer>(() =>
    Lifestyle.Singleton.CreateProducer(typeof(AbstractBase), type, container));
container.RegisterSingle<AbstractBase>(new LazyAbstractBaseProxy(
    new Lazy<AbstractBase>(() => (AbstractBase)lazy.Value.GetInstance()));

And if that's not possible, you can even skip the factory and inject a Func into consumers directly:

Type type = GetConfiguredAbstractBaseType();
var lazy = new Lazy<InstanceProducer>(() =>
    Lifestyle.Singleton.CreateProducer(typeof(AbstractBase), type, container));
container.RegisterSingle<Func<AbstractBase>>(() => (AbstractBase)lazy.Value.GetInstance());



回答2:


To respond to the primary question : How can I skip verification of an object in the container.

You can do it like this :

Container container = new Container();

Lifestyle singletonLifestyle = Lifestyle.CreateHybrid(
    lifestyleSelector: () => !container.IsVerifying,
    trueLifestyle: Lifestyle.Singleton,
    falseLifestyle: Lifestyle.Transient);

container.Register<TConcrete>(singletonLifestyle);



回答3:


You can register your Func<T> with a Lazy<T> that will load the InstanceProducer lazily as follows:

private static Lazy<T> Lazy<T>(Func<T> func) => new Lazy<T>(func);

public static void RegisterDelayedFunc<T>(this Container container, Lifestyle lifestyle)
    where T : class
{
    var lazy = Lazy(() => lifestyle.CreateProducer<T, T>(container));
    container.RegisterSingleton<Func<T>>(() => lazy.Value.GetInstance());
}

Container container = new Container();

container.RegisterDelayedFunc<Derived1>(Lifestyle.Singleton);
container.RegisterDelayedFunc<Derived2>(Lifestyle.Singleton);
container.RegisterDelayedFunc<Derived3>(Lifestyle.Singleton);

container.RegisterSingleton<MyFactory>();


来源:https://stackoverflow.com/questions/31688864/simple-injector-how-can-i-skip-verification-of-an-object-in-the-container

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