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

如何将multipart/form-data表单数据序列化为模型列表?

解决multipart/form-data请求中列表字段无法绑定到模型的问题

问题场景

提交包含文件与表单数据的multipart/form-data请求时,模型中的Alias、WorkerName和Payload字段可正常序列化,但Parameters、Workflow等列表字段始终为空,不过在Request.Form中能确认列表的参数值已正常发送。

控制器代码

[HttpPost]
[Route("add")]
[Consumes("multipart/form-data")]
public async Task<JobAddDto> Add([FromForm] JobInputInternalDto model)
{
    var form = Request.Form;
    //do stuff with model
}

相关Dto定义

public class JobInputDto
{
    public string Alias { get; set; } = string.Empty;
    public string WorkerName { get; set; } = string.Empty;
    public IEnumerable<ParameterDto> Parameters { get; set; } = new List<ParameterDto>();
    public IEnumerable<WorkflowOptionsDto> Workflow { get; set; } = new List<WorkflowOptionsDto>();
}

public class JobInputInternalDto : JobInputDto
{
    public IFormFile? Payload { get; set; } = null;
}

public class ParameterDto
{
    public int Order { get; set; }
    public string Name { get; set; } = string.Empty;
    public string Value { get; set; } = string.Empty;
}

public class WorkflowOptionsDto
{
    public int Order { get; set; }
    public string WorkerName { get; set; } = string.Empty;
    public string? WorkerVersion { get; set; } = null;

    public IEnumerable<ParameterDto> Parameters { get; set; } = Enumerable.Empty<ParameterDto>();
}

解决方案

方案1:使用索引式表单字段命名(无需修改后端)

ASP.NET Core的[FromForm]绑定列表时,要求表单字段名带上索引序号,以此识别列表项的顺序和层级:

  • 一级列表(如Parameters):字段名格式为Parameters[0].Order、Parameters[0].Name、Parameters[1].Value
  • 嵌套列表(如Workflow内部的Parameters):字段名格式为Workflow[0].WorkerName、Workflow[0].Parameters[0].Name

示例提交的form-data字段:

Alias: testAlias
WorkerName: testWorker
Payload: [选择的文件]
Parameters[0].Order: 1
Parameters[0].Name: param1
Parameters[0].Value: value1
Parameters[1].Order: 2
Parameters[1].Name: param2
Parameters[1].Value: value2
Workflow[0].Order: 1
Workflow[0].WorkerName: flowWorker1
Workflow[0].Parameters[0].Order: 1
Workflow[0].Parameters[0].Name: flowParam1
Workflow[0].Parameters[0].Value: flowValue1

只要前端按此格式提交,后端模型会自动完成列表绑定。

方案2:将列表序列化为JSON字符串提交

如果前端不方便按索引命名字段,可将列表转为JSON字符串,后端通过手动反序列化或自定义绑定器完成绑定。

方式A:手动反序列化(快速实现)

修改Dto,新增用于接收JSON字符串的字段,在控制器中手动反序列化:

public class JobInputDto
{
    public string Alias { get; set; } = string.Empty;
    public string WorkerName { get; set; } = string.Empty;
    // 接收前端提交的JSON字符串
    public string ParametersJson { get; set; } = string.Empty;
    public string WorkflowJson { get; set; } = string.Empty;
    // 原列表字段,后续赋值
    public IEnumerable<ParameterDto> Parameters { get; set; } = new List<ParameterDto>();
    public IEnumerable<WorkflowOptionsDto> Workflow { get; set; } = new List<WorkflowOptionsDto>();
}

控制器中处理反序列化:

[HttpPost]
[Route("add")]
[Consumes("multipart/form-data")]
public async Task<JobAddDto> Add([FromForm] JobInputInternalDto model)
{
    var form = Request.Form;
    // 反序列化Parameters
    if (!string.IsNullOrEmpty(model.ParametersJson))
    {
        model.Parameters = JsonSerializer.Deserialize<IEnumerable<ParameterDto>>(model.ParametersJson) ?? new List<ParameterDto>();
    }
    // 反序列化Workflow
    if (!string.IsNullOrEmpty(model.WorkflowJson))
    {
        model.Workflow = JsonSerializer.Deserialize<IEnumerable<WorkflowOptionsDto>>(model.WorkflowJson) ?? new List<WorkflowOptionsDto>();
    }
    //do stuff with model
}

前端提交时,将Parameters和Workflow列表序列化为JSON字符串,分别作为ParametersJson和WorkflowJson字段的值发送。

方式B:自定义模型绑定器(无需修改Dto结构)

创建通用的JSON列表绑定器:

public class JsonFormListBinder<T> : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext == null)
            throw new ArgumentNullException(nameof(bindingContext));

        var fieldName = bindingContext.FieldName;
        var valueProviderResult = bindingContext.ValueProvider.GetValue(fieldName);

        if (valueProviderResult == ValueProviderResult.None)
            return Task.CompletedTask;

        bindingContext.ModelState.SetModelValue(fieldName, valueProviderResult);

        var value = valueProviderResult.FirstValue;
        if (string.IsNullOrEmpty(value))
            return Task.CompletedTask;

        try
        {
            var model = JsonSerializer.Deserialize<IEnumerable<T>>(value, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
            bindingContext.Result = ModelBindingResult.Success(model);
        }
        catch (JsonException ex)
        {
            bindingContext.ModelState.TryAddModelError(fieldName, $"JSON反序列化失败: {ex.Message}");
        }

        return Task.CompletedTask;
    }
}

在Dto的列表字段上应用绑定器:

public class JobInputDto
{
    public string Alias { get; set; } = string.Empty;
    public string WorkerName { get; set; } = string.Empty;

    [ModelBinder(BinderType = typeof(JsonFormListBinder<ParameterDto>))]
    public IEnumerable<ParameterDto> Parameters { get; set; } = new List<ParameterDto>();

    [ModelBinder(BinderType = typeof(JsonFormListBinder<WorkflowOptionsDto>))]
    public IEnumerable<WorkflowOptionsDto> Workflow { get; set; } = new List<WorkflowOptionsDto>();
}

前端直接提交Parameters和Workflow字段的JSON字符串,后端会自动完成列表绑定。

方案3:混合绑定(文件+JSON整体)

将除文件外的所有数据序列化为一个JSON字段,控制器单独接收文件和JSON字符串,再合并为模型:

[HttpPost]
[Route("add")]
[Consumes("multipart/form-data")]
public async Task<JobAddDto> Add([FromForm] IFormFile? payload, [FromForm] string jobData)
{
    var jobDto = JsonSerializer.Deserialize<JobInputDto>(jobData, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }) ?? new JobInputDto();
    // 手动赋值文件字段
    var model = new JobInputInternalDto
    {
        Alias = jobDto.Alias,
        WorkerName = jobDto.WorkerName,
        Parameters = jobDto.Parameters,
        Workflow = jobDto.Workflow,
        Payload = payload
    };
    //do stuff with model
}

前端提交时,将Alias、WorkerName、Parameters、Workflow序列化为JSON字符串作为jobData字段,同时提交payload文件。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 05:24:20