ViewModel is Null in HttpPost method
Asked Answered
I

5

6

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>
Implode answered 28/3, 2014 at 13:52 Comment(3)
Please include your post action code as wellSegovia
Can we see a sample (maybe one item's worth) of the generated HTML for the notifications? Something tells me it's not referencing ListNotificaton[0].AM. [the shortcut you're using in your for is most likely the culprit, but can't be sure without the generated HTML]Gutty
@JeremyCook, here you go.Implode
G
6

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).

Gutty answered 28/3, 2014 at 15:4 Comment(0)
S
4

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>
Segovia answered 28/3, 2014 at 15:1 Comment(1)
+1. Though a little more work, the templating system is well worth it. It can be a little confusing at first, but once you get the hang of it a lot of the binding issues go away.Gutty
S
2

My first suggestion would be to try adding some random property to your SettingValueModel and adding it to the form as a hidden.

Something like

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

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

Then in your view add

@Html.HiddenFor(s=> s.TestValue)

When you submit your form, check to see if the SettingsViewModel isn't null. If the problem is just in the ListNotification serialization, then you might end up with an object with a TestValue of "Test" and null List Notifications. If that is the case, at least you know the problem is the ListNotifications.

Also try changing your for loop to this

  @for (int i = 0; i < Model.ListNotification.Count(); i++ )
     {

         <tr>
             <td>@Model.ListNotification[i].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>

Also I don't think FormMethod.Post is required in the form definition. Try all of these things. If none of them help, then I can only assume your PC is haunted =)

Six answered 28/3, 2014 at 15:14 Comment(0)
I
0

In my case, I had my setters as internal in my view model definition for some strange reason, thus the binding couldn't be set and stored:

public int PaymentType { get; internal set; }
Ipomoea answered 22/6, 2016 at 18:23 Comment(0)
M
-1

In POST action do you use right type of model?

[HttpPost]
public ActionResult Preferences(PortailT2A.Models.SettingsViewModel model)
{
   //...
}
Moil answered 28/3, 2014 at 14:8 Comment(1)
Yes I'm using the right type of model but thanks for your suggestion.Implode

© 2022 - 2024 — McMap. All rights reserved.