ApplicationServices 解析网络核心中不同范围的实例?

Roy*_*mir 5 c# dependency-injection .net-core asp.net-core

我使用 .net core 3.1 进行以下配置:

public interface IFoo
{
    public void Work();
}

public class Foo : IFoo
{
    readonly string MyGuid;
    public Foo()
    {
        MyGuid = Guid.NewGuid().ToString();
    }
    public void Work() { Console.WriteLine(MyGuid); }
}
Run Code Online (Sandbox Code Playgroud)

这是配置:

public void ConfigureServices(IServiceCollection services)
    {
        services.AddScoped<IFoo, Foo>();
        ...
    }

    
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IFoo foo, IServiceProvider myServiceProvider)
    {
        
        Console.WriteLine("Via ApplicationServices");
        app.ApplicationServices.GetService<IFoo>().Work();

        Console.WriteLine("Via IServiceProvider");
        myServiceProvider.GetService<IFoo>().Work();

        Console.WriteLine("Via IFoo injection");
        foo.Work();

    }
Run Code Online (Sandbox Code Playgroud)

结果是:

通过ApplicationServices 27e61428-2adf-4ffa-b27a-485b9c45471d <----不同
通过IServiceProvider c9e86865-2eeb-44db-b625-312f92533beb
通过IFoo注入c9e86865-2eeb-44db-b625-312f92533beb

更多,在控制器操作中,如果我使用IserviceProvider

   [HttpGet]
    public IActionResult Get([FromServices] IServiceProvider serviceProvider)
    {
     serviceProvider.GetService<IFoo>().Work();
     }
Run Code Online (Sandbox Code Playgroud)

我看到另一个不同的Guid 45d95a9d-4169-40a0-9eae-e29e85a3cc19:。

问题:

为什么方法中IServiceProvider的 和 Injection产生相同的 Guid ,而控制器的 action 和产生不同的?IFooConfigureapp.ApplicationServices.GetService

4此示例中有不同的指南

这是一项有范围的服务。它应该是同一个指南。

Nen*_*nad 9

TL;博士;

IFoo foomyServiceProvider.GetService<IFoo>()在两者都传递到方法之前使用来解决Configure。因此,您第二次IFoo从同一个实例解析同一个实例。myServiceProvider

app.ApplicationServices是应用程序的特殊根服务提供者。父母myServiceProvider。因此它解决了不同的问题IFoo

**当您尝试解析作用域服务时,默认行为应该是抛出异常。app.ApplicationServices

更长的解释

如果您有三个虚拟类:

class Singleton { }
class Scoped { }
class Transient { }
Run Code Online (Sandbox Code Playgroud)

然后您将它们注册为IServiceConfiguration

public static void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<Singleton>();
    services.AddScoped<Scoped>();
    services.AddTransient<Transient>();
}
Run Code Online (Sandbox Code Playgroud)

IServiceProvider现在,您在IServiceCollection命令行应用程序中创建“根” ,它看起来像这样:

ServiceCollection sc = new ServiceCollection();
ConfigureServices(sc);
ServiceProvider root = sc.BuildServiceProvider();
Run Code Online (Sandbox Code Playgroud)

根服务提供商行为(相当于app.ApplicationServices):

如果您现在测试root

  1. root.GetService<Singleton>();- 每次调用时都会返回相同的对象实例。
  2. root.GetService<Scoped>();- 每次调用时都会返回相同的对象实例。
  3. root.GetService<Transient>();每次调用都会返回新的对象实例。

儿童范围的服务提供者行为(例如:IServiceProviderConfigure方法中):

如果您现在创建子作用域并使用它自己的IServiceProvider

IServiceScope scope1 = root.CreateScope();
IServiceProvider sp1 = scope1.ServiceProvider;
Run Code Online (Sandbox Code Playgroud)
  1. sp1.GetService<Singleton>();- 每次调用时都会返回相同的对象实例root.GetService<Singleton>();Singleton无论您从哪个范围调用它,都是同一个实例。解决了将范围层次结构爬回到根服务提供者(无范围服务提供者)的问题。
  2. sp1.GetService<Scoped>();- 每次调用时都会返回相同的对象实例,但返回的实例不同root。对象实例缓存在当前范围内。每个作用域都会创建/缓存它自己的作用域实例。
  3. sp1.GetService<Transient>();每次调用它时都会返回新的对象实例,与根的行为相同。

root作用域之所以“特殊”只是因为它没有父作用域,因此从root技术上解析作用域或单例服务会执行相同的操作 - 返回的对象实例缓存在其root自身中。

这也解释了为什么你不能直接解决服务IServiceCollectionIServiceCollection没有范围层次结构和缓存基础设施IServiceProvider。它只包含 的列表ServiceDescriptor。此外,还不清楚服务实例应该缓存在哪个范围内。

ASP.NET 核心

对于 ASP.NET Core 根目录IServiceProviderapp.ApplicationServices. 方法接收从应用程序范围Configure创建的第一个子范围。root对于每个 HTTP 请求,应用程序范围都会创建子范围,用于解析所有服务,并且其本身会注入到该 HTTP 请求的控制器和视图中。它还用于在控制器构造函数或视图中注入所有其他类型。

IFoo解决

因此,您的foofromConfigure方法是使用 解析的myServiceProvider,然后它们都用作 的输入参数Configure。框架做了这样的事情:

ServiceProvider root = sc.BuildServiceProvider(validateScopes: true);
var appScope = root.CreateScope();
IFoo foo = appScope.ServiceProvider.GetService<IFoo>();
ConfigureServices(foo, appScope.ServiceProvider);

Run Code Online (Sandbox Code Playgroud)

当您在方法sp.GetService<IFoo>()内部调用时,它与已经从外部调用的方法Configure相同。创建不同的实例,正如它应该的那样。appScope.ServiceProvider.GetService<IFoo>();root.GetService<IFoo>()IFoo

更多 ASP.NET Core:

为了防止开发人员在尝试解析scoped服务时犯错误app.ApplicationServices,并且没有意识到它是应用程序范围(全局),而不是 HTTP 请求范围,默认情况下,ASP.NET CoreServiceProvider使用BuildServiceProvider重载创建根:

ServiceProvider root = sc.BuildServiceProvider(validateScopes: true);
Run Code Online (Sandbox Code Playgroud)

validateScopes: true 执行检查,验证作用域服务永远不会从根提供者处解析;否则为假。

但是,这可能取决于您使用的兼容模式。我猜这就是它允许您通过 解决范围服务的原因app.ApplicationServices