You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

问题根源

  1. 全局上下文篡改:原代码中手动修改IHttpContextAccessor.HttpContext,.NET 6+对异步上下文的管理更严格,手动赋值会破坏AsyncLocal维护的请求上下文关联,导致后续代码获取到null的HttpContext。
  2. Singleton服务设计缺陷:WebRequestContext作为Singleton服务,静态Current属性容易在异步流程中引发上下文混乱,多个并发请求可能互相干扰。

内容的提问来源于stack exchange,提问作者Velmurugan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 14:17:28