ASP.NET Core  

How to Implement Pagination in ASP.NET Core Web API?

Introduction

When building real-world applications using ASP.NET Core Web API, handling large amounts of data efficiently becomes very important. Imagine loading thousands of records in a single API response β€” it slows down performance, increases memory usage, and creates a poor user experience.

This is where Pagination in ASP.NET Core Web API comes into play.

Pagination helps you divide large datasets into smaller, manageable chunks (pages), making your API faster, scalable, and user-friendly.

What is Pagination?

Pagination is a technique used to split large data into smaller parts (pages).

Instead of returning all records at once, the API returns only a subset of data based on:

  • Page Number

  • Page Size

πŸ‘‰ Example:
If you have 1000 products:

  • Page 1 β†’ First 10 records

  • Page 2 β†’ Next 10 records

This improves:

  • API performance

  • Response time

  • User experience

Why Pagination is Important in Web APIs

Let’s understand why pagination is crucial in ASP.NET Core Web API:

1. Improves Performance

Fetching only required records reduces database load and improves speed.

2. Reduces Memory Usage

Large datasets can consume high memory. Pagination keeps responses lightweight.

3. Better User Experience

Users can easily navigate through pages instead of scrolling endlessly.

4. Scalable APIs

Pagination ensures your API works efficiently even when data grows.

Types of Pagination

There are mainly three types of pagination:

1. Offset-Based Pagination

Uses Skip and Take.

Example:

  • Skip 10 records

  • Take next 10 records

2. Page Number-Based Pagination

Uses PageNumber and PageSize.

Example:

  • PageNumber = 2

  • PageSize = 10

3. Cursor-Based Pagination (Advanced)

Uses a pointer (cursor) instead of page numbers.

Best for large and real-time datasets.

Step-by-Step: Implement Pagination in ASP.NET Core Web API

Let’s implement pagination using a simple example.

Step 1: Create a Model

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}

Step 2: Create Pagination Parameters Class

public class PaginationParams
{
    public int PageNumber { get; set; } = 1;
    public int PageSize { get; set; } = 10;

    private const int MaxPageSize = 50;

    public void Validate()
    {
        if (PageNumber <= 0) PageNumber = 1;
        if (PageSize <= 0) PageSize = 10;
        if (PageSize > MaxPageSize) PageSize = MaxPageSize;
    }
}

πŸ‘‰ This ensures:

  • Default values

  • Maximum limit to avoid performance issues

Step 3: Modify Repository or Service Layer

public async Task<IEnumerable<Product>> GetProductsAsync(PaginationParams paginationParams)
{
    paginationParams.Validate();

    return await _context.Products
        .Skip((paginationParams.PageNumber - 1) * paginationParams.PageSize)
        .Take(paginationParams.PageSize)
        .ToListAsync();
}

πŸ‘‰ Key logic:

  • Skip = (PageNumber - 1) * PageSize

  • Take = PageSize

Step 4: Update Controller

[HttpGet]
public async Task<IActionResult> GetProducts([FromQuery] PaginationParams paginationParams)
{
    var products = await _productService.GetProductsAsync(paginationParams);
    return Ok(products);
}

πŸ‘‰ Now API supports query parameters like:

/api/products?pageNumber=1&pageSize=10

Returning Pagination Metadata

It’s a good practice to send pagination details along with data.

Create Response Wrapper

public class PagedResponse<T>
{
    public IEnumerable<T> Data { get; set; }
    public int TotalRecords { get; set; }
    public int PageNumber { get; set; }
    public int PageSize { get; set; }
}

Update Service

public async Task<PagedResponse<Product>> GetProductsAsync(PaginationParams paginationParams)
{
    paginationParams.Validate();

    var query = _context.Products.AsQueryable();

    var totalRecords = await query.CountAsync();

    var data = await query
        .Skip((paginationParams.PageNumber - 1) * paginationParams.PageSize)
        .Take(paginationParams.PageSize)
        .ToListAsync();

    return new PagedResponse<Product>
    {
        Data = data,
        TotalRecords = totalRecords,
        PageNumber = paginationParams.PageNumber,
        PageSize = paginationParams.PageSize
    };
}

Controller Response

return Ok(await _productService.GetProductsAsync(paginationParams));

πŸ‘‰ Now response includes:

  • Total records

  • Current page

  • Page size

Advanced Pagination with Filtering and Sorting

Pagination is often used with filtering and sorting.

Example:

public async Task<IEnumerable<Product>> GetProductsAsync(string search, string sortBy, PaginationParams paginationParams)
{
    var query = _context.Products.AsQueryable();

    if (!string.IsNullOrEmpty(search))
    {
        query = query.Where(p => p.Name.Contains(search));
    }

    if (!string.IsNullOrEmpty(sortBy))
    {
        query = sortBy.ToLower() switch
        {
            "price" => query.OrderBy(p => p.Price),
            _ => query.OrderBy(p => p.Name)
        };
    }

    return await query
        .Skip((paginationParams.PageNumber - 1) * paginationParams.PageSize)
        .Take(paginationParams.PageSize)
        .ToListAsync();
}

πŸ‘‰ This makes your API powerful and flexible.

Best Practices for Pagination in ASP.NET Core

1. Always Set Maximum Page Size

Prevents heavy queries that can crash your API.

2. Use Async Methods

Improves performance and scalability.

3. Return Metadata

Helps frontend understand total pages.

4. Use Indexing in Database

Improves query performance.

5. Consider Cursor Pagination for Large Data

Better for real-time systems like feeds.

Common Mistakes to Avoid

  • ❌ Returning all data without pagination

  • ❌ Not validating PageSize

  • ❌ Ignoring total count

  • ❌ Using large page sizes

Real-World Example Use Case

Imagine an e-commerce application:

  • Products list β†’ paginated

  • Orders history β†’ paginated

  • Search results β†’ paginated

Without pagination, your API will become slow and inefficient.

Summary

Pagination in ASP.NET Core Web API is a must-have feature for building scalable and high-performance applications. By using techniques like Skip and Take, along with proper validation and metadata, you can create APIs that are fast, efficient, and user-friendly. Implementing pagination not only improves backend performance but also enhances the overall user experience by delivering data in a structured and manageable way.