Learn how to add pagination to large collections. In this video, Nate shows you how to build paging logic that you can reuse in any collection response.
- [Instructor] If you check out the exercise files for this video, you'll see a few things that I added. I added some models that represent a booking and an opening in the hotel, and their corresponding database entities. I also added some services to deal with those models and some routes as well. If you're following along in your own project you can compare and copy these files from the begin state of this video, or it may just be easier for you to open up the solution in the begin state and keep working from there. One of the new routes I added demonstrates a problem the API currently has with returning collections.
To demonstrate, I'll run the API and send a request with Postman. I'm going to request the route rooms slash openings which will return a list of all the openings for the hotel. As you can see, the response is quite large, there's a lot of elements in this collection. Let's add some pagination functionality to the API that we can reuse for any collection that returns a lot of items. First, I'll add a class that extends the base collection class.
I'll call this class paged collection of T. Just like the base collection class, this will be a generic class, and it'll extend from collection of T. The additional properties that we'll add on top of collection are a nullable int called offset, which will represent the offset this page has from the beginning of the collection. We'll also have another nullable int called limit, which will represent how many items to return in this current page. And also an int called size, which represents the total number of items in the entire collection regardless of the pagination.
We'll also include a number of navigation properties. We'll have a link called first, which will point to the beginning of the collection, and we'll have a link called previous, which points to the previous page, a next link, and finally a last link. I'll add some JSON.net attributes to make this a little nice when it gets serialized. In the case of these nullable ints, I don't want them to be included if they're null, so I'll set null value handling to ignore. For both offset and limit, and the same is true of the links, if there are no links to first, previous, next, or last, I want those to be ignored as well.
Alright, that takes care of the page collection class. The route we were hitting a moment ago is in the rooms controller. And the get all rooms opening async method. We need to modify this method to accept two new parameters, the offset and limit parameters. We could add these parameters directly to the method signature, or we can create a simple model that will hold the values and asp.net core will bind them automatically for us. Let's create a model called paging options. Like the page collection class, this will have a nullable int offset and a nullable limit.
One advantage of putting these parameters in a model class is that we can annotate them with some attributes. I can say use the range attribute from the data annotations namespace, and say the range of this needs to be between one and 9999 and the error message if this is invalid we can say offset must be greater than zero. For the limit parameter we'll say the range is between zero and 100, and the error message is limit must be greater than zero and less than 100.
ASP.net core will automatically validate these parameters for us and return an error to the client if the parameters are out of range. Now we can add this parameter model to the method signature. We'll accept a paging options called paging options, and we also have to tell asp.net core that this is going to come from the query stream, so we'll use the tag from query. Now that we have a paging options instance in the controller, we need to pass it down to the service. So we'll pass it to get openings async, and of course we'll have to go update the interface method as well.
In the I opening service, get openings async will now accept a paging options called paging options, and the default opening service will accept this as well. Now at the bottom of the get openings async method we can use link to apply this paging offset and limit values to the query. We can say var paged openings equals all openings dot skip paging options dot offset dot value, and take paging options dot limit dot value.
And instead of returning all openings we'll return paged openings. We need to add one more thing. The paged collection model includes the size property as well, which should reflect the total size of the collection. Let's create a small class to hold the size and the items together. We'll create one more model in the models folder called paged results of T. This class will simply contain an I enumerable of T called items.
And an int called total size. Now in the default opening service, we'll return a paged results of opening rather than an I enumerable of opening. And at the bottom we'll return a new paged results of opening with the items property set to paged openings and the total size property set to all openings dot count. We'll need to update the interface once more.
Now back in the rooms controller, the var openings variable is a paged results of opening. Instead of returning a collection, we can return a paged collection. Instead of openings dot to array we can say openings dot items dot to array, and for size, we can say openings dot total size. We can also pass back the offset and limit values just to reflect them to the client. We'll get this from paging options. There are a few more properties of page collection we could set but we'll test what we have so far.
I'll run the project and make a request using Postman again. Instead of requesting slash rooms slash openings I'll add some parameters onto our request here. I'll say limit equals five and offset equals five. And send the request. What we'll get in the response is a collection that contains only five items but reflects that the total size of the collection is actually 27 items. The five items in the collection response are the five items after the offset of the first five items in the collection.
There is one problem we need to fix. If we leave off these parameters, we'll get an error. The code throws an exception because we're accessing the value property of a nullable object and in the case of no parameters in the request, that object is null. We'll add some paging defaults next.
- REST vs. RPC
- Using HTTP methods (aka verbs)
- Returning JSON
- Creating a new API project
- Building a root controller
- Routing to controllers with templates
- Requiring HTTPS for security
- Creating resources and data models
- Returning data and resources from a controller
- Representing links (HREFs)
- Representing collections
- Sorting and searching collections
- Creating forms
- Caching and compression
- Authentication and authorization for RESTful APIs