通过依赖注入注入多个实现 [英] Injecting multiple implementations with Dependency injection

查看:109
本文介绍了通过依赖注入注入多个实现的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前正在研究ASP.NET Core项目,并希望使用内置的依赖注入(DI)功能.

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();
}

,并且想要多次实现ICar接口,

and want to implement the ICar interface multiple times like

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

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

,然后在Startup类中添加以下内容

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>();
 }

现在我必须在两个实现之间做出决定,并且我决定的类将在每个需要ICar实现的构造函数中设置. 但是我的想法是,如果请求的Controller是BMWController,则使用BMW实现,或者如果请求JaguarController,则使用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.

否则,DI对我而言毫无意义.我该如何正确处理此问题?

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?

在Unity中,可以进行类似这样的操作 container.RegisterType<IPerson, Male>("Male"); container.RegisterType<IPerson, Female>("Female");

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

推荐答案

要寻找的功能并不容易实现,至少在控制器中使用时,因为对控制器进行了特殊对待(默认情况下) ,控制器未在ServiceCollection中注册,因此未在容器中解析/实例化,而是在请求期间由ASP.NET Core实例化,另请参见

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).

使用内置的IoC容器,您只能通过工厂方法来完成此操作,这里是BmwCarFactory类的示例:

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>())));

故意将默认的IoC容器保持简单,以提供依赖项注入的基础知识,从而使您入门,而其他IoC容器则可以轻松地在其中进行插入并替换默认的实现.

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.

对于更高级的场景,鼓励用户使用支持更高级功能(程序集扫描,装饰器,条件/参数化依存关系等)的所选IoC.

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(我在我的项目中使用)支持这种高级方案.在AutoFac文档中,有4种情况(以及注释中建议使用@pwa的第3种情况):

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):

重构代码和类层次结构需要一些额外的开销,但是会大大简化注入服务的使用

Needs some additional overhead of refactoring your code and class hierarchy but heavily simplifies the consumption of injected services

文档对其进行了描述

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.使用密钥服务(此处)

它与以前的方法2非常相似,但是根据键而不是具体类型来解析服务

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

这与3.非常相似,但是您可以通过属性定义键.

This is quite similar to 3. but you define the keys via attribute.

其他容器(例如 Unity )具有特殊属性,例如DependencyAttribute,可用于注释依赖项,如

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)
    {
    }
}

但这和Autofac的第4个选项会使IoC容器泄漏到您的服务中,您应该考虑其他方法.

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

或者,您创建类和工厂,这些类和工厂根据某些约定来解析您的服务.例如ICarFactory:

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;
    }
}

然后像使用它

public class BmwController : Controller
{
    public ICarFactory carFactory;

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

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

替代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;
    }
}

这篇关于通过依赖注入注入多个实现的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆