Implementing Pagination in .NET API: A Simple Guide

Osama HaiDer
4 min readDec 1, 2024

--

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:

  1. CreateAsync: This is used when you’re working with IQueryable (e.g., from a database using Entity Framework). It performs the pagination asynchronously.
  2. 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.

--

--

Osama HaiDer
Osama HaiDer

Written by Osama HaiDer

SSE at TEO International | .Net | Azure | AWS | Web APIs | C#

No responses yet