Pages

Thursday, May 24, 2012

Custom Paging with the ASP.NET Repeater Control


Earlier this year I created an article on Efficient Server Side Paging with the ASP.NET Gridview control. I recently had to use the Repeater control with large amounts of data, and the only way to do this properly is to page through the data. Out of the box the Repeater control does not have paging. This is a scenario where you can use the PagedDataSource class. This class encapsulates the paging related properties of a data-bound control. This article will not only show you how to create custom paging, but how to create it with performance in mind.
For this example I’ll be connecting to the Northwind database. If you don’t have a copy of it, you can go here to download it.
Open Visual Studio 2008 and choose File > New > Web > ASP.NET Web Application. Add a new LINQ to SQL file to the project and connect to the Northwind database. Drag the Customers table onto the designer. This is the table that will be referenced in the example
 
Customer_Table
 
That’s our data access component done! Don’t you love LINQ to SQL! Let’s turn our attention back to the UI. Open the Default.aspx page and add a Repeater control:
<asp:Repeater ID="Repeater1" runat="server">
        <HeaderTemplate>
            <table>
        </HeaderTemplate>
        <ItemTemplate>
            <tr>
                <td>
                    <asp:Label runat="server" ID="lblContactName" Text='<%# Eval("Name") %>' />
                </td>
            </tr>
        </ItemTemplate>
        <FooterTemplate>
            </table>
        </FooterTemplate>
    </asp:Repeater>
    <table>
        <tr>
            <td>
                <asp:PlaceHolder ID="plcPaging" runat="server" />
                <br /><asp:Label runat="server" ID="lblPageName" />
            </td>
        </tr>
    </table>
In the above code I have created a simple Repeater control that will display the ContactName from the Customer table. Directly underneath the Repeater I have created one HTML table with onePlaceHolder control. The PlaceHolder will display the pages available to the Repeater.
The trick to get this to work is not DataBind the LINQ query to the Repeater, but to DataBind the LINQ query to the PagedDataSource object, then bind the PagedDataSource object to the Repeater. Let’s add the code to make this all happen.
 
C#
 
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
      {
            FetchData(10, 0);    
}
      else
      {
            plcPaging.Controls.Clear();
            CreatePagingControl();
}                      
}
 
private void FetchData(int take, int pageSize)
{          
using (NorthwindDataContext dc = new NorthwindDataContext())
      {
            var query = from p in dc.Customers
                        .OrderBy(o => o.ContactName)
                        .Take(take)
                        .Skip(pageSize)
                        select new
                        {
                              ID = p.CustomerID,
                              Name = p.ContactName,
                              Count = dc.Customers.Count()
};
               
PagedDataSource page = new PagedDataSource();
            page.AllowCustomPaging = true;
            page.AllowPaging = true;
            page.DataSource = query;
            page.PageSize = 10;
            Repeater1.DataSource = page;
            Repeater1.DataBind();
 
            if (!IsPostBack)
            {
                  RowCount = query.First().Count;
CreatePagingControl();
}
}
}
 
private void CreatePagingControl()
{  
for (int i = 0; i < (RowCount / 10) + 1; i++)
      {
            LinkButton lnk = new LinkButton();                
            lnk.Click += new EventHandler(lbl_Click);
            lnk.ID = "lnkPage" + (i + 1).ToString();
            lnk.Text = (i + 1).ToString();
            plcPaging.Controls.Add(lnk);
            Label spacer = new Label();
            spacer.Text = "&nbsp;";
            plcPaging.Controls.Add(spacer);
}
}
 
VB.NET
 
Protected Sub Page_Load(ByVal sender As ObjectByVal e As EventArgs)
If (Not IsPostBack) Then
             FetchData(10, 0)
       Else
             plcPaging.Controls.Clear()
             CreatePagingControl()
       End If
End Sub
 
Private Sub FetchData(ByVal take As IntegerByVal pageSize As Integer)
Using dc As New NorthwindDataContext()
             Dim query = From p In dc.Customers.OrderBy(Function(o) o.ContactName).Take(take).Skip(pageSize) _
                          Select New
                                          p.ContactName, Count = dc.Customers.Count()
                                          p.CustomerID, Name = p.ContactName, Count
                                          ID = p.CustomerID, Name
 
Dim page As New PagedDataSource()
                  page.AllowCustomPaging = True
                  page.AllowPaging = True
                  page.DataSource = query
                  page.PageSize = 10
                  Repeater1.DataSource = page
                  Repeater1.DataBind()
 
                  If (Not IsPostBack) Then
                        RowCount = query.First().Count
CreatePagingControl()
                  End If
End Using
End Sub
 
Private Sub CreatePagingControl()
For i As Integer = 0 To (RowCount / 10)
             Dim lnk As New LinkButton()
                  AddHandler lnk.Click, AddressOf lbl_Click
                  lnk.ID = "lnkPage" & (i + 1).ToString()
                  lnk.Text = (i + 1).ToString()
                  plcPaging.Controls.Add(lnk)
                  Dim spacer As New Label()
                  spacer.Text = "&nbsp;"
                   plcPaging.Controls.Add(spacer)
Next i
End Sub
 
There’s allot happening in the code above. The main method is FetchData. This method takes two parameters, take and skip, which are integer values that will be used to run the IQueryable Take andSkip methods. These methods create an SQL statement that only returns records between the rows starting at the Take value, and then skipping all the rows in the Skip value instead of the whole table. 
var query = from p in dc.Customers
                        .OrderBy(o => o.ContactName)
                        .Take(take)
                        .Skip(pageSize)
 
Next I have set the LINQ query as the DataSource for the PagedDataSource object. This is exactly the same as binding to any of the other controls such as the GridView, DropDownList or ListView. 
Last but not least is creating the links at the bottom of the Repeater control. This is taken care of by a loop which enumerates through the total number of records being returned by the LINQ query, and dividing that by 10. For each item in the loop and new LinkButton is created, along with a delegate for the LinkButton’s Click event. The Click event will be responsible for calling the FetchData method when the user pages through the data. Finally I am creating a Label control to provide space between each of the paging choices.
 
Because the paging controls are added dynamically, they’ll need to be added on subsequent page loads. In the page load event the controls are re-created if the Page.IsPostBack is true:
 
C#
 
if (!IsPostBack)
{
FetchData(10, 0);    
}
else
{
      plcPaging.Controls.Clear();
      CreatePagingControl();
}    
 
VB.NET
 
If (Not IsPostBack) Then
FetchData(10, 0)
Else
       plcPaging.Controls.Clear()
       CreatePagingControl()
End If
 
The last thing to do is add the Click event handler:
 
C#
 
void lbl_Click(object sender, EventArgs e)
{
LinkButton lnk = sender as LinkButton;
      int currentPage = int.Parse(lnk.Text);
      int take = currentPage * 10;
      int skip = currentPage == 1 ? 0 : take - 10; 
      FetchData(take, skip);
}
 
VB.NET
 
Private Sub lbl_Click(ByVal sender As ObjectByVal e As EventArgs)
Dim lnk As LinkButton = TryCast(sender, LinkButton)
       Dim currentPage As Integer = Integer.Parse(lnk.Text)
       Dim take As Integer = currentPage * 10
       Dim skip As Integer = If(currentPage = 1, 0, take - 10)
       FetchData(take, skip)
End Sub
 
If you run the project now you’ll see the Repeater control has paging enabled, and the bonus is the paging is using only what it needs to displays the records per page, not the entire table:
 
RecordPerPage
 
The PagedDataSource is a great resource when you need to create custom paging. And using this with LINQ’s Take and Skip methods is a great way to create paging that is highly efficient.
The entire source code of this article can be downloaded from here

No comments:

Post a Comment