ASP.Net MVC form post can't bind model list property

asp.net-mvc-5 c# entity-framework-6 model-binding razor

Question

I'm trying to create a survey page that can have textboxes and lists of radio buttons or checkbox fields. No matter what I try, I can't get model.Questions property to bind when the form is submitted; the model is created with a null Questions property.

Please tell me you have some idea that can help me!

The view model looks like this:

// Survey view model
public class Question
{
    public int Id { set; get; }
    public string QuestionText { set; get; }

    public bool IsHeading { get; set; }
    public bool IsList { get; set; }
    public bool IsCheckBox { get; set; }

    public List<Answer> Answers { set; get; }
    [Required]
    public string SelectedAnswer { set; get; }
    public string GivenAnswer { get; set; }
    public Question()
    {
        Answers = new List<Answer>();
        GivenAnswer = "-1";
        SelectedAnswer = "-1";
        QuestionText = "";
    }
}

public class Answer
{
    public int Id { set; get; }
    public string AnswerText { set; get; }
}

public class Assessment
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int LessonId { get; set; }
    public Module Lesson { get; set; }

    public SurveyType SurveyType { get; set; }

    public virtual List<Question> Questions { set; get; }
    public Assessment()
    {
        Questions = new List<Question>();
    }
}

The controller method looks like this:

 [HttpPost]
 public ActionResult Survey(Assessment model)
 {
     if (ModelState.IsValid)
     {
         foreach (var q in model.Questions)
         {
             var qId = q.Id;
             var selectedAnswer = q.SelectedAnswer;
             // Save the data 
         }
         return RedirectToAction("ThankYou", new { id = model.Id }); //PRG Pattern
     }
     //to do : reload questions and answers
     return View(model);
 }

Where Assessment is the main view model. Below are the view (Survey.cshtml) and Editor template (EditorTemplates/Question.cshtml, for each Question)

Survey.cshtml:

@model ZTTDD.Models.Assessment
@using ZTTDD.Models

<div class="row">
    <div class="col-md-9">
        @using (Html.BeginForm())
        {
            @Html.HiddenFor(m => m.Id)

            @Html.EditorFor(m => m.Questions)

            <input type="submit" />
        }
    </div>
    <div class="col-md-3">
        <p>
            @Html.ActionLink((string)ViewBag.LessonLinkBackText, "Lesson", "Lessons", new { id = Model.Lesson.Id }, null)
        </p>
        <p>
            @Html.ActionLink(linkName, linkMethod, "Lessons", new { id = Model.Lesson.Id }, null)
        </p>
    </div>
</div>

EditorTemplates/Question.cshtml

@model ZTTDD.Models.Question


@if (Model.IsHeading)
{
    <div class="survey-heading">
        <h4>@Model.QuestionText</h4>
    </div>
    <hr/>
}
@if (!Model.IsHeading)
{
    <div class="form-group">
        @Html.HiddenFor(m => m.Id)

        <p> @Model.QuestionText </p>

        @if (Model.IsList)
        {
            <div class="radio">
                @Html.HiddenFor(m => m.GivenAnswer)
                @foreach (var a in Model.Answers)
                {
                    if (Model.IsCheckBox)
                    {
                    <span class="form-horizontal">
                        @Html.CheckBox(String.Format("Questions_{0}__SelectedAnswer", Model.Id), false, new { value = "", name = String.Format("Questions[{0}].SelectedAnswer", Model.Id) })  @Html.Raw(a.AnswerText) &nbsp;&nbsp;
                    </span>
                    }
                    else
                    {
                        @Html.RadioButtonFor(b => b.SelectedAnswer, a.Id)  @Html.Raw(a.AnswerText) <br />
                    }
                }
            </div>
        }
        @if (!Model.IsList)
        {
            Model.GivenAnswer = "";
            @Html.HiddenFor(m => m.SelectedAnswer)
            @Html.TextAreaFor(m => m.GivenAnswer)
        }
    </div>
}

Here's a list of the keys that get posted:

    [0] "Id"    string
    [1] "Questions[1].Id"   string
    [2] "Questions[1].GivenAnswer"  string
    [3] "Questions[1].SelectedAnswer"   string
    [4] "Questions[2].Id"   string
    [5] "Questions[2].GivenAnswer"  string
    [6] "Questions[2].SelectedAnswer"   string
    [7] "Questions[3].Id"   string
    [8] "Questions[3].GivenAnswer"  string
    [9] "Questions[3].SelectedAnswer"   string
    [10]    "Questions[5].Id"   string
    [11]    "Questions[5].GivenAnswer"  string
    [12]    "Questions[5].SelectedAnswer"   string
    [13]    "Questions[6].Id"   string
    [14]    "Questions[6].GivenAnswer"  string
    [15]    "Questions[6].SelectedAnswer"   string
    [16]    "Questions[7].Id"   string
    [17]    "Questions[7].GivenAnswer"  string
    [18]    "Questions[7].SelectedAnswer"   string
    [19]    "Questions[9].Id"   string
    [20]    "Questions[9].SelectedAnswer"   string
    [21]    "Questions[9].GivenAnswer"  string
    [22]    "Questions[10].Id"  string
    [23]    "Questions[10].SelectedAnswer"  string
    [24]    "Questions[10].GivenAnswer" string

UPDATE:

Just a little clarification: A Question object can represent a heading (no fields associated) or input (either radio buttons, checkboxes or textarea). As far as I can tell, the values I need are posting properly. I'll try to grab them from the debugger and post here.

UPDATE:

This querystring below is from Request.Form.ToString(), with url-decoding and formatting for easier reading. As you can see, the values are posting, but for some reason are not getting bound to Assessment.Questions. Could this in fact be due to the xx value in Questions[xx] because it's not an index value, but an actual Id?

Id=1&
Questions[1].Id=2&
Questions[1].GivenAnswer=-1&
Questions[1].SelectedAnswer=2&
Questions[2].Id=3&
Questions[2].GivenAnswer=-1&
Questions[2].SelectedAnswer=5&
Questions[3].Id=5&
Questions[3].GivenAnswer=-1&
Questions[3].SelectedAnswer=8&
Questions[5].Id=7&
Questions[5].GivenAnswer=-1&
Questions[5].SelectedAnswer=12&
Questions[6].Id=8&
Questions[6].GivenAnswer=-1&
Questions[6].SelectedAnswer=15&
Questions[7].Id=9&
Questions[7].GivenAnswer=-1&
Questions[7].SelectedAnswer=18&
Questions[9].Id=11&
Questions[9].SelectedAnswer=-1&
Questions[9].GivenAnswer=sdfg sdfg dsfg&
Questions[10].Id=12&
Questions[10].SelectedAnswer=-1&
Questions[10].GivenAnswer=sdfg dfsg sdfg

LAST UPDATE:

The issue was the fact that I wasn't posting anything back to the server for Questions that are headings so the Questions index was in fact incomplete. Adding a hidden Id field with each heading fixed the problem.

1
3
1/28/2014 11:49:43 PM

Accepted Answer

I set up a basic project that utilised the code samples you posted, and some basic dummy data, and the questions collection was bound.

I'd guess then that there's something in the data for the particular survey that you're rendering that is causing an issue.

Looking at the code, I can see two possible issues:

  • When Question.IsHeader is true, I don't think anything will get posted back to the form for this question. I would guess then that the model binder doesn't pick up anything for this question. Which, from your form data, would look to be the case - it's missing indices for 0, 4 and 8. I don't actually know if this causes the model binder to fail, but it could do. You could try putting a HiddenFor for the question id, in the section of the Question editor template where IsHeading is true.
  • The @Html.CheckBox code you're using formats the input values using the Id of the question, not the index of the question in the list. I'd guess this will break the binding too.

Hopefully if you can fix these issues, the binding will work. I'd suggest trying it on a survey without any header questions, and without any checkbox or radio button questions, and see if the binding works then.

3
1/28/2014 9:49:53 PM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow