Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MVC 4 model coming back null

So I have a view that requires several different objects and lists of objects to be passed in and out that I have created a viewmodel for. My viewmodel looks like this

public class EditUserViewModel
{
    public ManageUsersViewModel ManageUsersViewModel { get; set; }
    public IEnumerable<StateModel> StateModel { get; set; }
}

The part I'm having trouble with is the StateModel which looks like this

public class StateModel
{
    public bool IsChecked { get; set; }
    public States States { get; set; }
    public UsersInStates UsersInStates { get; set; }
}

and contains this

[Table("States")]
public class States
{
    [Key]
    public int StateId { get; set; }
    public string State { get; set; }
}

[Table("UsersInStates")]
public class UsersInStates
{
    [Key, Column(Order = 1)]
    public int UserId { get; set; }
    [Key, Column(Order = 2)]
    public int StateId { get; set; }
    public string LicenseNumber { get; set; }
}

In my view I'm more or less trying to loop through the states and take user input for UsersInStates. This is how I'm trying to accomplish it but my entire StateModel comes back null. Going into the view the StateModel.States has data and the UsersInStates does not. This is what it looks like in my view

@foreach (var state in Model.StateModel)
{
    @Html.HiddenFor(m => state)
    <tr>
        <td>
            @Html.CheckBoxFor(m => state.IsChecked)
        </td>
        <td>
            @Html.Label(state.States.State)
        </td>
        <td>
            @Html.EditorFor(m => state.UsersInStates.LicenseNumber)
        </td>
    </tr>
}

Any advice would be much appreciated. Everything displays as it should and the ManageUsersViewModel part works fine it's just the StateModel data coming back to the controller is null and I'm not exactly sure how to make this work the way I'd like it to.

This is what the generated html looks like for the start of the table and the first row as requested

<table style="margin-left:auto; margin-right:auto; text-align:center">
<input id="state" name="state" type="hidden" value="WebSiteNew.Models.StateModel" />                    <tr>
                <td>
                    <input data-val="true" data-val-required="The IsChecked field is     required." id="state_IsChecked" name="state.IsChecked" type="checkbox" value="true" />    <input name="state.IsChecked" type="hidden" value="false" />
                </td>
                <td>
                    <label for="Alabama">Alabama</label>
                </td>
                <td>
                    <input class="text-box single-line" id="state_UsersInStates_LicenseNumber" name="state.UsersInStates.LicenseNumber" type="text" value="" />
                </td>
            </tr>

Answer:

Ok so to solve this I used a for loop as explained in both references listed in the answer below

@for (int i = 0; i < Model.StateModel.Count(); i++)
{
    <tr>
        <td>
            @Html.HiddenFor(m => m.StateModel[i].States.StateId)
            @Html.HiddenFor(m => m.StateModel[i].States.State)
            @Html.CheckBoxFor(m => m.StateModel[i].IsChecked)
        </td>
        <td>
            @Html.Label(Model.StateModel[i].States.State)
        </td>
        <td>
            @Html.EditorFor(m => m.StateModel[i].UsersInStates.LicenseNumber)
        </td>
    </tr>
}

Also a note to anyone looking at this, I had to change IEnumerable in my EditUsersViewModel to IList to allow for indexing.

like image 681
aw04 Avatar asked Mar 23 '23 23:03

aw04


2 Answers

So your issue is that the model binding isn't happening correctly.

Say you have these two models:

public class ParentModel
{
    public string Name {get;set;}
    public ChildModel Child {get;set;}
}

public class ChildModel
{
    public string ChildName {get;set;}
    public int SomeNumber {get;set;}
}

Your generated HTML (for your model binding to happen correctly) needs to look like this:

<input name="Name" value="(Not relevant to this example)"/>
<input name="Child.ChildName" value="(Not relevant to this example)" />

Note how the name field is structured - this is how MVC determines what input values map to which properties in your view model. With the nested property, the property name has to go in front of it.

With collections, it gets more complicated. The model binder needs to know which values go with which instance of a property.

For example, if we assume the ChildModel is of type IEnumerable in the previous example, your HTML might look something like this in order to model bind correctly:

<input name="Name" value="(Not relevant to this example)"/>
<input name="Child[0].ChildName" value="(Not relevant to this example)" />
<input name="Child[0].SomeNumber" value="(Not relevant to this example)"/>
<input name="Child[1].ChildName" value="(Not relevant to this example)" />
<input name="Child[1].SomeNumber" value="(Not relevant to this example)"/>

Take a look at these for how to fix it:

http://seesharpdeveloper.blogspot.com/2012/05/mvc-model-binding-to-list-of-complex.html http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx

Edit - It's also important to note that when the Html Helpers generate the name value, it's based on the lambda value that is passed in. So

@Html.CheckBoxFor(m => state.IsChecked)

will generate the following name

name="state.IsChecked"

Since you're within a foreach, you're getting the wrong value for the name.

like image 144
CorrugatedAir Avatar answered Apr 02 '23 10:04

CorrugatedAir


What are you trying to accomplish with the @Html.HiddenFor(m => state) - from the rendered HTML, that looks like your culprit right there. Would @Html.HiddenFor(m => state.StateId) be more appropriate?

Also, you could throw that into the first <td> element since it is hidden and will keep your HTML valid.

like image 40
jeffesp Avatar answered Apr 02 '23 08:04

jeffesp