ASP.NET中ModelState始终无效:Id与外键为空问题排查
问题描述
我有两个模型:Poll和PollOption。在Create动作中,需要向数据库添加一个Poll实例和一组PollOption实例,每个PollOption都要包含外键PollId。提交表单时,虽然在HTML里加了隐藏输入框,但表中的Id和外键始终为空,导致ModelState一直无法通过验证,这是什么原因?
模型代码
public class Poll { [Key] public int Id { get; set; } [Required] public string Question { get; set; } = ""; } public class PollOption { [Key] public int Id { get; set; } [Required] public string Option { get; set; } = ""; [Required] public int PollId { get; set; } [ForeignKey("PollId")] [ValidateNever] public Poll Poll { get; set; } }
视图模型代码
public class PollVM { public Poll Poll { get; set; } [ValidateNever] public List<PollOption> PollOptions { get; set; } }
动作方法代码
[HttpPost] [ValidateAntiForgeryToken] public IActionResult Create(PollVM pollVM) { if(ModelState.IsValid) { pollVM.PollOptions.ForEach(p => p.Poll = pollVM.Poll); _unitOfWork.PollOption.AddRange(pollVM.PollOptions); _unitOfWork.Save(); return RedirectToAction("Index"); } return View(pollVM); }
视图代码
@model PollVM <div class="index-container"> <div class="subcontainer"> <h1 class="title">Poll Creator</h1> <h2 class="subtitle">Complete the form below to create your poll!</h2> <form method="POST" asp-action="Create"> <input asp-for="Poll.Id" type="hidden" /> <div class="form-container"> <div class="form-input"> <label asp-for="Poll.Question">Question:</label> <input asp-for="Poll.Question" type="text" placeholder="Type the poll question here" required> </div> <div class="form-input"> <div class="form-input form-options"> <label>Answer Options:</label> <div class="form-option"> <input name="PollOptions[0].Id" type="hidden" /> <input name="PollOptions[0].PollId" type="hidden" /> <input name="PollOptions[0].Option" type="text" placeholder="Option 1" required> <img src="~/images/icons/cross.svg" alt="remove-option" width="30px" class="remove-option" onclick="removeOption(this)"> </div> <div class="form-option"> <input name="PollOptions[1].Id" type="hidden" /> <input name="PollOptions[1].PollId" type="hidden" /> <input name="PollOptions[1].Option" type="text" placeholder="Option 2" required> <img src="~/images/icons/cross.svg" alt="remove-option" width="30px" class="remove-option" onclick="removeOption(this)"> </div> </div> <button type="button" class="button side-button icon-button">Add Option <img src="~/images/icons/plus.svg" alt="add-option" width="25px"></button> </div> <button type="submit" class="button submit-button">Create!</button> </div> </form> </div> </div> @section Scripts { <script src="~/js/answer-options.js" asp-append-version="true"></script> }
问题原因与修复方案
核心问题
PollId必填验证冲突:PollOption的PollId标记了[Required],但创建Poll时这个Id还没生成(自增主键需保存后才会有值),前端提交时PollId为空,直接触发验证失败。- 隐藏输入框无实际值:视图里的
PollId隐藏框没有赋值,提交后自然为空,而此时Poll还未存入数据库,也没法提前获取Id。 - 动作方法逻辑顺序错误:当前代码先添加
PollOption,但Poll还没保存,没有生成有效Id,关联关系根本建立不了。
修复步骤
1. 调整模型验证
给PollOption的PollId保留[Required](保证数据库层面的完整性),但不需要前端提交这个值,后端创建时手动赋值即可。
2. 修改动作方法逻辑
先保存Poll实例,拿到生成的Id后,再给每个PollOption设置PollId,最后保存选项。
3. 移除不必要的隐藏输入
前端不需要提交Poll.Id、PollOption.Id和PollOption.PollId,这些值要么由数据库自增生成,要么由后端处理。
修改后的代码示例
模型代码(仅调整PollOption的注释)
public class PollOption { [Key] public int Id { get; set; } [Required] public string Option { get; set; } = ""; // 保留必填验证,后端创建时赋值 public int PollId { get; set; } [ForeignKey("PollId")] [ValidateNever] public Poll Poll { get; set; } }
视图模型代码(初始化列表避免空引用)
public class PollVM { public Poll Poll { get; set; } [ValidateNever] public List<PollOption> PollOptions { get; set; } = new List<PollOption>(); }
动作方法代码(调整保存顺序)
[HttpPost] [ValidateAntiForgeryToken] public IActionResult Create(PollVM pollVM) { if(ModelState.IsValid) { // 先保存Poll,获取自动生成的Id _unitOfWork.Poll.Add(pollVM.Poll); _unitOfWork.Save(); // 给每个选项设置关联的PollId foreach(var option in pollVM.PollOptions) { option.PollId = pollVM.Poll.Id; } _unitOfWork.PollOption.AddRange(pollVM.PollOptions); _unitOfWork.Save(); return RedirectToAction("Index"); } return View(pollVM); }
视图代码(移除多余隐藏框)
@model PollVM <div class="index-container"> <div class="subcontainer"> <h1 class="title">Poll Creator</h1> <h2 class="subtitle">Complete the form below to create your poll!</h2> <form method="POST" asp-action="Create"> <div class="form-container"> <div class="form-input"> <label asp-for="Poll.Question">Question:</label> <input asp-for="Poll.Question" type="text" placeholder="Type the poll question here" required> </div> <div class="form-input"> <div class="form-input form-options"> <label>Answer Options:</label> <div class="form-option"> <input asp-for="PollOptions[0].Option" type="text" placeholder="Option 1" required> <img src="~/images/icons/cross.svg" alt="remove-option" width="30px" class="remove-option" onclick="removeOption(this)"> </div> <div class="form-option"> <input asp-for="PollOptions[1].Option" type="text" placeholder="Option 2" required> <img src="~/images/icons/cross.svg" alt="remove-option" width="30px" class="remove-option" onclick="removeOption(this)"> </div> </div> <button type="button" class="button side-button icon-button">Add Option <img src="~/images/icons/plus.svg" alt="add-option" width="25px"></button> </div> <button type="submit" class="button submit-button">Create!</button> </div> </form> </div> </div> @section Scripts { <script src="~/js/answer-options.js" asp-append-version="true"></script> }
内容的提问来源于stack exchange,提问作者Khaled Al Hariri
相关产品推荐
相关产品推荐

