Search code examples
c#decoratorsimple-injector

How can I resolve decorators for objects created by factory in Simple Injector


So this is going to involve me showing a lot of my plumbing but I will try to keep it to the bare minimum to keep this question simple.

One of my API endpoints relies on external providers to complete the call. When a user sends a query to that endpoint they can specify which provider they want us to use when handling the query, let's say the providers are Bing and Google.

So I have an IProvider interface and two concrete implementations BingProvider and GoogleProvider (in my real API the provider interface is actually a generic interface but I am leaving the generics out to avoid making this messier than it has to be). I need to resolve the correct provider based on a field in the query. Simple Injector does not allow registering multiple concrete implementations of the same interface so I have to use a factory; I create one that looks something like this:

public class ProviderFactory
{
    private readonly Func<string, IProvider> _Selector;

    public ProviderFactory(Func<string, IProvider> selector)
    {
        this._Selector = selector;
    }

    public IProvider Get(string provider)
    {
        return this._Selector(provider);
    }
}

I register my factory with the container by doing something like this:

container.RegisterSingleton<ProviderFactory>(new ProviderFactory(provider =>
    {
        switch (provider)
        {
            case "Bing":
                return container.GetInstance<BingProvider>()
            case "Google":
                return container.GetInstance<GoogleProvider>()
            default:
                throw new ArgumentOutOfRangeException("Unknown provider: " + provider);
        }
    }));

I test it. It works. Fantastic.

Now I need to create and register multiple decorators for my IProvider. Every concrete implementation of IProvider must have these decorators applied when the container resolves them. For the sake of this example lets say I have Decorator1 and Decorator2 which implement IProvider. I register them with the container like this:

container.RegisterDecorator(typeof(IProvider), typeof(Decorator1), Lifestyle.Singleton);
container.RegisterDecorator(typeof(IProvider), typeof(Decorator2), Lifestyle.Singleton);

This is where the problem is. When my factory resolves an instance of BingProvider or GoogleProvider that is exactly what I get. What I want to get is an instance of Decorator2 which decorates an instance of Decorator1 which in turn decorates whichever concrete implementations of IProvider I requested. I assume this is because I am not specifically asking the container to resolve an instance of IProvider but rather I am asking for it to resolve a concrete implementation of IProvider.

I seem to have gotten myself all tangled up here and I am not sure what the best way is to resolve it.

Edit

Okay, after thinking about this some more I understand why the decorators will never be resolved. Take Decorator1 and BingProvider, for instance. Both Decorator1 and BingProvider implement IProvider but Decorator1 does not implement BingProvider so when I ask Simple Injector to resolve an instance of BingProvider it is not even possible for it to give me an instance of Decorator1. I am going to have to ask it to somehow resolve an instance of IProvider but give me the correct concrete implementation with the decorators in place.

Great that I understand but now I am less sure on how to proceed.

Update

Based on Steven's answer I modified my factory registration like this:

var bingProvider = Lifestyle.Singleton
    .CreateProducer<IProvider, BingProvider>(container);

var googleProvider = Lifestyle.Singleton
    .CreateProducer<IProvider, GoogleProvider>(container);

container.RegisterSingleton<ProviderFactory>(new ProviderFactory(provider =>
    {
        switch (provider)
        {
            case "Bing":
                return bingProvider.GetInstance();
            case "Google":
                return googleProvider.GetInstance();
            default:
                throw new ArgumentOutOfRangeException("Unknown provider: " + provider);
        }
    }));

My new problem is that when I run my unit test to verify the container the test fails with an error that looks like this (I had to doctor this error message up to make it match my example here, hopefully that does not cause anything to be lost in translation):

The configuration is invalid. The following diagnostic warnings were reported:
-[Torn Lifestyle] The registration for IProvider maps to the same implementation and lifestyle as the registration for IProvider does. They both map to Decorator1 (Singleton). This will cause each registration to resolve to a different instance: each registration will have its own instance.
-[Torn Lifestyle] The registration for IProvider maps to the same implementation and lifestyle as the registration for IProvider does. They both map to Decorator2 (Singleton). This will cause each registration to resolve to a different instance: each registration will have its own instance.
See the Error property for detailed information about the warnings. Please see https://simpleinjector.org/diagnostics how to fix problems and how to suppress individual warnings.


Solution

  • Simple Injector does not allow registering multiple concrete implementations of the same interface

    This statement is incorrect. There are actually multiple ways to do this. I think the three most common ways to do so are:

    1. Use conditional registrations
    2. Register a collection of types
    3. Manually create InstanceProducer instances.

    Especially option 1 and 3 seem most suited in your case, so let's start with option 3: Creating InstanceProducers:

    // Create two providers for IProvider according to the required lifestyle.
    var bing = Lifestyle.Singleton.CreateProducer<IProvider, BingProvider>(container);
    var google = Lifestyle.Singleton.CreateProducer<IProvider, GoogleProvider>(container);
    
    container.RegisterSingleton<ProviderFactory>(new ProviderFactory(provider => {
        switch (provider) {
            case "Bing": return bing.GetInstance();
            case "Google": return google.GetInstance();
            default: throw new ArgumentOutOfRangeException();
        }
    }));
    

    Here we create two InstanceProducer instances, one for each IProvider. The important part here is to create a producer for the IProvider abstraction, since this allows decorators for IProvider to be applied.

    Alternatively, you can choose to move the switch-case statement inside the ProviderFactory and supply it with two separate delegates; one for each provider. For instance:

    public ProviderFactory(Func<IProvider> bingProvider, Func<IProvider> googleProvider) { .. }
    
    public IProvider Get(string provider) {
        switch (provider) {
            case "Bing": return bingProvider();
            case "Google": return googleProvider();
            default: throw new ArgumentOutOfRangeException();
        }
    }
    

    The registration looks very similar to the previous:

    var bing = Lifestyle.Singleton.CreateProducer<IProvider, BingProvider>(container);
    var google = Lifestyle.Singleton.CreateProducer<IProvider, GoogleProvider>(container);
    
    container.RegisterSingleton<ProviderFactory>(new ProviderFactory(
        bingProvider: bing.GetInstance,
        googleProvider: google.GetInstance));
    

    Instead of injecting Func<T> delegates into the factory, depending on your needs it might work for you to inject IProvider directly. This means your constructor will look as follows:

    public ProviderFactory(IProvider bing, IProvider google) { ... }
    

    Now you can use conditional registrations on IProvider to remove the ambiguity on the constructor arguments:

    container.RegisterSingleton<ProviderFactory>();
    container.RegisterConditional<IProvider, BingProvider>(
        c => c.Consumer.Target.Name == "bing");
    container.RegisterConditional<IProvider, GoogleProvider>(
        c => c.Consumer.Target.Name == "google");
    

    Advantage of this is that you don't delay building the object graph; all providers are injected directly when the factory's consumer is resolved.

    Alternatively, you might also want to experiment with a design where you replace the factory with a dispatcher abstraction. Factory abstractions are often not the simplest solution for the consumer, because they now have to deal with both the factory type and the service abstraction being returned. A dispatcher or processor on the other hand, gives the consumer with one single abstraction. This makes the consumer (and its unit tests) often simpler.

    Such dispatcher will look a lot like the IProvider interface itself, but adds a string provider parameter to its instance methods. For instance:

    interface IProviderDispatcher {
        void DoSomething(string provider, ProviderData data);
    }
    

    Where the dispatcher's implementation might look as follows:

    public ProviderDispatcher(IProvider bing, IProvider google) { .. }
    
    public void DoSomething(string provider, ProviderData data) {
        this.Get(provider).DoSomething(data);
    }
    
    private IProvider Get(string provider) {
        switch (provider) {
            case "Bing": return this.bing;
            case "Google": return this.google;
            default: throw new ArgumentOutOfRangeException();
        }
    }
    

    The solution for the dispatcher can be the same af for the factory, but now we hide the extra step from the consumer.

    Even better would it be if we can remove the IProviderDispatcher abstraction completely, but this is only possible if the string provider runtime data is contextual data that is available during the request. In that case we might be able to do the following:

    interface IProviderContext {
        string CurrentProvider { get; }
    }
    

    And instead of a separate provider abstraction, we can have a proxy implementation on IProvider:

    class ProviderDispatcherProxy : IProvider {
        public ProviderDispatcherProxy(Func<IProvider> bingProvider, 
            Func<IProvider> googleProvider,
            IProviderContext providerContext) { ... }
    
        void IProvider.DoSomething(ProviderData data) {
            // Dispatch to the correct provider
            this.GetCurrentProvider.DoSomething(data);
        }
    
        private IProvider GetCurrentProvider() =>
            switch (this.providerContext.CurrentProvider) {
                case "Bing": return this.bingProvider();
                case "Google": return this.googleProvider();
                default: throw new ArgumentOutOfRangeException();
            }
        };
    }
    
    class AspNetProviderContext : IProviderContext {
        public CurrentProvider => HttpContext.Current.Request.QueryString["provider"];
    }
    

    Again, internally it's still much like before, but now, because the provider value is something we can resolve from an available ambient context (the HttpContext.Current), we will be able to let the consumer work with IProvider directly. You can register this as follows:

    container.RegisterSingleton<IProviderContext>(new AspNetProviderContext());
    
    var bing = Lifestyle.Singleton.CreateProducer<IProvider, BingProvider>(container);
    var google = Lifestyle.Singleton.CreateProducer<IProvider, GoogleProvider>(container);
    container.RegisterSingleton<IProvider>(new ProviderDispatcherProxy(
        bingProvider: bing.GetInstance,
        googleProvider: google.GetInstance));
    

    Now you can simply inject an IProvider into your consumers and dispatching will automatically happen for you on the background.