如何在非MVC的Razor页面提交JavaScript动态添加的输入值?
解决方案
针对你在Razor Pages中开发动态试题考试创建功能遇到的问题,给你三个可行的解决方案,按需选择:
方案1:前端维护数据,JSON序列化一次性提交
完全在前端动态生成试题表单,最后把所有考试信息(基础字段+试题)序列化成JSON提交,无需频繁发起后端请求。
前端代码(cshtml)
<form method="post"> <div> <label>考试名称:</label> <input id="examName" type="text" required /> </div> <div> <label>考试时长(分钟):</label> <input id="examDuration" type="number" min="1" required /> </div> <div> <label>选择试题类型:</label> <select id="questionType"> <option value="Open">开放题</option> <option value="Choice">选择题</option> <option value="Judge">判断题</option> </select> <button type="button" onclick="addQuestion()">添加试题</button> </div> <div id="questionsContainer"></div> <input type="hidden" id="questionsJson" name="QuestionsJson" /> <button type="submit">创建考试</button> </form> <script> let questionIndex = 0; function addQuestion() { const type = document.getElementById('questionType').value; const container = document.getElementById('questionsContainer'); const questionDiv = document.createElement('div'); questionDiv.className = 'question-item'; questionDiv.dataset.type = type; questionDiv.dataset.index = questionIndex; // 题目内容输入框 questionDiv.innerHTML += `<div><label>题目:</label><input class="q-content" type="text" required /></div>`; // 按类型生成对应字段 if (type === 'Choice') { questionDiv.innerHTML += ` <div class="options"> <div><label>选项1:</label><input class="q-option" type="text" required /></div> <div><label>选项2:</label><input class="q-option" type="text" required /></div> <button type="button" onclick="addMoreOption(this)">添加选项</button> </div> <div><label>正确选项索引:</label><input class="q-correct-index" type="number" min="0" required /></div> `; } else if (type === 'Judge') { questionDiv.innerHTML += ` <div> <label>正确答案:</label> <input type="radio" name="judge-${questionIndex}" value="true" required /> 对 <input type="radio" name="judge-${questionIndex}" value="false" required /> 错 </div> `; } // 删除按钮 questionDiv.innerHTML += `<button type="button" onclick="this.parentElement.remove()">删除</button>`; container.appendChild(questionDiv); questionIndex++; } function addMoreOption(btn) { const optionsDiv = btn.parentElement; const optionCount = optionsDiv.querySelectorAll('.q-option').length + 1; const newOption = document.createElement('div'); newOption.innerHTML = `<label>选项${optionCount}:</label><input class="q-option" type="text" required />`; optionsDiv.insertBefore(newOption, btn); } // 提交前序列化所有数据 document.querySelector('form').addEventListener('submit', function(e) { const examData = { name: document.getElementById('examName').value, duration: parseInt(document.getElementById('examDuration').value), questions: [] }; document.querySelectorAll('.question-item').forEach(item => { const type = item.dataset.type; const content = item.querySelector('.q-content').value; const question = { type, content }; if (type === 'Choice') { question.options = Array.from(item.querySelectorAll('.q-option')).map(opt => opt.value); question.correctIndex = parseInt(item.querySelector('.q-correct-index').value); } else if (type === 'Judge') { question.correctAnswer = item.querySelector(`input[name="judge-${item.dataset.index}"]:checked`).value === 'true'; } examData.questions.push(question); }); document.getElementById('questionsJson').value = JSON.stringify(examData); }); </script>
PageModel代码(cshtml.cs)
public class CreateExamModel : PageModel { [BindProperty] public string QuestionsJson { get; set; } public async Task<IActionResult> OnPostAsync() { if (!ModelState.IsValid) return Page(); // 反序列化提交的数据 var submitData = JsonSerializer.Deserialize<ExamSubmitData>(QuestionsJson); if (submitData == null || !submitData.questions.Any()) { ModelState.AddModelError("", "请至少添加一道试题"); return Page(); } // 转换为你的实体类并写入数据库 var exam = new Exam { Name = submitData.name, Duration = submitData.duration, Questions = submitData.questions.Select(q => { switch(q.type) { case "Open": return new OpenQuestion { Content = q.content }; case "Choice": return new ChoiceQuestion { Content = q.content, Options = q.options, CorrectOptionIndex = q.correctIndex }; case "Judge": return new JudgeQuestion { Content = q.content, CorrectAnswer = q.correctAnswer.Value }; default: throw new ArgumentException("无效的试题类型"); } }).ToList() }; // await _dbContext.Exams.AddAsync(exam); // await _dbContext.SaveChangesAsync(); return RedirectToPage("/Exams/Index"); } // 临时接收类 private class ExamSubmitData { public string name { get; set; } public int duration { get; set; } public List<QuestionSubmitData> questions { get; set; } } private class QuestionSubmitData { public string type { get; set; } public string content { get; set; } public List<string> options { get; set; } public int? correctIndex { get; set; } public bool? correctAnswer { get; set; } } // 你的实体类示例 public class Exam { public int Id { get; set; } public string Name { get; set; } public int Duration { get; set; } public List<BaseQuestion> Questions { get; set; } } public abstract class BaseQuestion { public int Id { get; set; } public string Content { get; set; } } public class OpenQuestion : BaseQuestion { } public class ChoiceQuestion : BaseQuestion { public List<string> Options { get; set; } public int CorrectOptionIndex { get; set; } } public class JudgeQuestion : BaseQuestion { public bool CorrectAnswer { get; set; } } }
方案2:利用ASP.NET模型绑定规则,动态生成带正确name的输入框
ASP.NET支持集合绑定,只要输入框的name属性符合集合名[索引].属性名格式,就能自动绑定到PageModel的集合属性。
PageModel代码
public class CreateExamModel : PageModel { [BindProperty] public ExamViewModel Exam { get; set; } = new ExamViewModel(); public async Task<IActionResult> OnPostAsync() { if (!ModelState.IsValid) return Page(); // 转换为实体并保存 // ... return RedirectToPage("/Exams/Index"); } public class ExamViewModel { public string Name { get; set; } public int Duration { get; set; } public List<QuestionViewModel> Questions { get; set; } = new List<QuestionViewModel>(); } public class QuestionViewModel { public string Type { get; set; } public string Content { get; set; } public List<string> Options { get; set; } = new List<string>(); public int? CorrectOptionIndex { get; set; } public bool? CorrectAnswer { get; set; } } }
前端代码
<form method="post"> <div> <label>考试名称:</label> <input asp-for="Exam.Name" type="text" required /> </div> <div> <label>考试时长(分钟):</label> <input asp-for="Exam.Duration" type="number" min="1" required /> </div> <div> <label>选择试题类型:</label> <select id="questionType"> <option value="Open">开放题</option> <option value="Choice">选择题</option> <option value="Judge">判断题</option> </select> <button type="button" onclick="addQuestion()">添加试题</button> </div> <div id="questionsContainer"></div> <button type="submit">创建考试</button> </form> <script> let questionIndex = 0; function addQuestion() { const type = document.getElementById('questionType').value; const container = document.getElementById('questionsContainer'); const questionDiv = document.createElement('div'); questionDiv.className = 'question-item'; // 隐藏字段存储试题类型 questionDiv.innerHTML += `<input type="hidden" name="Exam.Questions[${questionIndex}].Type" value="${type}" />`; // 题目内容 questionDiv.innerHTML += ` <div> <label>题目:</label> <input name="Exam.Questions[${questionIndex}].Content" type="text" required /> </div> `; if (type === 'Choice') { // 初始生成2个选项 for (let i = 0; i < 2; i++) { questionDiv.innerHTML += ` <div> <label>选项${i+1}:</label> <input name="Exam.Questions[${questionIndex}].Options[${i}]" type="text" required /> </div> `; } // 添加更多选项按钮 questionDiv.innerHTML += `<button type="button" onclick="addChoiceOption(${questionIndex}, this)">添加选项</button>`; // 正确选项索引 questionDiv.innerHTML += ` <div> <label>正确选项索引:</label> <input name="Exam.Questions[${questionIndex}].CorrectOptionIndex" type="number" min="0" required /> </div> `; } else if (type === 'Judge') { // 判断题答案 questionDiv.innerHTML += ` <div> <label>正确答案:</label> <input type="radio" name="Exam.Questions[${questionIndex}].CorrectAnswer" value="true" required /> 对 <input type="radio" name="Exam.Questions[${questionIndex}].CorrectAnswer" value="false" required /> 错 </div> `; } // 删除按钮 questionDiv.innerHTML += `<button type="button" onclick="this.parentElement.remove()">删除</button>`; container.appendChild(questionDiv); questionIndex++; } function addChoiceOption(qIndex, btn) { const optionsCount = btn.parentElement.querySelectorAll('[name^="Exam.Questions['+qIndex+'].Options"]').length; const newOption = document.createElement('div'); newOption.innerHTML = ` <label>选项${optionsCount+1}:</label> <input name="Exam.Questions[${qIndex}].Options[${optionsCount}]" type="text" required /> `; btn.parentElement.insertBefore(newOption, btn); } </script>
方案3:用Session暂存考试数据(解决你之前的加题丢失问题)
如果需要后端参与试题添加逻辑,可把考试对象存在Session中,每次加题时从Session取出修改,再存回去,避免频繁操作数据库。
第一步:配置Session
在Program.cs中添加Session服务并启用:
builder.Services.AddSession(options => { options.IdleTimeout = TimeSpan.FromMinutes(30); options.Cookie.HttpOnly = true; options.Cookie.IsEssential = true; }); // 启用Session(要放在UseRouting之后,UseAuthorization之前) app.UseSession();
PageModel代码
public class CreateExamModel : PageModel { [BindProperty] public Exam NewExam { get; set; } public IActionResult OnGet() { // 从Session取考试数据,没有则初始化 NewExam = HttpContext.Session.Get<Exam>("CurrentExam") ?? new Exam { Questions = new List<BaseQuestion>() }; return Page(); } public IActionResult OnPostAddQuestion(string questionType) { var exam = HttpContext.Session.Get<Exam>("CurrentExam") ?? new Exam { Questions = new List<BaseQuestion>() }; // 添加对应类型的试题 switch (questionType) { case "Open": exam.Questions.Add(new OpenQuestion()); break; case "Choice": exam.Questions.Add(new ChoiceQuestion { Options = new List<string> { "", "" } }); break; case "Judge": exam.Questions.Add(new JudgeQuestion()); break; } // 存回Session HttpContext.Session.Set<Exam>("CurrentExam", exam); return RedirectToPage(); } public async Task<IActionResult> OnPostAsync() { if (!ModelState.IsValid) return Page(); var exam = HttpContext.Session.Get<Exam>("CurrentExam"); if (exam == null || !exam.Questions.Any()) { ModelState.AddModelError("", "请先添加试题"); return Page(); } // 更新基础信息 exam.Name = NewExam.Name; exam.Duration = NewExam.Duration; // 保存到数据库 // await _dbContext.Exams.AddAsync(exam); // await _dbContext.SaveChangesAsync(); // 清除Session HttpContext.Session.Remove("CurrentExam"); return RedirectToPage("/Exams/Index"); } } // Session扩展方法 public static class SessionExtensions { public static void Set<T>(this ISession session, string key, T value) { session.SetString(key, JsonSerializer.Serialize(value)); } public static T Get<T>(this ISession session, string key) { var json = session.GetString(key); return json == null ? default : JsonSerializer.Deserialize<T>(json); } } // 实体类同方案1
前端代码
<form method="post"> <div> <label>考试名称:</label> <input asp-for="NewExam.Name" type="text" required /> </div> <div> <label>考试时长(分钟):</label> <input asp-for="NewExam.Duration" type="number" min="1" required /> </div> <div> <label>选择试题类型:</label> <select id="questionType"> <option value="Open">开放题</option> <option value="Choice">选择题</option> <option value="Judge">判断题</option> </select> <button type="submit" formaction="?handler=AddQuestion" formmethod="post">添加试题</button> </div> <div> <h4>已添加的试题</h4> @for (int i = 0; i < Model.NewExam.Questions.Count; i++) { <div class="question-item"> <input type="hidden" asp-for="NewExam.Questions[i].Type" /> <div> <label>题目:</label> <input asp-for="NewExam.Questions[i].Content" type="text" required /> </div> @if (Model.NewExam.Questions[i] is ChoiceQuestion choiceQ) { for (int j = 0; j < choiceQ.Options.Count; j++) { <div> <label>选项@(j+1):</label> <input asp-for="NewExam.Questions[i].Options[j]" type="text" required /> </div> } <button type="button" onclick="addChoiceOption(this, @i)">添加选项</button> <div> <label>正确选项索引:</label> <input asp-for="NewExam.Questions[i].CorrectOptionIndex" type="number" min="0" required /> </div> } @if (Model.NewExam.Questions[i] is JudgeQuestion judgeQ) { <div> <label>正确答案:</label> <input type="radio" asp-for="
相关产品推荐
相关产品推荐

