Why do we need pagination? We don’t want to return everything in a response that contains multiple items. The bandwidth is not enough if you’re sending 10’s of thousands items over the wire. Apart from that, there are other concerns: database querying limits and more.
So instead of returning everything, you serve smaller chunks, called pages. Every REST API that implements pagination is going to work pretty much the same way.
We will start by creating 2 contract classes.
The first class is a
Response<T> which is going to wrap around every thing we return in our API, except the errors.
So, instead of returning let’s say
Ok(new PostResponse(...)) , we are now going to return
Ok(new Response<PostResponse>(...) . You can see that the data are returned inside this top-level object.
Why did we do that?
Let’s say that we want to add some more metadata in each response object, in a compose-able manner, without breaking consumers. This is going to make more sense now that we are going to make a new class called
We’ve seen examples of using inheritance inside the contracts, we believe this is a bad idea because you introduce unnecessary coupling between them, thus being prone to accidentally affecting others as well. What we are going to do next is not a duck typing solution to maintain compatibility with the
Response<T>contract, is’s a completely different one.
We’re taking an
IEnumerable<T> as a constructor parameter because paged responses imply multiple items returned.
pageSize are going to be added as query parameters (
?page=X&pageSize=Y) — remember that query parameters are optional. The
previousPage properties are pointers to the URL of the next and previous pages.
Now that we’ve got our responses set up now, time to craft our
PaginationQuery . We are defaulting the page number to be the first page as well as the page size to be 100. This is really an application-specific constant. If you can or want to handle more pages, feel free to change these. You should also limit your page size on the second constructor and always check that the page number is ≥ 1 in order to avoid any errors.
We can now add a
[FromQuery] PaginationQuery paginationQuery inside any endpoint method’s parameters (we are going to use
ApiRoutes.Posts.GetAll for now). .NET Core is smart enough to map query parameters to our newly created class by name.
PaginationQuery class is a contract, we need to create a new domain object to map this to. We are going to name this
Along with a new mapping entry, as per the previous tutorials.
The next logical step would be to add a
var paginationFilter = _mapper.Map<PaginationFilter>(paginationQuery); and then pass this filter to our
_postService.GetAllAsync(); method (we are going to change it to accept it).
The way to implement paging with EF Core is very simple. We are just going to skip all the items until the first one of the requested page, and select page size items forward.
Our basic pagination code is in place. You could actually use this as-is, but we are going to take this a step further in order to add the previous and next page ‘pointers’.
We are going to create and register a new service called
UriService which is going to be the place where we fabricate any URL that is going to be used in our application.
We need a
baseUri private property because our APIs URIs can vary;
localhost:someport during development,
domain.com:otherport on production and so on.
That’s very simple code, fabricating URIs. Here’s the final controller code:
If the consumer requests no pagination at all, we just return a paged response without additional metadata. Else, we are going to calculate the next and previous pages, as well as make sanity page and page size checks. We extracted this to a different extension method to keep the code’s readability. Here it is:
The last tiny bit is to make sure our base uri is properly dynamic by registering in the
You can see that swagger ui has changed to include these 2 new parameters:
Finally, here’s a sample pagination response returning only 5 posts with the next page pointer:
Up Next: Health Checks