This recipe is sort of a mix of routing and tomfoolery. While the routing aspect of this is pretty simple, the underlying bits that populate the URL can be a bit more complex. For that reason, we will cover both the routing aspect and a reusable control for managing pagination in any data set.
dependencies
folder), so that we can create some data to play with. Global.asax
, so that we can add a new route to handle paging. We will create a route that will allow us to use our HomeController
without the customer knowing it, and that will expose a page
variable instead of the id
variable. Also, instead of making the page
variable optional, we will set the default value to page 1.Global.asax:
routes.MapRoute( "Products", // Route name "ProductCatalog/Products/{page}", // URL with parameters new { controller = "Home", action = "Products", page = 1 } // Parameter defaults );
Products
action to catch the page number in the HomeController
.Controllers/HomeController.cs:
public ActionResult Products(int page) { return View(); }
Product
class.Models/Products.cs:
public class Product { public string ProductName { get; set; } public double Price { get; set; } public string Description { get; set; } }
Pager
, which we will use to keep track of our pagination data such as what page we are on, how many pages we have in the set of data we are working with, whether there is a next or previous page, and so on. This is a simple class with a collection of properties. The constructor is used to set the data that this class will require.Models/Pager.cs:
public class Pager { public Pager (int currentPage, int numberOfPages, string action, string controller) { CurrentPage = currentPage; NumberOfPages = numberOfPages; Action = action; Controller = controller; } public bool ShowPrevious { get { if (CurrentPage == 1) return false; else return true; } } public bool ShowNext { get { if (CurrentPage < (NumberOfPages-1)) return true; else return false; } } public int PreviousPage { get { if (CurrentPage - 1 > 0) return CurrentPage - 1; else return 1; } } public int NextPage { get { if (CurrentPage + 1 <= NumberOfPages) return CurrentPage + 1; else return NumberOfPages; } } public int CurrentPage { get; set; } public int NumberOfPages { get; set; } public string Controller { get; set; } public string Action { get; set; } }
ProductsModel
class, which we will use to transfer our collection of product data as well as the Pager
instance. We will use these two bits of data to build the list of products data and to control how our paging control displays.Models/ProductsModel.cs:
public class ProductsModel { public List<Product> Products { get; set; } public Pager Pager { get; set; } }
Product, Pager
, and ProductsModel
created, we can move on to the implementation of our Products
action. In this action, we will create a list of products using NBuilder. Then we will set the properties of our Pager
class (current page, total number of pages, the action to process the next page, and the controller that the action lives in). We will then take the list of products and our configured pager and stick them into our ProductsModel
. With the ProductsModel
created and hydrated with our data, we will then pass that down to our view.Controllers/HomeController.cs:
public ActionResult Products(int page) { ProductsModel pm = new ProductsModel(); List<Product> products = Builder<Product> .CreateListOfSize(pageCount * pageCount) .Build().ToList(); Pager pager = new Pager(page, (products.Count/pageCount), "Products", "Home"); pm.Products = products.Skip(page * pageCount) .Take(pageCount).ToList(); pm.Pager = pager; return View(pm); }
Product
. When the view opens up, you will need to change the type that the page inherits from IEnumerable<Product>
to ProductsModel
. Then update the foreach
statement to iterate over a collection of Model.Products
. (I removed some of the fluff that the wizard adds.)Views/Home/Products.aspx:
<table>
<tr>
<th>
ProductName
</th>
<th>
Price
</th>
<th>
Description
</th>
</tr>
<% foreach (var item in Model.Products) { %>
<tr>
<td>
<%: item.ProductName %>
</td>
<td>
<%: String.Format("{0:C}", item.Price) %>
</td>
<td>
<%: item.Description %>
</td>
</tr>
<% } %>
</table>
Views/Shared
directory called PagerView
. This view will be strongly-typed, based on the Pager
class that we created earlier. Then we will use the properties we created on the Pager
class to show the next and previous ActionLinks
. We will also perform some logic to determine what page we are on, so that we can highlight the appropriate page link. We will also determine how many pages there are in the set of data, so that we can show a link for each page. (I added some CSS classes to the output to help control the look and feel.)Views/Shared/PagerView.ascx:
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl< MvcApplication1.Models.Pager>" %> <div class="Pager"> <% if(Model.ShowPrevious) { %> <div class="PagerPreviousPage"> <%= Html.ActionLink("<< Previous",Model.Action, Model.Controller, new { page = Model.PreviousPage }, null) %></div> <% } %> <div class="PagerPageNavigation"> <% for (int i = 1; i < Model.NumberOfPages;i++) { %> <% string className = "PagerPageNavItem"; string activeClassName = "PagerActivePageNavItem"; string currentClassName = ""; if (i == Model.CurrentPage) currentClassName = activeClassName; else currentClassName = className; %> <div class="<%= currentClassName %>"> <%= Html.ActionLink(i.ToString(),Model.Action, Model.Controller, new { @page = i}, null) %></div> <% } %> </div> <% if(Model.ShowNext) { %> <div class="PagerNextPage"> <%= Html.ActionLink("Next >>",Model.Action, Model.Controller, new { @page = Model.NextPage }, null) %></div> <% } %> </div>
Products.aspx
view. Add a call to Html.RenderPartial
above the table you created earlier. Specify the name of our new PagerView
. Also, pass in the instance of the Pager
class that we instantiated and hydrated earlier.Views/Shared/Products.aspx:
... <% Html.RenderPartial("PagerView", Model.Pager); %> <div style="display:block;clear:both;" /> ...
/ProductsCatalog/Products
URL. This will use the default Page value of one and should display ten product records. You can click on Next to get the next set of data. Then you can click on Previous. You can also click on the page number you want to see, or you can edit the URL page number directly.The route we created in the first part of the recipe catches all of the URLs that begin with ProductCatalog
. It also expects the Products
action to be called and sets the initial page to one. The majority of the magic was then handled by the Pager
class that we created, which manages all the paging data, such as what page we are currently viewing, how many pages there are, and so on. The other important part of the magic came in the form of our query structure—in that we controlled how many records to show and which set of data we were on.
Some of this logic could have probably been moved into the Pager
class, but it seemed more appropriate, and efficient to keep it with the query for this example. You could probably create a generic Pager
class that takes in a type to operate on. You could also extend the Pager
a bit to take in a repository class according to an interface with a standard method to call, that takes the current page number and the number of records to show. That is a bit bigger topic than this book is prepared to cover though.