.NET Core 2.1升级.NET 6后WebRequestContext.Localization空引用异常排查
问题解决方案
修复RenderActionAsync的上下文干扰问题
原代码中手动修改全局IHttpContextAccessor和IActionContextAccessor的行为,在.NET 6+中会破坏框架的异步上下文管理逻辑,导致后续请求上下文丢失。修改后的RenderActionAsync如下:
private static async Task<IHtmlContent> RenderActionAsync(this IHtmlHelper helper, string action, string controller, string area, object parameters = null) { var serviceProvider = helper.ViewContext.HttpContext.RequestServices; var actionSelector = serviceProvider.GetRequiredService<IActionSelector>(); // 构建路由数据 var routeData = new RouteData(); foreach (var router in helper.ViewContext.RouteData.Routers) { routeData.PushState(router, null, null); } routeData.PushState(null, new RouteValueDictionary(new { controller = controller, action = action, area = area }), null); routeData.PushState(null, new RouteValueDictionary(parameters ?? new { }), null); // 获取目标ActionDescriptor var routeContext = new RouteContext(helper.ViewContext.HttpContext) { RouteData = routeData }; var candidates = actionSelector.SelectCandidates(routeContext); var actionDescriptor = actionSelector.SelectBestCandidate(routeContext, candidates); // 创建独立的HttpContext,复制原上下文的Items确保数据可用 var httpContextFactory = serviceProvider.GetRequiredService<IHttpContextFactory>(); var newHttpContext = httpContextFactory.Create(helper.ViewContext.HttpContext.Features); newHttpContext.Response.Body = new MemoryStream(); foreach (var item in helper.ViewContext.HttpContext.Items) { newHttpContext.Items[item.Key] = item.Value; } // 创建独立的ActionContext,无需修改全局访问器 var actionContext = new ActionContext(newHttpContext, routeData, actionDescriptor); var invokerFactory = serviceProvider.GetRequiredService<IActionInvokerFactory>(); var invoker = invokerFactory.CreateInvoker(actionContext); try { await invoker.InvokeAsync(); newHttpContext.Response.Body.Position = 0; using (var reader = new StreamReader(newHttpContext.Response.Body)) { return new HtmlString(reader.ReadToEnd()); } } catch (Exception ex) { return new HtmlString(ex.Message); } finally { if (newHttpContext.Items.ContainsKey(typeof(IUrlHelper))) { newHttpContext.Items.Remove(typeof(IUrlHelper)); } } }
关键修改说明
- 移除对全局
IActionContextAccessor和IHttpContextAccessor的修改,避免破坏请求上下文的全局状态 - 将原HttpContext的Items复制到新上下文,确保被调用的Action能访问到
Localization等请求级数据 - 直接使用独立的ActionContext创建调用器,完全隔离子Action的执行上下文
修复WebRequestContext的服务注册与静态属性设计
当前WebRequestContext注册为Singleton且依赖静态Current属性,在多请求并发和异步场景下存在上下文混乱风险,建议调整为Scoped注册:
1. 修改WebRequestContext代码
public class WebRequestContext { private readonly IHttpContextAccessor _httpContextAccessor; public WebRequestContext(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } public Localization Localization { get => (Localization)GetFromContextStore("Localization"); set => AddToContextStore("Localization", value); } protected object GetFromContextStore(string key) { return _httpContextAccessor.HttpContext?.Items[key]; } protected object AddToContextStore(string key, object value) { if (_httpContextAccessor.HttpContext == null) return value; _httpContextAccessor.HttpContext.Items[key] = value; return value; } }
2. 更新服务注册代码
// 改为Scoped注册,每个请求对应一个WebRequestContext实例 services.AddScoped<WebRequestContext>(); services.AddHttpContextAccessor(); services.AddTransient<IActionContextAccessor, ActionContextAccessor>();
3. 兼容现有代码的过渡方案
如果无法立即替换所有WebRequestContext.Current的调用,可添加静态属性的兼容实现:
public class WebRequestContext { private readonly IHttpContextAccessor _httpContextAccessor; private static IServiceProvider _globalServiceProvider; public static void Initialize(IServiceProvider serviceProvider) { _globalServiceProvider = serviceProvider; } public WebRequestContext(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } public static WebRequestContext Current { get { var httpContextAccessor = _globalServiceProvider.GetRequiredService<IHttpContextAccessor>(); return httpContextAccessor.HttpContext.RequestServices.GetRequiredService<WebRequestContext>(); } } // 其余属性和方法不变 }
在Program.cs中初始化:
var app = builder.Build(); WebRequestContext.Initialize(app.Services);
问题根源
- 全局上下文篡改:原代码中手动修改
IHttpContextAccessor.HttpContext,.NET 6+对异步上下文的管理更严格,手动赋值会破坏AsyncLocal维护的请求上下文关联,导致后续代码获取到null的HttpContext。 - Singleton服务设计缺陷:
WebRequestContext作为Singleton服务,静态Current属性容易在异步流程中引发上下文混乱,多个并发请求可能互相干扰。
内容的提问来源于stack exchange,提问作者Velmurugan
相关产品推荐
相关产品推荐

