When you think ASP, think...
Recent Articles xml
All Articles
ASP.NET Articles
Related Web Technologies
User Tips!
Coding Tips
spgif spgif

Book Reviews
Sample Chapters
JavaScript Tutorials
MSDN Communities Hub
Official Docs
Stump the SQL Guru!
Web Hosts
Author an Article
spgif spgif

ASP ASP.NET ASP FAQs Feedback topnav-right
Print this Page!
Published: Wednesday, January 13, 2010

Using ASP.NET 3.5's ListView and DataPager Controls: The Ultimate DataPager Interface

By Scott Mitchell

A Multipart Series on ASP.NET's ListView and DataPager Controls
This article is one in a series of articles on ASP.NET's ListView and DataPager controls, which were introduced with ASP.NET version 3.5.

  • Displaying Data with the ListView - looks at the ListView control basics, with demos on how to display data using the LayoutTemplate and ItemTemplate.
  • Grouping Data with the ListView Control - shows how to render different formatting or encasing markup to every N rendered records.
  • Sorting Data with the ListView Control - shows how to include buttons to sort the ListView's data.
  • Paging Through Data with the ListView and DataPager Controls - shows how to page through the ListView's data using the DataPager control.
  • Grouping By a Data Field - learn how to group the data in a ListView based on the actual data coming from the database.
  • Deleting Data - see how to delete the data bound to the ListView control.
  • Editing Data - learn how to edit the data bound to the ListView control.
  • Inserting Data - explore inserting new records directly from within the ListView control's interface.
  • Creating an SEO-Friendly Paging Interface - learn how to configure the DataPager to render an SEO-friendly paging interface.
  • The Ultimate DataPager Interface - create the ultimate DataPager interface using ASP.NET Routing.
  • Introduction

    The previous installment in this ongoing article series showed how to configure the DataPager control to generate an SEO-friendly paging interface. By default, the DataPager renders its paging interface as a series of Buttons, LinkButtons, or ImageButtons that, when clicked, trigger a postback. The problem with postbacks is that they are not crawled by search engine spiders, meaning that with the default behavior only the first page of data will make it into the search engines' indexes. Fortunately, the DataPager's paging interface can be configured to include the page number in the querystring. When configured this way, the DataPager renders its paging interface using hyperlinks with URLs like Products.aspx?Page=PageNumber. With this approach a search engine will happily crawl through each page of data.

    Shortly after publishing Creating an SEO-Friendly Paging Interface, a number of readers asked if it would be possible to create a paging interface that moved the page number from the querystring into the URL. Rather than having a paging interface that linked to pages with URLs like Products.aspx?Page=PageNumber, these readers wanted to have URLs like: Products/PageNumber. Such terse, descriptive URLs are possible with ASP.NET Routing, a feature added to the .NET Framework 3.5 SP1. While typically used in ASP.NET MVC applications, ASP.NET Routing can also be used in Web Form applications.

    This article shows how to use ASP.NET Routing with the ListView and DataPager controls to create the ultimate paging interface. Read on to learn more!

    Please be sure to read Creating an SEO-Friendly Paging Interface before continuing on with this article...

    - continued -

    Using ASP.NET Routing in the Paging Interface

    One of the highlights of ASP.NET MVC is its ability to use ASP.NET Routing to create terse, "hackable", SEO-friendly URLs. While ASP.NET Routing is used by default in ASP.NET MVC applications, it can also be used in WebForm applications. Using ASP.NET Routing Without ASP.NET MVC gives detailed step-by-step instructions on what you need to do to start using ASP.NET Routing in a WebForms application.

    In a nutshell, using ASP.NET Routing entails defining routing rules, which indicate what route patterns map to what ASP.NET pages. Consider the demo we looked at in Creating an SEO-Friendly Paging Interface. In this demo we had a single ASP.NET page named QueryStringPaging.aspx that used a ListView and DataPager to display data from the Northwind database's Products table. A querystring field named Page was used to indicate the page of data to display. For example, to view the first page of data we'd visit QueryStringPaging.aspx, or QueryStringPaging.aspx?Page=1; to view the second page we'd visit QueryStringPaging.aspx?Page=2; and so on.

    Using ASP.NET Routing we can create a routing rule that defines a pattern of the form /Products/PageNumber that maps to the ASP.NET page QueryStringPaging.aspx?Page=PageNumber. With this routing rule in effect, a user can view the first page of products by visiting /Products, or /Products/1. To view the second page of data they'd visit /Products/2, and so on. Such a routing rule gives us an ideal URL for our product listing:

    • The URL is very readable.
    • The URL is "hackable." A user who is viewing the fourth page of data - /Products/4 - can infer that by removing the "/4" from the URL he will return to the first page of data.
    • The URLs are SEO-friendly
    To create such a paging interface we have two key tasks:
    1. Configure the website to use ASP.NET Routing and implement the routing rules, and
    2. Customize the markup generated by the DataPager so that the paging interface renders hyperlinks that use the appropriate URLs - /Products/PageNumber rather than QueryStringPaging.aspx?Page=PageNumber
    The remainder of this article details these two tasks. A complete, working example is available for download at the end of this article. Note that the demo available for download, and the code snippets examined throughout this article, are in Visual Basic. The Using ASP.NET Routing Without ASP.NET MVC article looks at performing the routing-related tasks using C#.

    Implementing ASP.NET Routing - Preamble

    To use ASP.NET Routing in a WebForms application you need to perform five steps:
    1. Add a reference to System.Web.Routing to your project.
    2. Add the UrlRoutingModule HTTP Module and UrlRoutingHandler HTTP Handler in Web.config.
    3. Define the routes in Global.asax.
    4. Create the route handler class(es).
    5. Create the ASP.NET page(s) that process the requests.
    This article focuses on steps 3, 4, and 5. For a discussion on steps 1 and 2, refer to Using ASP.NET Routing Without ASP.NET MVC. Finally, keep in mind that to use ASP.NET Routing you need to be using ASP.NET 3.5 SP1. If you are using Visual Studio 2008 SP1 or Visual Web Developer 2008 SP1 you're good to go.

    Implementing ASP.NET Routing - Step 3: Defining The Routes In Global.asax

    The Global.asax file can include event handlers for application-wide events. Whenever the application starts we need to define the routing rules. Therefore, these go in the Global.asax file's Application_Start event handler. For this demo we need a single routing rule of the form Products/Page. The following code implements this functionality:

    Sub Application_Start(sender As Object, e As EventArgs)
    End Sub

    Sub RegisterRoutes(routes As RouteCollection)
       ' Register a route for Categories/All
       Dim routeDefaults As New RouteValueDictionary()
       routeDefaults.Add("Page", "1")

       routes.Add("ProductRoute", New Route("Products/{Page}", routeDefaults, New ProductRouteHandler()))
    End Sub

    The Application_Start event handler fires whenever the application starts. It calls the RegisterRoutes method, passing in the site-wide collection of routing rules (RouteTable.Routes). The RegisterRoutes method then adds a new route. It starts by defining the default values for the route using a RouteValueDictionary. Specifically, a default value of 1 is defined for the Page parameter, which means if someone visits /Products - that is, there is no trailing page number - then a default page value of 1 will be used. The final line of code in the RegisterRoutes method adds a new route rule to the site-wide routes. The route is named ProductRoute and the routing is handled by the ProductRouteHandler.

    Implementing ASP.NET Routing - Step 4: Creating the Route Handler Class

    When ASP.NET detects an incoming request of the form /Products or /Products/somePageNumber it invokes the specified route handler class (ProductRouteHandler). This route handler class is pretty straightforward. In short, it sends the user onto the ASP.NET page that contains the ListView and DataPager, which I've named FancyPaging.aspx. It also needs to pass along the page number value in the route (such as 1, 2, etc.). This is passed from the ProductRouteHandler route handler class to the FancyPaging.aspx page via the HttpContext.Items collecting.

    Imports System.Web.Compilation
    Imports System.Web.Routing

    Public Class ProductRouteHandler
       Implements IRouteHandler

       Public Function GetHttpHandler(ByVal context As RequestContext) As IHttpHandler Implements IRouteHandler.GetHttpHandler
          Dim pageNumberValue As String = context.RouteData.Values("Page").ToString()
          If String.IsNullOrEmpty(pageNumberValue) Then
             HttpContext.Current.Items("PageNumber") = "1"
             HttpContext.Current.Items("PageNumber") = pageNumberValue
          End If

          Return CType(BuildManager.CreateInstanceFromVirtualPath("~/FancyPaging.aspx", GetType(Page)), Page)

       End Function
    End Class

    Implementing ASP.NET Routing - Step 5: Creating the ASP.NET Page That Processes The Requests

    The FancyPaging.aspx page uses ListView and DataPager controls to display the products from the Northwind database. A user may visit this page directly, by entering FancyPaging.aspx in their browser's Address bar, or they may reach it indirectly via the routing rule defined in step 3. FancyPaging.aspx is similar to the QueryStringPaging.aspx page examined in Creating an SEO-Friendly Paging Interface, with two notable exceptions:
    • In QueryStringPaging.aspx the DataPager would automatically inspect the querystring to determine which page to show. Since we've circumvented this process we need to write code for FancyPaging.aspx that displays the appropriate page of data based on the page specified through the route. Recall that this information is passed to FancyPaging.aspx via HttpContext.Current.Items("PageNumber").
    • The DataPager in QueryStringPaging.aspx rendered its own paging interface with very little work on our end. We used NextPreviousPagerField and NumericPagerField controls in the DataPager's <Fields> section to generate the paging interface. For FancyPaging.aspx we need to create the paging interface manually so that we can have the hyperlinks in the paging interface reference our custom URL structure (/Products/pageNumber rather than the format the DataPager uses (FancyPaging.aspx?Page=pageNumber).
    For now, let's focus on having the DataPager load the correct page of data. We'll tackle creating the paging interface later on in this article.

    When the FancyPaging.aspx page is first visited we need to determine which page of data to display. The following code in the Page_Load event handler starts by reading in the requested page number from the HttpContext.Items collection. Next, it computes the starting row index based on the page number and how many rows to show per page. Finally, it calls the DataPager's SetPageProperties method, passing in the starting row index, the maximum number of rows, and a value of True that instructs the DataPager to have the ListView rebind its data to get the correct page of data.

    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
       If Not Page.IsPostBack Then
          Dim PageNumber As Integer = Convert.ToInt32(HttpContext.Current.Items("PageNumber"))
          'Set the DataPager's properties based on the PageNumber route value
          Dim maximumRows As Integer = ProductListPager.PageSize
          Dim startRowIndex As Integer = (PageNumber - 1) * maximumRows
          ProductListPager.SetPageProperties(startRowIndex, maximumRows, True)
       End If
    End Sub

    Here, ProductListPager is the ID of the DataPager control on the page. ProductListPager.PageSize specifies how many records to show per page of data. The starting row index is computed by taking the requested page number, subtracting one, and then multiplying it by the number of records to show per page. The starting row index and number of records to show per page are then passed into the DataPager's SetPageProperties method, which instructs the DataPager to go retrieve the appropriate page of data.

    Generating the DataPager's Paging Interface

    At this point the FancyPaging.aspx page can be visited via the route /Products/pageNumber, which loads the appropriate page of products. However, the DataPager renders its paging interface with hyperlinks that send the user to FancyPaging.aspx?Page=pageNumber. We need to update the DataPager's paging interface so that the hyperlinks send the user to /Products/pageNumber.

    Unfortunately, the DataPager does not have a property you can set to customize the paging interface URL. Instead, we must use a TemplatePagerField and then craft the paging interface's markup by hand. (For more information on using the DataPager's TemplatePagerField see Paging Through Data with the ListView and DataPager Controls.) We are going to programmatically construct the markup for the paging interface, so our DataPager just needs an empty <PagerTemplate> within the TemplatePagerField:

    <asp:DataPager ID="ProductListPager" runat="server"
       PagedControlID="ProductList" PageSize="5">

    Then, in the ListView's PreRender event, we can build up this paging interface (see the code below). The code starts by determining the current page being viewed and the total number of pages. It then adds hyperlinks to the DataPager's template for the previous page, the next page, and numeric pages. Each paging interface hyperlink is added to the template via a call to the CreateLink method, which adds a Label Web control for disabled pager buttons, and a HyperLink Web control for enabled ones. The NavigateUrl property of the HyperLink is assigned to the value returned by the Helpers.FormatProductUrl(pageNumber) method, which uses the RouteTable.Routes collection's GetVirtualPath method to determine the appropriate routing URL for the specified page number.

    Protected Sub ProductList_PreRender(ByVal sender As Object, ByVal e As System.EventArgs) Handles ProductList.PreRender
       'Now add the paging interface...
       Dim currentPage As Integer = (ProductListPager.StartRowIndex / ProductListPager.PageSize) + 1
       Dim totalPages As Integer = ProductListPager.TotalRowCount / ProductListPager.PageSize

       Const NumericPageCount As Integer = 10

       'Create the paging interface
       Dim pagerTemplate As Control = ProductListPager.Controls(0)
       CreateLink(pagerTemplate, "<< Previous", currentPage - 1, False, currentPage = 1)

       Dim startPageIndex As Integer = Math.Max(1, currentPage - NumericPageCount \ 2)
       Dim endPageIndex As Integer = Math.Min(totalPages, currentPage + NumericPageCount \ 2)

       If totalPages > NumericPageCount Then
          If startPageIndex > 1 Then CreateLink(pagerTemplate, "1", 1, False, False)
          If startPageIndex > 2 Then CreateLink(pagerTemplate, "2", 2, False, False)
          If startPageIndex > 3 Then CreateLink(pagerTemplate, "...", 1, False, True)

          For i As Integer = startPageIndex To endPageIndex
             CreateLink(pagerTemplate, i, i, i = currentPage, False)

          If endPageIndex < totalPages - 2 Then CreateLink(pagerTemplate, "...", 1, False, True)
          If endPageIndex < totalPages - 1 Then CreateLink(pagerTemplate, totalPages - 1, totalPages - 1, False, False)
          If endPageIndex < totalPages Then CreateLink(pagerTemplate, totalPages, totalPages, False, False)
       End If

       CreateLink(pagerTemplate, "Next >>", currentPage + 1, False, currentPage = totalPages)
    End Sub

    Private Sub CreateLink(ByVal parent As Control, ByVal text As String, ByVal jumpToPageNumber As Integer, ByVal isSelected As Boolean, ByVal inactive As Boolean)
       If inactive Then
          Dim lbl As New Label
          lbl.Text = text
          lbl.CssClass = "pagerButtonDisabled"
          Dim lnk As New HyperLink
          lnk.Text = text
          If isSelected Then
             lnk.CssClass = "pagerButtonCurrentPage"
             lnk.CssClass = "pagerButton"
          End If
          lnk.NavigateUrl = Helpers.FormatProductUrl(jumpToPageNumber)
       End If
    End Sub

    The following two screen shots show the paging interface in action. The first screen shot shows the paging interface when first visited (/Products). Note how the Previous link is grayed out. Also note how the paging interface shows the last two page links, regardless of what page you're on. This lets the user quickly know how many total pages of data there are.

    The first page of data.

    The second one shows the paging interface when visiting 11th page of data (/Products/11). Note how the first two paging links are always visible. This enables users to quickly jump back to the beginning of the product list.

    The 11th page of data.

    Happy Programming!

  • By Scott Mitchell

    Further Readings:

  • Creating an SEO-Friendly Paging Interface
  • Using ASP.NET Routing Without ASP.NET MVC
  • ASP.NET Routing
  • Paging Through Data with the ListView and DataPager Controls
  • Attachments

  • Download the Demo (in ZIP format)

  • ASP.NET [1.x] [2.0] | ASPMessageboard.com | ASPFAQs.com | Advertise | Feedback | Author an Article