As far as using for
loops for the nested repeater behavior, I think that's the best way to do this in MVC. But I would suggest you use dedicated ViewModels.
ViewModel:
public class RadioQuestionListViewModel
{
public IEnumerable<RadioQuestionViewModel> Questions {get;set;}
}
public class RadioQuestionViewModel
{
public int QuestionNumber {get;set;}
public string InputName {get;set;}
public string QuestionPart1 {get;set;}
public string QuestionPart2 {get;set;}
public IEnumerable<RadioAnswerViewModel> PossibleAnswers {get;set;}
}
public class RadioAnswerViewModel
{
public int AnswerId {get;set;}
public string Text {get;set;}
}
Controller:
public ActionResult Details(int id)
{
var model = GetRadioQuestionListModelById(id);
return View(model);
}
View:
<% foreach (var question in Model) { %>
<h3>Question <%: question.QuestionNumber %></h3>
<p><%: question.QuestionPart1 %></p>
<p><%: question.QuestionPart2 %></p>
<% foreach (var answer in question.PossibleAnswers) { %>
<%: Html.RadioButton(question.InputName, answer.AnswerId) %>
<%: answer.Text %>
<% } %>
<% } %>
This approach has a few advantages:
- It prevents your view code from depending on your data access classes. The view code should only be responsible for deciding how the desired view model gets rendered to HTML.
- It keeps non-display-related logic out of your view code. If you later decide to page your questions, and are now showing questions 11-20 instead of 1-whatever, you can use the exact same view, because the controller took care of figuring out the question numbers to display.
- It makes it easier to avoid doing a
Array.IndexOf(Model.ToArray(), question)
and a database roundtrip inside a for
loop, which can become pretty costly if you have more than a few questions on the page.
And of course your radio buttons need to have a input name and value associated with them, or you'll have no way to retrieve this information when the form is submitted. By making the controller decide how the input name gets generated, you make it more obvious how the Details
method corresponds to your SaveAnswers
method.
Here's a possible implementation of GetRadioQuestionListModelById
:
public RadioQuestionListViewModel GetRadioQuestionListModelById(int id)
{
// Make sure my context gets disposed as soon as I'm done with it.
using(var context = new Models.Entities())
{
// Pull all the questions and answers out in a single round-trip
var questions = context.Questions
.Where(r => r.PROMId == id)
.Select(r => new RadioQuestionViewModel
{
QuestionPart1 = r.q.QuestionPart1,
QuestionPart2 = r.q.QuestionPart2,
PossibleAnswers = r.a.Select(
a => new RadioAnswerViewModel
{
AnswerId = a.AnswerId,
Text = a.Text
})
})
.ToList();
}
// Populate question number and name
for(int i = 0; i < questions.Count; i++)
{
var q = questions[i];
q.QuestionNumber = i;
q.InputName = "Question_" + i;
}
return new RadioQuestionListViewModel{Questions = questions};
}