Rodi
Rodi

Reputation: 122

ASP.NET MVC Partiel view model binding

I am quite new to ASP.NET MVC, and I am working on a small quiz app project. However I am stuck with a rather annoying problem.

Right now I have a few models, with questions, choices and the quiz. The quiz is connected to a form. I use partial views in the forms to display each question, in the question I use another partial view to display the choices.

Whenever I try to get which answers were given from the form, I get 0 and null back. I have tried a lot of things, but none of them have worked.

Here are my classes

Choice

    public class Choice
{
    public int Id { get; set; }
    public string Text { get; set; }
    public bool IsAnswer { get; set; }
    private Question question = new Question();
    public bool IsSelected { get; set; }

    public Question Question
    {
        get { return question; }
        set { question = value; }
    }
}

Question

    public class Question
{
    public int Id { get; set; }
    private IList<Choice> choices = new List<Choice>();
    public string Text { get; set; }
    public int OrderNumber { get; set; }

    public IList<Choice> Choices
    {
        get { return choices; }
        set { choices = value; }
    }
}

Quiz

    public class Quiz
{
    public int Id { get; set; }
    public IList<Question> questions = new List<Question>();
    public string Name { get; set; }

    public IList<Question> Questions
    {
        get { return questions; }
        set { questions = value; }
    }
}

My controller. It does always set 'zzz' to null and 0's.

    [HttpPost]
    public ActionResult Grade(Quiz zzz)
    {
        var grade = quizService.Grade(zzz);
        return View(grade);
    }

The views. I used to not have the ViewDataDictionary, but that gave nulls too. I changed it to a ViewDataDictionary, but it still gives nulls back.

Note: I did add the comments right now, they are not in the actual code

Publish

@model QuizApp.Models.Quiz
@{ ViewBag.Title = "Publish";}

@Html.Hidden("Id", Model.Id)
@using (Html.BeginForm("Grade","Quiz", FormMethod.Post))
{
    foreach (var question in Model.Questions){
       @Html.Partial("_Question", question, new ViewDataDictionary(){
           TemplateInfo = new TemplateInfo()
           { HtmlFieldPrefix = "Questions" }
       });
    }
    <input type="submit" value="Vesturen" />
}

Question

@model QuizApp.Models.Question
<div class="question">
@Html.Hidden("Quiz.Questions[" + Model.OrderNumber + "].Id", Model.Id)

//Shows the question
<b>@Model.Text </b><br/> 

//Adds choices to the question
@foreach (var choice in Model.Choices)
{
    @Html.Partial("_Choice", choice, new ViewDataDictionary()
{
    TemplateInfo = new TemplateInfo()
    { HtmlFieldPrefix = "Choices" }
});
}
</div>

And finally the choice Partial view

@model QuizApp.Models.Choice

@Html.RadioButton("Quiz.Questions[" + Model.Question.OrderNumber + "].Choices[0].Id", Model.Id)
@Model.Text

Does anyone see a solution to my problem, or maybe a workaround? Did I do something wrong, that I obviously do not know about? I don't understand why it keeps giving me nulls, since it should be working now to my knowledge.

Upvotes: 1

Views: 224

Answers (1)

Chris Pratt
Chris Pratt

Reputation: 239260

The following will work:

for (var i = 0; i < Model.Questions.Count(); i++){
   @Html.Partial("_Question", question, new ViewDataDictionary(){
       TemplateInfo = new TemplateInfo()
       { HtmlFieldPrefix = "Questions[" + i.ToString() + "]" }
   });
}

In other words, your field prefix needs to contain the index. Also, the index cannot be an arbitrary value, as you're attempting to do with Model.Question.OrderNumber. It needs to be the actual index within the list.

Alternatively, if you create the view Views\Shared\EditorTemplates\Question.cshtml along the lines of:

@model Namespace.To.Question

@Html.HiddenFor(m => m.Id)
...

Then, in your view, you can simple do:

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

And Razor will render that editor template for each item in Questions, all prefixed properly. Rinse and repeat with your other classes like Choice, etc.

Upvotes: 2

Related Questions