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

.NET 7 MVC中Grams字段导致ViewModel无法传递至控制器

.NET 7 MVC中Grams字段导致ViewModel无法传递到Post方法的问题

我开发了一个.NET 7 MVC应用,包含动态生成的分组列表,每组带有复选框、2个只读字段和1个数值字段。排查发现,只要表单中包含SelectedCheckboxes[i].Grams数值字段,整个ViewModel就无法传递到控制器的Post方法,无论是Create还是Edit操作均如此。

我已经尝试过以下操作,但都没有解决问题:

  • 检查数据类型
  • 新增独立字段
  • 清除模型状态
  • 修改数据类型(设置默认值、设为可空、标记必填或禁用验证)

相关代码

ViewModel代码

public class RecipeViewModel : BaseViewModel
{
    public RecipeDTO Recipe { get; set; } = new RecipeDTO();
    public List<RecipeDTO> Recipes { get; set; } = new List<RecipeDTO>();
    public List<SelectedCheckboxViewModel> SelectedCheckboxes { get; set; }
    public IEnumerable<Category> Categories { get; set; }
    public bool IsIngredientBeingSwitched { get; set; } = false;
}
public class SelectedCheckboxViewModel
{
    public int? Id { get; set; }
    public bool IsChecked { get; set; }
    public string Name { get; set; }
    public int CategoryId { get; set; }
    public int Grams { get; set; }
    public double Amount { get; set; }
    public Product Product { get; set; } = new Product();
    public string ProductId { get; set; }
    public int TotalKcal { get; set; }
    public double GL { get; set; }
}

控制器方法代码

[HttpPost]
public IActionResult Edit(RecipeViewModel model)
{
    // model here is always null
    if (model == null)
    {
        return RedirectToAction("Index");
    }
}

视图代码

@model RecipeViewModel

@{
    var url = Model.Recipe.Id == 0 ? Url.Action("Create", "Recipe") : Url.Action("Edit", "Recipe");
    url = Model.IsIngredientBeingSwitched && Model.Recipe.Id != 0 ? Url.Action("ChangeIngredients", "Recipe") : url;
}

<div class="container">
    <form method="post" action="@url">
        <div class="mb-3 row">
            <label asp-for="@Model.Recipe.TotalKcal"></label>
            <input id="totalRecipe" asp-for="@Model.Recipe.TotalKcal" class="form-control" readonly/>
            <label asp-for="@Model.Recipe.TotalGL"></label>
            <input id="totalRecipeGL" asp-for="@Model.Recipe.TotalGL" class="form-control" readonly />
        </div>
        <div class="mb-3 row">
            <input hidden asp-for="@Model.Recipe.Id" />
            <input hidden asp-for="@Model.Recipe.MealId" />
            <input hidden asp-for="@Model.Recipe.RecipeDayId" />
            <label asp-for="@Model.Recipe.Name"></label>
            <input asp-for="@Model.Recipe.Name" class="form-control" />
        </div>

        @foreach( Category category in Model.Categories)
        {
            <text>
                <div class="m-2"><h3>@category.Name</h3></div>
                <div class="row">
                    @for (var i = 0; i < Model.SelectedCheckboxes.Count(); i++)
                    {
                        if (Model.SelectedCheckboxes[i].CategoryId == category.Id)
                        {
                            <text>
                                <div class="col-auto">
                                    <div>
                                        <input class="form-number-input" asp-for="@Model.SelectedCheckboxes[i].IsChecked" type="checkbox">
                                        <input hidden asp-for="@Model.SelectedCheckboxes[i].Id" />
                                        <label class="form-check-label" asp-for="@Model.SelectedCheckboxes[i].IsChecked">@Model.SelectedCheckboxes[i].Name</label>
                                    </div>
                                    <div >
                                        <div>
                                            <label class="form-check-label" asp-for="@Model.SelectedCheckboxes[i].Grams">Amount: </label>
                                     <!-- Removing this bad boy solves the issue. However I need this value, too. -->
                                            <input class="form-control-sm" asp-for="@Model.SelectedCheckboxes[i].Grams"/> 
                                        </div>
                                        <div>
                                            <label class="form-check-label" asp-for="@Model.SelectedCheckboxes[i].TotalKcal">Selected kcals: </label>
                                            <input class="form-control-sm total-kcal" asp-for="@Model.SelectedCheckboxes[i].TotalKcal" readonly>
                                        </div>
                                        <div>
                                            <label class="form-check-label" asp-for="@Model.SelectedCheckboxes[i].GL">Load: </label>
                                            <input class="form-control-sm total-gl" asp-for="@Model.SelectedCheckboxes[i].GL" readonly>
                                        </div>
                                    </div>
                                </div>
                            </text>
                        }
                    }
                </div>
            </text>
        }
        <button type="submit" class="btn btn-primary mt-5">Save</button>
    </form>
</div>

解决建议

1. 排查ModelState验证错误

模型绑定失败时,model会为null,但ModelState会包含具体错误信息。修改控制器方法,先输出错误内容:

[HttpPost]
public IActionResult Edit(RecipeViewModel model)
{
    if (!ModelState.IsValid)
    {
        foreach (var error in ModelState.Values.SelectMany(v => v.Errors))
        {
            Console.WriteLine(error.ErrorMessage);
        }
        return View(model);
    }
    if (model == null)
    {
        return RedirectToAction("Index");
    }
    // 后续逻辑
}

通过错误信息可直接定位绑定失败原因(比如Grams字段输入非整数值)。

2. 修正列表绑定的索引连续性问题

当前视图嵌套循环导致表单字段索引不连续(跳过不符合CategoryId的项时,索引断层),而ASP.NET MVC模型绑定要求列表索引必须连续且从0开始。

解决方法:提前按Category分组SelectedCheckboxes,再对每个分组的子列表使用连续索引:

@foreach( var categoryGroup in Model.SelectedCheckboxes.GroupBy(sc => sc.CategoryId))
{
    var category = Model.Categories.First(c => c.Id == categoryGroup.Key);
    <text>
        <div class="m-2"><h3>@category.Name</h3></div>
        <div class="row">
            @for (var i = 0; i < categoryGroup.Count(); i++)
            {
                var item = categoryGroup.ElementAt(i);
                <text>
                    <div class="col-auto">
                        <div>
                            <input class="form-number-input" asp-for="@categoryGroup.ElementAt(i).IsChecked" type="checkbox">
                            <input hidden asp-for="@categoryGroup.ElementAt(i).Id" />
                            <label class="form-check-label" asp-for="@categoryGroup.ElementAt(i).IsChecked">@item.Name</label>
                        </div>
                        <div >
                            <div>
                                <label class="form-check-label" asp-for="@categoryGroup.ElementAt(i).Grams">Amount: </label>
                                <input class="form-control-sm" asp-for="@categoryGroup.ElementAt(i).Grams" type="number" min="0"/> 
                            </div>
                            <!-- 其他字段... -->
                        </div>
                    </div>
                </text>
            }
        </div>
    </text>
}

也可使用EditorTemplate渲染每个SelectedCheckboxViewModel项,自动处理索引问题。

3. 优化Grams字段的输入验证

确保用户只能输入有效的整数:

  • 在视图中设置input的type="number"并添加min="0",限制输入为非负整数
  • 在ViewModel的Grams字段添加验证特性:
[Required]
[Range(0, int.MaxValue, ErrorMessage = "请输入有效的克数")]
public int Grams { get; set; }

4. 排除不必要的绑定字段

SelectedCheckboxViewModel中的Product对象未在表单中提交,模型绑定尝试绑定该对象会导致错误。给它添加[BindNever]特性,告诉框架跳过此字段:

[BindNever]
public Product Product { get; set; } = new Product();

5. 显式指定绑定属性

在控制器方法中使用[Bind]特性,只绑定需要的字段,避免不必要的字段干扰:

[HttpPost]
public IActionResult Edit([Bind("Recipe,SelectedCheckboxes,IsIngredientBeingSwitched")] RecipeViewModel model)
{
    // 后续逻辑
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 14:35:56