3
\$\begingroup\$

I'm trying to get a good grasp on ASP.Net MVC, Razor and RadioButtons, so I tried to make a quiz to test my skills.

I implemented a working quiz form in the following way in Razor (not posting the controller that manages it and just showing the important part of the view file):

 @using (Html.BeginForm("Quiz", "CourseDetail", FormMethod.Post)) {
     <div class="question-list">
         @Html.HiddenFor(m => m.CurrentChapter.ChapterId)
         @for (int i = 0; i < Model.CurrentChapter.Questions.Count; i++) {
             @Html.HiddenFor(m => Model.CurrentChapter.Questions[i].Id)
             <div class="question @(Model.CurrentChapter.Questions[i].Resolved ? "question-resolved" : "")">
             <div class="question-number">@Model.CurrentChapter.Questions[i].Id</div>
             <div class="question-title">@Html.Raw(Model.CurrentChapter.Questions[i].QuestionText)</div>
                 <ul class="question-answers">
                     @for (int j = 0; j < Model.CurrentChapter.Questions[i].QuestionAnswers.Count; j++) {
                         <li class="@(Model.CurrentChapter.Questions[i].Resolved && Model.CurrentChapter.Questions[i].QuestionAnswers[j].Correct ? "correct-answer" : "")">
                             <label>
                                <span class="@(Model.CurrentChapter.Questions[i].Resolved && Model.CurrentChapter.Questions[i].QuestionAnswers[j].Correct && Model.CurrentChapter.Questions[i].AnsweredCorrectly ? "checked" : "")">
                                @Html.RadioButtonFor(m => m.CurrentChapter.Questions[i].SelectedAnswer.Id, Model.CurrentChapter.Questions[i].QuestionAnswers[j].Id) 
                                </span>
                                @Model.CurrentChapter.Questions[i].QuestionAnswers[j].AnswerText
                                </label>
                                </li>
                          }
                  </ul>
                  </div>
              }


     </div>
     <div class="row text-right">
         <div class="span3 offset9">
            <button type="submit" class="btn btn-primary btn-large btn-block">Compruebe los resultados</button>
          </div>
     </div>
 }

However, this is quite bloated to be put on a single file although it works (Maybe putting the the template for questions in a separate file). While trying to refactor, I found the following issues (which I would be grateful if someone could answer them too):

  1. Couldn't use foreach clauses because @Html.RadioButtonFor not @Html.HiddenFor didn't work with inner loop variables. ASP.Net model binding couldn't post well the variables.
  2. Couldn't use neither Html.DisplayFor nor EditorFor due to the previous posting problem.

So, how can I refactor this properly and separate in in several files while being able to post it properly? The underlying model is this one:

public class Course
{
    public string Title { get; set; }
    public string Description { get; set; }
    public string Category { get; set; }
    public string Image { get; set; }
    public string Link { get; set; }
    public PageReference PageRef { get; set; }
    public List<Chapter> Chapters { get; set; }

    public Course() {
        Chapters = new List<Chapter>();
    }
}

public class Chapter
{
    public Guid ChapterId { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }
    public string Category { get; set; }
    public string Image { get; set; }
    public string Link { get; set; }
    public List<Question> Questions { get; set; }

    public Chapter() {
        Questions = new List<Question>();
    }
}

public class Question
{
    public int Id { get; set; }
    public string QuestionText { get; set; }
    public List<Answer> QuestionAnswers { get; set; }
    public Answer SelectedAnswer { get; set; }
    public bool Resolved { get; set; }
    public bool AnsweredCorrectly { get; set; }

    public Question() {
        QuestionAnswers = new List<Answer>();
    }
}

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


    public static Answer Parse(string answer, int id)
    {
        return new Answer
        {
            Id = id,
            AnswerText = answer.Replace("*", ""),
            Correct = answer.StartsWith("*"),
        };
    }
}

public class CourseContentViewModel : PageViewModel<CoursePage>
{
    public CourseContentViewModel(CoursePage currentPage)
        : base(currentPage)
    {
    }

    public Course Course { get; set; }
    public Chapter CurrentChapter { get; set; }
}

public class QuizViewModel : CourseContentViewModel
{
    public QuizViewModel(CoursePage currentPage)
        : base(currentPage)
    {
    }

    public QuizViewModel() : base(null) {
    }

    public List<Question> Questions { get; set; }
    public bool HasAnswered { get; set; }
    public int NumberOfCorrectAnswers { get; set; }
}
\$\endgroup\$
  • 1
    \$\begingroup\$ Have you looked at writing your own EditorTemplates? \$\endgroup\$ – ediblecode Dec 3 '14 at 11:29
1
\$\begingroup\$

Is this the entire contents of the view file? If so, the model you are passing to your view has too much in it. You should create your view model such that it only passes what is necessary for the view. At first glance it seems that Questions is the only thing being used.

After you do that I would follow danrhul's suggestion and use EditorTemplates.

Next, there shouldn't be any logic in your view. For instance you have this:

@(Model.CurrentChapter.Questions[i].Resolved && Model.CurrentChapter.Questions[i].QuestionAnswers[j].Correct ? "correct-answer" : ""

That is domain specific logic that should be handled much before the view comes in to play. A sign things are off is looping and logic in your view(for future reference).

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.