ViewModel data is empty on form submit when using XPagedList for paging

Nico L 21 Reputation points
2021-06-04T03:43:44.453+00:00

Hello,

I followed the link below and trying to get X.PagedList to work when assigning a role to user(s) via form submit but I couldn't get it to work with the error 'Object reference not set to an instance of an object.' in the view page. I have VS 2019 and using .Net 5.

https://stackoverflow.com/questions/14258212/mvc-posting-ipagedlist

View Model:

    public class RoleUsersVM
    {
        public class RoleUsersViewModel
        {
            public string UserId { get; set; }
            public string UserName { get; set; }
            public bool IsSelected { get; set; }
            public int? Page { get; set; }
            public List<RoleUsersViewModel> Clients { get; set; }
            public IPagedList PagingMetaData { get; set; }
        }
    }

Controller code:

        [HttpGet]
        public async Task<IActionResult> AssignRoleToUsers(int? page, string id)
        {
            int pageNumber = page ?? 1; // if no page is specified, default to the first page (1)
            int pageSize = 3; // Get 3 for each requested page.

            var role = await _roleManager.FindByIdAsync(id);

            if (role == null)
            {
                ViewBag.ErrorMessage = $"Role with Id = {id} cannot be found";
                return View("~/Views/Shared/NotFound.cshtml");
            }

            ViewBag.roleId = role.Id;
            ViewBag.roleName = role.Name;

            RoleUsersViewModel pModel = new();
            IList<RoleUsersViewModel> model = new List<RoleUsersViewModel>();


            IQueryable<ApplicationUser> users = _userManager.Users.OrderBy(l => l.LastName);

            int totalCount = users.Count();

            var getUsers = users.Select(u => new { u.Id, u.FirstName, u.LastName }).Skip(pageSize * pageNumber).Take(pageSize).ToList();


            for (int i = 0; i < getUsers.Count; i++)        //.AsParallel().WithExecutionMode(ParallelExecutionMode.Default)
            {
                var roleUsersViewModel = new RoleUsersViewModel
                {
                    UserId = getUsers[i].Id,
                    UserName = getUsers[i].FirstName + " " + getUsers[i].LastName
                };

                bool isInthisRole = GetUserRole(getUsers[i].Id, role.Id);

                if (isInthisRole)  //(await _userManager.IsInRoleAsync(user, role.Name))
                {
                    roleUsersViewModel.IsSelected = true;
                }
                else
                {
                    roleUsersViewModel.IsSelected = false;
                }


                model.Add(roleUsersViewModel);
            }

            pModel.Clients = (List<RoleUsersViewModel>)model;

            IPagedList pMd = pModel.PagingMetaData;

            pMd = new StaticPagedList<RoleUsersViewModel>(pModel.Clients, pageNumber, pageSize, totalCount);


            return View("~/Areas/SiteAdmin/Views/RoleAdmin/AssignRoleToUsers.cshtml", pMd);
        }



        [HttpPost]
        public async Task<IActionResult> AssignRoleToUsers(string roleId, IEnumerable<RoleUsersViewModel> model)
        {

            var role = await _roleManager.FindByIdAsync(roleId);

            if (role == null)
            {
                ViewBag.ErrorMessage = $"Role with Id = {roleId} cannot be found";
                return View("~/Views/Shared/NotFound.cshtml");
            }

            foreach (var user in await _userManager.GetUsersInRoleAsync(role.Name))
            {
                var remResult = await _userManager.RemoveFromRoleAsync(user, role.Name);

                if (!remResult.Succeeded)
                {
                    ModelState.AddModelError("", $"Cannot remove user '{user.FirstName + " " + user.LastName}' from existing role {role.Name}");
                    return View("~/Areas/SiteAdmin/Views/RoleAdmin/AssignRoleToUsers.cshtml", model);
                }
            }

            var users = await _userManager.Users.OrderBy(l => l.LastName).ToListAsync();

            foreach (var user in users)
            {
                if (model.Where(x => x.IsSelected && x.UserId == user.Id).Any())
                {
                    var addResult = await _userManager.AddToRoleAsync(user, role.Name);

                    if (!addResult.Succeeded)
                    {
                        ModelState.AddModelError("", "Cannot add selected users to role");
                        return View("~/Areas/SiteAdmin/Views/RoleAdmin/AssignRoleToUsers.cshtml", model);
                    }
                }
            }

            return RedirectToAction("ListRoles");

        }

View:

using X.PagedList.Mvc.Core; <!--import to get HTML Helper-->
@using X.PagedList;
@using myAppCore.Areas.SiteAdmin.Models;

@model IPagedList<RoleUsersVM.RoleUsersViewModel>

@{
    ViewBag.Title = "Grant Users";
    var roleId = ViewBag.roleId;
    var roleName = ViewBag.roleName;
}




<div class="container-fluid">
    <br />
    <h3 class="text-left colorTitle">@ViewBag.Title</h3><hr /><br />

    <div class="row">

        <div class="w-50 mx-auto">

            <form method="post">
                <div class="card">
                    <div class="card-header text-center">
                        <h4>Grant users with role: <b>@roleName</b></h4>
                    </div>

                    <table class="table" border="1">
                        <tbody>
                            @foreach (var user in Model)
                            {
                                <tr>
                                    <td>
                                        <div class="card-body">
                                            <div class="mx-auto col-2">
                                                <div class="form-check m-3 text-nowrap">
                                                    <input type="hidden" asp-for="@user.UserId" />
                                                    <input type="hidden" asp-for="@user.UserName" />
                                                    <input asp-for="@user.IsSelected" class="form-check-input" />
                                                    <label class="form-check-label" asp-for="@user.IsSelected">
                                                        <b>@user.UserName</b>
                                                    </label>
                                                </div>
                                            </div>
                                            <div asp-validation-summary="All" class="text-danger"></div>
                                        </div>
                                    </td>
                                </tr>
                            }
                        </tbody>
                    </table>

                    <div class="row">
                        <div class="mx-auto float-right">
                            <!-- paging control for navigation to the previous page, next page, etc -->
                            @Html.PagedListPager(new StaticPagedList<RoleUsersVM.RoleUsersViewModel>((IEnumerable<RoleUsersVM.RoleUsersViewModel>)Model[0].Clients, (IPagedList)Model[0].PagingMetaData), page => Url.Action("AssignRoleToUsers", new { page = page, id = @roleId }),
                                new X.PagedList.Web.Common.PagedListRenderOptions
                                {
                                    DisplayItemSliceAndTotal = true,
                                    ContainerDivClasses = new[] { "navigation" },
                                    LiElementClasses = new[] { "page-item" },
                                    PageClasses = new[] { "page-link" }
                                })
                        </div>
                    </div><br />


                    <div class="card-footer">
                        <div class="float-right btn-group">
                            <input type="submit" value="Grant" asp-route-roleId="@roleId" class="btn btn-warning rounded" />
                            <a asp-action="ListRoles" class="btn btn-primary rounded ml-2">Cancel</a>
                        </div>
                    </div>
                </div>
            </form>


        </div>

    </div>
</div>


@section Scripts{
    <script>
        $(document).ready(function () {
            $('ul.pagination > li.disabled > a').addClass('page-link');
        });
    </script>
}

The view does receive the viewmodel data via HTTPGET; when I use VS debugger to examine the Model, I can see that it has 3 rows of users. However, I am getting the error 'Object reference not set to an instance of an object.' in the view page. I think the reason I get that error is because both Model[0].Clients and Model[0].PagingMetaData are null. I did check the controller code and it showed that both pModel.Clients and pModel.PagingMetaData do have user rows in them but in the view page, they are null.

Any ideas on how I can fix this null error?

Note: My main objective of this task is to have the viewmodel transfer rows of users with the assigned role upon a form submit (httppost) but I am not certain if I am using the right approach or not. The link at the top is for the old version of PagedList.

Thank you in advance.

ASP.NET Core
ASP.NET Core
A set of technologies in the .NET Framework for building web applications and XML web services.
4,622 questions
{count} votes

Accepted answer
  1. Yihui Sun-MSFT 801 Reputation points
    2021-06-09T03:23:33.127+00:00

    Hi @Nico L ,

    > I am still not getting the view model data on submit. It shows "model.count = 0"

    It looks like what you are experiencing is a new problem.

    I simplified the code you provided for testing and found that it is a Model binding problem.

    1. For targets that are collections of simple types, model binding looks for matches to parameter_name or property_name. If no match is found, it looks for one of the supported formats without the prefix.
    2. In other words, you are using foreach to traverse the data, so when you use tag helper, the name of the input in the rendered html cannot be correctly bound to the parameters in the action.
    3. You can use for loop data. You can refer to the code I tested below. Model
          public class RoleUsersViewModel
          {
              public string UserId { get; set; }
              public string UserName { get; set; }
              public bool IsSelected { get; set; }
          }
      

    Controller

        public class TestController : Controller
        {
            public IActionResult Index()
            {
                List&lt;RoleUsersViewModel&gt; test = new List&lt;RoleUsersViewModel&gt;();
                for(int i = 1; i &lt; 10; i++)
                {
                    test.Add(new RoleUsersViewModel
                    {
                        UserId = i.ToString(),
                        UserName = &#34;UserName&#34; + i.ToString(),
                        IsSelected = true
                    }); 
                }
                var pMd = new StaticPagedList&lt;RoleUsersViewModel&gt;(test, 1, 3, test.Count);
                return View(pMd);
            }
            [HttpPost]
            public IActionResult Index(string roleId, IEnumerable&lt;RoleUsersViewModel&gt; model)
            {
                return View();
            }
    

    View

        @using X.PagedList.Mvc.Core; 
        @using X.PagedList;
        @model IPagedList&lt;WebApplication24.Models.RoleUsersViewModel&gt;
        &lt;form method=&#34;post&#34;&gt;
            &lt;table class=&#34;table&#34; border=&#34;1&#34;&gt;
                &lt;tbody&gt;
                    @for(var i=0;i&lt;Model.Count;i++)
                    {
                        &lt;tr&gt;
                            &lt;td&gt;
                                &lt;div class=&#34;card-body&#34;&gt;
                                    &lt;div class=&#34;mx-auto col-2&#34;&gt;
                                        &lt;div class=&#34;form-check m-3 text-nowrap&#34;&gt;
                                            &lt;input type=&#34;hidden&#34; asp-for=&#34;@Model[i].UserId&#34; /&gt;
                                            &lt;input type=&#34;hidden&#34; asp-for=&#34;@Model[i].UserName&#34; /&gt;
                                            &lt;input asp-for=&#34;@Model[i].IsSelected&#34; class=&#34;form-check-input&#34; /&gt;
                                            &lt;label class=&#34;form-check-label&#34; asp-for=&#34;@Model[i].IsSelected&#34;&gt;
                                                &lt;b&gt;@Model[i].UserName&lt;/b&gt;
                                            &lt;/label&gt;
                                        &lt;/div&gt;
                                    &lt;/div&gt;
                                    &lt;div asp-validation-summary=&#34;All&#34; class=&#34;text-danger&#34;&gt;&lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/td&gt;
                        &lt;/tr&gt;
                    }
                &lt;/tbody&gt;
            &lt;/table&gt;
            &lt;input type=&#34;submit&#34; value=&#34;Grant&#34; asp-route-roleId=&#34;1&#34; class=&#34;btn btn-warning rounded&#34; /&gt;
        &lt;/form&gt;
    

    103620-result.gif


    If the answer is helpful, please click "Accept Answer" and upvote it. Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread. Best Regards, YihuiSun


3 additional answers

Sort by: Most helpful
  1. Nico L 21 Reputation points
    2021-06-04T17:22:07.92+00:00

    In case someone else has the same error in this case, I was able to resolve the error by using ViewBags as below:

    Controller:

            ViewBag.Clients = pModel.Clients;  
            ViewBag.PagingMetaData = pMd;  
    

    View:

    @azzedinehtmlsql .PagedListPager(new StaticPagedList<RoleUsersVM.RoleUsersViewModel>((IEnumerable<RoleUsersVM.RoleUsersViewModel>)ViewBag.Clients, (IPagedList)ViewBag.PagingMetaData), page => Url.Action("AssignRoleToUsers", new { page = page, id = @roleId }),

    0 comments No comments

  2. Nico L 21 Reputation points
    2021-06-04T17:31:17.59+00:00

    Update:

    I am still not getting the view model data on submit. It shows "model.count = 0" when using VS debugger to check the controller action below: Note: The variable roleId does have the value passed to it.

         [HttpPost]
         public async Task<IActionResult> AssignRoleToUsers(string roleId, IEnumerable<RoleUsersViewModel> model)
    

    Any ideas on how we can make it work with X.PagedList paging anyone? Please advise.

    Thanks in advance.

    0 comments No comments

  3. Nico L 21 Reputation points
    2021-07-08T20:00:45.757+00:00

    Hello,

    I would like to post a new question on this site today but I received the message below after clicking the submit button:

    Access Denied
    You don't have permission to access "http://learn.microsoft.com/answers/questions/ask.html" on this server.

    Reference #18.b77519b8.1625773871.8136a76

    Can someone tell me why please. Thanks.

    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.