Multiselect filtering a paged view in ASP.Net MVC

3.5k Views Asked by At

In my MVC 5 "Index" views, I use a paged list and have filters applied that are just drop down lists as follows (filtering list of users by role).

public ActionResult Index(int? roleId, int? page)
{
    var pageNumber = page ?? 1;
    var currentRoleId = roleId ?? 0;
    var Roles = db.Roles.OrderBy(r => r.Name).ToList();
    ViewBag.RoleId = new SelectList(Roles, "RoleId", "Name", currentRoleId);

    var users = db.Users;
    ViewBag.OnePageOfUsers = users.ToPagedList(pageNumber, 20); 

In my view I have the following to capture the filter change,

@Html.DropDownList("RoleId", null, new { onchange = "$(this.form).submit();" })

and the following to handle the paging with selected roleid

@Html.PagedListPager((IPagedList)ViewBag.OnePageOfUsers,
        page => Url.Action("Index", new
        {
            page,
            roleId = ViewBag.RoleId.SelectedValue
        }),
        PagedListRenderOptions.ClassicPlusFirstAndLast)

This all works fine, but now that I want to use a multiselect list (filter by multiple roles), I have hit a brickwall. I have got to the point where I can initialise the selected roles, and display the list, but when I go to change the filter selection, I cannot work out how to get the selection back to the controller. After lots of trial and error, my approach is as follows:

Created a view model to hold the filter list and selection.

public IEnumerable<int> SelectedRoleIds { get; set; }
public MultiSelectList Roles { get; set; }

Initialise my ViewModel in my controller...

public ActionResult Index(int ? page, int [] roles) 
{
    :
    model.SelectedRoleIds = new int[] { 2, 3 };// just for testing
    model.Roles = new MultiSelectList(RoleList, "RoleId", "Name", model.SelectedRoleIds);

In my view, I now have a ListBox for multiselection.

@Html.ListBoxFor(m => m.SelectedRoleIds, Model.Roles, new { multiple = "multiple", onchange = "$(this.form).submit();" })

And in my view I try to assign the selection...

@Html.PagedListPager((IPagedList)ViewBag.OnePageOfUsers,
        page => Url.Action("Index", new
        {
            page,
           roles = Model.SelectedRoleIds
        }),
        PagedListRenderOptions.ClassicPlusFirstAndLast)

Is my approach correct? I have gone around in circles, sometimes getting paging to work without filtering and vice versa. I have tried a POST action but then I can't get the paging to work. Possibly there is a much better way of achieving my goal.

1

There are 1 best solutions below

0
On BEST ANSWER

Overall my approach was on track. The key part that I was missing was sending the selected values from the view to the controller, which means converting an int array (say x[] = {1,2,3}) to query parameters x=1&x=2&x=3 so it can be passed back.

Here is my simplified overall solution for filtering an MVC paged list/index view using a multiselect list.

My View Model

public class UserViewModel
{
    public int [] SelectedRoleIds { get; set; }
    public MultiSelectList Roles { get; set; }
}

The Controller Action

public ActionResult Index(int? page, string[] SelectedRoleIds)
{
    UserViewModel model = new UserViewModel();
    var users = db.Users;
    var pageNumber = page ?? 1;
    var RoleList = db.Roles;
    model.Roles = new MultiSelectList(RoleList, "RoleId", "Name", model.SelectedRoleIds);
    if (SelectedRoleIds != null)
        model.SelectedRoleIds = Array.ConvertAll(SelectedRoleIds, int.Parse);

    // Filter the users by role selection
    if (model.SelectedRoleIds != null)
    {
        users = users.Where(a => model.SelectedRoleIds.All(requiredId => a.Roles.Any(role => role.RoleId == requiredId)));
    }

    ViewBag.OnePageOfUsers = users.ToPagedList(pageNumber, pagesize); 

    return View(model);
}

The View

@Html.ListBoxFor(m => m.SelectedRoleIds, Model.Roles, new { multiple = "multiple", onchange = "$(this.form).submit();" })
<a href="@Url.Action("Index")" class="btn btn-info btn-xs">Reset</a>

@Html.PagedListPager((IPagedList)ViewBag.OnePageOfUsers,
    page => Url.Action("Index", new { page = page }) + 
        (Model.SelectedRoleIds == null ? "" : "&" + string.Join("&", ((int[])Model.SelectedRoleIds).Select(x => "SelectedRoleIds=" + Url.Encode(x.ToString())))))