Three pieces of information are needed to implement paging:
- The current page number
- The total number of records to be displayed
- The number of records to display on each page (the page size)
Taking the simple approach, I add properties to the PageModel for each of these pieces of information:
The CurrentPage
has a default value of 1, and the number of records per page (PageSize
) is set at 10 by default.
The CurrentPage
has the BindProperty
attribute applied
so that it can automatically be bound from values passed as part of the request.
It also has the SupportsGet
property set to true
specifically so that the query string or route data can be used to pass the
value from request to request.
I also need to calculate the number of pages. I can do this by adding another property on the PageModel that performs the calculation:
Now all I need is some data. Usually this comes from a database, but I am using some
dummy data formatted as JSON that's read from a file. My JSON will deserialise to
a collection of Person
objects:
So I also add a property to the PageModel for that:
Here is my PersonService
class in full along with its interface:
This class is where I get my data from. It is analogous to a repository if you
prefer that approach. It has two methods of interest: GetCount
,
which returns the total number of items to be displayed; and
GetPaginatedResult
, which returns the data for the current page. The
GetPaginatedResult
method uses the LINQ Skip
and
Take
operators. Skip
bypasses the specified number of items
in the collection. If the CurrentPage
is 1, Skip
will
bypass zero elements ((1-1) * 10
). Then the Take
method retrieves the specified number of elements from that point in the
sequence. It will retrieve up to the number of elements represented by the
pageSize
parameter. Importantly, the order of the results is specified to
ensure that the contents of each page of results are consecutive.
If you are using Entity Framework for your data access, the Skip
and Take
operators translate to using whichever paging function is
supported by your database provider - the RowNumber function with a CTE, Offset and
Fetch, Limit and Offset etc.
I register this service with the dependency injection system, and inject it
into the PageModel where it is used in the OnGetAsync
method to populate the
Data
and Count
properties:
So here is the code for the content page:
When the page is run, the default values are used and the first 10 records are displayed, along with paging links for every page of records in the result:
As you hover over the paging links, you can see that the URLs generated by
the anchor tag helper include a query string value for currentpage
, which is the
route value specified by the asp-route
attribute:
If your prefer, you can remove the query string by adding a route template to the content page:
@page "{currentpage:int?}"
Now the currentpage
value will appear as a segment in the URL:
Fine so far, but what if the space for the paging links is constrained or there are many more
links?
They will wrap onto the next line which won't look so good. One solution is to
provide Prev and Next links, where the user can navigate
from one page to the next and back again using just two buttons. To manage that,
I add another pair of properties to the PageModel - ShowPrevious
and
ShowNext
:
These properties are readonly expression bodied properties.They are used to determine whether the Previous and Next buttons should appear:
This certainly saves space but could do with more improvement. So for the
final tweak, I will add a First and Last button, and use FontAwesome icons to
ensure that all of the links are the same size. So, two final properties are
added to the PageModel - ShowFirst
and ShowLast
:
And the content page is amended accordingly:
Here is the finished result:
On the first page, the Previous and First links are disabled, thanks to the styling from Bootstrap 4. They become active on subsequent pages:
Summary
Pagination in Razor Pages is actually very simple once you get a grip of the basics. You can get very effective paging links rendered just by using the anchor tag helper and Bootstrap which is included as part of the standard Razor Pages project template. You don't need to create special ViewModel or wrapper classes for the properties required to manage pagination . You can easily add them to the PageModel instead - unless you are likely to want to reuse pagination throughout the application.