MVC 模型绑定到列表 - 仅适用于列表中的第一项

Posted

技术标签:

【中文标题】MVC 模型绑定到列表 - 仅适用于列表中的第一项【英文标题】:MVC model binding to list - only works on first item in list 【发布时间】:2017-09-24 01:49:19 【问题描述】:

我有一个页面,其中包含多个表单来编辑单个测验的问题,每个问题都有自己的答案列表。因此,对于本测验中的每个问题,用户可以编辑问题(和答案)的表单,见下文:

@model OLTINT.Areas.admin.ViewModels.OldQuizQAViewModel
<h1>Edit @Model.QuizTitle quiz</h1>
<hr />
<p class="breadcrumb">
    @html.ActionLink(HttpUtility.HtmlDecode("&#9668;") + " Back to List", "Quizzes", new  id = Model.CourseID , new  @class = "" )
</p>
@for (int j = 0; j < Model.OldQuizQuestions.Count(); j++)

    using (Ajax.BeginForm("EditQuiz", "Course", null, new AjaxOptions
    
        HttpMethod = "POST",
        InsertionMode = InsertionMode.Replace,
        UpdateTargetId = "button"
    ))
    
        @Html.AntiForgeryToken()
        @Html.HiddenFor(model => model.QuizID)
        @Html.HiddenFor(model => model.OldQuizQuestions[j].QuizQuestionID)

        <p class="form_title">Question number @Model.OldQuizQuestions[j].Order</p>
        <div class="resize_input">@Html.EditorFor(model => model.OldQuizQuestions[j].Question)</div>
        <p class="form_title">@Html.LabelFor(model => model.OldQuizQuestions[j].Type)</p>
        <div class="resize_input">@Html.DropDownListFor(model => model.OldQuizQuestions[j].Type, ViewBag.types, "Please choose...", new  @class = "chosen-select" )</div>

        <p class="form_title">Choose correct answers</p>
        Char x = 'a';
        for (int i = 0; i < Model.OldQuizQuestions[j].OldQuizAnswers.Count(); i++)
        
            x++;
            if (i == 0)
            
                x = 'a';
            
            <div style="display:table; width:100%;">
                <div class="divTableCell" style="padding:0 10px 10px 0; vertical-align:middle; min-width:6%;">
                    @Html.CheckBoxFor(model => model.OldQuizQuestions[j].OldQuizAnswers[i].Correct, new  style = "" )
                    @Html.LabelFor(model => model.OldQuizQuestions[j].OldQuizAnswers[i].Correct, "["+ x +"]")
                </div>
                <div class="divTableCell quiz_input">
                    @Html.HiddenFor(model => model.OldQuizQuestions[j].OldQuizAnswers[i].QuizAnsID)
                    @Html.EditorFor(model => model.OldQuizQuestions[j].OldQuizAnswers[i].Answer)
                </div>
            </div>
        
        <div class="button_container">
            <p id="button"></p>
            @Html.ActionLink("Delete this question", "DeleteQuestion", new  id = Model.OldQuizQuestions[j].QuizQuestionID , new  @class = "button button_red button_not_full_width" )
            <input type="submit" value="Save" class="button button_orange button_not_full_width" />
        </div>
        <hr />
    

OldQuizQAViewModel:

public class OldQuizQAViewModel

    public int CourseID  get; set; 
    public int? QuizID  get; set; 
    public string QuizTitle  get; set; 
    public IList<OldQuizQuestions> OldQuizQuestions  get; set; 

旧测验问题:

public class OldQuizQuestions

    [Key]
    public int QuizQuestionID  get; set; 
    public int OldQuizID  get; set; 
    [Required]
    public string Question  get; set; 
    [Required]
    public int Order  get; set; 
    [Required]
    public int Type  get; set; 

    public virtual IList<OldQuizAnswers> OldQuizAnswers  get; set; 
    public virtual OldQuiz OldQuiz  get; set; 


旧测验答案:

public class OldQuizAnswers

    [Key]
    public int QuizAnsID  get; set; 
    public int QuizQuestionID  get; set; 
    public string Answer  get; set; 
    public int Order  get; set; 
    public bool Correct  get; set; 
    public bool Chosen  get; set; 

    public virtual OldQuizQuestions OldQuizQuestions  get; set; 

控制器:

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult EditQuiz(OldQuizQAViewModel model)
    
        var questiondata = model.OldQuizQuestions.Single();

        if (ModelState.IsValid)
        
            OldQuizQuestions updatequestion = db.OldQuizQuestions
                .SingleOrDefault(x => x.QuizQuestionID == questiondata.QuizQuestionID);

            updatequestion.Question = questiondata.Question;
            updatequestion.Type = questiondata.Type;

            db.Entry(updatequestion).State = EntityState.Modified;
            db.SaveChanges();

            foreach (var answer in questiondata.OldQuizAnswers)
            
                var updateanswer = updatequestion.OldQuizAnswers
                    .First(x => x.QuizAnsID == answer.QuizAnsID);

                updateanswer.Answer = answer.Answer;
                updateanswer.Correct = answer.Correct;

                db.Entry(updateanswer).State = EntityState.Modified;
                db.SaveChanges();
            

            return Content("<span style='font-weight:300; font-size:1.2em; color: green; '>Saved!</span>");
        
        return Content("<span class='errortext'>Please correct the marked fields!</span>");
    

现在,如果我想编辑第一个问题,这可以正常工作,但是当我编辑其他任何内容时,我的控制器只会显示 null 但是当我检查正在发布的数据时,一切都在那里(例如,当我尝试编辑问题 2 时):

我在这里查看了许多关于模型绑定到列表的查询,但没有任何帮助。谁能看出我哪里出了问题?

【问题讨论】:

多种形式的意义何在。您一次只能提交一份表格。只要有一个表格(里面有循环),这一切都会正常工作。默认情况下,DefaultModelBinder 只会将集合与以零开头且连续的索引器绑定,因此唯一符合该条件的表单是第一个。 @StephenMuecke 重点是我不希望每次用户编辑某些内容时都会提交 10 个问题的数据,但谢谢。 然后有链接和编辑页面一次编辑一个问题。您当前的实现永远无法工作,它的 UI 令人困惑,性能也很差 @StephenMuecke 这个用户界面是专门要求的。 所以可怜的用户编辑了一个问题,然后是另一个问题并点击了提交按钮,却发现只有一个编辑被提交并且他们不会更明智(假设您进行了更改以进行此操作工作) 【参考方案1】:

您面临的问题是由于对 asp.net 模型绑定与列表相关的工作方式的误解引起的。例如查看控制器操作 EditQuiz 中使用的视图模型。

public class OldQuizQAViewModel

    public int CourseID  get; set; 
    public int? QuizID  get; set; 
    public string QuizTitle  get; set; 
    public IList<OldQuizQuestions> OldQuizQuestions  get; set; 

为了使模型绑定能够与 IList 或任何其他集合一起使用,asp-net 期望您发布的表单数据具有顺序索引。您通过 POST 发送的表单数据已经有一个实现了集合的模型绑定的工作示例。查看表单数据:

突出显示的属性显示了如何正确绑定到集合,因为您在 OldQuizQAViewModel 中为 IList 的每个索引在 OldQuizAnswers 模型中设置属性 Correct 的值,并在单个请求中一次传递所有这些值。

而在同一个请求中,您只传递您希望将这些值绑定到 IList 集合中的特定索引的 OldQuizQuestions 的数据。

这就是为什么您第一次发布时,模型绑定成功,因为您引用了第一个索引 ([0]),而在第二个 POST 中您引用了第二个索引 (1) 而不是第一个,导致模型绑定失败。

有关模型绑定如何工作的更多信息,请参阅here。

【讨论】:

【参考方案2】:

您是否尝试过添加@Html.HiddenFor(model =&gt; model.OldQuizQuestions[j])

我读到这个“它在表单上为您传递的字段(来自您的模型)创建一个隐藏的输入。

这对于模型/视图模型中的字段很有用,您需要在页面上保留并在进行另一次调用时传回,但用户不应看到这些字段。”

答案来自https://***.com/a/3866720/8404545

可能是这里的问题

【讨论】:

以上是关于MVC 模型绑定到列表 - 仅适用于列表中的第一项的主要内容,如果未能解决你的问题,请参考以下文章

ListView 仅显示数组列表中的第一项

如何绑定到列表中的最后一项?

onchange = "Form.submit() 仅适用于我的下拉菜单中的第一个子下拉菜单

jquery ui自动完成仅显示数据库中的第一项

将复杂模型绑定到 MVC3 中的列表

将 JSON 数组绑定到 ASP.NET MVC 3 中的列表的故障模型