Implementing Pagination in .NET API: A Simple Guide
When building APIs, one of the most important features to implement is pagination. Pagination helps to divide large sets of data into smaller, manageable chunks. It improves the performance of your API by returning only a limited number of items per request, making it easier to handle and reducing the load on the server.
In this blog post, we’ll walk through the process of implementing pagination in a .NET API using two simple classes: PageParameters
and PagedList
. Let's break it down step by step.
1. Setting up the PageParameters
class
First, we need a class to accept the pagination details, such as the page number and the number of items per page. This is what the client (usually the frontend) will send to the API to tell it which page of data to fetch.
Here’s the PageParameters
class:
/// <summary>
/// Represents page parameters
/// </summary>
[DebuggerDisplay("PageNumber: {" + nameof(PageNumber) + "}, PageSize: {" + nameof(PageSize) + "}")]
public class PageParameters
{
/// <summary>
/// Use for page number that is passed from frontend.
/// </summary>
[JsonPropertyName("pageNumber")]
public int PageNumber { get; set; } = 1;
/// <summary>
/// Use for page size that is passed from frontend.
/// </summary>
[JsonPropertyName("pageSize")]
public int PageSize { get; set; } = 20;
}
Explanation:
PageNumber
: The page number requested (defaults to 1).PageSize
: The number of items per page (defaults to 20).
The JsonPropertyName
attribute is used to map the property names to what the frontend will send (e.g., pageNumber
and pageSize
).
2. Creating the PagedList
class
Next, we create a class called PagedList
that will hold the data we return. This class will store:
- The list of items for the current page.
- The total number of items in the database.
- Information about whether there are more pages (next and previous).
Here’s the PagedList
class:
public class PagedList<T>(List<T> items, int page, int pageSize, int totalCount)
{
public List<T> Items { get; } = items;
public int Page { get; } = page;
public int PageSize { get; } = pageSize;
public int TotalCount { get; } = totalCount;
public bool HasNextPage => Page * PageSize < TotalCount;
public bool HasPreviousPage => PageSize > 1;
public static async Task<PagedList<T>> CreateAsync(IQueryable<T> query, int page, int pageSize)
{
var totalCount = await query.CountAsync();
var items = await query.Skip((page - 1) * pageSize).Take(pageSize).ToListAsync();
return new PagedList<T>(items, page, pageSize, totalCount);
}
public static Task<PagedList<T>> Create(IEnumerable<T> query, int page, int pageSize)
{
var totalCount = query.Count();
var items = query.Skip((page - 1) * pageSize).Take(pageSize).ToList();
return Task.FromResult(new PagedList<T>(items, page, pageSize, totalCount));
}
}
Explanation:
- Items: A list of items that corresponds to the current page.
- Page: The current page number.
- PageSize: The number of items per page.
- TotalCount: The total number of items in the entire dataset.
- HasNextPage: A boolean that indicates if there is a next page.
- HasPreviousPage: A boolean that indicates if there is a previous page.
There are two methods for creating a PagedList
:
- CreateAsync: This is used when you’re working with IQueryable (e.g., from a database using Entity Framework). It performs the pagination asynchronously.
- Create: This is used when you’re working with IEnumerable (e.g., in-memory data). It works synchronously and returns a paged list.
3. Using Pagination in Your API
Now, let’s put everything together in a sample API endpoint. Suppose we have an API that returns a list of users from the database. Here’s how you can apply pagination:
[HttpGet("users")]
public async Task<IActionResult> GetUsers([FromQuery] PageParameters pageParameters)
{
var usersQuery = _context.Users.AsQueryable(); // Get users from the database.
// Use the PagedList class to get paged results
var pagedUsers = await PagedList<User>.CreateAsync(usersQuery, pageParameters.PageNumber, pageParameters.PageSize);
return Ok(pagedUsers);
}
Explanation:
PageParameters
is passed as a query parameter, which contains the page number and page size.- The query is passed to the
PagedList.CreateAsync
method to get the paged data. - The API then returns the paged data to the client.
4. Benefits of Pagination
Pagination offers several benefits:
- Performance: It reduces the number of records being fetched at once, improving the performance of both the server and client.
- User Experience: It makes it easier for users to browse large datasets in smaller chunks, instead of overwhelming them with too much data at once.
- Scalability: As the data grows, pagination helps ensure your API can handle the increase in data size without degrading performance.
Conclusion
Implementing pagination in your .NET API is a simple yet powerful way to handle large datasets efficiently. It helps with performance, improves user experience, and ensures scalability. The classes PageParameters
and PagedList
make it easy to implement pagination in your API, and you can customize them to fit your specific use case.
By following this guide, you should now have a clear understanding of how to implement pagination in your .NET API, making it both faster and more efficient.