ASP.NET Core Web API中如何通过[FromForm]接收包含IFormFile的对象数组并兼容Swagger UI?
ASP.NET Core Web API中如何通过[FromForm]接收包含IFormFile的对象数组并兼容Swagger UI?
我之前也碰到过一模一样的问题!默认的模型绑定和Swashbuckle对这种带文件的复杂对象数组支持确实不太友好,不过咱们可以分两步搞定:先解决模型绑定的正确性,再让Swagger UI也能正常使用。
第一步:让模型绑定正确接收数组
首先,ASP.NET Core的form-data模型绑定对数组的命名有明确要求,你得用索引式字段名来提交每个FormItem的属性。举个例子:
- 第一个键值对类型的
FormItem:- 字段名:
formFields[0].key,值填你的键名(比如username) - 字段名:
formFields[0].value,值填对应的字符串(比如john_doe)
- 字段名:
- 第二个文件类型的
FormItem:- 字段名:
formFields[1].key,值填avatar - 字段名:
formFields[1].file,选择要上传的文件
- 字段名:
另外,别忘了给FormItem加验证,确保每个项要么有value要么有file,不能同时存在或者都没有。写个自定义验证特性就行:
public class FormItemValidationAttribute : ValidationAttribute { protected override ValidationResult IsValid(object value, ValidationContext validationContext) { var item = value as FormItem; if (item == null) return ValidationResult.Success; bool hasValue = !string.IsNullOrEmpty(item.value); bool hasFile = item.file != null; if (hasValue == hasFile) { return new ValidationResult("每个FormItem必须且只能提供value或file中的一个"); } return ValidationResult.Success; } }
然后把这个特性加到FormItem类上:
[FormItemValidation] public class FormItem { public string key { get; set; } public string value { get; set; } public IFormFile file { get; set; } }
最后在控制器action里加上模型状态检查,确保输入合法:
[HttpPost("{businessKycId}")] public async Task<ActionResult> SaveAdditionalInfo( [FromForm] FormItem[] formFields, [FromRoute] Guid businessKycId, [FromQuery] string token) { if (!ModelState.IsValid) { return BadRequest(ModelState); } // 这里写你的业务逻辑 return Ok(); }
第二步:让Swagger UI支持数组和文件上传
默认的Swashbuckle不会自动渲染这种带文件的数组,咱们得自定义两个过滤器来调整Swagger的生成逻辑:
1. 编写Schema过滤器,定义数组项的结构
这个过滤器用来告诉SwaggerFormItem[]的正确结构:
public class FormItemArraySchemaFilter : ISchemaFilter { public void Apply(OpenApiSchema schema, SchemaFilterContext context) { if (context.Type == typeof(FormItem[])) { schema.Type = "array"; schema.Items = new OpenApiSchema { Type = "object", Properties = new Dictionary<string, OpenApiSchema> { {"key", new OpenApiSchema { Type = "string", Required = new HashSet<string> { "key" }}}, {"value", new OpenApiSchema { Type = "string", Nullable = true }}, {"file", new OpenApiSchema { Type = "string", Format = "binary", Nullable = true }} } }; } } }
2. 编写Operation过滤器,调整请求体的格式
这个过滤器用来把formFields参数转换成multipart/form-data的数组格式,同时保留路由和查询参数:
public class FormItemArrayOperationFilter : IOperationFilter { public void Apply(OpenApiOperation operation, OperationFilterContext context) { var formParam = operation.Parameters.FirstOrDefault(p => p.Name == "formFields"); if (formParam != null) { // 移除原来的参数定义 operation.Parameters.Remove(formParam); // 配置multipart/form-data请求体 if (operation.RequestBody == null) { operation.RequestBody = new OpenApiRequestBody(); } var mediaType = new OpenApiMediaType { Schema = new OpenApiSchema { Type = "object", Properties = new Dictionary<string, OpenApiSchema> { ["formFields"] = new OpenApiSchema { Type = "array", Items = new OpenApiSchema { Type = "object", Properties = new Dictionary<string, OpenApiSchema> { {"key", new OpenApiSchema { Type = "string", Required = new HashSet<string> { "key" }}}, {"value", new OpenApiSchema { Type = "string", Nullable = true }}, {"file", new OpenApiSchema { Type = "string", Format = "binary", Nullable = true }} } } } }, Required = new HashSet<string> { "formFields" } } }; // 设置编码规则,让Swagger正确处理数组 mediaType.Encoding["formFields"] = new OpenApiEncoding { Style = ParameterStyle.Form, Explode = true }; operation.RequestBody.Content["multipart/form-data"] = mediaType; } } }
3. 注册过滤器到Swagger服务
在Program.cs里把这两个过滤器加进去:
builder.Services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "你的API名称", Version = "v1" }); // 添加自定义过滤器 c.SchemaFilter<FormItemArraySchemaFilter>(); c.OperationFilter<FormItemArrayOperationFilter>(); });
最后测试一下
现在不管是用Postman按照索引字段名提交,还是在Swagger UI里点击“Add item”添加多个FormItem,选择输入value或者上传file,模型绑定都会正确接收,而且Swagger也能正常渲染表单啦!
内容来源于stack exchange
相关产品推荐
相关产品推荐

