Registering Multiple Implementations of an Interface with Service Collection in ASP.NET Core

Full source code here.

This is a simplistic approach to the problem and assumes that the dependencies created by the factory do not in turn have their own dependencies. This deficiency could be mediated by passing those dependencies into the factory. Having said that, it will be of use in some scenarios.

In a later post I will show how to use Autofac to do this properly.

The out-of-the-box dependency injection container provided by Microsoft as part of ASP.NET Core is fine for most scenarios, only in a few complicated cases have I needed to switch to Castle or AutoFac.

But there is one somewhat common case where the ServiceCollection feels a little deficient. There are times when you might have more than one implementation of an interface and you would like to choose between them at runtime.

There is no obvious way to do this.

You could try something like -

1public void ConfigureServices(IServiceCollection services)
3    services.AddScoped<IValuesService, PositiveValuesService>();
4    services.AddScoped<IValuesService, NegativeValuesService>();

But when you request an implementation of IValuesService in the constructor of a controller, the service collection will return the last registered service, in this case it will always return the NegativeValuesService.

But what if you wanted the PositiveValuesService in some controllers and the NegativeValuesService in other controllers. What if you needed to choose the service implementation based on the address of the request that the controller is handling.

Something like this - localhost:5000/NegativeValues – should use the NegativeValuesService. localhost:5000/PositiveValues – should use the PositiveValuesService.

Using a Factory

Instead of registering two implementations of the IValuesService, I’m going to create a ValuesServiceFactory that returns the right service based on the path of the request.

The ValuesServiceFactory has a method that looks like this -

 1public IValuesService GetValuesService()
 3    string path = _httpContextAccessor.HttpContext.Request.Path.Value.ToString().Trim('/').ToLower();
 4    switch (path)
 5    {
 6        case "positivevalues":
 7            return new PositiveValuesService(); // as mentioned above this only works if the values services have no dependencies
 8        case "negativevalues":
 9            return new NegativeValuesService();
10        default:
11            return new PositiveValuesService();
12    }

_httpContextAccessor is what lets me access the Request from inside the factory.

A few weeks ago I wrote about post about accessing the HttpRequest from inside the constructor of a controller or from the constructor of a service the controller depends on.

That post had a contrived example, but this post has a more practical use of the technique.

The constructor of the ValuesServiceFactory needs to take an IHttpContextAccessor.

1public ValuesServiceFactory(IHttpContextAccessor httpContextAccessor)
3    _httpContextAccessor = httpContextAccessor;

Back in Startup.cs, remove the code registering the two services and replace with a line to register the factory and the HttpContextAccessor.

1public void ConfigureServices(IServiceCollection services)
3    services.AddScoped<IValuesServiceFactory, ValuesServiceFactory>();
4    services.AddHttpContextAccessor();
5    //snip…

The controllers now take the implementation of IValuesServiceFactory and request the an IValuesService from the factory.

1private readonly IValuesService _valuesService;
3public NegativeValuesController(IValuesServiceFactory valuesServiceFactory)
5    _valuesService = valuesServiceFactory.GetValuesService();

Do the same in the other controller -

1private readonly IValuesService _valuesService;
3public PositiveValuesController(IValuesServiceFactory valuesServiceFactory)
5    _valuesService = valuesServiceFactory.GetValuesService();

That’s it, now you can choose between implementation of an interface. As I was working on this post I came across a post by Steve Gordon on this topic, he solves the same problem in a different way.

Full source code here.

comments powered by Disqus