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

.Net Core 3.1 WebApi调用stream.CopyToAsync后UseExceptionHandler失效问题

问题根因

核心原因

UseExceptionHandler 生效的前提是HTTP响应尚未启动(即 context.Response.HasStarted == false),此时异常处理中间件可以修改响应状态码、重写响应内容。你执行 await stream.CopyToAsync(originalBody) 时,已经将响应数据写入原始输出流,触发了响应头的发送,HasStarted 变为 true,后续抛出的异常无法再被异常处理中间件捕获处理。

次要触发原因

你在 finally 块中主动调用了 originalBody.Close(),提前关闭了框架托管的响应流,即便异常处理中间件要写入响应也没有可用的流载体。


修复方案

  1. 调整操作顺序:先执行所有可能抛出异常的逻辑(包括日志写入),确认无异常后再将内存流内容拷贝到原始响应流,避免未处理异常发生在响应启动之后。
  2. 移除手动关闭响应流的代码:原始响应流由ASP.NET Core框架生命周期管理,无需手动关闭,否则会破坏管道后续操作。
  3. 优化异常抛出写法:catch 块中直接 throw ex 会重置异常调用栈,改为 throw; 保留原始栈信息便于排查问题。

修改后代码示例

Invoke方法

public async Task Invoke(HttpContext context)
{   
    Stream originalBody = context.Response.Body;
    try
    {
        using var memStream = new MemoryStream();
        context.Response.Body = memStream;
        await _next(context).ConfigureAwait(false);
        // 先处理日志等可能抛异常的逻辑,最后再拷贝流到响应
        await LogResponse(context, originalBody, memStream).ConfigureAwait(false);
    }
    catch (Exception ex)
    {
        // 异常抛出前先把响应流改回原始流,保证异常处理中间件可以写入内容
        context.Response.Body = originalBody;
        throw; // 保留原始异常栈
    }
    finally
    {
        // 只需要重置响应流引用,不要手动关闭原始流
        context.Response.Body = originalBody;
    }
}

LogResponse方法

private async Task LogResponse(HttpContext context, Stream originalBody, MemoryStream stream)
{
    stream.Seek(0, SeekOrigin.Begin);
    string body = await new StreamReader(stream).ReadToEndAsync().ConfigureAwait(false);
    stream.Seek(0, SeekOrigin.Begin);
    
    // 先执行日志写入等可能抛异常的操作
    var resp = new ApiLogItemRequest()
    {
        AppName = "AppName",
        CreatedBy = "AppName",
        DateTime = DateTime.Now,                
        RawBodyJson = body,
        TraceIdentifier = context.TraceIdentifier
    };
    await _logHelper.LogRawHttpBodyItem(resp);

    // 所有逻辑执行成功后,最后再拷贝流到原始响应
    await stream.CopyToAsync(originalBody).ConfigureAwait(false);
}

可选优化

如果允许日志写入失败不影响接口正常返回,可以给日志操作单独套一层try/catch,避免日志异常导致接口报错:

try
{
    await _logHelper.LogRawHttpBodyItem(resp);
}
catch (Exception logEx)
{
    // 自行处理日志异常,比如本地打控制台日志,不抛出到上层
    Console.WriteLine($"日志写入失败:{logEx.Message}");
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.03 20:45:03