当我尝试运行后台任务时,我总是在该任务内创建一个新范围。随着更新到 3+,似乎在新的创建范围内,存在对原始请求的引用。以下代码将在 Debugger.Break() 语句上中断:
public class TestController : Controller
{
public readonly IServiceScopeFactory ServiceScopeFactory;
public TestController(
IServiceScopeFactory serviceScopeFactory)
{
this.ServiceScopeFactory = serviceScopeFactory;
}
// GET
public IActionResult Index()
{
Task.Run(() =>
{
using (var scope = ServiceScopeFactory.CreateScope())
{
var actionContextAccessor = scope.ServiceProvider.GetService<IActionContextAccessor>();
var actionContext = actionContextAccessor.ActionContext;
if (actionContext.ActionDescriptor != null)
Debugger.Break();
}
});
return Content("Test");
}
}
启动看起来像这样:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}
问题在于 httpContext 与新创建的作用域共享。当其中一个作用域被废弃时,它会影响另一个作用域。例如,使用 IUrlHelper,会导致“IFeatureCollection 已被处理”。
为了测试,我添加了一个测试 httpContext 是否相同。看来确实如此!
public IActionResult Index()
{
// Just for testing
var originalContext = this.HttpContext;
Task.Run(() =>
{
using (var scope = ServiceScopeFactory.CreateScope())
{
// Make sure the original request was disposed
Thread.Sleep(1000);
var actionContextAccessor = scope.ServiceProvider.GetService<IActionContextAccessor>();
var actionContext = actionContextAccessor.ActionContext;
if (originalContext == actionContext.HttpContext)
Debugger.Break();
}
});
return Content("Test");
}
对我来说,这似乎是奇怪的行为,因为我希望新范围不具有相同的 httpContext。它应该是一个新的范围。应该以其他方式创建作用域吗?
找到解决方案
在我的生产代码中,我使用 transient ActionContext 作用域,它尝试检测它是否正在处理请求或后台作用域,如下所示:
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>()
.AddTransient<ActionContext>((s) => {
var actionContextAccessor = serviceProvider.GetRequiredService<IActionContextAccessor>();
var actionContext = actionContextAccessor?.ActionContext;
// Create custom actioncontext
if (actionContext == null) {
// create a manual actionContext
}
return actionContext;
});
这似乎不再起作用了。如果 httpContext 通过 IHttpContextAccessor 存在,该解决方案似乎过于验证:
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>()
.AddTransient<ActionContext>((s) => {
var currentContextAccess = serviceProvider.GetService<IHttpContextAccessor>();
if (currentContextAccess.HttpContext == null) {
// create a manual actionContext
...
return actionContext;
}
var actionContextAccessor = serviceProvider.GetRequiredService<IActionContextAccessor>();
return actionContextAccessor.ActionContext;
});
最佳答案
For me, this seems like odd behaviour, cause I would except the scope not to be. Should the scope be created in another way?
为什么奇怪? IActionContextAccessor
是一个单例(与 IHttpContextAccessor
相同),因此即使在新创建的范围内也通常会返回相同的实例。
由于您没有等待 Task.Run
,因此您的请求将在任务完成之前完成。请求完成后你想如何访问HttpContext?仅在请求期间有效。您必须在启动新任务之前获取所有必需的数据,并将所需的值传递给后台任务。
HttpContext
仅在请求期间有效,并且由于您不等待它,因此请求会提前结束。
您的代码所做的是未定义的行为,请参阅 David 的指南
关于c# - asp.net core (3+) CreateScope() 上的共享上下文,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59985231/