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

Swagger UI中multipart/form-data对象数组上传问题及报错求助

问题解决:Swagger UI无法正确绑定对象列表(FromForm/Query参数)

问题概述

  • Swagger UI提交包含List<InterestDto>的请求时,API接收的列表始终为空;Postman构造的请求可正常解析。
  • 问题覆盖multipart/form-data([FromForm]绑定)和Query参数两种场景。
  • 尝试自定义CustomOperationFilter时触发KeyNotFoundException,提示找不到"Interests"键。

错误信息

System.Collections.Generic.KeyNotFoundException
  HResult=0x80131577
  Message=The given key 'Interests' was not present in the dictionary.
  Source=System.Private.CoreLib
  StackTrace:
   at System.ThrowHelper.ThrowKeyNotFoundException[T](T key)
   at System.Collections.Generic.Dictionary`2.get_Item(TKey key)
   at Flats4us.Helpers.CustomOperationFilter.Apply(OpenApiOperation operation, OperationFilterContext context) in C:\Dane\Projekty\Flats4Us\Flats4us\Flats4us\Helpers\CustomOperationFilter.cs:line 23
   at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GenerateOperation(ApiDescription apiDescription, SchemaRepository schemaRepository)

相关代码(原代码)

AuthController.cs

[HttpPost("register/Student")]
public async Task<ActionResult<User>> RegisterStudentAsync([FromForm] StudentRegisterDto request)
{
    try
    {
        await _userService.RegisterStudentAsync(request);
        return Ok("Registered successfully");
    }
    catch (Exception ex)
    {
        return BadRequest(ex.Message);
    }
}

StudentRegisterDto.cs

[ModelBinder(BinderType = typeof(MetadataValueModelBinder))]
public class StudentRegisterDto : OwnerStudentRegisterDto
{    
    // 其他属性...

    public List<InterestDto> Interests { get; set; }
}

Program.cs

builder.Services.AddSwaggerGen(options =>
{
    options.OperationFilter<CustomOperationFilter>();
    // 其他配置...
}

MetadataValueModelBinder.cs

public class MetadataValueModelBinder : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext == null)
            throw new ArgumentNullException(nameof(bindingContext));

        var values = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);

        if (values.Length == 0)
            return Task.CompletedTask;
        var options = new JsonSerializerOptions() { PropertyNameCaseInsensitive = true };

        var deserialized = JsonSerializer.Deserialize(values.FirstValue, bindingContext.ModelType, options);

        bindingContext.Result = ModelBindingResult.Success(deserialized);
        return Task.CompletedTask;
    }
}

CustomOperationFilter.cs

public class CustomOperationFilter : IOperationFilter
{
    public void Apply(OpenApiOperation operation, OperationFilterContext context)
    {

        if (operation.RequestBody != null && operation.RequestBody.Content.TryGetValue("multipart/form-data", out var openApiMediaType))
        {
            var options = new JsonSerializerOptions { WriteIndented = true };

            var array = new OpenApiArray
            {
                new OpenApiString(JsonSerializer.Serialize(new InterestDto {InterestId = 0, Name="string"}, options)),
            };

            openApiMediaType.Schema.Properties["Interests"].Example = array;
        };
    }
}

解决方案

1. 修复CustomOperationFilter的KeyNotFoundException

Swashbuckle默认会将C#的PascalCase属性名转换为驼峰式(如Interests→interests),所以原代码中直接访问"Interests"键会找不到。修改如下:

public class CustomOperationFilter : IOperationFilter
{
    public void Apply(OpenApiOperation operation, OperationFilterContext context)
    {
        // 处理multipart/form-data场景
        if (operation.RequestBody != null && operation.RequestBody.Content.TryGetValue("multipart/form-data", out var openApiMediaType))
        {
            if (openApiMediaType.Schema.Properties.TryGetValue("interests", out var interestsProperty))
            {
                var options = new JsonSerializerOptions { WriteIndented = true };
                // 生成JSON数组示例,提示用户输入格式
                var exampleJson = JsonSerializer.Serialize(new List<InterestDto>
                {
                    new InterestDto { InterestId = 0, Name = "阅读" },
                    new InterestDto { InterestId = 1, Name = "运动" }
                }, options);
                
                // 将Schema类型改为string,让Swagger显示文本框而非拆分字段
                interestsProperty.Type = "string";
                interestsProperty.Format = null;
                interestsProperty.Example = new OpenApiString(exampleJson);
            }
        }

        // 处理Query参数场景
        foreach (var parameter in operation.Parameters)
        {
            if (parameter.Name.Equals("interests", StringComparison.OrdinalIgnoreCase))
            {
                var options = new JsonSerializerOptions { WriteIndented = true };
                var exampleJson = JsonSerializer.Serialize(new List<InterestDto>
                {
                    new InterestDto { InterestId = 0, Name = "阅读" }
                }, options);
                parameter.Example = new OpenApiString(exampleJson);
                parameter.Schema.Type = "string";
            }
        }
    }
}

2. 确保ModelBinder兼容Swagger发送的格式

你的MetadataValueModelBinder已经支持将JSON字符串反序列化为List<InterestDto>,修改后的OperationFilter会让Swagger UI将Interests显示为文本框,用户输入JSON数组即可完成正确绑定。

3. 验证Query参数场景

如果Query参数仍有问题,在Dto的Interests属性上添加标注:

[FromQuery(Name = "Interests")]
public List<InterestDto> Interests { get; set; }

配合上面OperationFilter中对Query参数的处理,即可正常解析。

4. 可选:升级Swashbuckle.AspNetCore版本

确保使用最新版本的Swashbuckle.AspNetCore包,新版本优化了复杂对象列表的表单绑定支持,可能减少自定义代码的需求。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 21:28:18