ViewModel is Null in HttpPost method

asp.net-mvc c# entity-framework viewmodel

Question

I'm using ASP.NET MVC 4 and I built these ViewModels :

public class NotificationViewModel
{

    public string GroupDesc { get; set; }

    public bool AM { get; set; }

    public bool PM { get; set; }

    public int MaxNotif { get; set; }
}

public class SettingsViewModel
{
    public List<NotificationViewModel> ListNotification { get; set; }

    public SettingsViewModel()
    {
        ListNotification = new List<NotificationViewModel>();
    }
}

My View :

@model PortailT2A.Models.SettingsViewModel

@{
    ViewBag.Title = "Preferences";
    Layout = "~/Views/Shared/_LayoutAdmin.cshtml";
}

<h2>Preferences</h2>


@using(Html.BeginForm("Preferences", "Administrateur", FormMethod.Post))
{

    <table id="settingsTable">
        <tr>
            <th>Groupe</th>
            <th></th>
            <th>AM</th>
            <th>PM</th>
            <th>Limite de notifications</th>
        </tr>

    @for (int i = 0; i < Model.ListNotification.Count(); i++ )
    {
        var notif = Model.ListNotification[i];
        <tr>
            <td>@notif.GroupDesc </td>
            <td>Heure de notification</td>
            <td>@Html.CheckBoxFor(u => notif.AM)  </td>
            <td>@Html.CheckBoxFor(u => notif.PM)  </td>
            <td>@Html.TextBoxFor(u => notif.MaxNotif)</td>
        </tr>
        <tr/>


    }

    </table>


    <input type ="submit" value="Sauvegarder" />

}

My HttpGet method populates my ViewModel and returns it.

    [HttpGet]
    public ActionResult Preferences(long idUser)
    {
        context = new MainDatabaseEntities();

        List<NotificationViewModel> notifications = new List<NotificationViewModel>();

        SettingsViewModel settings = new SettingsViewModel();

        //Population...

        return View(settings);
    }

However, when I want to save the changes, I got a ViewModel which is null and I don't understand why. Any idea guys?

EDIT : My post method :

            [HttpPost]
            public ActionResult Preferences(SettingsViewModel sm)
            {
                //since here my ViewModel is null
                context = new MainDatabaseEntities();

                Utilisateur user = (from u in context.Utilisateurs where u.Username == User.Identity.Name select u).FirstOrDefault();

                //operations...

}

HTML generated :

<tr>
        <td>Groupe B </td>
        <td>Heure de notification</td>
        <td><input id="notif_AM" name="notif.AM" type="checkbox" value="true" /><input name="notif.AM" type="hidden" value="false" />  </td>
        <td><input checked="checked" id="notif_PM" name="notif.PM" type="checkbox" value="true" /><input name="notif.PM" type="hidden" value="false" />  </td>
        <td><input id="notif_MaxNotif" name="notif.MaxNotif" type="text" value="10" /></td>
    </tr>
1
6
5/14/2015 8:41:10 PM

Accepted Answer

List<T> can be tricky when modelbinding since it relies heavily on the indexed keys. The helpers need to know the index, but by assigning notif within your for loop they're losing the reference. Instead, try something like the following:

@for (int i = 0; i < Model.ListNotification.Count(); i++ )
{
    var notif = Model.ListNotification[i];
    <tr>
        <td>@notif.GroupDesc </td>
        <td>Heure de notification</td>
        <td>@Html.CheckBoxFor(u => u.ListNotification[i].AM)  </td>
        <td>@Html.CheckBoxFor(u => u.ListNotification[i].PM)  </td>
        <td>@Html.TextBoxFor(u => u.ListNotification[i].MaxNotif)</td>
    </tr>
    <tr/>
}

Which should then provide you with something like:

<tr>
    <td>Groupe B </td>
    <td>Heure de notification</td>
    <td>
        <input id="ListNotification[0]_AM" name="ListNotification[0].AM" type="checkbox" value="true" />
        <input name="ListNotification[0].AM" type="hidden" value="false" />
    </td>
    <td>
        <input checked="checked" id="ListNotification[0]_PM" name="ListNotification[0].PM" type="checkbox" value="true" />
        <input name="ListNotification[0].PM" type="hidden" value="false" />
    </td>
    <td>
        <input id="ListNotification[0]_MaxNotif" name="ListNotification[0].MaxNotif" type="text" value="10" />
    </td>
</tr>

Also, make sure to check ModelState.IsValid in your posted action to confirm the model was bound correctly. If not, you should see a list of errors in ModelState that would give you some indication as to where it may have failed.

Also, I don't see you dump GroupDesc anywhere (except to output). If this is necessary on the incoming model, you may consider using @Html.HiddenFor(x => x.ListNotifications[i].GroupDesc).

6
5/14/2015 8:36:28 PM

Popular Answer

You're not building your HTML correctly. What is posted back will not have the paths the model binder expects.

Consider replacing this:

@for (int i = 0; i < Model.ListNotification.Count(); i++ )
{
    var notif = Model.ListNotification[i];
    <tr>
        <td>@notif.GroupDesc </td>
        <td>Heure de notification</td>
        <td>@Html.CheckBoxFor(u => notif.AM)  </td>
        <td>@Html.CheckBoxFor(u => notif.PM)  </td>
        <td>@Html.TextBoxFor(u => notif.MaxNotif)</td>
    </tr>
    <tr/>
}

with this:

@Html.DisplayModelFor(m => m.ListNotification)

and add a template like this to /Views/{YourController}/{YourAction}/EditorTemplates/NotificationViewModel.cshtml

@model NotificationViewModel
<tr>
    <td>@Model.GroupDesc</td>
    <td>Heure de notification</td>
    <td>@Html.CheckBoxFor(m => m.AM)</td>
    <td>@Html.CheckBoxFor(m => m.PM)</td>
    <td>@Html.TextBoxFor(m => m.MaxNotif)</td>
</tr>


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow