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

ASP.NET MVC如何同时绑定对象与数组并提交至控制器?

问题分析与ASP.NET MVC原生解决方案

现有方案的问题根源

  1. 415不支持的媒体类型错误:控制器方法同时接受表单绑定的PaymentCondition和[FromBody]标记的列表,ASP.NET无法同时解析两种请求格式(表单为application/x-www-form-urlencoded,JSON为application/json),且你的AJAX仅发送了列表数据,未传递PaymentCondition字段,导致参数绑定失败。
  2. 两次请求:<input type="submit">点击时会先触发onclick中的AJAX请求,随后浏览器默认触发表单提交,因此产生两次请求。

方案一:原生表单+ViewModel(非异步提交)

这是ASP.NET MVC最原生的实现方式,通过ViewModel封装主模型与列表数据,利用自动模型绑定完成提交,无需额外AJAX代码。

1. 定义ViewModel

将主模型和列表数据封装到同一个ViewModel中:

public class PaymentConditionWithInstalmentsViewModel
{
    public string Name { get; set; }
    public decimal Fee { get; set; }
    public decimal Discount { get; set; }
    public decimal Fine { get; set; }
    public int? Id { get; set; }

    public List<InstalmentDTO> Instalments { get; set; } = new List<InstalmentDTO>();
}

public class InstalmentDTO
{
    public int Number { get; set; }
    public int Days { get; set; }
    public decimal Percentage { get; set; }
    public int PaymentMethodId { get; set; }
}

2. 控制器代码

移除[FromBody]标记,直接接收ViewModel参数:

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> CreateWithInstalment(PaymentConditionWithInstalmentsViewModel model)
{
    if (ModelState.IsValid)
    {
        // 转换ViewModel为EF实体
        var paymentCondition = new PaymentCondition
        {
            Name = model.Name,
            Fee = model.Fee,
            Discount = model.Discount,
            Fine = model.Fine,
            Id = model.Id ?? 0
        };

        // 处理分期付款列表
        foreach (var instalmentDto in model.Instalments)
        {
            // 转换为实体并关联到paymentCondition
        }

        await _context.SaveChangesAsync();
        return RedirectToAction(nameof(Index));
    }
    return View(model);
}

3. 强类型视图代码

通过循环生成列表字段,ASP.NET会自动处理列表的模型绑定:

@model PaymentConditionWithInstalmentsViewModel

<form asp-action="CreateWithInstalment" method="post">
    <!-- 主模型字段 -->
    <div class="form-group">
        <label asp-for="Name"></label>
        <input asp-for="Name" class="form-control" />
        <span asp-validation-for="Name" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="Fee"></label>
        <input asp-for="Fee" class="form-control" />
        <span asp-validation-for="Fee" class="text-danger"></span>
    </div>
    <!-- 省略Discount、Fine、Id等字段 -->

    <!-- 分期付款列表容器 -->
    <div id="instalments-container">
        @for (int i = 0; i < Model.Instalments.Count; i++)
        {
            <div class="instalment-item">
                <h5>分期付款 @(i+1)</h5>
                <input type="hidden" asp-for="Instalments[i].Number" />
                <div class="form-group">
                    <label asp-for="Instalments[i].Days"></label>
                    <input asp-for="Instalments[i].Days" class="form-control" />
                </div>
                <div class="form-group">
                    <label asp-for="Instalments[i].Percentage"></label>
                    <input asp-for="Instalments[i].Percentage" class="form-control" step="0.01" />
                </div>
                <div class="form-group">
                    <label asp-for="Instalments[i].PaymentMethodId"></label>
                    <select asp-for="Instalments[i].PaymentMethodId" class="form-control"></select>
                </div>
                <button type="button" class="btn btn-danger" onclick="removeInstalment(this)">删除</button>
            </div>
        }
    </div>

    <button type="button" class="btn btn-secondary" onclick="addInstalment()">添加分期付款</button>
    <button type="submit" class="btn btn-primary">创建</button>
</form>

<script>
    // 动态添加分期付款项
    function addInstalment() {
        const container = document.getElementById('instalments-container');
        const itemCount = container.children.length;
        const newItem = document.createElement('div');
        newItem.className = 'instalment-item';
        newItem.innerHTML = `
            <h5>分期付款 ${itemCount + 1}</h5>
            <input type="hidden" name="Instalments[${itemCount}].Number" value="${itemCount + 1}" />
            <div class="form-group">
                <label>天数</label>
                <input type="number" name="Instalments[${itemCount}].Days" class="form-control" />
            </div>
            <div class="form-group">
                <label>百分比</label>
                <input type="number" step="0.01" name="Instalments[${itemCount}].Percentage" class="form-control" />
            </div>
            <div class="form-group">
                <label>支付方式ID</label>
                <select name="Instalments[${itemCount}].PaymentMethodId" class="form-control"></select>
            </div>
            <button type="button" class="btn btn-danger" onclick="removeInstalment(this)">删除</button>
        `;
        container.appendChild(newItem);
    }

    // 删除分期付款项并更新索引
    function removeInstalment(btn) {
        btn.parentElement.remove();
        const items = document.querySelectorAll('.instalment-item');
        items.forEach((item, index) => {
            const inputs = item.querySelectorAll('input, select');
            inputs.forEach(input => {
                input.name = input.name.replace(/Instalments\[\d+\]/, `Instalments[${index}]`);
            });
            item.querySelector('h5').textContent = `分期付款 ${index + 1}`;
        });
    }
</script>

方案二:Ajax.BeginForm(异步提交)

如果需要异步提交体验,可使用ASP.NET MVC原生的Ajax.BeginForm,无需手动编写jQuery AJAX逻辑。

1. 引入依赖脚本

确保视图中引入以下脚本:

<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/jquery-ajax-unobtrusive/jquery.unobtrusive-ajax.min.js"></script>

2. 视图代码

替换普通表单为Ajax.BeginForm:

@model PaymentConditionWithInstalmentsViewModel

@using (Ajax.BeginForm("CreateWithInstalment", "PaymentConditions", new AjaxOptions {
    HttpMethod = "POST",
    OnSuccess = "handleSubmitSuccess",
    OnFailure = "handleSubmitFailure"
}, new { id = "paymentForm" }))
{
    @Html.AntiForgeryToken()

    <!-- 主模型字段和列表字段与方案一完全一致 -->

    <button type="button" class="btn btn-secondary" onclick="addInstalment()">添加分期付款</button>
    <button type="submit" class="btn btn-primary">创建</button>
}

<script>
    // addInstalment和removeInstalment函数与方案一相同

    function handleSubmitSuccess() {
        console.log("提交成功");
        // 处理成功逻辑,如刷新页面或跳转
    }

    function handleSubmitFailure() {
        console.log("提交失败");
        // 处理失败逻辑,如提示错误
    }
</script>

3. 控制器代码

与方案一完全相同,无需修改。

内容的提问来源于stack exchange,提问作者Eduardo H Anacleto

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 12:07:06