Home >Backend Development >C#.Net Tutorial >DataList and Repeater data paging
Introduction
Paging and sorting are frequently used functions when displaying data. For example, when searching for books about ASP.NET in an online bookstore, there may be hundreds or thousands of results, but only ten items are listed on each page. And the results can be sorted according to title (book title), price (price), page count (number of pages), author name (author), etc. As we have discussed in Pagination and Sorting Report Data, GridView, DetailsView, and FormView all have built-in paging functionality, which can be enabled by just checking a checkbox. GridView also supports built-in sorting.
Unfortunately, neither DataList nor Repeater provides built-in paging and sorting capabilities. In this chapter we will learn how to add paging and sorting support to DataList and Repeater. We need to create a paging interface, display the correct page record, and record the browsed page during the postback process. Although this takes more time and writes more code than GridView, DetailsView, and FormView, it also provides more scalability.
Note: This chapter focuses on paging. In the next chapter we will learn about sorting.
Step 1: Add paging and sorting tutorial pages
First add the pages required for this chapter and the next chapter. Create a folder called PagingSortingDataListRepeater and add the following 5 pages, remembering to select Site.master for them all.
Default.aspx
Paging.aspx
Sorting.aspx
SortingWithDefaultPaging.aspx
SortingWithCustomPaging.aspx
Figure 1 : Create page
Then open the Default.aspx page and drag a SectionLevelTutorialListing.ascx user control from the UserControls folder. We have used this user control many times. See Master Pages and Site Navigation .
Figure 2: Adding User Controls
In order to list the sorting and pagination tutorials, we need to add them to the site map. . Open the Web.sitemap file and add the following markup to the "Editing and Deleting with the DataList" () node:
<siteMapNode url="~/PagingSortingDataListRepeater/Default.aspx" title="Paging and Sorting with the DataList and Repeater" description="Paging and Sorting the Data in the DataList and Repeater Controls"> <siteMapNode url="~/PagingSortingDataListRepeater/Paging.aspx" title="Paging" description="Learn how to page through the data shown in the DataList and Repeater controls." /> <siteMapNode url="~/PagingSortingDataListRepeater/Sorting.aspx" title="Sorting" description="Sort the data displayed in a DataList or Repeater control." /> <siteMapNode url="~/PagingSortingDataListRepeater/SortingWithDefaultPaging.aspx" title="Sorting with Default Paging" description="Create a DataList or Repeater control that is paged using default paging and can be sorted." /> <siteMapNode url="~/PagingSortingDataListRepeater/SortingWithCustomPaging.aspx" title="Sorting with Custom Paging" description="Learn how to sort the data displayed in a DataList or Repeater control that uses custom paging." /> </siteMapNode>
Figure 3: Update Site Map
Review paging
Earlier we learned how to use GridView, DetailsView, and FormView to paginate. These three controls all provide a function called default paging. You just need to check "Enable Paging" from the smart label. When using default paging, every time data is requested - whether for the first page or another page - the GridView, DetailsView, and FormView will re-request all data. Data for a specific page is then displayed based on the requested page index and the number of records displayed per page, while other data (that is, data that was requested but not displayed) is ignored. We have discussed default paging in detail in Paging and Sorting Report Data.
Default paging requires all data every time, so it is not suitable when there is a large amount of data. For example, imagine displaying 10 items of data per page, for a total of 50,000 items. Each time the user views a page, 50,000 items of data are requested from the database, and only 10 of them are displayed.
Custom paging only returns the requested data each time, thus solving the performance problem of default paging. When using custom paging, we need to write effective SQL statements that return the correct records. We learned here to use the ROW_NUMBER() keyword of SQL Server2005 to create such a statement.
Using default paging in DataList or Repeater, we can use PagedDataSource class to wrap the content that needs to be paginated in ProductsDataTable. The PagedDataSource class has a DataSource property that can be assigned to any enumeration type object, and PageSize (the number of records displayed per page) and CurrentPageIndex (the index of the current page). Once these properties are set, the PagedDataSource can be used as the data source for any data control. PagedDataSource returns appropriate records based on PageSize and CurrentPageIndex. Figure 4 describes the functionality of the PagedDataSource class.
Figure 4: PagedDataSource uses a pageable interface to wrap enumeration objects
PagedDataSource objects can be created and configured directly in the BLL and bound to DataList or Repeater through ObjectDataSource. Or you can do this directly in the code behind the ASP.NET page. If we use the latter method, we cannot use the ObjectDataSource and should bind the paging data to the DataList or Repeater directly programmatically.
PagedDataSource对象也有支持自定义分页的属性。但是在这里我们将不讨论它,因为我们在ProductsBLL类里已经有一个可以精确的返回需要显示的记录的方法。本章我们将学习如何通过在ProductsBLL类里添加一个返回合适的PagedDataSource对象的方法来实现默认分页。下章我们再讨论自定义分页。
第二步: 在BLL里添加默认的分页方法
ProductsBLL类里现在有一个返回所有product的方法–GetProducts()–和一个返回特定子集的方法–GetProductsPaged(startRowIndex,maximumRows)。当使用默认分页时,GridView, DetailsView, FormView 使用GetProducts()方法获取所有的product,但是在内部使用PagedDataSource来显示正确的记录子集。在DataList和Repeater里实现同样的功能,我们可以在BLL里创建一个模拟这种行为的方法。
在ProductsBLL里添加一个带两个整型参数的方法,名为GetProductsAsPagedDataSource:
pageIndex – 显示的页的索引,从0开始
pageSize – 每页显示的记录数.
GetProductsAsPagedDataSource首先从GetProducts()里获取所有的记录。然后创建一个PagedDataSource对象,将CurrentPageIndex和PageSize属性设置为传进来的参数,pageIndex和pageSize。方法的最后返回这个配置过的PagedDataSource。
[System.ComponentModel.DataObjectMethodAttribute (System.ComponentModel.DataObjectMethodType.Select, false)] public PagedDataSource GetProductsAsPagedDataSource(int pageIndex, int pageSize) { // Get ALL of the products Northwind.ProductsDataTable products = GetProducts(); // Limit the results through a PagedDataSource PagedDataSource pagedData = new PagedDataSource(); pagedData.DataSource = products.Rows; pagedData.AllowPaging = true; pagedData.CurrentPageIndex = pageIndex; pagedData.PageSize = pageSize; return pagedData; }
第三步: 在DataList里使用默认分页显示Product
完成GetProductsAsPagedDataSource方法后,我们现在来创建一个提供默认分页的DataList或Repeater。打开PagingSortingDataListRepeater文件夹下的Paging.aspx页,拖一个DataList进来,将ID设为ProductsDefaultPaging。通过智能标签创建一个名为ProductsDefaultPagingDataSource的ObjectDataSource并用GetProductsAsPagedDataSource方法配置它。
图 5: 创建并配置ObjectDataSource
在UPDATE, INSERT, DELETE 标签的下拉列表里都选择“(None)”.
图 6: 在UPDATE, INSERT, DELETE 标签的下拉里选择“(None)”
因为GetProductsAsPagedDataSource方法需要两个参数,因此向导会提示我们选择参数源。page index和page size的值必须在postback过程中记下来。它们可以存在view state,querystring,session里或用其它技术来记录。本章我们使用querystring。
分别使用querystring字段“pageIndex” 和“pageSize”来配置pageIndex和pageSize。见图7。由于用户第一次浏览页的时候没有querystring,因此还需要设置这两个参数的默认值。将pageIndex的默认值设为0(表示显示第一页数据),将pageSize的默认值设为4。
图 7: 配置参数
配置完ObjectDataSource后,Visual Studio自动为DataList创建一个ItemTemplate。修改它让它只显示product的name,category和supplier。将DataList的RepeatColumns属性设为2,Width设为“100%”, ItemStyle的Width设为 “50%”. 这样的设置会为两列提供相同的间距。完成这些后DataList和ObjectDataSource的标记语言看起来应该如下:
<asp:DataList ID="ProductsDefaultPaging" runat="server" Width="100%" DataKeyField="ProductID" DataSourceID="ProductsDefaultPagingDataSource" RepeatColumns="2" EnableViewState="False"> <ItemTemplate> <h4><asp:Label ID="ProductNameLabel" runat="server" Text='<%# Eval("ProductName") %>'></asp:Label></h4> Category: <asp:Label ID="CategoryNameLabel" runat="server" Text='<%# Eval("CategoryName") %>'></asp:Label><br /> Supplier: <asp:Label ID="SupplierNameLabel" runat="server" Text='<%# Eval("SupplierName") %>'></asp:Label><br /> <br /> <br /> </ItemTemplate> <ItemStyle Width="50%" /> </asp:DataList> <asp:ObjectDataSource ID="ProductsDefaultPagingDataSource" runat="server" OldValuesParameterFormatString="original_{0}" TypeName="ProductsBLL" SelectMethod="GetProductsAsPagedDataSource"> <SelectParameters> <asp:QueryStringParameter DefaultValue="0" Name="pageIndex" QueryStringField="pageIndex" Type="Int32" /> <asp:QueryStringParameter DefaultValue="4" Name="pageSize" QueryStringField="pageSize" Type="Int32" /> </SelectParameters> </asp:ObjectDataSource>
注意:由于这里我们不实现任何更新或删除的功能,你可以禁用DataList的view state来减少页面的大小。
第一次浏览页的时候,querystring里没有提供pageIndex 和pageSize的值,因此将使用默认的0和4。见图8。DataList将显示4条product记录。
图 8: 显示4条Product
由于还没有分页的界面,因此用户现在还不能直接导航到第二页。我们将在第四步里创建分页界面。现在我们只能直接在querystring里指定分页的参数来进行分页。例如,我们可以将地址从Paging.aspx改为Paging.aspx?pageIndex=2,然后回车,来浏览第二页。这样就可以看到第二页的数据了,见图9。
图 9: 显示第二页数据
第四步: 创建分页界面
有很多不同的完成分页界面的方法。GridView, DetailsView, FormView 提供了4种不同的界面:
Next, Previous(后一页,前一页) – 用户可以浏览上一页或下一页.
Next, Previous, First(第一页), Last (最后一页)– 除了上面的功能,这个还包含第一页和最后一页。
Numeric (数字)–在分页界面上列出页数,用户可以随意的选择一个页 .
Numeric, First, Last – 在上一个功能的基础上增加了第一页和最后一页.
对DataList 和Repeater而言,我们需要决定它的分页界面并实现它。这其中包含了需要创建web控件和当特定页的button被点时显示请求的页。另外某些分页界面的控件可能需要禁用。例如,当使用Next, Previous, First, Last这个模式来显示时,如果浏览第一页数据,那么第一页和前一页的button应该被禁用。
本章我们使用 Next, Previous, First, Last界面。添加4个button,并将ID分别设为FirstPage,PrevPage,NextPage和LastPage。将Text设为“”, “Last >>”.
<asp:Button runat="server" ID="FirstPage" Text="<< First" /> <asp:Button runat="server" ID="PrevPage" Text="< Prev" /> <asp:Button runat="server" ID="NextPage" Text="Next >" /> <asp:Button runat="server" ID="LastPage" Text="Last >>" />
然后为每个button创建一个Click事件处理。呆会我们将添加代码来显示请求的页。
记下分页的总记录数
不管选择哪种分页界面,我们都需要计算和记下分页的总记录数。总的行数(和page size)来决定总的页数,它决定了那些分页界面的控件需要增加或启用。在我们创建的Next, Previous, First, Last 界面里,page count(页数)在两种情况下需要被用到:
判断我们是否在浏览最后一页,这种情况下Next 和Last buttons 需要禁用。
如果用户点了Last button我们需要将它转到最后一页,它的索引等于page count减1。
page count通过总行数除以page size(页数)来计算得到。例如我们要分页79条记录,每页显示4条,那么page count为20(79/4)。如果我们使用数字分页界面,就可以通过这个信息来决定要显示多少个数字页的button。如果分页界面只包含Next 和Last buttons,可以通过page count来什么时候禁用Next 和Last buttons。
如果分页界面包含Last button(最后一页),我们需要在postback过程中记下分页的总记录数,这样在点Last button的时候我们可以获得最后一页的索引。为了方便实现这个,我们在ASP.NET页的后台代码里创建一个TotalRowCount属性来将这个值保存到view state里。
private int TotalRowCount { get { object o = ViewState["TotalRowCount"]; if (o == null) return -1; else return (int)o; } set { ViewState["TotalRowCount"] = value; } }
除了TotalRowCount外,还需要为page index,page size和page count创建页面级的只读属性来方便读取。
private int PageIndex { get { if (!string.IsNullOrEmpty(Request.QueryString["pageIndex"])) return Convert.ToInt32(Request.QueryString["pageIndex"]); else return 0; } } private int PageSize { get { if (!string.IsNullOrEmpty(Request.QueryString["pageSize"])) return Convert.ToInt32(Request.QueryString["pageSize"]); else return 4; } } private int PageCount { get { if (TotalRowCount <= 0 || PageSize <= 0) return 1; else return ((TotalRowCount + PageSize) - 1) / PageSize; } }
获取分页的总记录数
从ObjectDataSource的Select()方法返回一个PagedDataSource对象包含所有的product记录,即使只有一部分会在DataList里显示。PagedDataSource的Count property 返回将在DataList里显示的项的数目。DataSourceCount property 返回PagedDataSource里的所有项的的总数目。因此我们需要将ASP.NET页的TotalRowCount属性赋值为PagedDataSource的DataSourceCount。
我们为ObjectDataSource的Selectd事件创建一个event handler来完成这些。在Selectd的event handler里我们获取ObjectDataSource的Select()方法的返回值–在这种情况下是PagedDataSource。
protected void ProductsDefaultPagingDataSource_Selected (object sender, ObjectDataSourceStatusEventArgs e) { // Reference the PagedDataSource bound to the DataList PagedDataSource pagedData = (PagedDataSource)e.ReturnValue; // Remember the total number of records being paged through // across postbacks TotalRowCount = pagedData.DataSourceCount; }
显示请求的页的数据
当用户点分页界面上的button时,我们需要显示请求的页的数据。由于分页的参数在querystring里指定,因此使用Response.Redirect(url)来让用户重新请求带合适分页参数的Paging.aspx页。例如,显示第二页的数据,我们将用户重定向到Paging.aspx?pageIndex=1。
创建一个RedirectUser(sendUserToPageIndex)方法来重定向用户到Paging.aspx?pageIndex=sendUserToPageIndex。然后在四个按钮的Click事件处理里调用这个方法。在FirstPageClick里调用RedirectUser(0),在PrevPageClick里调用RedirectUser(PageIndex-1)。
protected void FirstPage_Click(object sender, EventArgs e) { // Send the user to the first page RedirectUser(0); } protected void PrevPage_Click(object sender, EventArgs e) { // Send the user to the previous page RedirectUser(PageIndex - 1); } protected void NextPage_Click(object sender, EventArgs e) { // Send the user to the next page RedirectUser(PageIndex + 1); } protected void LastPage_Click(object sender, EventArgs e) { // Send the user to the last page RedirectUser(PageCount - 1); } private void RedirectUser(int sendUserToPageIndex) { // Send the user to the requested page Response.Redirect(string.Format("Paging.aspx?pageIndex={0}&pageSize={1}", sendUserToPageIndex, PageSize)); }
完成Click事件处理后,DataList的记录现在可以通过button来分页了,你可以测试一下。
禁用分页控件
现在无论浏览哪页四个按钮都是可用的。然而我们在浏览第一页时需要禁用 First 和Previous buttons ,在浏览最后一页时需要禁用Next 和Last buttons。通过ObjectDataSource的Select()方法返回的PagedDataSource对象有几个属性– IsFirstPage 和 IsLastPage –通过它们可以判断用户浏览的是否是第一或最后一页数据。添加下面的代码到ObjectDataSource的Selected事件处理里:
// Configure the paging interface based on the data in the PagedDataSource FirstPage.Enabled = !pagedData.IsFirstPage; PrevPage.Enabled = !pagedData.IsFirstPage; NextPage.Enabled = !pagedData.IsLastPage; LastPage.Enabled = !pagedData.IsLastPage;
添加完后,当浏览第一页时,First 和Previous buttons 将被禁用。当浏览最后一页时,Next 和 Last buttons 将被禁用。
我们最后来实现在分页界面里通知用户他们当前是浏览的哪页和一共有多少页。添加一个Label控件并将ID设为CurrentPageNumber。在ObjectDataSource的Selected事件处理中设置它的Text属性,让它显示当前浏览的页(PageIndex+1)和总页数(PageCount)。
// Display the current page being viewed... CurrentPageNumber.Text = string.Format("You are viewing page {0} of {1}...", PageIndex + 1, PageCount);
图10是第一次浏览Paging.aspx页的样子。由于querystring是空的,因此DataList默认显示最开始的4条product。First 和Previous buttons 被禁用。点Next 会显示下面的4条记录(见图11),而First 和Previous buttons 同时被启用了。
图 10: 第一页数据
图 11: 第二页数据
注意:分页界面可以进一步改善,比如增加允许用户来指定每页显示多少记录。例如添加一个DropDownList列出page size的选项,比如5, 10, 25, 50, 和ALL。用户选择了page size后会重定向到Paging.aspx?pageIndex=0&pageSize=selectedPageSize。我将这个作为练习留给读者自己完成。
使用自定义分页
DataList使用没有效率的默认分页技术。当大数据量时,我们需要使用自定义分页。虽然实现的细节有所不同,但是分页里的概念和默认分页是一样的。默认分页时,使用ProductsBLL类的GetProductsPaged方法(而不是GetProductsAsPagedDataSource)。正如在大数据量时提高分页的效率 里讨论的那样,GetProductsPaged需要传入开始行的索引和行的最大数目。这些参数可以通过默认分页里使用的querystring里的pageIndex和pageSize参数来保存。
由于自定义分页里没有PagedDataSource,所以需要其它技术来决定总记录数和判断我们是否显示第一或最后一页数据。ProductsBLL类的TotalNumberOfProducts()方法返回roduct的总记录数。而为了判断是否浏览的是第一页数据,我们需要检查开始行的索引–如果是0,则表示在浏览第一页。如果开始行的索引加上最大的行数大于或等于总记录数则表示在最后一页.我们将在下章详细的讨论如何实现自定义分页。
总结
DataList和Repeater都没有提供象GridView, DetailsView, FormView 那样的分页的支持,这样的功能需要我们来实现。最简单的实现方法是使用默认分页,将所有的product都包装到PagedDataSource里,然后绑定PagedDataSource到DataList或Repeater。本章我们在ProductsBLL类里添加GetProductsAsPagedDataSource方法,它返回PagedDataSource。ProductsBLL类已经包含了自定义分页需要的方法– GetProductsPaged和TotalNumberOfProducts。
不管是自定义方法里获取精确的记录还是默认方法里获取所有记录,我们都需要手动添加分页界面。本章我们创建的是包含4个button控件的Next, Previous, First, Last interface 。当然还添加了一个显示当前页和总页数的Label控件。
更多DataList and Repeater data paging相关文章请关注PHP中文网!