The built-in Dependency Injection (DI) in ASP.NET Core is very useful, but how do you deal with an interface has multiple implementations? Can the runtime choose one of these implementations based on configuration? Is there a way to get rid of reflection? Let me show you how to dynamically select a specific implementation of an interface at run time according to the configuration file without reflection in ASP.NET Core.

First of all, this demand actually comes from my own Blog system. My images can be stored in two locations: Azure Blob and File System, so I wrote an interface with 2 sets of implementations. I want to be able to switch to cloud or local file systems at any time. Because this code is complex, I will one concise examples to demonstrate this trick.

Interface Definition and Two Implementations


Interface

public interface IHelloer
{
    string SayHello();
}

Implementations

public class HelloerA : IHelloer
{
    public string SayHello()
    {
        return $"Hello from {nameof(HelloerA)}";
    }
}

public class HelloerB : IHelloer
{
    public string SayHello()
    {
        return $"Hello from {nameof(HelloerB)}";
    }
}

Dependency Injection


This is exactly the same for registering regular DI in ASP.NET Core, we register both HelloerA and HelloerB into the DI container.

services.AddTransient<IHelloer, HelloerA>();
services.AddTransient<IHelloer, HelloerB>();

Constructor Injection

Just now we registered two sets of implementations of the same interface, so which implementation will ASP.NET Core runtime choose? Try this:

public IHelloer Hello { get; set; }

public HomeController(IHelloer hello)
{
    Hello = hello;
}

And show the result in browser

public IActionResult Index()
{
    var message = Hello.SayHello();
    return Content(message);
}

The result is HelloerB, which is the last item we registered in DI container.

Here comes the question, what if I want to use HelloerA at runtime?

The Constructor Can Actually Inject Like This

In fact, in ASP.NET Core, when you register multiple implementations for an interface, the constructor can be injected with a collection of that interface, which is all registered implementations.

private IEnumerable<IHelloer> Helloers { get; set; }

public HomeController(IEnumerable<IHelloer> helloers)
{
    Helloers = helloers;
}

Now, we can use HelloerA like this

public IActionResult Index()
{
    var message = Helloers.First().SayHello();
    return Content(message);
}

Of course, in the project, we absolutely can not write hard code like this, we want to select a specific implementation through the configuration file. Please continue reading.

Configuration File

Add a new setting in appsettings.json

"AppSettings": {
  "CurrentHelloer": "HelloerA"
}

Create a class for it

public class AppSettings
{
    public string CurrentHelloer { get; set; }
}

Register this into DI and also constructor

services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));
private AppSettings AppSettings { get; set; }
private IEnumerable<IHelloer> Helloers { get; set; }

public HomeController(IEnumerable<IHelloer> helloers, IOptions<AppSettings> settings)
{
    Helloers = helloers;
    AppSettings = settings.Value;
}

This allows you to select the implementation by the corresponding name from the interface collection using LINQ very easily, depending on the configuration file.

public IActionResult Index()
{
    var helloer = Helloers.FirstOrDefault(h => h.GetType().Name == AppSettings.CurrentHelloer);
    var message = helloer?.SayHello();
    return Content(message);
}

Now, according to our settings, the result will be HelloerA.

However, I used reflection in this code, some may say it can impact the performance of your application. Let's see how we can get rid of the reflection.

Get Rid of Reflection

We used reflection for nothing more than getting a class name in order to get a implementation at run time. So as long as you add a CurrentName attribute to each class, you will also be able to do it. And we can use nameof() to increase the maintainability of the code.

Modify the interface

public interface IHelloer
{
    string CurrentName { get; }
    string SayHello();
}

Modify the implmentations

public class HelloerA : IHelloer
{
    public string CurrentName => nameof(HelloerA);

    public string SayHello()
    {
        return $"Hello from {nameof(HelloerA)}";
    }
}

public class HelloerB : IHelloer
{
    public string CurrentName => nameof(HelloerB);

    public string SayHello()
    {
        return $"Hello from {nameof(HelloerB)}";
    }
}

Modify LINQ

public IActionResult Index()
{
    var helloer = Helloers.FirstOrDefault(h => h.CurrentName == AppSettings.CurrentHelloer);
    var message = helloer?.SayHello();
    return Content(message);
}

Now our code runs perfectly!

Caveat


When you inject a collection of interface implementations, every single implementation will be instantiated. So if you only use one implementation in your application after the application started, please register the implementation according to the configuration in DI container (Startup.Configure). If at application runtime, you need to choose from multiple implementations, you can use this solution without problem. Just watch out for some code in your constructor that can have side effects if they are being instantiated.