question

NicoL-0098 avatar image
0 Votes"
NicoL-0098 asked NicoL-0098 answered

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

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.





dotnet-aspnet-core-mvc
· 2
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Hi @NicoL-0098,

Any ideas on how I can fix this null error?

Have you set a breakpoint on this line of code, and then check whether the value of Clients in pMd is null?

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

102434-capture.jpg

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.

In addition, I want to confirm your needs with you:

  • Do you want to show users and whether they have been assigned roles and can assign roles to users?

0 Votes 0 ·
capture.jpg (20.5 KiB)

1) Yes, I did. I used VS debugger in controller and it has the records of users.

2) Yes, it is to assign a role to users.


Thank you.

0 Votes 0 ·
YihuiSun-MSFT avatar image
0 Votes"
YihuiSun-MSFT answered NicoL-0098 commented

Hi @NicoL-0098,

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<RoleUsersViewModel> test = new List<RoleUsersViewModel>();
             for(int i = 1; i < 10; i++)
             {
                 test.Add(new RoleUsersViewModel
                 {
                     UserId = i.ToString(),
                     UserName = "UserName" + i.ToString(),
                     IsSelected = true
                 }); 
             }
             var pMd = new StaticPagedList<RoleUsersViewModel>(test, 1, 3, test.Count);
             return View(pMd);
         }
         [HttpPost]
         public IActionResult Index(string roleId, IEnumerable<RoleUsersViewModel> model)
         {
             return View();
         }

View

     @using X.PagedList.Mvc.Core; 
     @using X.PagedList;
     @model IPagedList<WebApplication24.Models.RoleUsersViewModel>
     <form method="post">
         <table class="table" border="1">
             <tbody>
                 @for(var i=0;i<Model.Count;i++)
                 {
                     <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="@Model[i].UserId" />
                                         <input type="hidden" asp-for="@Model[i].UserName" />
                                         <input asp-for="@Model[i].IsSelected" class="form-check-input" />
                                         <label class="form-check-label" asp-for="@Model[i].IsSelected">
                                             <b>@Model[i].UserName</b>
                                         </label>
                                     </div>
                                 </div>
                                 <div asp-validation-summary="All" class="text-danger"></div>
                             </div>
                         </td>
                     </tr>
                 }
             </tbody>
         </table>
         <input type="submit" value="Grant" asp-route-roleId="1" class="btn btn-warning rounded" />
     </form>

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


result.gif (769.0 KiB)
· 2
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Hello,

Thank you for fixing it. I have always used .Net core html tag helpers and they always work fine with view models. For this case, the view model was working fine until I added X.PagedList to it. So I guess we should not use html tag helpers when using X.PagedList?

Thanks again.

0 Votes 0 ·

@YihuiSun-MSFT

After fixing the for loop in the view, the model works great. Thank you.

I have a quick question please.

As you already know my code, I use the two lines below to either add or remove users from a role:

arResult = await _userManager.AddToRoleAsync(getUser, role.Name);

arResult = await _userManager.RemoveFromRoleAsync(getUser, role.Name);

They both work but they are slow in process. Each of them takes about 6 seconds to finish processing.

Do you know why that is? Is there a way that I can improve them?

Thanks in advance.

0 Votes 0 ·
NicoL-0098 avatar image
0 Votes"
NicoL-0098 answered

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://docs.microsoft.com/answers/questions/ask.html" on this server.

Reference #18.b77519b8.1625773871.8136a76


Can someone tell me why please. Thanks.

5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

NicoL-0098 avatar image
0 Votes"
NicoL-0098 answered

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.

5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

NicoL-0098 avatar image
0 Votes"
NicoL-0098 answered

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:

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

5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.