使用依赖注入注入多个实现

时间:2016-12-03 09:36:58

标签: c# dependency-injection asp.net-core asp.net-core-mvc

我目前正在开发ASP.NET核心项目,并希望使用内置的依赖注入(DI)功能。

好吧,我开始使用界面:

ICar
{
    string Drive();
}

并希望多次实现ICar接口,例如

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

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

并在Startup

中添加以下内容
public void ConfigureServices(IServiceCollection services)
{
     // Add framework services.
     services.AddMvc();
     services.AddTransient<ICar, BMW>(); 
     // or 
     services.AddTransient<ICar, Jaguar>();
 }

现在我必须在两个实现之间做出决定,我决定的类将在每个需要ICar实现的构造函数中设置。 但我的想法是,如果要求的控制器是BMWController,那么如果要求使用JaguarController,则使用BMW实施或使用Jaguar

否则DI对我没有意义。我该如何正确处理这个问题?

为了更好地理解我的问题,请查看此图片:https://media-www-asp.azureedge.net/media/44907/dependency-injection-golf.png?raw=true 依赖解析器如何工作以及我可以在ASP.NET Core中将其设置在何处?

在Unity中,可以制作这样的东西     container.RegisterType<IPerson, Male>("Male");     container.RegisterType<IPerson, Female>("Female");

并调用正确的类型

[Dependency("Male")]IPerson malePerson

1 个答案:

答案 0 :(得分:19)

您正在寻找的功能并不容易实现,至少当您在控制器中使用它时,因为控制器被特别处理(默认情况下,控制器未注册ServiceCollection因此没有由容器解析/实例化,而是在请求期间由ASP.NET Core实例化,另请参阅my related answer)上的解释和示例。

使用内置的IoC容器,你只能通过工厂方法来实现,这里有一个BmwCarFactory类的例子:

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

默认IoC容器有意保持简单,以提供依赖注入的基础知识,以帮助您入门,并使其他IoC容器能够轻松插入并替换默认实现。

对于更高级的场景,鼓励用户使用他们选择的IoC,它支持更高级的功能(汇编扫描,装饰器,条件/参数化依赖等等。

AutoFac(我在我的项目中使用)支持此类高级方案。在AutoFac文档中有4个场景(与评论中建议的@pwas的第3个场景一起):

1。重新设计你的课程

需要一些额外的开销来重构代码和类层次结构,但却大大简化了注入服务的消耗

2。更改注册

如果您不愿意或无法更改代码,则文档会将其描述为here

// 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。使用键控服务(here

它与之前的2.方法非常相似,但是基于键而不是具体类型来解析服务

4。使用元数据

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

Unity等其他容器具有特殊属性,例如DependencyAttribute,您可以使用它来注释依赖关系,例如

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

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

或者,您可以创建基于某些约定来解析服务的类和工厂。例如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;
    }
}