C# .NET API接收任意键值对:Swagger输入框显示异常问题
解决.NET API接收任意FormUrlEncoded键值对并让Swagger显示输入框的问题
问题背景
我使用C# .NET控制器和Swagger构建API,需要接收POST请求中任意数量的键值对(请求体格式为key1=val1&key2=val2&key3=val3a&key3=val3b这类表单编码格式),要求:
- 请求体能自动解析为
IFormCollection - Swagger UI提供对应的输入框用于提交数据
当前代码可正常处理请求,但Swagger未显示输入框:
[HttpPost] [Consumes(MediaTypeNames.Application.FormUrlEncoded)] [ProducesResponseType(StatusCodes.Status204NoContent)] public async Task<IActionResult> SetKeys() { var keyvals = await Request.Body.ToDictionary(); // 自定义Stream扩展方法 if (keyvals.Count == 0) return BadRequest("No data received"); // 数据库更新逻辑 return NoContent(); //204 }
尝试过以下写法均无法满足需求:
public async Task<IActionResult> SetKeys([FromForm] string content) public async Task<IActionResult> SetKeys([FromBody] string content) public async Task<IActionResult> SetKeys([FromForm] IFormCollection content) public async Task<IActionResult> SetKeys([FromBody] IFormCollection content) public async Task<IActionResult> SetKeys([FromBody] List<KeyValuePair<string, string>> content)
若将Consumes设为text/plain,Swagger会显示输入框,但需手动解析键值对,且失去Content-Type校验:
[HttpPost] [Consumes(MediaTypeNames.Text.Plain)] [ProducesResponseType(typeof(string), StatusCodes.Status200OK, MediaTypeNames.Application.FormUrlEncoded)] public async Task<IActionResult> SetKeys([FromBody] string content) { var keyvals = await content.ToDictionary(); // 自定义String扩展方法 if (keyvals.Count == 0) return BadRequest("No data received"); // 数据库更新逻辑 return NoContent(); //204 }
自定义InputFormatter后,Swagger虽能选择application/x-www-form-urlencoded,但会将内容按字符拆分(如key1=val1被拆为0=k&1=e&2=y...):
using System.Net.Mime; using Microsoft.AspNetCore.Mvc.Formatters; namespace RequestRouter.Utilities; public class RawBodyInputFormatter : InputFormatter { public RawBodyInputFormatter() { this.SupportedMediaTypes.Add(MediaTypeNames.Application.FormUrlEncoded); //this.SupportedMediaTypes.Add(MediaTypeNames.Text.Plain); } public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context) { var request = context.HttpContext.Request; using var reader = new StreamReader(request.Body); var content = await reader.ReadToEndAsync(); return await InputFormatterResult.SuccessAsync(content); } protected override bool CanReadType(Type type) { return type == typeof(string); } }
解决方案
要同时满足自动解析和Swagger输入框需求,需完成以下两步:
1. 修正控制器方法参数
使用[FromForm] IFormCollection作为参数,保留Consumes特性指定表单编码格式:
[HttpPost] [Consumes(MediaTypeNames.Application.FormUrlEncoded)] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task<IActionResult> SetKeys([FromForm] IFormCollection formData) { if (formData.Count == 0) return BadRequest("未接收到数据"); // 处理重复键示例:获取key3的所有值 var key3Values = formData.GetValues("key3"); // 数据库更新逻辑 return NoContent(); }
2. 配置Swagger支持动态表单参数
默认Swagger对IFormCollection生成固定表单,需添加自定义操作过滤器实现任意键值对输入:
创建操作过滤器
using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.SwaggerGen; public class FormCollectionOperationFilter : IOperationFilter { public void Apply(OpenApiOperation operation, OperationFilterContext context) { var formParam = context.ApiDescription.ParameterDescriptions .FirstOrDefault(p => p.Type == typeof(IFormCollection)); if (formParam != null) { // 移除自动生成的无效参数 operation.Parameters.Clear(); // 添加支持任意键值对的表单请求体 operation.RequestBody = new OpenApiRequestBody { Content = new Dictionary<string, OpenApiMediaType> { { MediaTypeNames.Application.FormUrlEncoded, new OpenApiMediaType { Schema = new OpenApiSchema { Type = "object", AdditionalProperties = new OpenApiSchema { Type = "string" } } } } } }; } } }
注册过滤器到Swagger
在Program.cs中添加配置:
builder.Services.AddSwaggerGen(c => { c.OperationFilter<FormCollectionOperationFilter>(); // 其他Swagger配置(如文档标题、版本等) });
效果说明
- Swagger UI会显示可动态添加键值对的表单区域,符合
application/x-www-form-urlencoded格式要求 IFormCollection自动解析请求体,重复键可通过GetValues方法获取所有值- 保留Content-Type校验,仅当请求头为
application/x-www-form-urlencoded时才会被正确处理
内容的提问来源于stack exchange,提问作者Christian
相关产品推荐
相关产品推荐

