24

I'm currently working on a ASP.NET Core Project and want to use the built-in Dependency Injection (DI) functionality.

Well, I started with an interface:

ICar
{
    string Drive();
}

and want to implement the ICar interface multiple times like

public class BMW : ICar
{
    public string Drive(){...};
}

public class Jaguar : ICar
{
    public string Drive(){...};
}

and add the following in the Startup class

public void ConfigureServices(IServiceCollection services)
{
     // Add framework services.
     services.AddMvc();
     services.AddTransient<ICar, BMW>(); 
     // or 
     services.AddTransient<ICar, Jaguar>();
 }

Now I have to make a decision between two implementations and my decided class will set in every constructor that needs an ICar implementation. But my idea was to say, if the requested Controller is BMWController, then use BMW implementation or use Jaguar if the JaguarController is requested.

Otherwise DI don't make sense for me. How can i handle this issue properly?

For better understanding my problem take a look to this pic: https://media-www-asp.azureedge.net/media/44907/dependency-injection-golf.png?raw=true How does the dependency resolver work and where can i set it up in ASP.NET Core?

In Unity it's possible to make something like this container.RegisterType<IPerson, Male>("Male"); container.RegisterType<IPerson, Female>("Female");

and call the correct type like this

[Dependency("Male")]IPerson malePerson
DrScrum Milestone
  • 277
  • 1
  • 2
  • 8
  • 1
    I think that idea is wrong. If you have BMWController that needs concrete car implementation, injecting ICar has no sense - that the magic of DI - you say "gimme implementation of ICar" and don't care what is it. I'd prepare a separate interface: `interface IBmwCar: ICar { ... }` and inject it into `BMWController`. Other idea is to create factory. f.e `class CarFactory : ICarFactory { ICar GimmeCar() }` where you have mapped controller type to concrete implementation. – pwas Dec 03 '16 at 10:24
  • Isn't it an overflow of Interfaces if I user something like IBmwCar : ICar for all brands i want to implement? I just have Drive as method and will not have any other. Well, i would create then IBMW, IJaguar... with the same signature like ICar and and use a statement like this for each brand i have implemented: services.AddTransient(); Obviously i could use Factory Pattern but isn't there any solution to do my work with DI? – DrScrum Milestone Dec 03 '16 at 11:09
  • one thing about the DI is you can take a dependency on IEnumerable and get all the implementations that have been registered, if no implementations have been registered you get an empty list rather than a failure to resolve it. So if ICar has some identifier property you could find the one you need in the list. – Joe Audette Dec 03 '16 at 12:08

1 Answers1

25

The functionality you are looking for isn't easy to implement, at least when you are using it in the controller because controllers are treated a bit specially (By default, controllers aren't registered with ServiceCollection and hence not resolved/instantiated by the container and instead instantiated by ASP.NET Core during the request, see also the explanation and example on my related answer).

With built-in IoC container, you can only do it via factory method, here with an example on a BmwCarFactory class:

services.AddScoped<ICar, BmwCar>();
services.AddScoped<BmwCar>();
services.AddScoped<BmwCarFactory>(p => new BmwCarFactory(p.GetRequiredService<BmwCar>())));

The default IoC container is intentionally kept simple to provide basics of dependency injection to get you started and for other IoC containers to be able to easily plugin in there and replace the default implementation.

For more advanced scenarios the users are encouraged to use an IoC of their choice which supports more advanced features (assembly scan, decorators, conditional/parameterized dependencies, etc.

AutoFac (which I use in my projects) supports such advanced scenarios. In the AutoFac documentation there are 4 scenarios (altogether with the 3rd which @pwas suggested in the comments):

##1. Redesign your classes Needs some additional overhead of refactoring your code and class hierarchy but heavily simplifies the consumption of injected services

##2. Change the registrations The docs describe it here, if you are unwilling or unable to change the code.

// Attach resolved parameters to override Autofac's
// lookup just on the ISender parameters.
builder.RegisterType<ShippingProcessor>()
       .WithParameter(
         new ResolvedParameter(
           (pi, ctx) => pi.ParameterType == typeof(ISender),
           (pi, ctx) => ctx.Resolve<PostalServiceSender>()));
builder.RegisterType<CustomerNotifier>();
       .WithParameter(
         new ResolvedParameter(
           (pi, ctx) => pi.ParameterType == typeof(ISender),
           (pi, ctx) => ctx.Resolve<EmailNotifier>()));
var container = builder.Build();

##3. Using keyed services (here) It is pretty similar to the previous approach to 2. but resolves the services based on a key, rather than their concrete type

##4. Use Metadata This is quite similar to 3. but you define the keys via attribute.

Other containers like Unity have special attributes, like DependencyAttribute which you can use to annotate the dependency, like

public class BmwController : Controller
{
    public BmwController([Dependency("Bmw")ICar car)
    {
    }
}

But this and the 4th option of Autofac make the IoC container leak into your services and you should consider the other approaches.

Alternatively you create classes and factories which resolve your services based on some conventions. For example a ICarFactory:

public ICarFactory
{
    ICar Create(string carType);
}

public CarFactory : ICarFactory
{
    public IServiceProvider provider;

    public CarFactory(IServiceProvider provider)
    {
        this.provider = provider;
    }

    public ICar Create(string carType)
    {
        if(type==null)
            throw new ArgumentNullException(nameof(carType));

        var fullQualifedName = $"MyProject.Business.Models.Cars.{carType}Car";
        Type carType = Type.GetType(fullQualifedName);
        if(carType==null)
            throw new InvalidOperationException($"'{carType}' is not a valid car type.");

        ICar car = provider.GetService(carType);
        if(car==null)
            throw new InvalidOperationException($"Can't resolve '{carType.Fullname}'. Make sure it's registered with the IoC container.");

        return car;
    }
}

Then use it like

public class BmwController : Controller
{
    public ICarFactory carFactory;

    public BmwController(ICarFactory carFactory)
    {
        this.carFactory = carFactory;

        // Get the car
        ICar bmw = carFactory.Create("Bmw");
    }
}

##Alternative to IServiceProvider

// alternatively inject IEnumerable<ICar>
public CarFactory : ICarFactory
{
    public IEnumerable<ICar> cars;

    public CarFactory(IEnumerable<ICar> cars)
    {
        this.cars = cars;
    }

    public ICar Create(string carType)
    {
        if(type==null)
            throw new ArgumentNullException(nameof(carType));

        var carName = "${carType}Car";
        var car = cars.Where(c => c.GetType().Name == carName).SingleOrDefault();

        if(car==null)
            throw new InvalidOperationException($"Can't resolve '{carName}.'. Make sure it's registered with the IoC container.");

        return car;
    }
}
Max Play
  • 3,717
  • 1
  • 22
  • 39
Tseng
  • 61,549
  • 15
  • 193
  • 205
  • 3
    Also added alternative to @JoeAudette's comment using `IEnumerable` to avoid dependency on the container – Tseng Dec 03 '16 at 12:51
  • That is exactly what i was looking for, thank you! I used Unity before in my Projects and i was wondering if there a possibility to so in nativ DI of ASP.NET Core. I got the answer thanks! – DrScrum Milestone Dec 03 '16 at 12:54
  • i just have an additionally question to Point 1 for better understanding. Should one Interface have only one implementation? – DrScrum Milestone Dec 03 '16 at 14:21
  • Depends. It can has multiple implementations, but you choose only one during registration. Example may be `IUserRepository` having either `SqliteUserRepository` and `SqlServerUserRepositrory` and you only register one of it. If you register multiple registrations you can't easily inject them depending on context or you resolve all of them with `IEnumerable`. So if you want to pass `IBmwCar` w/o using the other options then yes, it should only have one implementation – Tseng Dec 03 '16 at 14:26
  • Thanks for such a detailed answer. When we have multiple implementations we generally rely on the DI to resolve the service based the registrations. Just curious to know if there are any further side effects of such a scenario, other than our increased dependency on the container. – Gaurav Gahlot Feb 06 '17 at 08:28
  • There shouldn't be any effect, other that it's more pain when you switch your DI container and that it feels a bit more... "service locator"-like rather than "Di"-like – Tseng Feb 06 '17 at 10:12