I'm building a questionnaire app in asp.net mvc, and I'm having trouble with the module binder for a list of complex types.
First: I can't get Questionnaire.IList<QuestionGroup>.IList<Question> object graph to work with the binder. So in the sample code below i use only one level IList.
Second: I would love to pass in my repository/factory instead of class to the binder, that way i could forgo the mapping between the form questions and the db questions, just working on one set of objects. Or at least something a bit more pretty than this code;).
Anyone got a pointer in the right direction for me?
The controller:
public class QuestionnaireController : Controller
{
#region Constructors
public QuestionnaireController(IRepositoryWithTypedId<QuestionGroup, string> questionnaireRepository)
{
repository = questionnaireRepository;
}
#endregion
public ActionResult Create(string Id)
{
if (!string.IsNullOrEmpty(Id))
{
QuestionGroup questionnaire = repository.Get(Id);
return View(questionnaire);
}
else return RedirectToAction("Index");
}
[Transaction]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(string Id, QuestionGroup questionGroup)
{
QuestionGroup dbQuestionGroup = repository.Get(Id);
//Map Questions in form to Questions from db
foreach (var question in dbQuestionGroup.Questions)
{
Question persQuestion =
(from item in questionGroup.Questions where item.QuestionID.Equals(question.ID) select item)
.SingleOrDefault();
if (persQuestion != null)
{
question.Answer = persQuestion.Answer;
}
}
//Validate Questions(db)
bool valid = true;
foreach (var question in dbQuestionGroup.Questions)
{
if (!question.IsValid())
{
foreach (var item in question.ValidationMessages)
{
ViewData.ModelState.AddModelError("questionGroup.Questions[" + question.ID + "]." +
item.PropertyName,
item.Message);
}
valid = false;
}
}
//Return the same form with validation info appended
if (!valid)
{
return View(dbQuestionGroup);
}
//Persist to db and redirect to Complete
else
{
repository.SaveOrUpdate(dbQuestionGroup);
return RedirectToAction("Complete");
}
}
private IRepositoryWithTypedId<QuestionGroup, string> repository;
}
The partial view (main view just does a foreach on Questions in QuestionGroup and renders this view for each Question)
<div class="Question">
<div class="QuestionTitle">
<%=ViewData.Model.Description %>
</div>
<input name="questionGroup.Questions.Index" value='<%=ViewData.Model.ID %>' type="hidden" />
<input name='<%="questionGroup.Questions[" + ViewData.Model.ID + "].QuestionID" %>' value='<%=ViewData.Model.ID %>' type="hidden" />
<div class="QuestionText">
<%switch (ViewData.Model.Type.ToLower())
{
case QuestionType.Text:%>
<%=Html.TextBox("questionGroup.Questions[" + ViewData.Model.ID + "].Answer")%>
<% break;
case QuestionType.Number:%>
<%=Html.TextBox("questionGroup.Questions[" + ViewData.Model.ID + "].Answer")%>
<%= Html.ValidationMessage("questionGroup.Questions[" + ViewData.Model.ID + "].Answer")%>
<% break;
case QuestionType.PhoneNumber:%>
<%=Html.TextBox("questionGroup.Questions[" + ViewData.Model.ID + "].Answer")%>
<% break;
case QuestionType.Email:%>
<%=Html.TextBox("questionGroup.Questions[" + ViewData.Model.ID + "].Answer")%>
<%= Html.ValidationMessage("response.Questions[" + ViewData.Model.ID + "].Answer")%>
<% break;
case QuestionType.Date:%>
<%=Html.TextBox("questionGroup.Questions[" + ViewData.Model.ID + "].Answer")%>
<% break;
case QuestionType.YesNo:%>
<%=Html.RadioButton("questionGroup.Questions[" + ViewData.Model.ID + "].Answer", "0", true)%>Ikke valgt<br />
<%=Html.RadioButton("questionGroup.Questions[" + ViewData.Model.ID + "].Answer", "true", false)%>Ja<br />
<%=Html.RadioButton("questionGroup.Questions[" + ViewData.Model.ID + "].Answer", "false", false)%>Nei<br />
<% break;
case QuestionType.Alternative:%>
<%=Html.DropDownList("questionGroup.Questions[" + ViewData.Model.ID + "].Answer", new SelectList(ViewData.Model.Alternatives, "ID", "Description"))%>
<% break;
}%>
</div>
</div>