问题描述
我目前正在处理一个 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
接口多次
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?
为了更好地理解我的问题,看看这张照片:https://media-www-asp.azureedge.net/media/44907/dependency-injection-golf.png?raw=true依赖解析器是如何工作的,我可以在 ASP.NET Core 的什么位置进行设置?
For better understanding my problem take a look to this pic: https://media-www-asp.azureedge.net/media/44907/dependency-injection-golf.png?raw=trueHow does the dependency resolver work and where can i set it up in ASP.NET Core?
在 Unity 中可以制作这样的东西container.RegisterType<IPerson, Male>("Male");
container.RegisterType("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 种情况(连同@pwas 在评论中建议的第 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):
##1.重新设计你的课程重构代码和类层次结构需要一些额外开销,但大大简化了注入服务的消耗
##1. Redesign your classesNeeds some additional overhead of refactoring your code and class hierarchy but heavily simplifies the consumption of injected services
##2.更改注册文档描述它 如果您不愿意或无法更改代码,请在此处.
##2. Change the registrationsThe 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
##4.使用元数据这与 3 非常相似.但是您通过属性定义键.
##4. Use MetadataThis 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 的替代方案
##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;
}
}
这篇关于使用依赖注入注入多个实现的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!