.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
相关产品推荐
相关产品推荐

