ApplicationServices 在 net core 中解析不同范围的实例?
ApplicationServices resolves a different scoped instance in 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); }
}
这是配置:
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();
}
结果是:
Via ApplicationServices 27e61428-2adf-4ffa-b27a-485b9c45471d <----different
Via IServiceProvider c9e86865-2eeb-44db-b625-312f92533beb
Via IFoo injection c9e86865-2eeb-44db-b625-312f92533beb
更多,在控制器操作中,如果我使用 IserviceProvider
:
[HttpGet]
public IActionResult Get([FromServices] IServiceProvider serviceProvider)
{
serviceProvider.GetService<IFoo>().Work();
}
我看到 另一个不同的 Guid : 45d95a9d-4169-40a0-9eae-e29e85a3cc19
.
问题:
为什么 IServiceProvider
和 IFoo
的注入在 Configure
方法中产生相同的 Guid ,而控制器操作和 app.ApplicationServices.GetService
产生不同的?
此示例中有 4
个不同的 guid
这是一个范围服务。它应该是同一个 Guid。
更新答案:
为了能够为您提供相同的实例,DI 引擎需要知道当前范围。
Startup
class 中的 Configure
方法最初由 ASP.NET 核心引擎调用,而不是由用户的请求调用,这是范围服务的方式是用来使用的。
现在,如果您改用这个示例:
IServiceScope scope = app.ApplicationServices.CreateScope();
scope.ServiceProvider.GetService<IFoo>().Work();
只要您从同一个 IServiceScope
实例使用同一个 ServiceProvider
,您就可以访问同一个 IFoo
对象并获得相同的 GUID。
旧答案:
作用域服务意味着存在时间很短(作用域),并在需要时被删除。
来自Dependency injection in .NET
a scoped lifetime indicates that services are created once per client request (connection)
此外,
Do not resolve a scoped service from a singleton and be careful not to do so indirectly, for example, through a transient service.
app.ApplicationServices.GetService
将尝试为您提供一个不是该服务注册的单例。
您看到的是预期的行为。如果您需要该服务在应用程序的整个生命周期内都具有相同的实例,则必须将其注册为单例。
阅读有关 the singleton lifetime 的更多信息,如果您在任何地方都需要相同的值,请将您的代码从 services.AddScoped<IFoo, Foo>()
更改为 services.AddSingleton<IFoo, Foo>()
。
创建 IApplicationBuilder“app”并传递 到 Configure 方法。
IApplicationBuilder is available to the Configure method, but it isn't registered in the service container. Hosting creates an IApplicationBuilder and passes it directly to Configure.
另一方面,IServiceProvider“myServiceProvider”实际上是 activated/created 和 注入:
Services can be injected into the Startup constructor and the Startup.Configure method.
Any service registered with the DI container can be injected into the Startup.Configure method
所以我们在两个不同的范围内,所以 guid 正如预期的那样不同。
通过 ApplicationServices 27e61428-2adf-4ffa-b27a-485b9c45471d <---- 在范围“A”中创建
通过 IServiceProvider c9e86865-2eeb-44db-b625-312f92533beb <-- 在范围“B”中创建
通过 IFoo 注入 c9e86865-2eeb-44db-b625-312f92533beb < -- 在范围“B”中创建
当您在控制器中时,每个请求都将具有全新的范围,与上面讨论的范围不同:
https://docs.microsoft.com/en-us/dotnet/core/extensions/dependency-injection#scoped
在我看来,您希望控制器中的 guid 也相同,您应该知道,对于每个请求,都会创建一个控制器实例,并且 DI 链会在每个请求的新范围内启动请求。
TL;DR;
IFoo foo
在传递到 Configure
方法之前使用 myServiceProvider.GetService<IFoo>()
解析。因此,您第二次从同一个 myServiceProvider
实例解析同一个 IFoo
实例。
app.ApplicationServices
是应用程序的特殊根服务提供者。 myServiceProvider
的父级。因此它解决了不同的 IFoo
.
**app.ApplicationServices
的默认行为应该是在您尝试解析作用域服务时抛出异常。
更长的解释
如果你有三个假人类:
class Singleton { }
class Scoped { }
class Transient { }
然后您在 IServiceConfiguration
中注册它们:
public static void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<Singleton>();
services.AddScoped<Scoped>();
services.AddTransient<Transient>();
}
现在,您从 IServiceCollection
创建您的“root”IServiceProvider
- 在命令行应用程序中,它看起来像这样:
ServiceCollection sc = new ServiceCollection();
ConfigureServices(sc);
ServiceProvider root = sc.BuildServiceProvider();
根服务提供商行为(相当于app.ApplicationServices
):
如果你现在测试root
:
root.GetService<Singleton>();
- 每次调用 returns 同一个对象实例。
root.GetService<Scoped>();
- 每次调用 returns 同一个对象实例。
root.GetService<Transient>();
每次调用returns新建对象实例。
子范围服务提供商行为(例如:Configure
方法中的IServiceProvider
):
如果您现在创建子范围并使用它自己的 IServiceProvider
:
IServiceScope scope1 = root.CreateScope();
IServiceProvider sp1 = scope1.ServiceProvider;
sp1.GetService<Singleton>();
- 每次调用 returns 相同的对象实例 whichroot.GetService<Singleton>();
returns。 Singleton
是同一个实例,无论您从哪个范围调用它。已解决将范围的层次结构爬回到根服务提供者(无范围的服务提供者)。
sp1.GetService<Scoped>();
- 每次调用 returns 相同的对象实例,但与 root
returns 不同的实例。对象实例缓存在当前范围内。每个范围 creates/caches 都是自己的范围实例。
sp1.GetService<Transient>();
每次调用 returns 新对象实例,与根相同的行为。
root
作用域是“特殊的”只是因为它没有父作用域,所以从 root
解析作用域或单例服务在技术上做同样的事情 - 返回的对象实例缓存在 root
本身。
这也解释了为什么您不能直接从 IServiceCollection
解析服务。 IServiceCollection
没有 IServiceProvider
具有的范围层次结构和缓存基础结构。它只包含 ServiceDescriptor
的列表。此外,不清楚应该在哪个范围内缓存服务实例。
ASP.NET核心
对于ASP.NET核心根IServiceProvider
是app.ApplicationServices
。 Configure
方法接收从 root
- 应用程序范围创建的第一个子范围。对于每个 HTTP 请求,application-scope 创建子范围,用于解析所有服务,并且它本身被注入到该 HTTP 请求的控制器和视图中。它还用于在控制器构造函数或视图中注入所有其他类型。
IFoo
分辨率
因此,Configure
方法中的 foo
使用 myServiceProvider
解析,然后它们都用作 Configure
的输入参数。框架做这样的事情:
ServiceProvider root = sc.BuildServiceProvider(validateScopes: true);
var appScope = root.CreateScope();
IFoo foo = appScope.ServiceProvider.GetService<IFoo>();
ConfigureServices(foo, appScope.ServiceProvider);
当您在 Configure
方法内部调用 sp.GetService<IFoo>()
时,它与已经从外部调用的 appScope.ServiceProvider.GetService<IFoo>();
相同。 root.GetService<IFoo>()
创建不同的 IFoo
实例,这是应该的。
更多ASP.NET核心:
为了防止开发人员在尝试从 app.ApplicationServices
解析 scoped
服务时犯错误,但没有意识到它是应用程序范围(全局),而不是 HTTP 请求范围,默认情况下,ASP.NET 核心创建根 ServiceProvider
使用 BuildServiceProvider
重载:
ServiceProvider root = sc.BuildServiceProvider(validateScopes: true);
validateScopes: true to perform check verifying that scoped services never gets resolved from root provider; otherwise false.
但是,这可能取决于您使用的兼容模式。我猜这就是为什么它允许您通过 app.ApplicationServices
.
解析作用域服务的原因
我正在使用具有此配置的 .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); }
}
这是配置:
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();
}
结果是:
Via ApplicationServices 27e61428-2adf-4ffa-b27a-485b9c45471d <----different
Via IServiceProvider c9e86865-2eeb-44db-b625-312f92533beb
Via IFoo injection c9e86865-2eeb-44db-b625-312f92533beb
更多,在控制器操作中,如果我使用 IserviceProvider
:
[HttpGet]
public IActionResult Get([FromServices] IServiceProvider serviceProvider)
{
serviceProvider.GetService<IFoo>().Work();
}
我看到 另一个不同的 Guid : 45d95a9d-4169-40a0-9eae-e29e85a3cc19
.
问题:
为什么 IServiceProvider
和 IFoo
的注入在 Configure
方法中产生相同的 Guid ,而控制器操作和 app.ApplicationServices.GetService
产生不同的?
此示例中有 4
个不同的 guid
这是一个范围服务。它应该是同一个 Guid。
更新答案: 为了能够为您提供相同的实例,DI 引擎需要知道当前范围。
Startup
class 中的 Configure
方法最初由 ASP.NET 核心引擎调用,而不是由用户的请求调用,这是范围服务的方式是用来使用的。
现在,如果您改用这个示例:
IServiceScope scope = app.ApplicationServices.CreateScope();
scope.ServiceProvider.GetService<IFoo>().Work();
只要您从同一个 IServiceScope
实例使用同一个 ServiceProvider
,您就可以访问同一个 IFoo
对象并获得相同的 GUID。
旧答案: 作用域服务意味着存在时间很短(作用域),并在需要时被删除。
来自Dependency injection in .NET
a scoped lifetime indicates that services are created once per client request (connection)
此外,
Do not resolve a scoped service from a singleton and be careful not to do so indirectly, for example, through a transient service.
app.ApplicationServices.GetService
将尝试为您提供一个不是该服务注册的单例。
您看到的是预期的行为。如果您需要该服务在应用程序的整个生命周期内都具有相同的实例,则必须将其注册为单例。
阅读有关 the singleton lifetime 的更多信息,如果您在任何地方都需要相同的值,请将您的代码从 services.AddScoped<IFoo, Foo>()
更改为 services.AddSingleton<IFoo, Foo>()
。
创建 IApplicationBuilder“app”并传递 到 Configure 方法。
IApplicationBuilder is available to the Configure method, but it isn't registered in the service container. Hosting creates an IApplicationBuilder and passes it directly to Configure.
另一方面,IServiceProvider“myServiceProvider”实际上是 activated/created 和 注入:
Services can be injected into the Startup constructor and the Startup.Configure method.
Any service registered with the DI container can be injected into the Startup.Configure method
所以我们在两个不同的范围内,所以 guid 正如预期的那样不同。
通过 ApplicationServices 27e61428-2adf-4ffa-b27a-485b9c45471d <---- 在范围“A”中创建
通过 IServiceProvider c9e86865-2eeb-44db-b625-312f92533beb <-- 在范围“B”中创建
通过 IFoo 注入 c9e86865-2eeb-44db-b625-312f92533beb < -- 在范围“B”中创建
当您在控制器中时,每个请求都将具有全新的范围,与上面讨论的范围不同:
https://docs.microsoft.com/en-us/dotnet/core/extensions/dependency-injection#scoped
在我看来,您希望控制器中的 guid 也相同,您应该知道,对于每个请求,都会创建一个控制器实例,并且 DI 链会在每个请求的新范围内启动请求。
TL;DR;
IFoo foo
在传递到 Configure
方法之前使用 myServiceProvider.GetService<IFoo>()
解析。因此,您第二次从同一个 myServiceProvider
实例解析同一个 IFoo
实例。
app.ApplicationServices
是应用程序的特殊根服务提供者。 myServiceProvider
的父级。因此它解决了不同的 IFoo
.
**app.ApplicationServices
的默认行为应该是在您尝试解析作用域服务时抛出异常。
更长的解释
如果你有三个假人类:
class Singleton { }
class Scoped { }
class Transient { }
然后您在 IServiceConfiguration
中注册它们:
public static void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<Singleton>();
services.AddScoped<Scoped>();
services.AddTransient<Transient>();
}
现在,您从 IServiceCollection
创建您的“root”IServiceProvider
- 在命令行应用程序中,它看起来像这样:
ServiceCollection sc = new ServiceCollection();
ConfigureServices(sc);
ServiceProvider root = sc.BuildServiceProvider();
根服务提供商行为(相当于app.ApplicationServices
):
如果你现在测试root
:
root.GetService<Singleton>();
- 每次调用 returns 同一个对象实例。root.GetService<Scoped>();
- 每次调用 returns 同一个对象实例。root.GetService<Transient>();
每次调用returns新建对象实例。
子范围服务提供商行为(例如:Configure
方法中的IServiceProvider
):
如果您现在创建子范围并使用它自己的 IServiceProvider
:
IServiceScope scope1 = root.CreateScope();
IServiceProvider sp1 = scope1.ServiceProvider;
sp1.GetService<Singleton>();
- 每次调用 returns 相同的对象实例 whichroot.GetService<Singleton>();
returns。Singleton
是同一个实例,无论您从哪个范围调用它。已解决将范围的层次结构爬回到根服务提供者(无范围的服务提供者)。sp1.GetService<Scoped>();
- 每次调用 returns 相同的对象实例,但与root
returns 不同的实例。对象实例缓存在当前范围内。每个范围 creates/caches 都是自己的范围实例。sp1.GetService<Transient>();
每次调用 returns 新对象实例,与根相同的行为。
root
作用域是“特殊的”只是因为它没有父作用域,所以从 root
解析作用域或单例服务在技术上做同样的事情 - 返回的对象实例缓存在 root
本身。
这也解释了为什么您不能直接从 IServiceCollection
解析服务。 IServiceCollection
没有 IServiceProvider
具有的范围层次结构和缓存基础结构。它只包含 ServiceDescriptor
的列表。此外,不清楚应该在哪个范围内缓存服务实例。
ASP.NET核心
对于ASP.NET核心根IServiceProvider
是app.ApplicationServices
。 Configure
方法接收从 root
- 应用程序范围创建的第一个子范围。对于每个 HTTP 请求,application-scope 创建子范围,用于解析所有服务,并且它本身被注入到该 HTTP 请求的控制器和视图中。它还用于在控制器构造函数或视图中注入所有其他类型。
IFoo
分辨率
因此,Configure
方法中的 foo
使用 myServiceProvider
解析,然后它们都用作 Configure
的输入参数。框架做这样的事情:
ServiceProvider root = sc.BuildServiceProvider(validateScopes: true);
var appScope = root.CreateScope();
IFoo foo = appScope.ServiceProvider.GetService<IFoo>();
ConfigureServices(foo, appScope.ServiceProvider);
当您在 Configure
方法内部调用 sp.GetService<IFoo>()
时,它与已经从外部调用的 appScope.ServiceProvider.GetService<IFoo>();
相同。 root.GetService<IFoo>()
创建不同的 IFoo
实例,这是应该的。
更多ASP.NET核心:
为了防止开发人员在尝试从 app.ApplicationServices
解析 scoped
服务时犯错误,但没有意识到它是应用程序范围(全局),而不是 HTTP 请求范围,默认情况下,ASP.NET 核心创建根 ServiceProvider
使用 BuildServiceProvider
重载:
ServiceProvider root = sc.BuildServiceProvider(validateScopes: true);
validateScopes: true to perform check verifying that scoped services never gets resolved from root provider; otherwise false.
但是,这可能取决于您使用的兼容模式。我猜这就是为什么它允许您通过 app.ApplicationServices
.