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
- Install the PagedList.Mvc package via NuGet. This automatically includes the PagedList dependency.
- 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.