Question

I have an MVC 4 blogging application and I am trying to add an Admin Panel that allows an admin to Edit/Delete Accounts that have registered. I have the view and models in place, but I cannot figure out why my models are not being communicated correctly. Here is my code:

First I have a simple ViewModel for my Admin panel. It just contains a list that will hold all the registered users of the web app.

    public class AdminViewModel
    {

        public List<User> UserList { get; set; }
    }

Here is my controller that fetches the users as a list and sends them to the Admin view

[HttpGet]
     public ActionResult Admin()
        {
            AdminViewModel avm = new AdminViewModel();
            avm.UserList = Db.Users.ToList();
            return View(avm);
        }

Then here is my view that displays all the account information, has a Delete link to remove a user, and some checkboxes to change the abilities of the user.

@using cpts483.Models.ViewModels
@using cpts483.Models

@model AdminViewModel

<h1>Admin Control Panel</h1>

@using (@Html.BeginForm("UpdateUsers", "Home", FormMethod.Post))
{ 
<table>
    <tr>
        <th></th>
        <th>Id</th>
        <th>Email</th>
        <th>First Name</th>
        <th>Last Name</th>
        <th>IsAdmin</th>
        <th>CanWriteArticles</th>
    </tr>
    <tbody>
        @foreach (User u in Model.UserList)
        {
            <tr>
                <td>
                    @Html.ActionLink("Delete", "DeleteUser", new {id = u.Id})
                </td>
                <td>@u.Id</td>
                <td>@u.Email</td>
                <td>@u.FirstName</td>
                <td>@u.LastName</td>
                <td>
                    @Html.CheckBoxFor(m => m.UserList.Find(us => us.Id == u.Id).IsAdmin)
                </td>
                <td>
                    @Html.CheckBoxFor(m => m.UserList.Find(us => us.Id == u.Id).CanWriteArticles)
                </td>
            </tr>
        }
    </tbody>
</table>
    <input type="submit" value="Save Changes" id="update-users-btn" />
}

Now, I thought this would bind my AdminViewModel.UserList to the checkboxes in the DOM and then when he form is submitted, that same AdminViewModel would get sent to my post back handler with the updated data. However, instead of that, the handler below has an AdminViewModel parameter that is null every time. What am I doing wrong here?

[HttpPost]
        public ActionResult UpdateUsers(AdminViewModel avm) // THIS avm IS ALWAYS NULL
        {
            foreach (User u in avm.UserList)
            {
                Db.Users.Attach(u);
            }

            Db.SaveChanges();
            return RedirectToAction("Admin");
        }

No correct solution

OTHER TIPS

I did a small prototype for you, Check it out -

Let your models be as shown below. For simplicity sake I ignored some properties.

public class AdminViewModel
{
    public List<User> UserList { get; set; }
}
public class User
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public bool IsAdmin { get; set; }
}

Then the controller action which will render the View -

    public ActionResult Index()
    {
        AdminViewModel model = new AdminViewModel();
        model.UserList = new List<User>();
        model.UserList.Add(new User() { FirstName = "Rami", IsAdmin = true, Id = 10 });
        model.UserList.Add(new User() { FirstName = "James", IsAdmin = false, Id = 20 });
        return View(model);
    }

Then your view should be as shown below. I used hiddenFields to persist some text properties.

@model MVC.Controllers.AdminViewModel

@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>

@using (Html.BeginForm("Submit", "Person", FormMethod.Post))
{

    <table>
        @for (int i = 0; i < Model.UserList.Count; i++)
        {
            <tr>
                <td>
                    @Html.DisplayFor(modelItem => Model.UserList[i].Id)
                    @Html.HiddenFor(modelItem => Model.UserList[i].Id)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => Model.UserList[i].FirstName)
                    @Html.HiddenFor(modelItem => Model.UserList[i].FirstName)
                </td>
                <td>
                    @Html.CheckBoxFor(modelItem => Model.UserList[i].IsAdmin)
                </td>
            </tr>
        }
    </table>
    <input type="submit" value="Save" />
}

When submit button is clicked it will hit the following controller action -

    public ActionResult Submit(AdminViewModel model)
    {
        return View();
    }

And when you put a breakpoint, you can see you persisted model as below -

enter image description here

Good question! Your Problem is here:

<td>
    @Html.CheckBoxFor(m => m.UserList.Find(us => us.Id == u.Id).IsAdmin)
</td>
<td>
    @Html.CheckBoxFor(m => m.UserList.Find(us => us.Id == u.Id).CanWriteArticles)
</td>

That is, View didn't bind to model correctly. I know you wanna act on all table rows one time. So I profoundly suggest you to read Model Containing List of Models (MVC-3, Razor) question and believe your problem will be solved.

And if you wanna know what is enctype = "multipart/form-data", read What does enctype='multipart/form-data' mean?.

regards.

This works for me in MVC 5

  1. Creating an instance in the constructor makes sure you won't get a null
  2. Bind the List explicitly, so you'll get the answer from the view

    [Bind(Include = "UserList")]
    public class AdminViewModel
    {
        public AdminViewModel()
        {
            this.UserList = new List<User>();
        }
    
        public List<User> UserList { get; set; }
    }
    
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top