ASP.NET Core 3+中CreateScope()共享原请求上下文的问题及解决
I ran into a tricky problem when working with background tasks in ASP.NET Core 3+: when creating a new service scope inside a background task, the new scope ended up referencing the original request's context. This caused unexpected errors when either scope was disposed—like getting a "IFeatureCollection has been disposed of" exception when using IUrlHelper.
Problem Breakdown
When I spun up a new IServiceScope via ServiceScopeFactory.CreateScope() inside a Task.Run() block, the HttpContext in that new scope was identical to the original request's context. Disposing either scope would invalidate the context for both, leading to runtime errors when accessing request-dependent services.
Reproduction Code
Test Controller
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"); } }
Startup Configuration
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?}"); }); } }
Failed Original Approach
At first, I tried injecting ActionContext by relying solely on IActionContextAccessor, but this didn't work. The accessor still pulled the original request's context even in the background scope:
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>() .AddTransient<ActionContext>((s) => { var actionContextAccessor = serviceProvider.GetRequiredService<IActionContextAccessor>(); var actionContext = actionContextAccessor?.ActionContext; // Create custom actioncontext if null if (actionContext == null) { // create a manual actionContext } return actionContext; });
Final Working Solution
The fix was to add a check using IHttpContextAccessor to determine if we're in an active request context. If there's no active HttpContext (like in a background task), we create a manual, isolated ActionContext:
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>() .AddTransient<ActionContext>((s) => { var currentContextAccess = serviceProvider.GetService<IHttpContextAccessor>(); if (currentContextAccess.HttpContext == null) { // Example of creating a manual ActionContext: // var httpContext = new DefaultHttpContext(); // var actionContext = new ActionContext(httpContext, new RouteData(), new ControllerActionDescriptor()); // return actionContext; } var actionContextAccessor = serviceProvider.GetRequiredService<IActionContextAccessor>(); return actionContextAccessor.ActionContext; });
This ensures background tasks get their own independent ActionContext instead of inheriting the disposed request context, eliminating the disposal-related errors.
内容的提问来源于stack exchange,提问作者Willow Media

