2

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>
bdukes
  • 152,002
  • 23
  • 148
  • 175
Bernt
  • 249
  • 6
  • 12

2 Answers2

2

I have written how to do this with MvcContrib.FluentHtml:

One problem I see is that you are setting the value of your indexer of your Question list item to the Questionaire ID, which is two levels up. The value should be the ID of the question, if it exists, or some proxy value (such as a negative number) that tells you it's a new instance.

Tim Scott
  • 15,106
  • 9
  • 65
  • 79
1

You can always write your own custom ModelBinder which would require you to write about two methods. See the UpdateCollection method here. You could then passs your repository to your custom ModelBinder.

Todd Smith
  • 17,084
  • 11
  • 59
  • 78