如何拦截控制器执行后中间件抛出的错误并阻止提前启动响应?
问题:如何确保TransactionMiddleware执行完毕前不发送响应并捕获其异常
我用UseExceptionHandler处理错误,常规场景没问题,但它抓不到后续注册的中间件抛出的异常。我的TransactionMiddleware会在控制器动作执行完成后把所有变更保存到数据库(包括提交事务、插入更新等),这个过程可能因为数据库约束失败。想知道有没有办法在这个中间件完全执行完之前不启动响应?
我的Program.cs代码
var builder = WebApplication.CreateBuilder(args); new Startup(builder.Configuration).ConfigureServices(builder.Services); app.UseExceptionHandler(errorApp => { errorApp.Run(async context => { context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; // await ExcludedCode()... }); }); app.UseSwagger() .UseSwaggerUI(); app.UseRouting() .UseCors() .UseAuthentication() .UseAuthorization() .UseMiddleware<LanguageMiddleWare>() .UseMiddleware<TransactionMiddleWare>() .UseEndpoints(endpoints => { endpoints.MapControllers(); }); app.Run();
简化版TransactionMiddleWare类
public class TransactionMiddleWare { private readonly RequestDelegate next; public TransactionMiddleWare(RequestDelegate next) { this.next = next; } public async Task Invoke(HttpContext context, IDataContext dataContext) { try { await next(context); } catch (PartialExecutionException) { this.Commit(context, dataContext); throw; } this.Commit(context, dataContext); } private void Commit(HttpContext context, IDataContext dataContext) { if (this.ShouldTransactionCommited(context)) dataContext.SaveChanges(); else throw new Exception("Exception example for clarity."); } private bool ShouldTransactionBeCommited(HttpContext context) { return true; // Actual code omitted for brevity. } }
控制器返回示例(无特殊逻辑)
[ApiController] [Route("advertisment")] public class AdvertismentController : ControllerBase { private readonly IAdvertismentService advertismentService; private NLog.ILogger log; public AdvertismentController( IAdvertismentService advertismentService) { this.log = LogManager.GetCurrentClassLogger(); this.advertismentService = advertismentService; } [HttpPost] public Result<Guid> Create([FromForm] CreateAdvertismentMultipartFormModel request) { var id = this.advertismentService.Create(request); return new Result<Guid> { Data = id }; } }
解决方案
核心问题是控制器执行完成后,ASP.NET Core会立刻开始向客户端发送响应,此时后续中间件抛出的异常无法被UseExceptionHandler捕获,也无法修改响应。解决思路是缓存控制器的输出,等TransactionMiddleware的Commit操作完成后再发送响应,具体步骤如下:
1. 修改TransactionMiddleware的Invoke方法,缓存响应流
public async Task Invoke(HttpContext context, IDataContext dataContext) { // 保存原始响应流,替换为内存流临时存储输出 var originalResponseBody = context.Response.Body; using var memoryStream = new MemoryStream(); context.Response.Body = memoryStream; try { // 执行后续中间件(包括控制器) await next(context); // 执行数据库提交操作 this.Commit(context, dataContext); // 提交成功,把缓存的响应写回原始流发送给客户端 memoryStream.Seek(0, SeekOrigin.Begin); await memoryStream.CopyToAsync(originalResponseBody); } catch (Exception ex) { // 提交失败,清空已写入的响应内容 context.Response.Clear(); context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; // 返回自定义错误响应(可根据需求调整格式) await context.Response.WriteAsJsonAsync(new { ErrorMessage = ex.Message }); } finally { // 恢复原始响应流 context.Response.Body = originalResponseBody; } }
2. 调整异常处理逻辑(可选)
如果仍然想保留UseExceptionHandler,需要确保它注册在所有中间件最前面(你当前已经做到)。不过上面的代码已经在中间件内部处理了异常,两种方式选其一即可,避免重复处理。
3. 修正代码小问题
注意你的Commit方法中调用的ShouldTransactionCommited是拼写错误,应该和实际方法名ShouldTransactionBeCommited保持一致。
内容的提问来源于stack exchange,提问作者Devosaur
相关产品推荐
相关产品推荐

