views:

136

answers:

3

I have a view that is strongly typed:

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MPKwithMVC.Models.SmartFormViewModel>" %> 

Works great to generate the view, but when I post, I have an ActionResult defined:

[AcceptVerbs(HttpVerbs.Post)]  
public ActionResult Next(MPKwithMVC.Models.SmartFormViewModel model)   
{ .. }  

Which I would imagine get hit when my next button is clicked (it works if I change the argument to a FormsCollection). I instead get a message saying "No parameterless constructor defined for this object".

What am I doing wrong?

My SmartFormsViewModel is:

    [Serializable]
public class SmartFormViewModel
{
    public List<Question> Questions { get;  set; }
    public List<Answer> Answers { get;  set; }

    public SmartFormViewModel(List<Question> questions, List<Answer> answers)
    {
        this.Questions = questions;
        this.Answers = answers;
    }

    public SmartFormViewModel()
    {
    }
}

And here is the View:

    <%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MPKwithMVC.Models.SmartFormViewModel>" %>
<%@ Import Namespace="MPKwithMVC.Models" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    SmartForms
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

    <h2>
        Questionaire</h2>
    <% using (Html.BeginForm("Next", "SmartForms"))
       { %>
    <div style="float: left; margin-right: 2em;">
        <% Html.RenderPartial("NavigationPanel", Model); %>
    </div>
    <div>
        <table>
            <%
                foreach (Question question in (Model.Questions))
                { %>
            <tr>
                <td>
                    <div style="text-align: right; width: 20em;">
                        <%= Html.Encode(question.QuestionText)%>
                    </div>
                </td>
                <td>
                    <div style="float: left;">
                        <% if (question.QuestionType == 1)
                           { %>
                        <%= Html.TextBoxFor(model => model.Answers[(int)question.QuestionID - 1].AnswerValue) %>
                        <% } %>
                        <% if (question.QuestionType == 2)
                           { %>
                        <%= Html.RadioButton("yn" + question.QuestionID, "Yes", false)%>Yes
                        <%= Html.RadioButton("yn" + question.QuestionID, "No", true)%>No
                        <% } %>
                    </div>
                    <% if (question.Required == true)
                       { %>
                    <div style="color: Red; float: right; margin-left: 3px;">
                        *</div>
                    <% } %>
                </td>
            </tr>
            <%
                } %>
            <tr>
                <td>
                    <%
           if (ViewData["errorMsg"] != null)
                       {%>
                       <div style="color:Red;">
                        <%= Html.Encode(ViewData["errorMsg"].ToString()) %>
                       </div>
                    <% } %>
                </td>
                <td>
                    <div style="margin-top: 1em;">

                        <button name="button" value="next">Next</button>

                    </div>
                </td>
            </tr>
        </table>
    </div>
    <% } %>

</asp:Content>
+1  A: 

As the error message suggest, your SmartFormViewModel class needs to contain a parameterless constructor.

This is a great example of what you would like to do

I would also suggest you use the strongly typed helpers from Html extensions to generate your form fields.

e.g.

Html.HiddenFor(x => x.SomeField) 
Html.TextBoxFor(x => x.SomeEditableField)
Yannis
Thanks Yannis, but my Model does contain a parameterless constructor. What about the submit button, can you provide the code for that? Currently I am doing <button name="button" value="next"> Next Section</button>Where my Action is defined: [AcceptVerbs(HttpVerbs.Post)] public ActionResult Next(SmartFormViewModel model) { .. }
Mark Kadlec
+1  A: 

Make sure that your <form>'s action points to your Next method.

SLaks
Thanks SLaks, I have that defined in my BeginForm(), I added the View in my original post now. I'm stumped why I am getting the message, doesn't make sense.
Mark Kadlec
Look at the POST in Fiddler.
SLaks
What should I be using to submit the document? I tried adding <%= Html.ActionLink("Next", "Next", "SmartForms") %> instead of the button, and it actually worked, but the model was null so the model did not get passed back. The post in Fiddler was fine, was going to the Next Action URL.
Mark Kadlec
+1  A: 

Have a look at the input names that are being generated within your HTML. I think you have an issue with the naming of your controls thus the default model binding is failing since you mentioned that the using FormCollection works correctly. I am making this assumption since I don't know what your Questions and Answers classes look like

<%= Html.TextBoxFor(model => 
        model.Answers[(int)question.QuestionID - 1].AnswerValue) %>

Won't this render something similar to the following; which if I'm not mistaken will not bind to your model. The same applies for the RadioButtons.

<input type="text" name="Answers[0].AnswerValue" id="Answers_0__AnswerValue" value="somevalues"/>

The RadioButton helper should be

<%= Html.RadioButton("Questions[" + question.QuestionID + "].ID", "Yes", false)%> // you now get a list of questions

<input type="radio" name="Questions[1].ID" id="Questions_1__ID" value="No" checked="checked"/>

There are some ways you can try to resolve this:

  1. Correctly name your input controls so that they match you ViewModel. (This includes posting back all the required fields for you model - I think that that default values are used when the model binding occurs if its not posted)
  2. Create a custom model binder
  3. You may need to tell the Binder what the Prefix of the input fields are. ([Bind] attribute) to specifically include or exclude form fields.
  4. Create a new Model containing the values you expect to post back

I think that overall that your approach needs to slightly change. From the info provided, your Q&As are closely related. Depending on the question type your 'answer' is either boolean or freetext. At the moment your are not posting a List<Questions> back to the server. Answers, yes, but they are not I don't think that it is recognised as List<Answers>.

Haacked has a post which I think is related to your issue and this SO question further indicates that it may still applly to ASP-MVC-2.

Ahmad
I think you might be on to something Ahmad, but isn't Answers[0].AnswerValue valid? (It's what I am looking to bind to since Answers is simply a list).I have a list of Questions and a list of Answers in my ViewModel, how should I name my input controls to match the ViewModel? I'll gladly rename if necessary.
Mark Kadlec
The issue is a dual one 1) the form fields need to be sequentially indexed to bind correctly and 2) its failing to create the SmartFormViewModel since its failing to decide what the Type of the Model is (since it is Complex). I suggest that you actually Create a new ViewModel for the Post back to the `Next` method with the fields that you actually require.
Ahmad
I think you are right Ahmad, I will try. It makes me wonder why it is failing to decide, the object names are named pretty accurately (Answers[3].AnswerValue seems pretty specific!).I definitely think that is it though, I will try your suggestion.
Mark Kadlec