Building a Sortable Paginated Table Component in ASP.NET MVC with PagedList

Overveiw

This article demonstrates how to create a sortable, paginated table component in ASP.NET MVC using the PagedList library. The implementation uses AJAX for seamless data updates without page reloads.

Prerequisites

  1. Install the PagedList.Mvc package via NuGet. This automatically includes the PagedList dependency.
  2. Include jquery.unobtrusive-ajax.min.js to enable asynchronous form submissions with Ajax.BeginForm.
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-ajax-unobtrusive/3.2.6/jquery.unobtrusive-ajax.min.js"></script>

ViewModel Structure

The view model wraps the paginated data along with filter and sorting parameters:

public class ProductViewModel
{
    public IPagedList<Product> Items { get; set; }
    public string SearchTerm { get; set; }
    public string CategoryFilter { get; set; }
    public string SortOrder { get; set; }
    public string PageSize { get; set; }
    public int TotalRecords { get; set; }
}

Controller Implementation

The controller handles the initial request and subsequent AJAX calls:

public class ProductController : Controller
{
    private readonly IProductRepository _repository;

    public ProductController(IProductRepository repository)
    {
        _repository = repository;
    }

    public ActionResult Index(string search, string category, string sort, int? page, int? size)
    {
        int pageNumber = page ?? 1;
        int pageSize = size ?? 10;
        string sortOrder = sort ?? "asc";

        var products = _repository.GetFilteredProducts(search, category, sortOrder);
        var pagedProducts = products.ToPagedList(pageNumber, pageSize);

        var viewModel = new ProductViewModel
        {
            Items = pagedProducts,
            SearchTerm = search,
            CategoryFilter = category,
            SortOrder = sortOrder,
            PageSize = pageSize.ToString(),
            TotalRecords = products.Count()
        };

        if (Request.IsAjaxRequest())
        {
            return PartialView("_ProductTable", viewModel);
        }

        return View(viewModel);
    }
}

View with Ajax Form

The main view uses Ajax.BeginForm to enable asynchronous updates:

@model ProductViewModel
@using PagedList.Mvc

<div id="table-container">
    @using (Ajax.BeginForm("Index", "Product", 
        new AjaxOptions 
        { 
            UpdateTargetId = "table-container", 
            InsertionMode = InsertionMode.Replace 
        }))
    {
        <div class="search-panel">
            <input type="text" name="search" value="@Model.SearchTerm" placeholder="Search products..." />
            <select name="category">
                <option value="">All Categories</option>
                <option value="electronics" selected="@(Model.CategoryFilter == "electronics")">Electronics</option>
                <option value="clothing" selected="@(Model.CategoryFilter == "clothing")">Clothing</option>
            </select>
            <select name="sort">
                <option value="asc" selected="@(Model.SortOrder == "asc")">Name A-Z</option>
                <option value="desc" selected="@(Model.SortOrder == "desc")">Name Z-A</option>
                <option value="price_asc" selected="@(Model.SortOrder == "price_asc")">Price Low-High</option>
                <option value="price_desc" selected="@(Model.SortOrder == "price_desc")">Price High-Low</option>
            </select>
            <button type="submit">Apply Filters</button>
        </div>
    }

    <table class="data-table">
        <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
                <th>Category</th>
                <th>Price</th>
                <th>Stock</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var item in Model.Items)
            {
                <tr>
                    <td>@item.Id</td>
                    <td>@item.Name</td>
                    <td>@item.Category</td>
                    <td>@item.Price.ToString("C")</td>
                    <td>@item.Stock</td>
                </tr>
            }
        </tbody>
    </table>

    <div class="pagination">
        @Html.PagedListPager(Model.Items, 
            page => Url.Action("Index", new { 
                search = Model.SearchTerm, 
                category = Model.CategoryFilter, 
                sort = Model.SortOrder, 
                page = page, 
                size = Model.PageSize 
            }),
            PagedListRenderOptions.EnableUnobtrusiveAjaxReplacing(
                new AjaxOptions { UpdateTargetId = "table-container" }))
    </div>
</div>

Key Implementasion Details

Ajax Form Configuration: Ajax.BeginForm submits the form asynchronously and updatess the content within the UpdateTargetId element without a full page reload.

Parameter Matching: The form input names must correspond exactly to the action method parameters for model binding to work correctly.

PagedListPager Options: The pager supports AJAX replacement through PagedListRenderOptions.EnableUnobtrusiveAjaxReplacing, which wraps pager links with the appropriate AJAX attributes.

Partial View: The controller returns a partial view for AJAX requests, allowing only the table section to refresh while maintaining the rest of the page state.

Tags: ASP.NET MVC PagedList Pagination Ajax web development

Posted on Mon, 11 May 2026 01:45:23 +0000 by Procode