ApplicationServices 解析网络核心中不同范围的实例? [英] ApplicationServices resolves a different scoped instance in net core?

查看:19
本文介绍了ApplicationServices 解析网络核心中不同范围的实例?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用具有此配置的 .net core 3.1 :

公共接口IFoo{公共无效工作();}公共类 Foo : IFoo{只读字符串 MyGuid;公共 Foo(){MyGuid = Guid.NewGuid().ToString();}public void Work() { Console.WriteLine(MyGuid);}}

这是配置:

public void ConfigureServices(IServiceCollection services){services.AddScoped();...}公共无效配置(IApplicationBuilder app,IWebHostEnvironment env,IFoo foo,IServiceProvider myServiceProvider){Console.WriteLine(通过应用服务");app.ApplicationServices.GetService().Work();Console.WriteLine(通过 IServiceProvider");myServiceProvider.GetService().Work();Console.WriteLine(通过 IFoo 注入");foo.Work();}

结果是:

<块引用>

通过 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().Work();}

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

问题:

为什么Configure方法中IServiceProviderIFoo的Injection产生相同的Guid,而controller action和app.ApplicationServices.GetService 产生不同的结果?

本例中有 4 个不同的 guid

这是一项范围的服务.它应该是相同的 Guid.

解决方案

TL;DR;

IFoo foo 使用 myServiceProvider.GetService() 解析,然后两者都被传递到 Configure 方法.因此,您第二次从同一个 myServiceProvider 实例解析同一个 IFoo 实例.

app.ApplicationServices 是应用程序的特殊根服务提供者.myServiceProvider 的父级.因此它解析不同的IFoo.

**app.ApplicationServices 的默认行为应该是在您尝试解析范围服务时抛出异常.

更长的解释

如果你有三个虚拟类:

class Singleton { }类范围{}类瞬态{}

然后在 IServiceConfiguration 中注册它们:

public static void ConfigureServices(IServiceCollection services){services.AddSingleton();services.AddScoped();services.AddTransient();}

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

ServiceCollection sc = new ServiceCollection();配置服务(sc);ServiceProvider root = sc.BuildServiceProvider();

ROOT SERVICE PROVIDER BEHAVIOR(相当于app.ApplicationServices):

如果你现在测试 root:

  1. root.GetService(); - 每次调用都返回相同的对象实例.
  2. root.GetService(); - 每次调用都返回相同的对象实例.
  3. root.GetService(); 每次调用都会返回新的对象实例.

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

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

IServiceScope scope1 = root.CreateScope();IServiceProvider sp1 = scope1.ServiceProvider;

  1. sp1.GetService(); - 每次调用它都会返回与root.GetService(); 返回的相同对象实例.Singleton 无论从哪个范围调用它都是相同的实例.解决了将范围的层次结构爬回到根服务提供者(无范围的服务提供者)的问题.
  2. sp1.GetService(); - 每次调用它都返回相同的对象实例,但与 root 返回的实例不同.对象实例缓存在当前范围内.每个作用域都会创建/缓存它自己的作用域实例.
  3. sp1.GetService(); 每次调用它都会返回新的对象实例,与根的行为相同.

root 范围是特殊的";只是因为它没有父作用域,所以从 root 解析作用域或单例服务在技术上做同样的事情 - 返回的对象实例缓存在 root 本身中.

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

ASP.NET Core

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

IFoo 解析

因此,Configure 方法中的 foo 使用 myServiceProvider 解析,然后它们都用作 Configure<的输入参数/代码>.框架做这样的事情:

ServiceProvider root = sc.BuildServiceProvider(validateScopes: true);var appScope = root.CreateScope();IFoo foo = appScope.ServiceProvider.GetService();配置服务(foo, appScope.ServiceProvider);

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

更多 ASP.NET Core:

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

ServiceProvider root = sc.BuildServiceProvider(validateScopes: true);

<块引用>

validateScopes: true 执行检查验证范围服务永远不会从根提供程序解析;否则为假.

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

I'm using .net core 3.1 with this configuration :

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); }
}

And this is the configuration :

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();

    }

Result are :

Via ApplicationServices 27e61428-2adf-4ffa-b27a-485b9c45471d <----different
Via IServiceProvider c9e86865-2eeb-44db-b625-312f92533beb
Via IFoo injection c9e86865-2eeb-44db-b625-312f92533beb

More , in the controller action , If I use IserviceProvider :

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

I see another different Guid : 45d95a9d-4169-40a0-9eae-e29e85a3cc19.

Question:

Why does the IServiceProvider and Injection of IFoo in the Configure method yield the same Guid , while the controller action and app.ApplicationServices.GetService yield different ones?

There are 4 different guids in this example

It's a scoped service. It supposed to be the same Guid.

解决方案

TL;DR;

IFoo foo is resolved using myServiceProvider.GetService<IFoo>() before both get passed into Configure method. So you're resolving same IFoo instance from the same myServiceProvider instance for the 2nd time.

app.ApplicationServices is special root service provider of the application. Parent of myServiceProvider. Thus it resolves different IFoo.

**Default behavior of app.ApplicationServices should be to throw exception when you try to resolve scoped service.

LONGER EXPLANATION

If you have three dummy classes:

class Singleton { }
class Scoped { }
class Transient { }

And you register them as such in IServiceConfiguration:

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

Now, you create your "root" IServiceProvider from IServiceCollection - in command-line app, it would look like this:

ServiceCollection sc = new ServiceCollection();
ConfigureServices(sc);
ServiceProvider root = sc.BuildServiceProvider();

ROOT SERVICE PROVIDER BEHAVIOR (equivalent to app.ApplicationServices):

If you now test root:

  1. root.GetService<Singleton>(); - every time it's called returns same object instance.
  2. root.GetService<Scoped>(); - every time it's called returns same object instance.
  3. root.GetService<Transient>(); every time it's called returns new object instance.

CHILD-SCOPE SERVICE PROVIDER BEHAVIOR (eg: IServiceProvider in Configure method):

If you now create child scope and use it's own IServiceProvider:

IServiceScope scope1 = root.CreateScope();
IServiceProvider sp1 = scope1.ServiceProvider;

  1. sp1.GetService<Singleton>(); - every time it's called returns same object instance whichroot.GetService<Singleton>(); returns. Singleton is the same instance no matter from which scope you call it. It is resolved climbing the hierarchy of scopes back to the root service provider (scopeless one).
  2. sp1.GetService<Scoped>(); - every time it's called returns same object instance, but not the same instance that root returns. Object instance is cached on the current scope. Every scope creates/caches it's own scoped instance.
  3. sp1.GetService<Transient>(); every time it's called returns new object instance, same behavior like for the root.

root scope is "special" only because it has no parent scope, so resolving scoped or singleton service from the root technically does the same thing - object instance returned is cached in the root itself.

This also explains why you cannot resolve service from IServiceCollection directly. IServiceCollection does not have hierarchy of scopes and caching infrastructure which IServiceProvider has. It just contains list of ServiceDescriptor. In addition, it would be unclear in which scope service instance should be cached.

ASP.NET Core

For ASP.NET Core root IServiceProvider is app.ApplicationServices. Configure method receives first child-scope created from the root - application-scope. For every HTTP request application-scope creates child-scope which is used to resolve all services and is itself injected in controllers and views of that HTTP request. It is also used to inject all other types in a controller constructor or a view.

IFoo resolution

So, your foo from Configure method is resolved using myServiceProvider, then they both get used as input parameters for Configure. Framework does something like this:

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

When you call sp.GetService<IFoo>() inside of Configure method it is identical to appScope.ServiceProvider.GetService<IFoo>(); that was already called from the outside. root.GetService<IFoo>() creates different IFoo instance, as it should.

More ASP.NET Core:

To prevent developers making mistake trying to resolve scoped service from the app.ApplicationServices and not realizing that it is application scoped (global), instead of being scoped to HTTP request, by default, ASP.NET Core creates root ServiceProvider using BuildServiceProvider overload:

ServiceProvider root = sc.BuildServiceProvider(validateScopes: true);

validateScopes: true to perform check verifying that scoped services never gets resolved from root provider; otherwise false.

However, this might depend on compatibility mode you're using. I'm guessing that's the reason why it allows you to resolve scoped service via app.ApplicationServices.

这篇关于ApplicationServices 解析网络核心中不同范围的实例?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆