mvc3 submit model empty

2019-07-21 16:54发布

问题:

I have a problem which I don't understand and there doesn't seem to be an easy way to debug the problem. I'm sure it's simple.

@model StartStop.ServiceResources.UserSettings

my MVC3 view is bound a specific model;

 public class Setting
{
    public Int64 SettingID { get; set; }
    public Int64 UserID { get; set; }
    public int PreferenceType { get; set; }
    public string PreferenceName { get; set; }
    public bool PreferenceBool { get; set; }
    public int PreferenceInt { get; set; }
    public string PreferenceString { get; set; }
    public DateTime CreatedOn { get; set; }
    public DateTime ModifiedOn { get; set; }

}

public class UserSettings
{
    public Int64 UserID { get; set; }
    public List<Setting> Settings { get; set; }
}

the view lists out the check boxes which represent the list;

  @using (Html.BeginForm("ManageAccount","Account", FormMethod.Post))
        {
            <table class="tbl" cellspacing="0">

                <tr>
                    <th>Preference</th>
                    <th>Setting</th>
                </tr>

                @if (Model != null)
                {
                    foreach (var item in Model.Settings.ToList())
                    {
                        <tr>
                            <td>@item.PreferenceName
                            </td>
                            <td>
                                @if (item.PreferenceType == 2)
                                {
                                    @Html.CheckBoxFor(modelItem => item.PreferenceBool)
                                }
                            </td>
                        </tr>

                    }
                }
            </table>

            <input type="submit" value="Save Changes" class="action medium" />

        }

All good, I load the data into the view it renders the view and picks up the correct settings. However, when I do a post at the bottom, the view model returns a null! I'm not sure why...

 [HttpPost]
    [Authorize]
    public ActionResult ManageAccount(StartStop.ServiceResources.UserSettings model)
    {
        if (ModelState.IsValid)
        {

            foreach (StartStop.ServiceResources.Setting oSetting in model.Settings)
            {
                StartStop.Helpers.UserPreferences.SaveUserSetting(oSetting);
            }
        }
        return View(model); 
    }

Can anyone help?

回答1:

The problem is on the following line in your view:

@Html.CheckBoxFor(modelItem => item.PreferenceBool)

I see people writing the following lambda expression modelItem => item.SomeProperty in their views very often and asking why the model binder doesn't correctly bind collection properties on their view models.

This won't generate proper name for the checkbox so that the default model binder is able to recreate the Settings collection. I would recommend you reading the following blog post to better understand the correct format that the model binder expects.

Try like this:

@model StartStop.ServiceResources.UserSettings
@using (Html.BeginForm("ManageAccount", "Account", FormMethod.Post))
{
    <table class="tbl" cellspacing="0">
        <tr>
            <th>Preference</th>
            <th>Setting</th>
       </tr>

       @if (Model != null)
       {
           for (var i = 0; i < Model.Settings.Count; i++)
           {
               <tr>
                   <td>@Model.Settings[i].PreferenceName</td>
                   <td>
                       @if (Model.Settings[i].PreferenceType == 2)
                       {
                           @Html.CheckBoxFor(x => x.Settings[i].PreferenceBool)
                       }
                    </td>
                </tr>
           }
       }
    </table>

    <input type="submit" value="Save Changes" class="action medium" />
}

This being said, I would recommend you using editor templates, like so:

@using (Html.BeginForm("ManageAccount","Account", FormMethod.Post))
{
    <table class="tbl" cellspacing="0">
        <tr>
            <th>Preference</th>
            <th>Setting</th>
       </tr>

       @if (Model != null)
       {
           @Html.EditorFor(x => x.Settings)
       }
    </table>

    <input type="submit" value="Save Changes" class="action medium" />
}

and then define a custom editor template which will automatcially be rendered for each element of the Settings collection (~/Views/Shared/EditorTemplates/Setting.cshtml):

@model StartStop.ServiceResources.Setting
<tr>
    <td>@Model.PreferenceName</td>
    <td>
         @if (Model.PreferenceType == 2)
         {
             @Html.CheckBoxFor(x => x.PreferenceBool)
         }
     </td>
 </tr>

Also the only input field that I can see in this form is the checkbox which is bound to the PreferenceBool property on your model. So inside your POST controller action you will get the Settings list property initialized but don't expect to find any values for the other properties in this Setting class unless of course you include input fields for them in the form (and more precisely in the editor template that I have shown).