Custom Paging with the ASP.NET Repeater Control

Posted by: Malcolm Sheridan , on 7/2/2009, in Category ASP.NET
Views: 157118
Abstract: The following article demonstrates how to use the Repeater control with LINQ to SQL to create efficient server side paging.
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 one PlaceHolder 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 Object, ByVal 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 Integer, ByVal 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 and Skip 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 Object, ByVal 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

Give a +1 to this article if you think it was well written. Thanks!
Recommended Articles
Malcolm Sheridan is a Microsoft awarded MVP in ASP.NET, a Telerik Insider and a regular presenter at conferences and user groups throughout Australia and New Zealand. Being an ASP.NET guy, his focus is on web technologies and has been for the past 10 years. He loves working with ASP.NET MVC these days and also loves getting his hands dirty with jQuery and JavaScript. He also writes technical articles on ASP.NET for SitePoint and other various websites. Follow him on twitter @malcolmsheridan


Page copy protected against web site content infringement by Copyscape


User Feedback
Comment posted by Will Klaus on Monday, July 27, 2009 10:20 AM
Very good tip.
Thanks.
Comment posted by kittuemani on Tuesday, August 4, 2009 8:17 AM
Nice
Comment posted by kbdotnet on Tuesday, August 25, 2009 11:43 PM
Nice article...Do add a DIGG this/Tweet this tag also..
Comment posted by rob on Wednesday, August 26, 2009 3:20 AM
Great article! I read it on: http://thatstoday.com/article/626198/custom-paging-with-the-aspnet-repeater-control
Comment posted by David Johnson on Wednesday, August 26, 2009 5:21 AM
So you post a code for post back paging - not paging with get parameters, and the code is password to prevent plagiarism. Why....?
Comment posted by Malcolm Sheridan on Wednesday, August 26, 2009 6:44 AM
@rob,@kbdotnet,@kittuemani  
I'm glad you enjoyed reading the article.
Comment posted by Shuaib on Wednesday, August 26, 2009 10:25 AM
Great post. But wondering why not use ListView and DataPager instead? ListView has no ViewState, it is light weight and if you use it prperly it does the job of Repeater. Just a thought. :)
Comment posted by Carol on Thursday, August 27, 2009 4:13 AM
David: The password is available to all registered users or newsletter subscribers and it is just a one time process.
Comment posted by Carol on Thursday, August 27, 2009 4:22 AM
KbDotnet, Rob: Thanks. I will forward your request to add the DIGG and Tweet this button.
Comment posted by vinny on Friday, August 28, 2009 10:48 AM
Very good article indeed.  Thank you for posting this article.
Comment posted by neoandrew on Friday, August 28, 2009 1:30 PM
Where is teh password ?
Comment posted by Chris Airey on Tuesday, September 1, 2009 8:18 AM
Good article but I can't see why you are using the PagedDataSource. Why not just bind your results directly?
Comment posted by Malcolm Sheridan on Tuesday, September 1, 2009 8:03 PM
@Chris
You need to use the PagedDataSource to enable paging with the Repeater control.  The Repeater doesn't have any built-in paging functions.
Comment posted by ryla on Wednesday, October 14, 2009 2:34 AM
Nice tutorial. How bout adding paging style? such that the current page isnt underlined and such?
Comment posted by Malcolm Sheridan on Wednesday, November 18, 2009 3:36 AM
@ryla
Glad you liked the article. Adding a style is possible.  You'd need to assign the CssClass to a css class when you're building the page numbers.
Comment posted by bong on Saturday, December 12, 2009 4:44 AM
something problem with the RowCount???
Comment posted by samir on Saturday, May 15, 2010 8:46 AM
Hello,
      this is very nice article. i have tried the same code but lbl_click event is not fired first time. when i click on "2" then page will be clear. can you please mail me the solution what can i do for the same.
thanks
samir
Comment posted by kamag on Friday, May 28, 2010 7:38 AM
Thx , but i need help. I run this aplication and i have this exception:
The method 'Skip' is only supported for sorted input in LINQ to Entities. The method 'OrderBy' must be called before the method 'Skip'.
I don't know why? :-(
Comment posted by Chris on Friday, June 18, 2010 8:10 AM
I am really struggling with this. I know I am pretty new to it but using VB on the code behind page I couldn't find a way to get the data context to define but then realised I had to name it on the dataclasses property (is this correct). That sorted, I then couldn't get rowcount to be delared unless I did something like Dim RowCount as Integer which stops the error but the page still doesn't work. Any help greatly appreciated.
Comment posted by Chris on Friday, June 18, 2010 8:25 AM
I am really struggling with this. I know I am pretty new to it but using VB on the code behind page I couldn't find a way to get the data context to define but then realised I had to name it on the dataclasses property (is this correct). That sorted, I then couldn't get rowcount to be delared unless I did something like Dim RowCount as Integer which stops the error but the page still doesn't work. Any help greatly appreciated.
Comment posted by Chris on Friday, June 18, 2010 8:46 AM
I am really struggling with this. I know I am pretty new to it but using VB on the code behind page I couldn't find a way to get the data context to define but then realised I had to name it on the dataclasses property (is this correct). That sorted, I then couldn't get rowcount to be delared unless I did something like Dim RowCount as Integer which stops the error but the page still doesn't work. Any help greatly appreciated.
Comment posted by SPKUmar on Friday, July 30, 2010 10:12 AM
Kindly let me know where to declare RowCount ....and why u r using this...Else this Article has no use...Because that is the key point of this Article.
Regards,
SpKumar

Comment posted by Leelavathy Ramesh on Thursday, October 7, 2010 9:34 AM
Very good article, fit my need exactly. Ofcourse I'm using Generic List and have not tried LINQ so modified a little and got a solution.

Thanks a ton
Comment posted by Clemilson on Friday, November 19, 2010 8:36 AM
Chris, you just need to insert the code

  Private Property RowCount() As Integer
        Get
            Return CInt(ViewState("RowCount"))
        End Get
        Set(ByVal value As Integer)
            ViewState("RowCount") = value
        End Set
    End Property

Comment posted by Alex on Monday, December 13, 2010 10:51 AM
im trying to get this up and running on my db and an getting the error
Compiler Error Message: CS1061: 'ApprenticeDataContext' does not contain a definition for 'apprentice' and no extension method 'apprentice' accepting a first argument of type 'ApprenticeDataContext' could be found (are you missing a using directive or an assembly reference?)

i have added my linq file and ive looked at the code through the entire download and cant see why it isnt working, where is it giong wrong?
Thanks

Source Error:


Line 28: private void FetchData(int take, int pageSize) {          
Line 29: using (ApprenticeDataContext dc = new ApprenticeDataContext())      {
Line 30:             var query = from p in dc.apprentice
Line 31:                         .OrderBy(o => o.surname)
Line 32:                         .Take(take)

Source File: e:\net folder\repeater2.aspx.cs    Line: 30
Comment posted by Alex on Monday, December 13, 2010 11:18 AM
ok that was my bad i spelt wrong but now im getting this

Line 40:             if (!IsPostBack)        {
Line 41:                   RowCount = query.First().Count;
Line 42:                   CreatePagingControl();
Line 43:             }
Comment posted by Alex on Monday, December 13, 2010 11:48 AM
ok that was my bad i spelt wrong but now im getting this

Line 40:             if (!IsPostBack)        {
Line 41:                   RowCount = query.First().Count;
Line 42:                   CreatePagingControl();
Line 43:             }
Comment posted by Alex on Monday, December 13, 2010 11:48 AM
ok last one!

System.NullReferenceException: Object reference not set to an instance of an object.

Source Error:


Line 73:             lnk.ID = "lnkPage" + (i + 1).ToString();
Line 74:             lnk.Text = (i + 1).ToString();
Line 75:             plcPaging.Controls.Add(lnk);
Line 76:             Label spacer = new Label();
Line 77:             spacer.Text = "&nbsp;";

Source File: e:\net folder\repeater2.aspx.cs    Line: 75

this one i dont know how to fix...
Comment posted by RowCount on Monday, March 28, 2011 12:18 AM
RowCount ???
Comment posted by Thanks A lot !!!!!!!!!!!!!!!!!!!!!!!!!!! on Sunday, July 17, 2011 7:09 PM
Thanks A lot !!!!!!!!!!!!!!!!!!!!!!!!!!!
Great Article!!!!!!
Comment posted by Dorababu on Saturday, August 13, 2011 3:31 AM
Great article. Is it possible to do this with a plain sql type query instead of Linq
Comment posted by Narayan on Wednesday, August 24, 2011 2:31 AM
Thanks a lot dear,!!!!!!!!!!
Great A
Comment posted by Robert Ojwiya on Thursday, September 8, 2011 8:30 AM
Excellent Article. A point of note though. If the repeater is in an UpdatePanel make you place the Placeholder outside the UpdatePanel.
Comment posted by Robert Ojwiya on Thursday, September 8, 2011 8:48 AM
Excellent Article. A point of note though. If the repeater is in an UpdatePanel make you place the Placeholder outside the UpdatePanel.
Comment posted by leila on Thursday, October 20, 2011 3:12 AM
Thanks a lot
very usefull
Comment posted by Amber on Wednesday, November 30, 2011 12:30 PM
I don't understand this bit here:

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

You are getting the name, setting the alias as Count, and then counting the rows in the table.

Then you are getting the cID with an alias of Name, and then assigning it the number of rows that returned in the first part.(? So far, Count and Name is a number of rows?)

Then you are assigning the ID to be the Count because you assigned Name with Count in the last bit.

Anyone explain that better to me? Also, is there some syntax issues in there? Does there need to be commas, parenthesis, or underscores? It also doesn't like the 'New' part in the 'Select New'
Comment posted by sridhar on Thursday, December 8, 2011 6:42 AM
here,I need next and previous buttons.how can i do this
Comment posted by sam on Friday, December 16, 2011 5:46 AM
i am getting problem, the click event of paging control fires only first time next time when i tried to click nothing is happening.
Comment posted by s.somaraju on Saturday, December 24, 2011 2:13 AM
void Link_Click(object sender, EventArgs e)
    {

        LinkButton lnk = sender as LinkButton;

        int currentPage = int.Parse(lnk.Text);

        int take = (currentPage * 5)/currentPage;

        //int take2 = currentPage * 5;

        int skip = currentPage == 1 ? 0 : (currentPage * 5 - 5);

        FetchData(take, skip);

    }

it has to be handled like this for 5 records
Comment posted by shub on Monday, February 13, 2012 7:46 AM
when i click on the link, the
code does not execute, it just reloads the page.
Where am i going so wrong? i don't understand what's missing
Comment posted by shub on Monday, February 13, 2012 7:56 AM
when i click on the link, the
code does not execute, it just reloads the page.
Where am i going so wrong? i don't understand what's missing
Comment posted by shub on Monday, February 13, 2012 8:05 AM
when i click on the link, the
code does not execute, it just reloads the page.
Where am i going so wrong? i don't understand what's missing
Comment posted by shub on Monday, February 13, 2012 8:14 AM
when i click on the link, the
code does not execute, it just reloads the page.
Where am i going so wrong? i don't understand what's missing
Comment posted by Ogechi on Tuesday, August 7, 2012 4:53 PM
I am trying to add paging to a web page that reads 100s of images from a local folder. Is there a way to do this? I would like to display 40 images per page. It seems like most custom pagination only work when reading items from a database table.
Comment posted by ret on Tuesday, September 18, 2012 6:30 AM
erqwfwretf
Comment posted by Mohd on Friday, November 9, 2012 1:23 AM
degdfg
Comment posted by gf on Monday, November 26, 2012 10:05 AM
f
Comment posted by jayant on Wednesday, March 20, 2013 3:39 AM
Very useful article
Comment posted by Justin Gates on Tuesday, April 2, 2013 3:51 PM
I have looked over this several times and there is no reason you are using a PagedDataSource.  PagedDataSource is not really even used.

If you strip out the PagedDataSource and just bind to the query itself, you will see it works exactly the same.  You are limiting the data returned to a particular page of data, but the PagedDataSource has nothing to do with that.  You are building the paging controls dynamically and handling the page change event with the lbl_click handler.  You could use a local variable for the page size rather than using the PagedDataSource

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()
};
              
            Repeater1.DataSource = query ;
            Repeater1.DataBind();

            if (!IsPostBack)
            {
                  RowCount = query.First().Count;
CreatePagingControl();
}
}
Comment posted by Chirag Patel on Tuesday, July 30, 2013 5:35 AM
I have chnaged some code otherwise it will give incorrect no of records.
Int32 MaxLimit = ((RowCount % 25) == 0 ? (RowCount / 25) : (RowCount / 25) + 1);

for (Int32 i = 0; i < MaxLimit; i++)
{

}
Comment posted by Logan Young on Saturday, November 16, 2013 2:02 PM
I'm sorry to ask what might be a real dumbass question, but wtf is RowCount? I can't find a declaration for it anywhere
Comment posted by Phillipe G on Sunday, November 17, 2013 3:58 AM
@Logan: D/l the source code. The declaration is not shown in the article
Comment posted by Mickey on Sunday, December 8, 2013 5:08 PM
Hi there,

Does anyone know how to modify the paging controls creation to be
only two linkbuttons with Next/Previous functionality?

Best Regards.
Comment posted by Conax on Tuesday, December 10, 2013 5:30 PM
I was going to ask the exact same question as Justin Gates.
Comment posted by Alexander on Sunday, January 12, 2014 5:52 PM
Hello, Malcolm.

Thanks for an article, but linkbutton's events aren't hiring. I spent 2 hours yesterday, and I found that you must create them on page_init ...
Comment posted by Alexander on Sunday, January 12, 2014 5:54 PM
Mickey,

you can found next-prev-version here: http://www.4guysfromrolla.com/articles/081804-1.aspx
Comment posted by BJ on Wednesday, March 12, 2014 1:08 AM
How do I apply css to the selected page?
Comment posted by BJ on Wednesday, March 12, 2014 3:43 PM
Sorry I was not very clear.  How do I add css to the page number label that is currently being displayed.
Comment posted by Vishal on Tuesday, October 7, 2014 5:34 AM
Very nice article.

Post your comment
Name:  
E-mail: (Will not be displayed)
Comment:
Insert Cancel