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();
解决方案
问题根源
- Session无法访问:你配置的异常处理中间件使用了空的内部管道(
app.UseExceptionHandler(c => {})),这个内部管道没有注册Session中间件。当触发异常处理时,内部管道不会执行Session初始化逻辑,导致HttpContext.Session未就绪,调用时抛出异常。 - 作用域服务重新创建:你在
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
相关产品推荐
相关产品推荐

