9.2 Paging Posts
We can display our published posts, but we want something more than just a simple list. So let's implement a paging feature.
1.Introduction2 lessons, 05:56
2.Managing Posts3 lessons, 38:13
3.Tag Management2 lessons, 27:29
4.Storing Data3 lessons, 47:22
5.Testing Functionality2 lessons, 33:06
6.User Management3 lessons, 1:11:48
7.Security and User Authentication2 lessons, 34:17
8.Enhancing the User Experience2 lessons, 36:38
9.The Front-End2 lessons, 36:01
10.Conclusion1 lesson, 01:22
9.2 Paging Posts
Implementing the paging feature for our index is going to be relatively straightforward. Primarily, we just need a way of retrieving a page of posts. So that means that we need to revisit our post repository. So inside of the interface for our repository, let's add a definition for a method that returns a task of IEnumerable of Post, and let's call this GetPageAsync. And we need two things, we need the pageNumber. And the page number is going to start at one. Although in terms of the database itself, the page starts at zero. So inside of the method, we are going to have to adjust the actual page number, but for all intents and purposes, page numbers going to start with one. And we also need the amount of posts within a page. So we can call that pageSize. And it would also be helpful to have the amount of published posts. Because we're going to need that later. So let's add another definition for a property called CountPublished. And we only need a getter, we don't need a public setter here. So now let's just implement these members. Let's go to the concrete class. And I like to define my properties first and then my method. So at the top of the class I'm going to implement that property. So public int and CountPublished. And it has a getter that is going to, first of all, create a CMS context object. So we need to new up that constructor. And then we basically want to return db.Posts. And we also need a where clause, because we want those posts that have a published date less than the current date and time, and really that's it. Though we don't need the Where, we can use the Count method here. So this is going to give us exactly what we want, the amount of published posts. So that's relatively easy. Now we need to implement the method to retrieve a page of posts. So public async, and we will paste in that definition. And once again, we need our database object. So we will new up, CmsContext. And we want to return db.Posts. Now we can do ahead and select those that are published, so we will once again compare the published property with DateTime.Now. But we should also include all of the author information, so let's Include("Author"). And we also want to .OrderByDescending. And that is by the published date as well, so we will specify the Published property. And then here's the magic, we are going to use the Skip method to skip past all of the pages that we don't need. So here we need our page number minus one. And we also need to multiply that by the page size. So, let's create a variable. And I'm just going to call it skip where we take the page number minus one, because we are one behind whatever specified as the page number. And then we want to multiply that difference by the page size, so we need a parentheses around our subtraction operation, and then multiply by pageSize. So that is what we are going to pass to Skip. And then the other part of the recipe is the Take method, where we specify how many posts that we want to take. And then finally, we will call the .ToArrayAsync method. And that's really all that we need to do for this method. Although we do need an await. So with that done, we can close our repository and the interface for our repository. And let's go to the home controller. And let's add a private readonly field called pageSize, and let's set this as 2. This way we can set it once and use it everywhere that we need to inside of our controller, and then if we need to change it, all we have to do is change that value. Ideally, this would be a setting in the database or in the config file so that we don't have to recompile our code, but for the sake of simplicity, we're just going to put it here. So let's go to the Index method. And we need to do something other than just retrieving the published posts, because now we want only a certain amount of published posts. In fact, the index is the first page. So we want to call our new GetPageAsync method. This is the first page, and we will use the _pageSize property that we just created, and then we will pass those posts onto the view. Now, we need a little bit more here, because we need some links to either go to the previous page, or go to the next page, and these links need to be smart. If there's not a previous page, as there is in the case of index, Then we don't need to show a previous page link, but if there is a next page then we need to show that as well, otherwise we don't want to. So this is where the count of how many published posts that we have is going to come into play. So let's use our ViewBag here, ideally we would have a post service that would return a view model that would have all of the paging information in it so that we wouldn't have to use the ViewBag. But once again we're keeping things a little bit simple, so we're going to use the ViewBag. And let's create a property called PreviousPage, and this is going to be the page number of the previous page. In the case of the index, the previous page is zero because we are already at page one. But we do need to calculate what is the next page. So we want the ViewBag once again. And let's create a property called NextPage and in order to determine what the next page is, we need to take the amount of published posts and divide that by our page size. And we really need this to be more than just an integer value. If there are any decimal places, that essentially means that we have another page. So we want to take that into account as well. So we can use the Decimal data types Divide method. And we can specify _posts.CountPublished. This is going to get the amount of published posts that we have, and we want to divide that by our _pageSize, and we want to check to see if this is greater than one, because once again we are at the first page and in the case of the Index method we are only concerned with if there are more pages than page one. So if it is, then the next page is page 2. Otherwise, we can use something that is not a valid page number, which would be -1, or we can use zero, but I like to use -1 in this case. Let's add another action method, one for displaying an individual page. So this is going to be asynchronous. So it will return a Task, and lets just call it Page and its going to accept the page number. And we can initialize that with a value of one. For the route, we can say that this is going to be page, and then we will have our page parameter, but we can also use a constraint that this has to be an integer value. So we have our page, and we can check to see if page is less than two, then we can just redirect to the index. So we can return RedirectToAction and specify index, and that will take care of the first page. But then we essentially want to do what we did inside of the Index method, and that is retrieve the posts for the given page, and then set the previous page and the next page properties. But here we have to be a little more dynamic. We can't hard code in these numeric values. So the first argument that we pass to the GetPageAsync method is going to be our page parameter, and then for the previous page, we're going to say page- 1, and then for the next page, we still want to divide the amount of published posts by the _pagesize, but we want to see if that is greater than what we have inside of page. And if it is, then we want to say page + 1, because that is the next page. Otherwise we will just say- 1 just like we did before, and then we will pass those posts onto the View. Although in this case, we can basically reuse the index view, so we won't have to create one specifically for the page so let's go to our index view. And we are going to need to add some modifications here. The first is going to be the link for the previous page. So we want to check if ViewBag.PreviousPage is > 0, then we want to display that link. So we're going to use the ActionLink method. We will use the text of Previous Page and then the action will be page. And then we just need to set in the other route values. So that's going to be inside an anonymous object. And we want the controller to be home. And we also need to set the page. And we can set that to ViewBag.PreviousPage. And then we want to do the same type of thing for the next page, so let's just copy this code. But let's also add a couple of spaces between these links. So we can do that with a non-breaking space. And then let's paste in the code. Instead of checking for previous page, we want NextPage. And we want to basically ensure that NextPage is >- 1. Because that is our default value for not having a page. And then the text is going to be, Next Page. The action is, once again, page. Controller is home, but for the page value, that's going to be NextPage. And that should be really all that we need to do. So let's run this, and let's see what we get. I don't remember how many posts that we have, but we at least have more than one, so we should be able to go in-between the different pages. So let's go to the root of our application. That's going to pull up the first page. In here, we have two things and we have a next page, so we can click on Next Page, and that will take us to the next page. And then we can click on Next Page again, so we have three pages of posts. We can see that we are at the end of the pages now, so all we can do is go back. So if we do Previous Page, we go back to page two. If we do Previous Page again, that takes us back to the index. Now, this implementation works, and there's something to say about working code, but I would prefer to have a post service, something that would give us more than just an IEnumerable of post. And in this case, it would be nice to have a view model, one that would not have only the posts that we need, but also the paging information that way we wouldn't have to use the view bag. It's not there is something wrong with the view bag, because it is useful in many situations, such as what we just did, but to me paging information is part of the model and thus would like to pass that information on with the rest of the model.