如何将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
相关产品推荐
相关产品推荐

