ASP.NET Core中通过jQuery Ajax传递含表格数据的表单至控制器问题
问题解决方案
核心原因
ASP.NET Core的模型绑定规则比传统MVC更严格,尤其对集合类型的命名格式、请求内容类型的要求更明确,你遇到的表格数据为null的问题,大概率是表单字段命名不符合绑定规则或Ajax请求的序列化/内容类型配置错误导致的。
分步修复
1. 确保ViewModel集合属性初始化
在主ViewModel中初始化集合属性,避免绑定时空引用:
public class UserFormViewModel { public string UserName { get; set; } public string Department { get; set; } // 初始化集合,防止绑定后为null public List<DealerHistoryViewModel> DealerHistories { get; set; } = new List<DealerHistoryViewModel>(); } public class DealerHistoryViewModel { public string DealerName { get; set; } public DateTime? StartDate { get; set; } public DateTime? EndDate { get; set; } }
2. 修正视图中表格字段的name属性
表格行的表单元素name必须严格遵循 集合属性名[索引].子属性名 的格式,且索引必须连续(0,1,2...),不能断号:
<form id="userForm"> <!-- 普通表单字段 --> <input type="text" name="UserName" placeholder="用户名" /> <select name="Department"> <option value="销售部">销售部</option> <option value="市场部">市场部</option> </select> <!-- 经销商历史表格 --> <table id="dealerTable"> <thead> <tr> <th>经销商名称</th> <th>开始日期</th> <th>结束日期</th> <th>操作</th> </tr> </thead> <tbody> <!-- 初始行,索引从0开始 --> <tr> <td><input type="text" name="DealerHistories[0].DealerName" /></td> <td><input type="date" name="DealerHistories[0].StartDate" /></td> <td><input type="date" name="DealerHistories[0].EndDate" /></td> <td><button type="button" class="removeRow">删除</button></td> </tr> </tbody> </table> <button type="button" id="addRow">添加行</button> <button type="button" id="submitForm">提交</button> </form>
3. 修正添加/删除行的脚本逻辑
添加行时要生成连续的索引,删除行后必须重新整理所有行的索引,避免断号导致模型绑定失败:
// 添加新行 $('#addRow').click(function() { var rowCount = $('#dealerTable tbody tr').length; var newRow = ` <tr> <td><input type="text" name="DealerHistories[${rowCount}].DealerName" /></td> <td><input type="date" name="DealerHistories[${rowCount}].StartDate" /></td> <td><input type="date" name="DealerHistories[${rowCount}].EndDate" /></td> <td><button type="button" class="removeRow">删除</button></td> </tr>`; $('#dealerTable tbody').append(newRow); }); // 删除行并重设索引 $(document).on('click', '.removeRow', function() { $(this).closest('tr').remove(); // 重新设置所有行的索引 $('#dealerTable tbody tr').each(function(index) { $(this).find('input[name^="DealerHistories"]').each(function() { var newName = $(this).attr('name').replace(/\[(\d+)\]/, `[${index}]`); $(this).attr('name', newName); }); }); });
4. 修正Ajax请求与控制器配置
根据数据传递方式选择对应配置:
方式一:表单编码格式(推荐,和传统MVC逻辑一致)
直接用serialize()序列化表单,控制器参数标记[FromForm]:
$('#submitForm').click(function(e) { e.preventDefault(); var formData = $('#userForm').serialize(); $.ajax({ url: '/User/SaveUser', type: 'POST', data: formData, success: function(res) { console.log('提交成功'); }, error: function(xhr) { console.error('提交失败'); } }); });
控制器代码:
[HttpPost] public IActionResult SaveUser([FromForm] UserFormViewModel model) { if (ModelState.IsValid) { // 处理业务逻辑 return Ok("保存成功"); } return BadRequest(ModelState); }
方式二:JSON格式传递
如果需要用JSON传递,需手动构建包含数组的JSON对象,控制器参数标记[FromBody]:
$('#submitForm').click(function(e) { e.preventDefault(); // 构建表单数据对象 var formData = { UserName: $('input[name="UserName"]').val(), Department: $('select[name="Department"]').val(), DealerHistories: [] }; // 收集表格数据 $('#dealerTable tbody tr').each(function() { formData.DealerHistories.push({ DealerName: $(this).find('input[name$=".DealerName"]').val(), StartDate: $(this).find('input[name$=".StartDate"]').val(), EndDate: $(this).find('input[name$=".EndDate"]').val() }); }); $.ajax({ url: '/User/SaveUser', type: 'POST', contentType: 'application/json', data: JSON.stringify(formData), success: function(res) { console.log('提交成功'); }, error: function(xhr) { console.error('提交失败'); } }); });
控制器代码:
[HttpPost] public IActionResult SaveUser([FromBody] UserFormViewModel model) { if (ModelState.IsValid) { // 处理业务逻辑 return Ok("保存成功"); } return BadRequest(ModelState); }
常见错误排查点
- 表格字段name格式错误:比如写成
DealerName[0]而不是DealerHistories[0].DealerName - 索引断号:删除行后未重新整理索引,导致绑定跳过后续行
- 请求contentType不匹配:用了JSON序列化但没设置
contentType: 'application/json',或表单编码格式却用了[FromBody] - ViewModel集合未初始化:未给
DealerHistories赋值新的List,导致绑定后仍为null
内容的提问来源于stack exchange,提问作者Subho
相关产品推荐
相关产品推荐

