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

Asp.Net Core中IExceptionHandler丢失请求上下文问题求助

问题描述

我正尝试创建全局IExceptionHandler捕获未处理异常(这类异常在代码规范的情况下本不应出现),目的是获取请求数据并向管理员发送告警邮件,但遇到两个问题:

  • 调用HttpContext.Session时抛出InvalidOperationException,即便已经在Program.cs中执行了app.UseSession();
  • 作用域服务会被重新创建,而非复用当前请求的实例(已在服务构造函数中设断点确认)。

这导致我无法获取Session信息(比如HttpContext.Session.Id或ISession字典中的数据),也无法复用作用域服务收集日志信息。

补充:测试程序基于VS2022的Razor Pages模板创建,代码片段如下:


CustomScopedService

public class CustomScopedService
{
    public CustomScopedService()
    {
        // 在此处设置断点,点击"Generate Unhandled Error"按钮时会触发两次:
        // 1. 注入到TestModel构造函数时创建
        // 2. 在CustomUnhandledExceptionHandler中调用GetRequiredService时创建
    }

    public string Id { get; } = Guid.NewGuid().ToString();
}

CustomUnhandledExceptionHandler

public class CustomUnhandledExceptionHandler : IExceptionHandler
{
    public CustomUnhandledExceptionHandler(IServiceScopeFactory serviceScopeFactory)
    {
        this.ServiceScopeFactory = serviceScopeFactory;
    }

    public IServiceScopeFactory ServiceScopeFactory { get; }

    public ValueTask<bool> TryHandleAsync(HttpContext httpContext, Exception exception, CancellationToken cancellationToken)
    {
        using var scope = this.ServiceScopeFactory.CreateScope();
        var service = scope.ServiceProvider.GetRequiredService<CustomScopedService>();

        var id = service.Id; // Id会变化,说明是新实例
        var user = httpContext.Session.GetString("user"); // 抛出InvalidOperationException
        return ValueTask.FromResult(false);
    }
}

Test.cshtml

@page
@model ExceptionHandlerTest.Pages.TestModel
<form method="post" asp-page-handler="UnhandledError">
    <div>Id: @Model.Service.Id</div>
    <div><input type="text" name="userName" placeholder="Username" value="@HttpContext.Session.GetString("user")" /></div>
    <button class="btn btn-danger">Generate Unhandled Error</button>
</form>

Test.cshtml.cs

public class TestModel : PageModel
{
    public TestModel(CustomScopedService service)
    {
        this.Service = service;
    }
    public CustomScopedService Service { get; }

    public void OnGet()
    {
    }

    public void OnPostUnhandledError(string? userName)
    {
        this.HttpContext.Session.SetString("user", userName ?? "foo");
        throw new Exception("Unhandled exception test");
    }
}

Program.cs

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddScoped<CustomScopedService>();

builder.Services.AddSession(); // <---
builder.Services.AddMemoryCache(); // <---

builder.Services.AddExceptionHandler<CustomUnhandledExceptionHandler>(); // <---
var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseExceptionHandler(c => { }); // <---
}
else
{
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseSession(); // <---

app.UseAuthorization();

app.MapRazorPages();

app.Run();

解决方案

问题根源

  1. Session无法访问:你配置的异常处理中间件使用了空的内部管道(app.UseExceptionHandler(c => {})),这个内部管道没有注册Session中间件。当触发异常处理时,内部管道不会执行Session初始化逻辑,导致HttpContext.Session未就绪,调用时抛出异常。
  2. 作用域服务重新创建:你在TryHandleAsync中通过IServiceScopeFactory.CreateScope()创建了新的服务作用域,自然会生成新的作用域服务实例。实际上当前请求的服务作用域仍绑定在HttpContext.RequestServices上,直接从这里获取即可复用请求内的实例。

修改步骤及代码

1. 配置异常处理内部管道

在UseExceptionHandler的配置中添加Session中间件,确保异常处理流程能访问Session:

// 修改Program.cs中的异常处理配置
if (app.Environment.IsDevelopment())
{
    app.UseExceptionHandler(appBuilder =>
    {
        // 为异常处理的内部管道添加Session中间件
        appBuilder.UseSession();
        // 若需要其他中间件(如授权、静态文件),也可在此添加
    });
}

2. 复用当前请求的服务作用域

修改CustomUnhandledExceptionHandler,直接通过HttpContext.RequestServices获取作用域服务,无需创建新作用域:

public class CustomUnhandledExceptionHandler : IExceptionHandler
{
    // 无需注入IServiceScopeFactory
    public ValueTask<bool> TryHandleAsync(HttpContext httpContext, Exception exception, CancellationToken cancellationToken)
    {
        // 从当前请求的作用域获取服务,复用实例
        var service = httpContext.RequestServices.GetRequiredService<CustomScopedService>();

        var id = service.Id; // 现在Id与TestModel中的实例一致
        var user = httpContext.Session.GetString("user"); // 可正常获取Session数据
        
        // 在此添加告警邮件发送逻辑

        return ValueTask.FromResult(false);
    }
}

验证效果

  • 点击"Generate Unhandled Error"按钮后,CustomScopedService的构造函数只会触发一次(注入到TestModel时);
  • HttpContext.Session.GetString("user")可正常获取到值,不再抛出异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 15:07:02