1

In my MVC.NET project I used scaffolding templates. Initially ther were binded to one DTO model. Now I decided I wanted to link it to a ViewModel, because I have two multiselects I need to use to pass values. This is how my ViewModel looks:

public class CreateQuestionModel
{
   public Question Question { get; set; }
   public List<int> PoliticianIds { get; set; }
   public List<int> TopicIds { get; set; }
}

My Create POST method that is getting a ViewModel from the View:

[HttpPost]
[ValidateAntiForgeryToken]
[Authorize(Roles = "Regular")]
public ActionResult Create(CreateQuestionModel question)
{
  if (ModelState.IsValid)
  {
    int id = WebSecurity.CurrentUserId;
    manager.CreateQuestion(question.Question, id, question.PoliticianIds, question.TopicIds);
    return RedirectToAction("Index");
  }

  return View(question);
}

And my Create.cshtml looks like this:

@model PoliticiOnline.Models.CreateQuestionModel
@{
ViewBag.Title = "Stel een vraag!";
}
<head>
<link rel="stylesheet" href="~/Content/Questions.css" type="text/css" />
@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/jqueryval")
<script src="@Url.Content("~/Scripts/Extra/Chosen/chosen.jquery.min.js")" type="text/javascript"></script>
<link rel="stylesheet" href="@Url.Content("~/Scripts/Extra/Chosen/chosen.min.css")" type="text/css">   
<script src="@Url.Content("~/Scripts/Extra/select2-3.4.6/select2.min.js")" type="text/javascript"></script>
<link rel="stylesheet" href="@Url.Content("~/Scripts/Extra/select2-3.4.6/select2.css")" type="text/css">
</head>

<h2>Stel een vraag!</h2>
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
@Html.ValidationSummary(true)
<fieldset>
    <legend>Vraag</legend>
    <div class="general-question">
        <div class="editor-label">
            @Html.LabelFor(model => model.Question.GeneralQuestion, "Algemene Vraag")
        </div>
        <div class="editor-field">
            @Html.TextBox("Question.GeneralQuestion", new { @class = "general-question-edit" })
            @Html.ValidationMessageFor(model => model.Question.GeneralQuestion)
        </div>
    </div>

    <div id="geadresseerde-politici">
        @Html.LabelFor(model => model.PoliticianIds, "Geadresseerde Politicians:")
        @Html.ListBox("PoliticianIds", (MultiSelectList)ViewBag.Politicians, new { @id = "polDrop" })
    </div>

    <div class="editor-label">
        @Html.LabelFor(model => model.Question.Explanation, "Extra Uitleg")
    </div>
    <div class="editor-field">
        @Html.TextArea("Question.Explanation", new { @class = "explanation-textarea-edit" })
        @Html.ValidationMessageFor(model => model.Question.Explanation)
    </div>

    <div>
        @Html.LabelFor(model => model.TopicIds, "Kies je thema's (maximum 2):")
        @Html.ListBox("TopicIds", (MultiSelectList)ViewBag.Topics, new { @id = "select2select", @style = "width: 500px"})
    </div>

    <p>
        <input type="submit" value="Indienen!" />
    </p>
</fieldset>
}

<div>
@Html.ActionLink("Back to List", "Index")
</div>


<script type="text/javascript">
function format(topic) {
    if (topic.css == 'optionGroup') {
        return "<b>" + topic.text + "</b>";
    } else {
        return "<i>&nbsp;&nbsp;&nbsp;" + topic.text + "<i>";
    }
}

$("#select2select").select2({
    placeholder: "Selecteer een thema...",
    maximumSelectionSize: 2,
    formatResult: format,
    escapeMarkup: function(m) {
        return m;
    }
});
</script>

The <script> section at the bottom doesn't really matter but I pasted it anyway, I'm using the jQuery plugin select2 for the ListBox.

This is a way of binding the textboxes and such to ViewModel properties, I found this on Stackoverflow. I also tried the classic way using @Html.EditorFor and @HtmlListBoxFor but the ViewModel's properties are always null.

What am I doing wrong/ what am I overlooking?

EDIT: I put a constructor in the ViewModel, now the ViewModel (CreateQuestionModel) is not null anymore, but the values are still default values (not the ones from the form). My ViewModel now looks like:

public class CreateQuestionModel
{
public Question Question { get; set; }
public List<int> PoliticianIds { get; set; }
public List<int> TopicIds { get; set; }

public CreateQuestionModel()
{
  Question = new Question();
  PoliticianIds = new List<int>();
  TopicIds = new List<int>();
}
}

SOLUTION Commenter Yoeri provided the solution, you can see it below in my answer on this question!

4
  • the constructor won't help: it runs when your model gets initialized, so it has nothing to do with modelbinding. I'm currently looking at it in a testcase. First tip: use @Html.TextBoxFor with lambda's rather then @Html.Textbox(""). Using strings is very prone to errors! Commented Apr 7, 2014 at 10:12
  • Have you actually debugged your controller method to ascertain that the 'question' parameter is null? Commented Apr 7, 2014 at 10:25
  • I did and I just found out what I was doing wrong, I'll post the answer! Commented Apr 7, 2014 at 10:26
  • @E.V.d.B. in response to your comment that the returned view model properties are their 'default' values, I solved this error by fixing a dumb mistake. In my view model, I forgot to declare the {get; set;} for each property, e.g. TopicId, PoliticianId, Question Commented Jan 22, 2015 at 23:14

3 Answers 3

3

My dumb error that caused all the properties in the ViewModel to be default values on POSTing to the Controller. I did not declare the {get; set;} for each property.

ERROR

public class ApplicationViewModel
{
    public Application App;        
    public SelectList SelectSimple;
    public ApplicationViewModel() 
    {
        // Create/Init App & SelectSimple
    }
}

CORRECT

public class ApplicationViewModel
{
    public Application App { get; set; }
    public SelectList SelectSimple { get; set; }
    public ApplicationViewModel() 
    {
        // Create/Init App & SelectSimple
    }

}

COMMENT

It bears repeating that the Controller needs an [HttpGet] that passes non-null ViewModel to the View; and Controller needs an [HttpPost] to receive the ViewModel on the submit by the user.

Sign up to request clarification or add additional context in comments.

Comments

1

Okay guys, I just found out what I was doing wrong. I made an extremely stupid mistake, can't believe I've been struggling for 2 days with this now.

The signature of my Create POST method was:

public ActionResult Create(CreateQuestionModel question)
{
    ...
}

It should be:

public ActionResult Create(CreateQuestionModel createQuestionModel)
{
    ...
}

I just had to change the parameter form CreateQuestionModel question to CreateQuestionModel createQuestionModel, it was as simple as that, now everything works.

UPDATE Thanks to Yoeri I now understand why it was giving problems in the first place:

Initially I named the parameter CreateQuestionModel question, this didn't work because I have a model class named Question. So basically the parameter name has to be either the name of the model you are using in the Create View, or any other name as long as it's not a name of another model class!

Thanks Yoeri!

6 Comments

Good thing you got it working but this is very odd. The name of your parameter shouldn't matter.
Could you try to change the name of the parameter again? I was able to reproduce your issue and after renaming is back again, the binding works as expected.
I found the solution in this StackOverflow thread, where the solution marked was also changing the parameter.
@Yoeri Do you mean changing it back from createQuestionModel to question and then trying it out?
I think I found the real problem. You have a model called "Question" and I think it confuses the modelbinder. Try changing the name to eg. "model" (or anything that's not a class in your solution).
|
0

Try,

@using(Html.BeginForm("Create", "*controller-name*",FormMethod.Post))

1 Comment

Just tried it, model still null :( (and then I put a constructor in the ViewModel and no it is not null but all its values are null ...)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.