In this article, we’re going to learn how to implement paging in ASP.NET Core Web API. Paging (pagination) is one of the most important concepts in building RESTful APIs.

As a matter of fact, we don’t want to return a collection of all resources when querying our API. That can cause performance issues and it’s in no way optimized for public or private APIs. It can cause massive slowdowns and even application crashes in severe cases.

The source code for this article can be found on the GitHub repo. If you want to follow along with the article, you can use the start branch and if you want to get the final solution or if you get stuck, switch to the end branch.

NOTE: Some degree of previous knowledge is needed to follow this article. It relies heavily on the ASP.NET Core Web API series on Code Maze, so if you are not sure how to set up the database or how the underlying architecture works, we strongly suggest you go through the series.

We’ll discuss what paging is, the easiest way to implement it, and then improve on that solution some more to create a more readable and flexible codebase.

So let’s see what we’re going to talk about in this article exactly:

Let’s start.

What is Paging?

Paging refers to getting partial results from an API. Imagine having millions of results in the database and having your application try to return all of them at once.

Not only that would be an extremely ineffective way of returning the results, but it could also possibly have devastating effects on the application itself or the hardware it runs on. Moreover, every client has limited memory resources and it needs to restrict the number of shown results.

Thus, we need a way to return a set number of results to the client in order to avoid these consequences.

Let’s see how we can do that.

Initial Implementation

Before we do any changes to the source code, let’s inspect how it looks right now, and how you would probably begin with any project.

In our case, we have the OwnerController which does all the necessary actions on the Owner entity.

One particular action that stands out, and that we need to change is the GetOwners() action:

Which calls GetOwners() from OwnerRepository:

FindAll() method is just a method from a Base Repository class that return the whole set of owners.

As you can see it’s a straightforward action, meant to return all the owners from the database ordered by name.

And it does just that.

But, in our case, that’s just a few account owners (five). What if there were thousands or even millions of people in the database (you wish, but still, imagine another kind of entity). End then add to that, a few thousand of API consumers.

We would end up with a very long query that returns A LOT of data.

The best-case scenario would be that you started out with a small number of owners that increased slowly over time so you can notice the slow decline in performance. Other scenarios are far less benign for your application and machines (imagine hosting it in the cloud and not having proper caching in place).

So, having that in mind, let’s modify this method to support paging.

Paging Implementation

Mind you, we don’t want to change the base repository logic or implement any business logic in the controller.

What we want to achieve is something like this: https://localhost:5001/api/owners?pageNumber=2&pageSize=2. This should return the second set of two owners we have in our database.

We also want to constraint our API not to return all the owners even if someone calls https://localhost:5001/api/owners.

Let’s start by changing the controller:

A few things to take note here:

  • We’re calling the GetOwners method from the OwnerRepository, which doesn’t exist yet, but we’ll implement it soon
  • We’re using [FromQuery] to point out that we’ll be using query parameters to define which page and how many owners we are requesting
  • OwnerParameters class is the container for the actual parameters

We also need to actually create OwnerParameters class since we are passing it as an argument to our controller. Let’s create it in the Models folder of the Entities project:

We are using constant maxPageSize to restrict our API to a maximum of 50 owners. We have two public properties – PageNumber and PageSize. If not set by the caller, PageNumber will be set to 1, and PageSize to 10.

Now, let’s implement the most important part, the repository logic.

We need to extend the GetOwners() method in the IOwnerRepository interface and in the OwnerRepository class:

And the logic:

Ok, the easiest way to explain this is by example.

Say we need to get the results for the third page of our website, counting 20 as the number of results we want. That would mean we want to skip the first ((3 – 1) * 20) = 40 results, and then take the next 20 and return them to the caller.

Does that make sense?

Testing the Solution

Now, in our database we only have only a few owners, so let’s try something like this:


This should return the next subset of owners:

If that’s what you got, you’re on the right track.

Now, what can we do to improve this solution?

Improving the Solution

Since we’re returning just a subset of results to the caller, we might as well have a PagedList instead of List.

PagedList will inherit from the List class and will add some more to it. We can also, move the skip/take logic to the PagedList since it makes more sense.

Let’s implement it.

Implementing PagedList Class

We don’t want our skip/take logic implemented inside our repository. Let’s create a class for it:

As you can see, we’ve transferred the skip/take logic to the static method inside of the PagedList class. We’ve added a few more properties, that will come in handy as metadata for our response.

HasPrevious is true if CurrentPage is larger than 1, and HasNext is calculated if CurrentPage is smaller than the number of total pages. TotalPages is calculated too, by dividing the number of items by the page size and then rounding it to the larger number since a page needs to exist even if there is one item on it.

Now that we’ve cleared that out, let’s change our OwnerRepository and OwnerController accordingly.

First, we need to change our repo (don’t forget to change the interface too):

And then the controller:

Now, if we send the same request as we did earlier https://localhost:5001/api/owners?pageNumber=2&pageSize=2, we get the same exact result:

But now we have some additional useful information in X-Pagination response header:

postman response headers paging

Response headers

As you can see, all of our metadata is here. We can use this information when building any kind of frontend pagination functionality. You can play around with different requests to see how it works in other scenarios.

There is one more thing we can do to make our solution even more generic. We have the OwnerParameters class, but what if we want to use it in our AccountController? Parameters that we send to the Account controller might be different. Maybe not for paging, but we’ll send a bunch of different parameters later on and we need to separate the parameter classes.

Let’s see how to improve it.

Creating a Parent Parameters Class

First, let’s create an abstract class QueryStringParameters. We’ll use this class to implement mutually used functionalities for every parameter class we will implement. And since we have OwnerController and AccountController, that means we need to create OwnerParameters and AccountParameters classes.

Let’s start by defining QueryStringParameters class inside the Models folder of the Entities project:

We’ve also moved our paging logic inside the class since it will be valid for any entity we might want to return through the repository.

Now, we need to create AccountParameters class, and then inherit the QueryStringParameters class in both the OwnerParameters and the AccountParameters classes.

Remove the logic from OwnerParameters and inherit QueryStringParameters:

And create AccountParameters class inside the Models folder too:

Now, these classes look a bit empty now, but soon we’ll be populating them with other useful parameters and we’ll see what the real benefit is. For now, it’s important that we have a way to send a  different set of parameters for AccountController and OwnerController.

Now we can do something like this too, inside our AccountController:

And due to the inheritance of the paging parameters through the QueryStringParameters class, we get the same behavior. Neat.

That’s about it, let’s summarize.


Paging is a useful and important concept in building any API out there. Without it, our application would probably slow down considerably or just plain drop dead.

The solution we’ve implemented is not perfect, far from it, but you got the point. We’ve isolated different parts of the paging mechanism and we can go even further and make it a bit more generic. But you can do it as an exercise and implement it in your own project. You can also find one frontend application of paging in our Angular Material paging article.

In this article we’ve covered:

  • The easiest way to implement pagination in ASP.NET Core Web API
  • Tested the solution in a real-world scenario
  • Improved that solution by introducing PagedList entity and separated our parameters for different controllers

Hope you liked this article and that you’ve learned something new or useful from it.

If you have enjoyed reading this article and if you would like to receive the notifications about the freshly published .NET Core content we encourage you to subscribe to our blog.