Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MVC4 Complex Type Model is null after post

This is my model

public class AdministrationModel
{
  public string FirstName { get; set; }
  public string LastName { get; set; }
  public string EmailAddress { get; set; }
  public bool IsApproved { get; set; }
}

This is my controller

public ActionResult GetTabContent(string id)
{
  switch (id)
  {
   case "tab3":
   model = GetAllUsersInfo();
   viewName = "Administration";
   break;
   }
   return View(viewName);
 }

  private List<AdministrationModel> GetAllUsersInfo()
  {
    List<AdministrationModel> userList = new List<AdministrationModel>();
    foreach (MembershipUser user in Membership.GetAllUsers())
    {
      UserProfile userProfile = UserProfile.GetUserProfile(user.UserName);
      userList.Add(new AdministrationModel { EmailAddress = user.Email,                       IsApproved = user.IsApproved, FirstName = userProfile.FirstName, LastName = userProfile.LastName });
    }

    return userList;
  }

This is my View

@model List<AdminContainerModel>
@using (Html.BeginForm("Administration", "Account"))
{
  <fieldset>
    <div>
      @foreach (AdministrationModel AM in Model)
      {
        <div>
         <div class="colFull">@Html.DisplayFor(modelItem => AM.FirstName)</div>
         <div class="colFull">@Html.DisplayFor(modelItem => AM.LastName)</div>
         <div class="colFull">@Html.DisplayFor(modelItem => AM.EmailAddress)</div>
         <div class="colPartial"><input type="checkbox" checked="@AM.IsApproved"/>            </div>
      <div class="clear"></div>
    </div>
  }
</div>
 <input type="submit" value="Update Account" />
 </fieldset>
}

When the user clicks the Update Account button it goes to the controller

  [HttpPost]
  public ActionResult Administration(List<AdministrationModel> model)
  {
     return View();
  }

inside this method, model is always null. however the View that renders everything is perfect and shows what I want it to show. What am I doing wrong?

like image 825
Vijay V Avatar asked Dec 21 '22 23:12

Vijay V


2 Answers

When using collections, in order to correctly process them so they are model-bound on post without any additional leg work, you need to make sure they are indexed correctly, you can do this by using a for loop, something like:

@for (int i = 0; i < Model.Count; i++)
{
    @Html.HiddenFor(m => m[i].FirstName)
    @Html.HiddenFor(m => m[i].LastName)
    @Html.HiddenFor(m => m[i].EmailAddress)
    <div>
        <div class="colFull">@Html.DisplayFor(m => m[i].FirstName)</div>
        <div class="colFull">@Html.DisplayFor(m => m[i].LastName)</div>
        <div class="colFull">@Html.DisplayFor(m => m[i].EmailAddress)</div>
        <div class="colPartial">@Html.CheckBoxFor(m => m[i].IsApproved)</div>
        <div class="clear"></div>
    </div>
}

That should model bind without any other code :)

Edit: Sorry I forgot, displayFors by default don't put the correct properties on for model binding, added hiddenFors for the other fields that don't have an editorFor

Edit2: Based on your other question in the comment, if it was public facing and you didn't want them to change any of the hidden for values using the dev tools, try the following:

Ok so you don't want them to change the hiddenFors, that's fine, but you will need some sort of ID so you know which client is which when the data is posted, I suggest that instead of having these in the above code:

@Html.HiddenFor(m => m[i].FirstName)
@Html.HiddenFor(m => m[i].LastName)
@Html.HiddenFor(m => m[i].EmailAddress)

Replace them with:

@Html.HiddenFor(m => m[i].ClientId)

That way you're not posting back the firstname, lastname or email address, just a reference to the actual client that is ticked, unticked.

To answer your other question in the comment about keeping track of the original values, in your controller method you can just go and get the original values from the database, then here's how you can detect which ones are different, something like:

[HttpPost]
public ActionResult Administration(List<AdministrationModel> model)
{
    var originalMatches = GetAllUsersInfo();

    var differences = (from o in originalMatches
                      join c in model on o.ClientId equals c.ClientId
                      where c.IsApproved != o.IsApproved).ToList()

    return View();
}
like image 62
mattytommo Avatar answered Jan 05 '23 20:01

mattytommo


Your @model directive is incorrect, it should be the fully qualified type name.

In this case:

 @model TheNamespace.AdministrationModel

After your updated question.

Try using:

@Html.EditorFor(Model)

This will call a specialized Editor Template for your list.

http://blogs.msdn.com/b/nunos/archive/2010/02/08/quick-tips-about-asp-net-mvc-editor-templates.aspx

like image 25
Alex Avatar answered Jan 05 '23 21:01

Alex