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

DrS*_*one 13 c# dependency-injection asp.net-core-mvc asp.net-core

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

好吧,我开始使用界面:

ICar
{
    string Drive();
}
Run Code Online (Sandbox Code Playgroud)

并希望ICar多次实现这个界面

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

public class Jaguar : ICar
{
    public string Drive(){...};
}
Run Code Online (Sandbox Code Playgroud)

并在Startup课程中添加以下内容

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

现在我必须在两个实现之间做出决定,我决定的类将在每个需要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
Run Code Online (Sandbox Code Playgroud)

Tse*_*eng 19

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

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

services.AddScoped<ICar, BmwCar>();
services.AddScoped<BmwCar>();
services.AddScoped<BmwCarFactory>(p => new BmwCarFactory(p.GetRequiredService<BmwCar>())));
Run Code Online (Sandbox Code Playgroud)

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

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

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

1.重新设计你的课程

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

2.更改注册

如果您不愿意或无法更改代码,则文档会在此处对其进行描述.

// 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();
Run Code Online (Sandbox Code Playgroud)

3.使用键控服务(这里)

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

4.使用元数据

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

Unity这样的其他容器具有特殊属性,DependencyAttribute您可以使用它们来注释依赖项,例如

public class BmwController : Controller
{
    public BmwController([Dependency("Bmw")ICar car)
    {
    }
}
Run Code Online (Sandbox Code Playgroud)

但是这个和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;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后就像使用它一样

public class BmwController : Controller
{
    public ICarFactory carFactory;

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

        // Get the car
        ICar bmw = carFactory.Create("Bmw");
    }
}
Run Code Online (Sandbox Code Playgroud)

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 还使用`IEnumerable <ICar>`添加了@ JoeAudette评论的替代方法,以避免对容器的依赖 (2认同)