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

Jos*_*iel 9 c# dependency-injection .net-core asp.net-core asp.net-core-2.0

我有一个简单的服务,包含一个List<Foo>.在Startup.cs中,我正在使用该services.addScoped<Foo, Foo>()方法.

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

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

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

FooService.cs

public class FooService
{
    public List<Foo> Foos = new List<Foo>();
}
Run Code Online (Sandbox Code Playgroud)

Startup.cs

...
public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddScoped<FooService, FooService>();
}
Run Code Online (Sandbox Code Playgroud)

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

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());
    }
}
Run Code Online (Sandbox Code Playgroud)

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

}
Run Code Online (Sandbox Code Playgroud)

Evk*_*Evk 10

那是因为当你注入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);    
}
Run Code Online (Sandbox Code Playgroud)

但更好的方法是将它注入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);    
}
Run Code Online (Sandbox Code Playgroud)


Dav*_*idG 5

首先,您不应该使用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);

    }
}
Run Code Online (Sandbox Code Playgroud)