使用附加服务创建子范围

t3c*_*b0t 9 c# dependency-injection .net-6.0

使用 Autofac,创建容器的子范围并注册附加服务非常容易。我如何通过 .net 的依赖注入实现相同的目标?

IServiceProvider尝试注入到需要创建子容器的类中,仅提供了CreateScope()创建IServiceScopeagian 的方法,该方法仅具有该ServiceProvider属性,而无法注册其他服务。我还应该注入其他什么东西来允许我向容器注册更多服务吗?

Enr*_*one 12

我不知道 Autofac 是如何工作的,但我可以向你解释 Microsoft DI 是如何工作的。

首先,Microsoft DI 容器的设计使您拥有两个主要抽象:

  • IServiceCollection:这是您用来在应用程序中注册服务的对象。将此视为实际 DI 容器的构建器对象。
  • ServiceProvider:这是实际的 DI 容器,您可以IServiceCollection通过调用IServiceCollection.BuildServiceProvider扩展方法从对象中获取它。该对象的行为由接口描述IServiceProviderServiceProvider类实现IServiceProvider接口)

因此,使用 DI 容器是一个两步操作:首先,您需要一个IServiceCollection对象,以便您可以通过指定实现类型和生命周期(瞬态、作用域或单例)来注册服务,然后您可以构建一个对象ServiceProvider并使用它来解决您的应用程序中的服务。接口实际使用的具体类型IServiceCollectionServiceCollection类。

当您构建服务集合以获取实例时ServiceProvider,您实际上获得了应用程序的根容器。它被称为根容器,因为您可以创建一个服务提供者的层次结构,其根位于应用程序的根容器中。

给定应用程序的根容器,为了创建子容器,您需要创建一个作用域,它基本上是用于解析服务的作用域。每个范围都有自己的容器,它是一个实现IServiceProvider接口的对象,您可以使用该接口来解析该范围内的服务。

这是执行此操作的一般模式:

// create the IServiceCollection instance
var services = new ServiceCollection();

// register services on the IServiceCollection instance
services.AddSingleton<IFooService, FooService>();
services.AddScoped<IBarService, BarService>();
services.AddTransient<IBuzzService, BuzzService>();

// create the root container
using var rootContaier = services.BuildServiceProvider();

// create a scope
using var scope = rootContainer.CreateScope();

// gets a reference to the container for the scope
var scopeContainer = scope.ServiceProvider;

// use the scope container to resolve services
var fooService = scopeContainer.GetRequiredService<IFooService>();
var barService = scopeContainer.GetRequiredService<IBarService>();
var buzzService = scopeContainer.GetRequiredService<IBuzzService>();

// do whatever you want with the resolved services
fooService.Foo();
barService.Bar();
buzzService.Buzz();
Run Code Online (Sandbox Code Playgroud)

这些依赖解析范围非常重要,因为它们定义了使用范围服务提供者解析的服务的生命周期。这些是规则:

  1. 使用单例生命周期注册的服务始终由应用程序根容器解析。即使您使用子容器来解析单例服务,实际的服务解析也会委托给根容器。实现该IDisposable接口的单例服务在根容器被释放时被释放,这通常发生在应用程序关闭时。每个单例服务实际上从根容器解析一次,并且在应用程序的整个生命周期中重复使用同一个实例。
  2. 使用瞬态生命周期注册的服务基本上与单例服务相反。每次解决服务时都会创建一个全新的实例。给定一个范围,每次使用范围服务提供者来解析瞬态服务时,范围服务提供者都会创建并跟踪实施类型的全新实例。当范围被处置时,从该范围解析并具有一次性实施类型的所有瞬态服务也将被处置
  3. 注册到作用域生命周期的服务对于它们创建的作用域来说就像单例一样。如果您有范围并使用范围服务提供程序来解析范围服务,则仅在第一次解析服务时才会创建实施类型的全新实例。解析同一范围内的同一服务的所有后续请求都将通过重用第一次在该范围内解析该服务时创建的实现类型的实例来实现。作用域服务由作用域跟踪:这很有用,因为实现类型为一次性的作用域服务将在作用域被释放时被释放(与瞬态服务的行为相同)。

从前面的规则中,您可以得出第四条规则:永远不要使用根容器来解析服务,始终创建一个作用域并从该作用域解析服务(记住在完成后处置该作用域)。

您应该这样做,因为作用域和瞬态服务由作用域跟踪并在作用域释放时释放。如果您尝试从根容器解析瞬态和作用域服务,它们将由根容器跟踪,并且仅在根容器被处置时才会被处置:这通常发生在应用程序关闭时,因为根容器的生命周期是基本上是应用程序的生命周期。换句话说,如果不遵守第四条规则,就会造成内存泄漏。

请注意,如果您使用采用布尔参数的重载IServiceCollection.BuildServiceProvider,您可以要求服务集合构建一个服务提供程序,该服务提供程序实际上检查范围服务是否从未从根容器解析。这样,您将获得对第四条规则的部分检查(仅检查范围服务是否来自根容器的危险解决方案)。

回到你的问题,服务注册阶段都是在同一个IServiceCollection实例上完成的,并且不知道服务解析范围的概念。范围仅在服务解析的后期有用,用于定义已解析服务的生命周期,如上所述。

一旦您从服务集合实例构建了根容器,服务注册阶段就完成了,根据我的了解,您只能解析服务,不允许进行额外的注册。