views:

1711

answers:

4

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>
+1  A: 

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
A: 

Thanks, ill give that a try. Anyone got any info available on the nested lists issue to?

Bernt
This is not an answer. Use a comment or edit your question.
Geoffrey Chetwood
+2  A: 

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
A: 

Thanks for the input Tim, ill look into fluenthtml, looks really nice ;).

But it doesn't answer my question, who maybe was a bit unclear. The sample above is working, i pass the question to the model in the RenderPartial so it is the Question.Id that is used as indexer.

    <% foreach (var question in ViewData.Model.Questions)
       {%>
    <% Html.RenderPartial("Partial/Question", question, ViewData); %>
    <% } %>

The thing I'm still unable to get working is passing a list of lists to the views.

My datastructure is Questionnaire.QuestionGroup.Question. In this sample i have only implemented QuestionGroup.Question, because indexing List<List<Question>> where the outer list is QuestionGroups will not run correctly.

I'm trying to do:

    <div class="Question">
        <div class="QuestionTitle">
            <%=ViewData.Model.Description %>
        </div>
        <input name="questionnaire.questionGroup.Index" value="GroupId"
        <input name="questionnaire.questionGroup[GroupId].Questions.Index" value='<%=ViewData.Model.ID %>' type="hidden" />
        <div class="QuestionText">
        <%switch (ViewData.Model.Type.ToLower())
        {
        case QuestionType.Text:%>
        <%=Html.TextBox("questionnaire.questionGroup[GroupId].Questions[" + ViewData.Model.ID + "].Answer")%>
        <% break;

Where GroupId is a indexer on the List of QuestionGroups.

Bernt
This is not an answer. Use a comment or edit your question.
Geoffrey Chetwood