Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/284.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/asp.net-core/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 为什么作用域服务解析为同一请求的两个不同实例?_C#_Asp.net Core_Dependency Injection_.net Core_Asp.net Core 2.0 - Fatal编程技术网

C# 为什么作用域服务解析为同一请求的两个不同实例?

C# 为什么作用域服务解析为同一请求的两个不同实例?,c#,asp.net-core,dependency-injection,.net-core,asp.net-core-2.0,C#,Asp.net Core,Dependency Injection,.net Core,Asp.net Core 2.0,我有一个简单的服务,它包含一个列表。在Startup.cs中,我使用的是services.addScope()方法 我在两个不同的地方(控制器和中间件)注入服务实例,对于单个请求,我希望得到相同的实例。然而,这似乎没有发生 即使我在控制器操作中将一个Foo添加到列表中,中间件中的Foo列表始终为空。为什么会这样 我已尝试使用AddSingleton()将服务注册更改为singleton,效果如预期。但是,这必须限定为当前请求的范围。非常感谢任何帮助或想法 FooService.cs public

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

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

即使我在控制器操作中将一个Foo添加到列表中,中间件中的Foo列表始终为空。为什么会这样

我已尝试使用
AddSingleton()
将服务注册更改为singleton,效果如预期。但是,这必须限定为当前请求的范围。非常感谢任何帮助或想法

FooService.cs

public class FooService
{
    public List<Foo> Foos = new List<Foo>();
}
...
public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddScoped<FooService, FooService>();
}
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());
    }
}
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);

}
foodmiddleware.cs

public class FooService
{
    public List<Foo> Foos = new List<Foo>();
}
...
public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddScoped<FooService, FooService>();
}
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());
    }
}
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
,而是使用适当的DI系统,将其作为参数传递到
Invoke
方法中

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

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

    }
}

您确定控制器代码是在中间件代码之前执行的吗?请发布一个最小的、完整的、可验证的示例。例如,显示
FooService
Foo
的注册。是的,我已经在不同的场景中多次使用了它。另外,我不相信响应会在控制器操作结束之前启动。@Stillgar我认为这是
响应的情况。在请求启动很久之后,当发送响应头时就会启动
。无论如何,您不应该使用
GetService
,我想知道这是否使用了
ApplicationServices
容器,它实际上与使其成为单例一样。相反,您应该将服务作为参数注入
Invoke
方法中。谢谢。第二个原因是我的直觉,因此我尝试使用
serviceProvider.GetService()
在OnStarting()方法中检索服务实例,我将阅读您引用的讨论-还不确定是否有好的解决方案。我需要访问作用域服务并相应地修改响应-但我希望以“适当”的方式执行,而不是试图直接从
FooService
使用HttpContext。我很困惑,这里没有什么建议您需要
FooService
中的HTTP上下文作为执行控制器操作的一部分,我正在将Foo实例添加到作用域FooService公开的列表中。这可能发生在任何情况下(直接在控制器、存储库等中),然后,在发送响应之前,我将根据FooService收集的信息设置响应头。是的,但为什么这意味着
FooService
需要了解上下文的任何信息?太好了!现在这是完全有道理的。