如何将foreach生成的单选框值传入ASP.NET Core MVC控制器?
问题解决:ASP.NET Core MVC 收集动态单选框答案
问题背景
我有一个存储在数据库中的试题列表测试,试题数量未知,要求用户必须回答所有问题才能提交(类似Google Forms),需使用单选按钮选择答案。目前已通过视图生成动态单选框组,但不清楚如何在控制器的Vote动作中获取所有单选框的值,尝试的视图模型绑定始终为空。
当前视图代码:
@{ string title = "Тест"; ViewBag.Title = title; IEnumerable<Question>? questions = ViewBag.Questions; IEnumerable<Answer>? answers = ViewBag.Answers; } <form class="m-5" asp-area="" asp-controller="Test" asp-action="Vote" method="post"> @foreach (var question in questions) { <div class="mb-3"> <h5>@question.Content</h5> @foreach (var answer in question.Answers) { <div class="form-check"> <input class="form-check-input" type="radio" name="radio-group-@question.Id" id="@answer.Id" value="option-@answer.Id"> <label class="form-check-label" for="@answer.Id"> @answer.Content </label> </div> } </div> } <button class="btn btn-primary w-100 py-2" type="submit">Отправить</button> </form>
控制器代码:
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using StudyExamExample.Domain; using StudyExamExample.Models; namespace StudyExamExample.Controllers; public class TestController(DataManager dataManager) : Controller { private readonly DataManager _dataManager = dataManager; [Authorize] public async Task<IActionResult> Index() { ViewBag.Questions = await _dataManager.QuestionsRepository.GetEntitiesAsync(); ViewBag.Answers = await _dataManager.AnswersRepository.GetEntitiesAsync(); return View(); } [Authorize] public async Task<IActionResult> Vote(/* 需要填写参数 */) { return View(); } }
浏览器提交的数据格式(ID为负数是数据库特殊设置):
radio-group--5=option--15 radio-group--4=option--12 radio-group--3=option--8 radio-group--2=option--6 radio-group--1=option--3
解决方案
方案一:直接通过Request.Form获取所有提交值
不需要修改现有视图,直接在控制器中读取表单数据:
[Authorize] [HttpPost] public async Task<IActionResult> Vote() { // 遍历所有表单键,筛选出单选框组的提交值 var userAnswers = new Dictionary<int, int>(); foreach (var key in Request.Form.Keys) { if (key.StartsWith("radio-group-")) { // 提取问题ID(去掉前缀,处理负号) if (int.TryParse(key.Replace("radio-group-", ""), out int questionId)) { // 提取答案ID(去掉value前缀) var value = Request.Form[key]; if (int.TryParse(value.Replace("option-", ""), out int answerId)) { userAnswers.Add(questionId, answerId); } } } } // 验证是否所有问题都已回答 var allQuestions = await _dataManager.QuestionsRepository.GetEntitiesAsync(); if (userAnswers.Count != allQuestions.Count()) { ModelState.AddModelError("", "请回答所有问题"); ViewBag.Questions = allQuestions; ViewBag.Answers = await _dataManager.AnswersRepository.GetEntitiesAsync(); return View("Index"); } // 这里可以处理用户答案,比如存入数据库 // ... return RedirectToAction("Result"); // 跳转结果页 }
方案二:修改视图和模型实现强类型绑定
如果希望使用强类型模型绑定,需要调整视图中单选框的name属性,使其匹配模型结构:
1. 创建合适的视图模型
namespace StudyExamExample.Models; // 单个问题的答案提交模型 public class QuestionAnswerViewModel { public int QuestionId { get; set; } public int SelectedAnswerId { get; set; } } // 整个测试的提交模型 public class TestSubmissionViewModel { public List<QuestionAnswerViewModel> Answers { get; set; } = new(); }
2. 修改视图生成正确的表单name属性
@{ string title = "Тест"; ViewBag.Title = title; IEnumerable<Question>? questions = ViewBag.Questions; } <form class="m-5" asp-area="" asp-controller="Test" asp-action="Vote" method="post"> @for (int i = 0; i < questions.Count(); i++) { var question = questions.ElementAt(i); <div class="mb-3"> <h5>@question.Content</h5> <!-- 隐藏字段存储问题ID --> <input type="hidden" name="Answers[@i].QuestionId" value="@question.Id" /> @foreach (var answer in question.Answers) { <div class="form-check"> <input class="form-check-input" type="radio" name="Answers[@i].SelectedAnswerId" id="answer_@question.Id_@answer.Id" value="@answer.Id"> <label class="form-check-label" for="answer_@question.Id_@answer.Id"> @answer.Content </label> </div> } </div> } <button class="btn btn-primary w-100 py-2" type="submit">Отправить</button> </form>
3. 控制器中接收模型并验证
[Authorize] [HttpPost] public async Task<IActionResult> Vote(TestSubmissionViewModel submission) { if (!ModelState.IsValid) { ViewBag.Questions = await _dataManager.QuestionsRepository.GetEntitiesAsync(); ViewBag.Answers = await _dataManager.AnswersRepository.GetEntitiesAsync(); return View("Index"); } // 验证是否所有问题都有答案 var allQuestions = await _dataManager.QuestionsRepository.GetEntitiesAsync(); if (submission.Answers.Count != allQuestions.Count()) { ModelState.AddModelError("", "请回答所有问题"); ViewBag.Questions = allQuestions; ViewBag.Answers = await _dataManager.AnswersRepository.GetEntitiesAsync(); return View("Index"); } // 处理用户答案逻辑 // ... return RedirectToAction("Result"); }
额外:前端验证必填项
为了提升用户体验,可以添加前端JS验证,确保所有单选框组都已选择:
<script> document.querySelector('form').addEventListener('submit', function(e) { const radioGroups = {}; // 收集所有单选框组 document.querySelectorAll('input[type="radio"]').forEach(radio => { const groupName = radio.name; if (!radioGroups[groupName]) { radioGroups[groupName] = false; } if (radio.checked) { radioGroups[groupName] = true; } }); // 检查是否有未选择的组 const unselected = Object.values(radioGroups).some(selected => !selected); if (unselected) { e.preventDefault(); alert('请回答所有问题'); } }); </script>
内容的提问来源于stack exchange,提问作者Aycon
相关产品推荐
相关产品推荐

