为什么Scoped服务解析为同一请求的两个不同实例?

我有一个包含List的简单服务。 在Startup.cs中,我使用的是services.addScoped()方法。

我在两个不同的地方(控制器和中间件)注入服务实例,对于单个请求,我希望获得相同的实例。 但是,这似乎并没有发生。

即使我在Controller Action中向List添加Foo,中间件中的Foo列表也始终为空。 为什么是这样?

我已经尝试使用AddSingleton()将服务注册更改为单例,并且它按预期工作。 但是,这必须限制在当前请求范围内。 非常感谢任何帮助或想法!

FooService.cs

 public class FooService { public List Foos = new List(); } 

Startup.cs

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

[以下是我注入服务的两个地方,导致两个不同的实例]

MyController.cs

 public class MyController : Controller { public MyController(FooService fooService) { this.fooService = fooService; } [HttpPost] public void TestAddFoo() { //add foo to List this.fooService.Foos.Add(new Foo()); } } 

FooMiddleware.cs

 public AppMessageMiddleware(RequestDelegate next, IServiceProvider serviceProvider) { this.next = next; this.serviceProvider = serviceProvider; } public async Task Invoke(HttpContext context) { context.Response.OnStarting(() => { var fooService = this.serviceProvider.GetService(typeof(FooService)) as FooService; var fooCount = fooService.Foos.Count; // always equals zero return Task.CompletedTask; }); await this.next(context); } 

那是因为当你将IServiceProvider注入你的中间件时 – 那是“全局”提供者,而不是请求范围。 调用中间件构造函数时没有请求(中间件在启动时创建一次),因此它不能是请求范围的容器。

请求启动时,将创建新的DI范围,并且与此范围相关的IServiceProvider用于解析服务,包括将服务注入控制器。 所以你的控制器从请求范围解析FooService (因为注入构造函数),但你的中间件从“父”服务提供者(根范围)解析它,所以它是不同的。 解决此问题的一种方法是使用HttpContext.RequestServices

 public async Task Invoke(HttpContext context) { context.Response.OnStarting(() => { var fooService = context.RequestServices.GetService(typeof(FooService)) as FooService; var fooCount = fooService.Foos.Count; // always equals zero return Task.CompletedTask; }); await this.next(context); } 

但更好的方法是将它注入Invoke方法本身,然后它将是请求作用域:

 public async Task Invoke(HttpContext context, FooService fooService) { context.Response.OnStarting(() => { var fooCount = fooService.Foos.Count; // always equals zero return Task.CompletedTask; }); await this.next(context); } 

首先,您不应该使用GetService ,通过将其作为参数传递给Invoke方法来使用适当的DI系统。

其次,您获得不同对象的原因是因为在应用程序初始化阶段,中间件的构造函数在任何请求的范围之外被调用。 因此,那里使用的容器是全球提供商。 请参阅此处以获得良好的讨论。

 public class AppMessageMiddleware { private readonly RequestDelegate _next; public AppMessageMiddleware(RequestDelegate next, IServiceProvider serviceProvider) { _next = next; } //Note the new parameter here: vvvvvvvvvvvvvvvvvvvvv public async Task Invoke(HttpContext context, FooService fooService) { context.Response.OnStarting(() => { var fooCount = fooService.Foos.Count; return Task.CompletedTask; }); await _next(context); } }