PAYATHON 2026

ASP.NET Core 6 Web API 如何返回原始 JSON 并避免 chunked 响应?

数据阿明

结论

将控制器的返回类型从 IResult 改为 IActionResult,通过 ControllerBase.Content(...) 返回已经序列化的 JSON 字符串。

在 ASP.NET Core 6 中,ContentResult 会直接写入字符串,并根据实际编码计算 Content-Length。如果响应没有经过压缩、响应体改写或代理重新处理,普通 HTTP/1.1 响应就可以使用确定的长度,以满足嵌入式客户端的接收要求。

为什么响应里会出现 contentType 和 statusCode?

根据提供的控制器签名和响应结构,问题出在结果对象的处理方式。ASP.NET Core 6 的 MVC 控制器不能像 Minimal API 那样直接执行这里返回的 IResult。Results.Text(...) 创建的结果对象会进入普通对象的处理流程,公开属性被序列化后,响应中就出现了 content、contentType 和 statusCode。这与 .NET 6 的 ActionResultTypeMapper 实现一致。

换用其他 Results.* 方法,仍无法解决控制器返回类型与结果执行方式的问题。额外字段也不是 chunked encoding 添加的:响应体序列化和 HTTP 消息分帧是两个不同的环节。

使用 ContentResult 返回原始 JSON

将以下方法放在现有的 ControllerBase 控制器中。示例使用一个简单的 status 对象,实际代码保留原有业务数据即可:

using System.Text;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;

// 放在现有 ControllerBase 控制器类中
[HttpPost]
public IActionResult Post()
{
    var status = new { Status = "OK" };

    string response = JsonConvert.SerializeObject(
        status,
        new JsonSerializerSettings
        {
            ContractResolver = new CamelCasePropertyNamesContractResolver()
        });

    return Content(response, "application/json", Encoding.UTF8);
}

如果方法内部还有异步业务操作,将签名改为 public async Task<IActionResult> Post(),保留原有的 await,最后返回同一个 Content(...) 结果。

控制器的 Content(...) 方法返回 MVC 的 ContentResult。它会把字符串直接写入响应体,不会再次将其作为 JSON 字符串序列化。

.NET 6 的 ContentResultExecutor 源码使用所选编码的 GetByteCount(result.Content) 设置 Response.ContentLength,然后写入内容,通常无需手动设置长度。

检查响应是否符合设备要求

对于上面的紧凑 JSON,如果响应内容没有被后续组件改变,预期响应如下:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 15

{"status":"OK"}

Content-Length 表示响应体的字节数。示例中的 JSON 使用 UTF-8 编码后为 15 字节;如果加入空格、换行或中文,需要按实际内容重新计算,不能直接使用 response.Length。

修改后,检查设备实际连接地址返回的响应头和响应体,确认没有额外包装字段,且 HTTP/1.1 响应没有使用 Transfer-Encoding: chunked。以上是预期行为,并非对当前项目的实测结果。

如果仍然出现 chunked 响应

优先检查响应压缩中间件、统一响应包装过滤器、自定义中间件和反向代理。这些组件可能改变响应体、移除已有的长度信息,或重新决定传输方式。响应压缩尤其需要注意:压缩前的字节数不能直接作为压缩后的 Content-Length。

对于必须依赖确定长度的设备接口,应确保最终输出链路保留准确的 Content-Length,并避免在长度计算后继续追加或转换响应内容。不要通过手动删除 Transfer-Encoding 来代替正确生成响应。

备注:内容仅供参考。