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.
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:
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.