Custom SEO friendly paging with ASP.NET Repeater or DataList control

Posted by in ASP.NET category on for Intermediate level | Views : 93212 red flag

This article shows how to achieve Custom SEO Friendly paging for controls like Repeater or DataList those doesn't support paging out of box.

 Download source code for Custom SEO friendly paging with ASP.NET Repeater or DataList control


This article gives A to Z solution to do SEO Friendly custom paging for ASP.NET Repeater and DataList controls that doesn't support paging out of box. To my experience this is one of the most efficient way of doing custom paging as this doesn't require you to keep ViewState of any of the controls enabled and one of the best thing is that it is SEO friendly so search engine like Google and Bing can crawl all records of the pages easily.

My sample page looks like below.

How to do SEO friendly custom paging

In order to explain custom paging for Repeater or DataList control, I have taken a small example of a database table that has AutoID and Title column. I am using SQL Server 2005 database in this sample.

ASPX Page - Repeater Control

Lets see the code for the repeater control.

<asp:Label ID="lblMessage" runat="Server" ForeColor="Red" EnableViewState="False"></asp:Label>

h3>Repeater: Custom SEO Friendly Paging</h3>

<asp:Repeater ID="Repeater1" runat="server" EnableViewState="false">
      <b><%# Eval("AutoID") %>.</b> <%# Eval("Title") %><br />

asp:Literal ID="litPaging" runat="server" EnableViewState="False"></asp:Literal>

The first line Label (lblMessage) is to show if any error occurs. The ItemTemplate for the repeater control contains the AutoID and Title columns of the database. The last line is the Literal (litPaging) control that will list the links of different page numbers. Notice that all my controls has EnableViewState=false. This will ensure that no viewstate is being stored in the page to maintain its state and makes our page download faster.

Code behind page

using System;

using System.Data;

using System.Configuration;

using System.Collections;

using System.Web;

using System.Web.Security;

using System.Web.UI;

using System.Web.UI.WebControls;

using System.Web.UI.WebControls.WebParts;

using System.Web.UI.HtmlControls;

using System.Data.SqlClient;

using System.Text;

public partial class paging : System.Web.UI.Page


string connStr = ConfigurationManager.AppSettings["ArtSQLConnStr"].ToString();

int _startIndex = 0;

int _thisPage = 1;

int _pageSize = 10;

int _totalNumberOfRows = 0;

protected void Page_Load(object sender, EventArgs e)


if (!IsPostBack)


// check for current index

if (Request["start"] != null && Request["page"] != null)


int.TryParse(Request["start"].ToString(), out _startIndex);

int.TryParse(Request["page"].ToString(), out _thisPage);


BindGridViewArticels(); // bind articles



private void BindGridViewArticels()


DataTable dTable = new DataTable();

using (SqlConnection conn = new SqlConnection(connStr))


using (SqlCommand dCmd = new SqlCommand())


SqlParameter[] prms = new SqlParameter[4];

prms[0] = new SqlParameter("@startRowIndex", SqlDbType.Int);

prms[0].Value = _startIndex;

prms[1] = new SqlParameter("@pageSize", SqlDbType.Int);

prms[1].Value = _pageSize;

prms[3] = new SqlParameter("@totalCount", SqlDbType.Int);

prms[3].Direction = ParameterDirection.Output;

dCmd.CommandText = "LoadData";

dCmd.CommandType = CommandType.StoredProcedure;


dCmd.Connection = conn;

using (SqlDataAdapter dAd = new SqlDataAdapter())


// assign the select command to the Adapter object

dAd.SelectCommand = dCmd;

// now open the connection



conn.Close(); // close the connection


_totalNumberOfRows = int.Parse(prms[3].Value.ToString());



Repeater1.DataSource = dTable;



litPaging.Text = GetPagingDone(_thisPage, _totalNumberOfRows, _pageSize,

"/csharp/paging.aspx", "");



private string GetPagingDone(int thisPageNo, int totalCount, int pageSize, string pageName, string extraQstringToAdd)


int pageno = 0;

int start = 0;

int loop = totalCount / pageSize;

int remainder = totalCount % pageSize;

StringBuilder strB = new StringBuilder("<br /><b><font color=\"green\">Page:</font> ", 500);

for (int i = 0; i < loop; i++)


pageno = i + 1;

if (pageno.Equals(thisPageNo))

strB.Append(pageno + "&nbsp;| ");


strB.Append("<a href=\"" + pageName + "?start=" + start + "&page=" + pageno + extraQstringToAdd + "\" title=\"Go to Page " + pageno + "\">" + pageno + "</a> | ");

start += pageSize;


if (remainder > 0)



if (pageno.Equals(thisPageNo))

strB.Append("<b>" + pageno + "&nbsp;</b>| ");


strB.Append("<a href=\"" + pageName + "?start=" + start + "&page=" + pageno + extraQstringToAdd + "\" title=\"Go to Page " + pageno + "\">" + pageno + "</a> | ");


return strB.ToString() + "</b></span>";



Get solutions of the .NET problems with video explanations, .pdf and source code in .NET How to's.

In the above code snippet, I have a 4 page level variables related with custom paging, they are:

  1. _startIndex - This is to store the start index of the row in the result set, this will help us to get the next page data
  2. _thisPage - This is to store the current page number
  3. _pageSize - This is to store the page size (no. of records per page)
  4. _totalNumberOfRows - This is to store the total number of rows in the database. This will help us in calculating the number of pages.

First, let's look at the BindGridViewArticles() method in above code snippets. This method contains simple SQL Server data access code that is using LoadData stored procedure with two input parameter (startRowIndex and pageSize) and one output parameter (totalCount). The code for LoadData stored procedure is below.

CREATE PROCEDURE [dbo].[LoadDataTest]

@startRowIndex int,

@pageSize int,

@totalCount int output




SET @totalCount = 0

SET @startRowIndex = @startRowIndex + 1



Select SampleTable.*, ROW_NUMBER() OVER (ORDER BY SampleTable.AutoID DESC) as RowNum

FROM SampleTable WHERE

SampleTable.Active = 1

) as ArticleList


RowNum BETWEEN @startRowIndex AND (@startRowIndex + @pageSize) - 1



SELECT @totalCount = Count(AutoID) FROM SampleTable WHERE Active = 1



The first SELECT statement is getting paginated data from the database and last SELECT statement is getting the total number of active records from database. Note that 1st SELECT statement is only supported from SQL Server 2005 onward version of the SQL Server.

After executing the stored procedure in the BindGridViewArticles() method, I am binding the Repeate control with the resulst set (DataTable). The last line of this method is to write the number of pages and proper links for those pages.

The paging links are being formed in the GetPagingDone method that is taking five parameters, they are:

  1. thisPageNo - This is the value of current page being shown
  2. totalCount - This is the total number of records returned from the last SELECT statement of the stored procedure.
  3. pageSize - This is the size of the page (records per page)
  4. pageName - This is the name of the page where paging is being done (current page). Its good practice to keep this function in common utility class so that if you need to access this function from different pages in your project you can use this, in that case this parameter will be useful. (You can avoid passing this parameter by getting the current requested page with the help of Request object, so if you want to do that you can modify this method to use Request object).
  5. extraQueryStringToAdd - This is extra string you want to add with your paging links (In real scenario, you may want to pass your search filter parameters or category ids), I do not want to use this so I have kept empty.

The code inside GetPagingDone method is not very complicated, it's math to divide the total number of records by pageSize , loop through the dividend result (loop vairable) and write the paging links. If there is reminder write the last paging links as well.

If you want to achieve the same funtionality with the DatList control, simply replace the Repeater control code with the DataList control code like below.

<asp:Label ID="lblMessage" runat="Server" ForeColor="Red" EnableViewState="False"></asp:Label>

h3>Repeater: Custom SEO Friendly Paging</h3>

<asp:DataList ID="DataList1" runat="server" EnableViewState="False">
      <b><%# Eval("AutoID") %>.</b> <%# Eval("Title") %><br />

asp:Literal ID="litPaging" runat="server" EnableViewState="False"></asp:Literal>

Once you are done, run your code and you should see Custom SEO friendly paging of your records as displayed in the above picture. As EnableViewState property of all related controls are false so no need to worry about the ViewState size of the page. The links of different pages are hyperlink with querystring values so search engines will quickly grab them and crawl all those page.

This article described how to achieve Fast Custom SEO Friendly paging with Repeater and DataList controls. This technique can not only be applied in Repeater and DataList controls but also it can be used in other Data controls like GridView, DataGrid etc.

Hope you liked this article, please subscribe for RSS feed to get subsequent article alert in your email directly.

Page copy protected against web site content infringement by Copyscape

About the Author

Full Name: Sheo Narayan
Member Level: HonoraryPlatinum
Member Status: Administrator
Member Since: 7/8/2008 6:32:14 PM
Country: India
Regards, Sheo Narayan

Ex-Microsoft MVP, Author, Writer, Mentor & architecting applications since year 2001. Connect me on | |

Login to vote for this post.

Comments or Responses

Posted by: Questpond on: 7/7/2009
nice to see you writing again.....Welcome to your own home back.
Posted by: SheoNarayan on: 7/7/2009
:) Thanks Shivprasad.

I always wanted to do in regular basis but sometimes time doesn't allow. Trying to write every week now.

Posted by: Jacksmit on: 8/3/2009
On the articles that I found on SEO they all state that passing a value in the querystring makes the crawler ignore the page that is linked to? The way I understand it the crawler see's all these links going to the same page because it cannot apply the querystring.

What is the purpose of doing it this way? Does it only make the viewstate less? I found articles that stated that Google only reads a certain characters down a page and the viewstate counts against that number, correct?

I found a long-winded article that writes the viewstate to the end of the page for this purpose..?

Thank you
Posted by: SheoNarayan on: 8/4/2009
I do not think that querystring values are ignored by Google or other search engines, you can browse following two links of google. First search for "WCF Interview Questions" and Second "C# interview questions". Both search results gives the result of page but different querystrings value.

If I treat above statement as base, I can say that Google or other search engines do count the querystring values and treat them as different pages.

Your other statement is also true in this case that it makes the viewstate very less and of course ViewState is also counted as page contents by Google and other search engines and if it ignores the limit set by google, remaining contents will be ignored (This statement is based on what I have learned till now about the google).

Personally I do not like having a lot of viewstate on the page either in the beginning or end of the page but of course there are some scenarios where you can't avoid viewstate and in that case that article might be very useful.

Sheo Narayan

Posted by: Jacksmit on: 8/6/2009
Well responded Sheo. I do not have any SEO skills and am looking at improving my SEO rankings and getting very little help. It is my opinion that by adding the title ie. wcf+interview+questions in the querystring it is indexed by google but if you only had something like ie. then google cannot index it. I never thought about doing it your way but I do not like "bastarising" my URL like that.

I agree that your way works but what I did was to use URL re-write. That did a re-write on Application_BeginRequest in global.asax. I do an app.Context.RewritePath from a URL like ie. to the actual path that never appears in the URL(search?articleID=456)

Posted by: SheoNarayan on: 8/6/2009
I am not sure if I am getting you but I am also not adding "wcf+interview+questions" in the url of the WCF Interview Questions pages, the actual url is that has been indexed by google.


Login to post response

Comment using Facebook(Author doesn't get notification)