Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Determining which implementation to inject at runtime using .NET Core dependency injection

I have three types of users in my application, let's say Type1, Type2 and Type3. Then i want to create one service implementation for each type, let's say i have a service to get photos, i would have three services : Type1PhotosService, Type2PhotosService and Type3PhotosService, each of them implementing IPhotosService.

In the web api, i would inject IPhotosService :

IPhotosService _service;

public PhotosController(IPhotosService service){
   _service = service;
} 

The web api uses token authentication with claims. So what i want to achieve, is for each user, depending on the claim he has : type1 or type2 or type3, the correct implementation of the service will be automatically injected rather than injecting a single service in the startup file. What i want to avoid, is having one service, with a bunch of switch and if statements to return the correct data depending on user type and the roles he has.

EDIT: some comments were wondering what's the point of three implementations, so here are more details to give it a little more sense. The service is a job finder service, and the application has three different profiles : candidate, employer and administration. Each of these profiles need a proper implementation. So rather than having three methods GetCandidateJobs, GetEmployerJobs and GetAdministrationJobs inside the same service and switch on the user type, i preferred to have one implementation per profile type, then depending on the profile type, use the correct implementation.

like image 597
badr slaoui Avatar asked Dec 28 '18 15:12

badr slaoui


2 Answers

Without Using a Separate IoC Container

Here's an approach that's way easier than configuring your app to use another IoC container and then configuring that container. After working through this with Windsor this solution seems a whole lot easier.

This approach is simplest if you can use a singleton instance of each service implementation.

We'll start with an interface, some implementations, and the factory we can inject which will return an implementation selected at runtime based on some input.

public interface ICustomService { }
public class CustomServiceOne : ICustomService { }
public class CustomServiceTwo : ICustomService { }
public class CustomServiceThree : ICustomService { }

public interface ICustomServiceFactory
{
    ICustomService Create(string input);
}

Here's a really crude implementation of the factory. (Didn't use string constants, or polish it at all.)

public class CustomServiceFactory : ICustomServiceFactory
{
    private readonly Dictionary<string, ICustomService> _services 
        = new Dictionary<string, ICustomService>(StringComparer.OrdinalIgnoreCase);

    public CustomServiceFactory(IServiceProvider serviceProvider)
    {
        _services.Add("TypeOne", serviceProvider.GetService<CustomServiceOne>());
        _services.Add("TypeTwo", serviceProvider.GetService<CustomServiceTwo>());
        _services.Add("TypeThree", serviceProvider.GetService<CustomServiceThree>());
    }

    public ICustomService Create(string input)
    {
        return _services.ContainsKey(input) ? _services[input] : _services["TypeOne"];
    }
}

This assumes that you've already registered CustomServiceOne, CustomServiceTwo, etc. with the IServiceCollection. They would not be registered as interface implementations, since that's not how we're resolving them. This class will simply resolve each one and put them in a dictionary so that you can retrieve them by name.

In this case the factory method takes a string, but you could inspect any type or multiple arguments to determine which implementation to return. Even the use of a string as the dictionary key is arbitrary. And, just as an example, I provided fallback behavior to return some default implementation. It might make more sense to throw an exception instead if you can't determine the right implementation to return.

Another alternative, depending on your needs, would be to resolve the implementation within the factory when it's requested. To the extent possible I try to keep most classes stateless so that I can resolve and reuse a single instance.

To register the factory with the IServiceCollection at startup we would do this:

services.AddSingleton<ICustomServiceFactory>(provider => 
    new CustomServiceFactory(provider));

The IServiceProvider will be injected into the factory when the factory is resolved, and then the factory will use it to resolve the service.

Here's the corresponding unit tests. The test method is the identical to the one used in the Windsor answer, which "proves" that we can transparently replace one factory implementation with another and change other stuff in the composition root without breaking stuff.

public class Tests
{
    private IServiceProvider _serviceProvider;
    [SetUp]
    public void Setup()
    {
        var services = new ServiceCollection();
        services.AddSingleton<CustomServiceOne>();
        services.AddSingleton<CustomServiceTwo>();
        services.AddSingleton<CustomServiceThree>();
        services.AddSingleton<ICustomServiceFactory>(provider => 
            new CustomServiceFactory(provider));
        _serviceProvider = services.BuildServiceProvider();
    }

    [TestCase("TypeOne", typeof(CustomServiceOne))]
    [TestCase("TypeTwo", typeof(CustomServiceTwo))]
    [TestCase("TYPEThree", typeof(CustomServiceThree))]
    [TestCase("unknown", typeof(CustomServiceOne))]
    public void FactoryReturnsExpectedService(string input, Type expectedType)
    {
        var factory = _serviceProvider.GetService<ICustomServiceFactory>();
        var service = factory.Create(input);
        Assert.IsInstanceOf(expectedType, service);
    }
}

As in the Windsor example, this is written to avoid any reference to the container outside of the composition root. If a class depends on ICustomServiceFactory and ICustomService you could switch between this implementation, the Windsor implementation, or any other implementation of the factory.

like image 111
Scott Hannen Avatar answered Sep 17 '22 12:09

Scott Hannen


I am going to go out on a limb here and say that the attempt to utilize dependency injection for this purpose is sub-optimal. Normally this would be handled by a Factory pattern that produces service implementations using the dreaded if and switch statements. A simple example is:

public interface IPhotoService { 
     Photo CreatePhoto(params);
}

public class PhotoServiceFactory {
    private readonly IPhotoService _type1;
    private readonly IPhotoService _type2;
    private readonly IPhotoService _type3;
    public PhotoServiceFactory(IDependency1 d1, IDependency2 d2, ...etc) {
        _type1 = new ConcreteServiceA(d1);
        _type2 = new ConcreteServiceB(d2);
        _type3 = new ConcreteServiceC(etc);
    }
    public IPhotoService Create(User user) {
        switch(user.Claim) {
            case ClaimEnum.Type1:
                return _type1;
            case ClaimEnum.Type2:
                return _type2;
            case ClaimEnum.Type3:
                return _type3;
            default:
                throw new NotImplementedException
        }
    }
}

Then in your controller:

public class PhotosController {
    IPhotoServiceFactory _factory;
    public PhotosController(IPhotoServiceFactory factory){
       _factory = factory;
    } 
    public IHttpActionResult GetPhoto() {
       var photoServiceToUse = _factory.Create(User);
       var photo = photoServiceToUse.CreatePhoto(params);
       return Ok(photo);
    }
}

Alternately just use the concrete classes as arguments in the constructor and follow a similar logic as to the above.

like image 30
C Bauer Avatar answered Sep 19 '22 12:09

C Bauer