.NET 5 Web API接收Webhook POST请求时string入参为null
.NET 5 Web API 接收JSON类型Webhook原始字符串方案
问题说明
使用.NET 5开发Web API接口接收Webhook推送时,存在以下现象:
- 推送请求头为
Content-Type: application/json; charset=UTF-8,请求体为标准JSON结构,示例如下:
{ "user_ip": "00.00.00.00", "date_start": "2022-01-01T14:05:27.546Z", "user_referrer": "N/A", "user_os": "Windows (deprecated)", "id": "000001", "items": [ { "id": "1", "position": 2, "value": "01/01/2022" }, { "id": "0", "position": 8, "value": "Smith" }, { "id": "1", "position": 22, "value": "7843377133" }, { "values": [ { "position": 2, "value": "Coffee" }, { "position": 5, "value": "Tea" } ], "id": "14", "position": 7 }, { "id": "15", "position": 8, "value": "This is a test for API" } ], "user_device": "Desktop" }
- 若接口定义与JSON结构匹配的实体类作为
[FromBody]入参,框架可正常完成反序列化;若直接用string类型作为[FromBody]入参,参数值始终为null,错误写法示例:
[ApiController] [Route("api/webhooks/[controller]")] public class FormController : BaseApiController { [HttpPost("Form")] public async Task<ActionResult> Form([FromBody] string val) { FormDataModel FS = new FormDataModel(){ ResultJsonString = val }; return Ok(new {Data = FS}); } }
- 业务场景不需要框架自动反序列化对象,需要直接获取原始JSON请求字符串。
问题原因
.NET 5默认内置的System.Text.Json输入格式化器,处理application/json类型请求时,默认将请求体作为完整JSON值反序列化为目标参数类型:当参数类型为string时,格式化器会要求请求体是一个被双引号包裹的JSON字符串字面量(例如"\"test content\""),直接传入JSON对象/数组结构时,反序列化string类型失败,就会给参数赋值null。
可行解决方案
以下方案均可实现原始JSON字符串获取,可根据业务场景选择:
- 方案1:直接读取请求体流(通用性最高)
不依赖框架模型绑定,直接从请求对象的Body流中读取原始内容,不需要修改全局配置,适合单个接口需要获取原始请求的场景。注意ASP.NET Core默认请求体流只能读取一次,读取前需要开启缓冲,读取完成后重置流位置,避免后续组件读取失败。
代码实现:
[ApiController] [Route("api/webhooks/[controller]")] public class FormController : BaseApiController { [HttpPost("Form")] public async Task<ActionResult> Form() { // 开启请求缓冲,支持多次读取请求体 Request.EnableBuffering(); // 按UTF8编码读取原始请求内容,与推送方编码保持一致 using var reader = new StreamReader( Request.Body, Encoding.UTF8, leaveOpen: true); string rawJson = await reader.ReadToEndAsync(); // 重置请求体流位置到起点,不影响后续中间件/过滤器读取 Request.Body.Position = 0; FormDataModel FS = new FormDataModel() { ResultJsonString = rawJson }; return Ok(new {Data = FS}); } }
- 方案2:自定义InputFormatter(保留原有接口签名)
自定义输入格式化器,指定当[FromBody]参数类型为string、请求类型为application/json时,直接读取请求体原始字符串返回,注册到全局MVC配置后,原有[FromBody] string val的写法即可正常工作。
第一步,自定义格式化类:
public class RawJsonStringInputFormatter : TextInputFormatter { public RawJsonStringInputFormatter() { // 配置支持的媒体类型和编码 SupportedMediaTypes.Add("application/json"); SupportedEncodings.Add(Encoding.UTF8); } protected override bool CanReadType(Type type) { // 仅对string类型参数生效 return type == typeof(string); } public override async Task<InputFormatterResult> ReadRequestBodyAsync( InputFormatterContext context, Encoding encoding) { using var reader = new StreamReader( context.HttpContext.Request.Body, encoding, leaveOpen: true); string rawContent = await reader.ReadToEndAsync(); return await InputFormatterResult.SuccessAsync(rawContent); } }
第二步,在Startup.ConfigureServices中注册格式化器,注意插入到格式化器列表的最前端,优先于默认JSON格式化器生效:
public void ConfigureServices(IServiceCollection services) { services.AddControllers(options => { options.InputFormatters.Insert(0, new RawJsonStringInputFormatter()); }); }
- 方案3:使用JsonElement接收(零配置最简实现)
System.Text.Json原生支持将JSON请求绑定到JsonElement类型,绑定完成后调用GetRawText()方法即可直接获取原始JSON字符串,不需要修改全局配置,也不需要手动处理流。
代码实现:
[HttpPost("Form")] public async Task<ActionResult> Form([FromBody] JsonElement val) { // 获取原始JSON文本 string rawJson = val.GetRawText(); FormDataModel FS = new FormDataModel() { ResultJsonString = rawJson }; return Ok(new {Data = FS}); }
注意:该方案下框架会先对请求JSON做格式校验,如果JSON格式不合法会直接返回400错误,无法拿到原始错误内容,若业务需要记录格式非法的请求原文,不要使用该方案。
内容的提问来源于stack exchange,提问作者Jet813
相关产品推荐
相关产品推荐

