Implement Pagination in ASP.NET Core Web API and Entity Framework Core


Welcome, everyone, to the new blog! Here, we'll learn how to return paginated results using ASP.NET Core Web API and Entity Framework Core. This will allow us to retrieve rows from our SQL Server database in a paginated format as an API response.
Before we dive into the implementation, let's first understand what pagination is.

What is Pagination?

In the context of API responses, pagination is a technique used to divide a large number of records into smaller, manageable chunks, which are then returned to the client as separate pages.

Why use Pagination?

When we have a large number of records to retrieve and display to the client, returning and displaying all the records at once can significantly increase the load on both the server and the client, leading to longer response times and a poor user experience. This can result in a sluggish interface on the client side, which is frustrating for users.

By implementing pagination, we can divide the large set of records into smaller, more manageable pages. The server will then return only one page at a time in response to a client's request, along with relevant information like the total number of records and pages. This approach reduces network load and makes it easier for the client to handle the data, resulting in a smoother and more responsive user experience.

In this tutorial, we’ll use our existing EmployeeManagement project to learn how to implement a paginated response. Currently, our API returns all employee records at once, which can be problematic if there are a large number of records. Handling all these records simultaneously can be time-consuming and can degrade the user interface. To address this, we’ll add pagination to our API.

Here is the link to the previous blog, which tells us how to CRUD using ASP.NET Core Web API and Entity Framework Core.

Existing Implementation

Below is the existing Get() action method in our EmployeeController, which currently returns all employee records at once.

// GET: api/<EmployeesController>

[HttpGet]

public async Task Get()

{

var results = await _employeeService.GetAllEmployees();

return Ok(results);

}

As we can see, the method simply returns the response from the GetAllEmployees() method. Additionally, it does not accept any query parameters for PageSize or PageIndex, meaning it retrieves and returns all records without any pagination.

Here is the GetAllEmployees() method declaration in the IEmployeeService interface:

Task<IEnumerable<EmployeeModel>> GetAllEmployees();

And here is the definition of the GetAllEmployees() method in the EmployeeService class:

public async Task<IEnumerable<EmployeeModel>> GetAllEmployees()

{

return await employeeDBContext.Employees.ToListAsync();

}

Let's Implement Pagination.

The GET api/Employees endpoint must return pagination information along with the list of paginated employee data. The following properties will be included in the response:
  • TotalRecords - Indicates the total number of records present in the database.
  • CurrentPageIndex - Specifies the page number for which the list is returned.
  • PageSize - Specifies the number of records present on the current page.
  • TotalPages - Indicates the total number of pages, based on the PageSize.
To achieve this, we'll create a ResponseModel class that contains the above pagination properties. We'll design this as a generic class so it can be used across all APIs that require pagination. In our case, we've named the model class PaginatedListModel and placed it under the Models folder.
Here's the implementation: 

namespace EmployeeManagement.Models

{

public class PaginatedListModel<T>

{

public int TotalRecords { get; set; } public int CurrentPageIndex { get; set; } public int PageSize { get; set; } public int TotalPages { get; set; } public List<T> Items { get; set; } public PaginatedListModel(List<T> items, int totalRecords, int currPageIndex, int pageSize) { Items = items; TotalRecords = totalRecords; CurrentPageIndex = currPageIndex; PageSize = pageSize; TotalPages = (int)Math.Ceiling((decimal)totalRecords/(decimal)pageSize); } } }

To fetch records for a specific page, we need the values for pageIndex and pageSize, which will tell us which records to return for that page.

Updating the GetAllEmployees Method

We need to modify the method declaration of GetAllEmployees in the IEmployeeService interface to return a paginated response to the EmployeeController:

Task<PaginatedListModel<EmployeeModel>> GetAllEmployees(int pageIndex, int pageSize);

Here, we've used the existing EmployeeModel class in place of T. The EmployeeModel contains information about an employee.

Since we've updated the declaration of GetAllEmployees in the interface, we also need to update the implementation in the EmployeeService class:

public async Task<PaginatedListModel<EmployeeModel>> GetAllEmployees(int pageIndex, int pageSize) { var totalRecords = employeeDBContext.Employees.Count(); var employees = await employeeDBContext.Employees.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToListAsync(); return new PaginatedListModel<EmployeeModel>(employees, totalRecords, pageIndex, pageSize); }

In this method, pageIndex and pageSize are used to determine which records to fetch from the database. The Skip method skips records from the previous pages, and Take fetches the specified number of records for the current page.

Modifying the Controller to Accept Pagination Parameters

To fetch records for a specific page, the client must pass the values for pageIndex (i.e., page number) and pageSize (i.e., number of records per page) as query parameters in the API request. 
Here's how to update the controller:

// GET: api/<EmployeesController> [HttpGet] public async Task<IActionResult> Get(int pageIndex = 1, int pageSize = 10) { var results = await _employeeService.GetAllEmployees(pageIndex, pageSize); return Ok(results); }

We've added two query parameters here:

  • pageIndex - Specifies the page number to return. It defaults to 1, so if no parameter is passed, the service method will default to returning the first page.
  • pageSize - Specifies how many rows should be returned on a page. It defaults to 10, meaning the service method will return 10 records per page if no parameter is passed.

Now let's run the project.

We've added some records to our Employees table to test the pagination effectively. 

Below is a screenshot of the Swagger UI, showing the request and response for our paginated GET API.

Swagger UI Request and Response

Screenshot: API Request for Paginated Data (Page 1, PageSize 10)

Here, we've passed pageIndex = 1 and pageSize = 10. This means the API will return records for the first page, with each page containing up to 10 records. As a result, the response will include the first 10 records from the dataset.


Screenshot: API Response for Paginated Data (Page 1, PageSize 10)

We can see the paginated response in the screenshot. By checking the CurrentPageIndex and PageSize values, we can verify that the correct page information is returned as expected. The response also indicates that there are a total of 21 records in the dataset. With the current PageSize value of 10, there will be a total of 3 pages needed to view all the records.

Screenshot: API Request and Response for Paginated Data (Page 2, PageSize 10)

Here is the request and response for the 2nd page, using the same PageSize of 10. Note that you can adjust the PageSize parameter as needed to change the number of records returned per page.

Conclusion

In this blog, we have explored how to implement pagination in an ASP.NET Core Web API using Entity Framework Core. We started by understanding the concept of pagination and its importance in handling large datasets efficiently. We then walked through updating our existing EmployeeController to include pagination, creating a PaginatedListModel to encapsulate pagination details, and modifying the service and controller to handle paginated responses.

By applying pagination, we significantly improved the performance and usability of our API, providing a better experience for clients by reducing the load and enabling manageable chunks of data retrieval.

Implementing pagination is a crucial step in building scalable and user-friendly APIs. With the techniques covered here, you can ensure that your APIs handle large volumes of data efficiently while maintaining a responsive and intuitive user experience.

Thank you for following along. We hope this guide has been helpful in understanding and implementing pagination in your own projects. If you have any questions or need further assistance, feel free to leave a comment or reach out.


Comments