Injecting multiple implementations with Dependency injection

跟風遠走 提交于 2019-12-02 17:18:35
Tseng

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;
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!