为什么会选择第三方DI容器而不是内置的ASP.NET Core DI容器?

61

目前关于依赖注入的文档缺乏 - 依赖注入。使用内置DI与现有解决方案(如Ninject,Autofac,StructureMap)相比的优缺点是什么?默认的依赖注入当前存在哪些限制(如果有)?


1
目前是否有基于dotnetcore的标准DI容器(如ninject等)的实现? - Abhijeet Patel
到目前为止,大多数(如果不是全部)DI容器都与.NET Core和.NET Standard兼容。 - Steven
4个回答

64
任何具有合理规模的应用程序的产品开发,如果采用松耦合和遵循SOLID原则的方法,则.NET Core的DI容器(MS.DI)不适用,因为:
  • 这不利于验证您的配置,使得诊断由常见的配置错误引起的问题变得非常困难。在一个相当大的应用程序中,自己发现这些错误实际上是相当困难的。 更新:MS.DI的第3版现在包含一个名为ValidateOnBuild的功能,但它唯一的作用是检查是否可以满足所有注册的所有依赖项。
  • 无法以可维护的方式使用拦截器或装饰器应用横切关注点。这使得维护任何相当大的应用程序更加昂贵。 更新:有几个第三方库试图填补这个空白,但由于MS.DI的限制,它们无法完全填补这个空白(例如装饰开放式通用注册、确保装饰实例的处理等)。
  • 虽然MS.DI支持将开放式泛型抽象映射到开放式泛型实现,但其实现方式相当naive,无法处理带类型约束、更复杂的泛型类型映射和协变性的泛型类型。
  • 无法使用自动连接按条件/上下文进行注册,使得注册仅被注入到某个特定的使用者集合中。例如,当有两个组件Service1Service2都依赖于ILogger时,您可能希望将Service1注入为NullLogger,将Service2注入为FileLogger,或者您希望将Service1注入为Logger < Service1 >,将Service2注入为Logger < Service2 >
这些限制存在的主要原因是因为内置容器的目标是提供依赖注入功能,特别是为框架本身提供依赖注入能力,同时保持其功能集最小化,希望更成熟的依赖注入容器能够与之集成。换句话说,它试图充当最小公共分母(LCD)。由于其LCD功能,它永远无法成长为一个对应用程序开发实用的完整依赖注入容器(除非打破成为LCD的承诺)。
如果您开始一个新的简单项目,我的建议是应用Pure DI。这意味着您在组合根内手动连接组件,不使用容器,也不要创建您自己的DI容器。相反,您通过插入您自定义的IControllerActivator来解决您的类型。稍后,当自动装配、自动注册和拦截等特性可以提高组合根的可维护性时,请切换到适合您要求的已建立的DI库。

3
在ASP.NET Core 2.0中仍然有效吗? - Legends
4
没问题,这个答案在使用 .NET Core 2.0 时仍然适用。 - Steven
1
@Steven:.net core 2.2和3-preview有任何改进吗? - leox
1
@leox 不是的!而且你不必期望在这个领域有任何进展。微软已经把自己限制在了容器中。他们的 DI 容器永远不会发展成为一个功能丰富的 DI 容器。 - Steven
3
没问题。对于 .NET 5.0,情况仍然如此。 - Steven
显示剩余6条评论

23

这里解释一下:

  • Transient - 每次都创建新实例
  • Scoped - 在当前作用域内创建单个实例,相当于当前作用域中的 Singleton
  • Singleton - 只创建一个实例并且它的行为像单例模式
  • Instance - 指定特定实例始终可用,你需要负责其初始创建

Alpha 版本有以下限制:

  • 仅支持构造函数注入
  • 只能解析具有一个且仅有一个公共构造函数的类型
  • 不支持高级功能(如每个线程范围或自动发现)

如果你没有写非常复杂的产品,那么默认的 DI 容器就足够了。在其他情况下,你可以尝试已经提到的具有高级功能的库。

我的建议是从默认容器开始,并在(如果)遇到无法处理的情况时更改实现方式。


1
Alpha版本已不再相关。 - NucS

7

Additionally, can someone help me to understand what is the difference between these registrations?

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<IService, Service>();
    services.AddScoped<IService, Service>();
    services.AddSingleton<IService, Service>();
    services.AddInstance(service);
}

https://stackoverflow.com/revisions/30681477/8

  • Transient - 每次检索时都会实例化
  • Scoped - 每个http请求实例化一次,并在整个http请求的生命周期内可用
  • Singleton - 仅实例化一次,并在应用程序的整个生命周期内可用
  • Instance - 等同于单例模式,但您提供对象实例而不是框架创建实例

来源: http://www.khalidabuhakmeh.com/asp-vnext-dependency-injection-lifecycles, http://dotnetliberty.com/index.php/2015/10/15/asp-net-5-mvc6-dependency-injection-in-6-steps/

为了更好地解释为什么它被称为作用域,考虑以下代码:

// app startup
var services = new ServiceCollection();
services.AddScoped<IService, Service>();
var startupServices = services.BuildServiceProvider();
// request init
var requestServices = statupServices.CreateScope().ServiceProvider;

在 ASP.NET 中,HttpContext.RequestServices 的创建方式与上述类似,其中在请求开始时调用 CreateScope。但是它也可以在 ASP.NET 之外的上下文中使用,在这种情况下,范围的含义可能与每个请求不同。

6
回答你的第一个问题:看起来ASP.NET文档已经更新,现在明确说明了每种注册类型的含义。
ASP.NET服务可以配置以下生命周期:
瞬态(Transient):每次请求时都会创建一个新的服务实例。这种生命周期最适合轻量级、无状态的服务。
作用域(Scoped):每个请求只会创建一次服务实例。
单例(Singleton):第一次请求时会创建一个服务实例,随后的每个请求都将使用同一个实例。如果您的应用程序需要单例行为,则建议允许服务容器管理服务的生命周期,而不是实现单例设计模式并自己管理对象的生命周期。
实例(仅限 RTM 前):您可以选择直接向服务容器添加一个实例。如果这样做,所有后续请求都将使用该实例(此技术将创建一个 Singleton 作用域的实例)。Instance 服务和 Singleton 服务之间的一个关键区别是,Instance 服务是在 ConfigureServices 中创建的,而 Singleton 服务是在首次请求时进行惰性加载的。

RTM更新

请注意,在Asp.Net Core RTM文档中,Instance被移除了。Instance基本上与Singleton相同,但它们具有不同的初始化语义(Singleton是惰性加载)。但现在没有AddInstance API,只有AddSignleton可以接受已创建的实例。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接