Search code examples
c#-4.0dependency-injectioninversion-of-controlsimple-injector

injecting different implementations and configurations of same interface into different clients


Suppose I have an interface IStorage and multiple implementations of it, e.g.:

class FileStorage : IStorage
{
    public FileStorage(string filePath)
    {            
    }
}

class HttpStorage : Storage
{
    public HttpStorage(IHttpClient httpClient)
    {
    }
}

Now I have multiple classes that I want to register in my application and each of them needs a different IStorage instance.

  • All instances of ClassA (implementing and registered via IClassA) need a singleton FileStorage with "C:\Temp\foo.txt" as filePath.
  • All instances of ClassB (implementing and registered via IClassB) need a singleton FileStorage with "C:\Temp\bar.txt" as filePath.
  • All instances of ClassC (implementing and registered via IClassC) need a singleton HttpStorage with the registered singleton of IHttpClient.

How can I achieve the above without falling back to creating most of the dependency graph manually?


Solution

  • The primary question to ask every time you think you need this is: Do I violate the Liskov Substitution Principle. You are breaking the LSP in case the implementations aren't interchangeable for one another. If ClassA breaks when you inject an HttpStorage into it, you are breaking the LSP. In that case, you should give each implementation each own abstraction, such as IFileStorage and IHttpStorage.

    My first impression is that you are not violating LSP. Simple Injector v3 contains a RegisterConditional method that allows you to do conditional registrations. With Simple Injector v3.1 you can make the registration as follows:

    Lifestyle transient = Lifestyle.Transient;
    
    container.RegisterConditional(typeof(IStorage),
        transient.CreateRegistration(() => new FileStorage(@"C:\Temp\foo.txt"), container),
        c => c.Consumer.ImplementationType == typeof(ClassA));
    
    container.RegisterConditional(typeof(IStorage),
        transient.CreateRegistration(() => new FileStorage(@"C:\Temp\bar.txt"), container),
        c => c.Consumer.ImplementationType == typeof(ClassB));
    
    container.RegisterConditional(typeof(IStorage),
        transient.CreateRegistration(() => new HttpStorage(new HttpClient()), container),
        c => c.Consumer.ImplementationType == typeof(ClassC));
    

    The RegisterConditional overload that accepts an Registration instance is new in v3.1.