我目前正在开发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
答案 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个场景一起):
需要一些额外的开销来重构代码和类层次结构,但却大大简化了注入服务的消耗
如果您不愿意或无法更改代码,则文档会将其描述为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();
它与之前的2.方法非常相似,但是基于键而不是具体类型来解析服务
这与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");
}
}
// 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;
}
}