Learning SharePoint Part VII – List Pagination

A very small group of you saw this post go up and then come right back down again.  I found a bug about 30 seconds after posting this.    Here is the updated post.  NOTE: The bug had to do with not fully understanding how the pagination handled moving backwards in the list.  It appeared to be doing it correctly, but when I started to look closely at the actual items, it turned out that it was ALWAYS MOVING FORWARD.  The key is that I missed the PagedPrev query string element, which I now am including correctly.  The code has been updated below.  Beware of of voodoo programming.  🙂

It seems like everything takes just a little longer to learn than it should with SharePoint.  I am building a webpart that browses list items and allows the user to perform special actions on the list item.  After creating item styles and coming up with a CSS container to match the repeating pattern I needed, I went to add pagination to the item browser.   Pagination is usually represented in the user interface by little left-right arrows or “next page”/”previous page”.  This should be easy and it is, once you understand how SharePoint does pagination.


The key to SharePoint list pagination is the SPListItemCollectionPosition class. The documentation’s one example didn’t help me with my specific requirements.  In particular, I need to page through the list data sorted by the “Created” date field.

The SPListItemCollectionPosition’s constructor takes one string parameter called PagingInfo.  The SDK’s description of this parameter is particularly useless: Gets or sets paging information used to generate the next page of data.  What about the string’s structure and contents?  How do you use it?   Another friendly MSDN blogger, Deepak Badki, had part of the answer here.

But rather than just copy his code, I needed to understand it first. 

If you setup a list view such that it is sorted and the number of items to display is less than the number of items, you can examine the query strings resulting from paging from one page to the next and back.  Here are some examples:

First Page


Next Page


Next Page


Previous Page


In this particular instance, the list view is configured to sort by the created date.  Let’s break down the the query string:

AllItems.aspx? – The list page

Paged=TRUE – Indicates that the list is paged.

PagedPrev=TRUE – This element only appears when the previous page is visited.

p_Created= – The first sort by parameter, which is “Created” prefixed with “p_”.  The right hand is the encoded universal date and time.

p_ID= – This is the ID of the previous page’s last item’s ID.  This is important.

View= – This is the encoded GUID for the current list. 

PageFirstRow= – The ID of the current page’s first item. 

Newbie Webpart Developer

I am definitely new to webpart development.  I have done some work in ASP.NET but not for a while.  I know that sounds strange given that so much of what we do involves web development, but I have been a back-end guy for a long, long time.  So, this is really exciting for me and a bit painful.  ASP.NET’s attempt to be like WinForms in many ways offers tremendous advantage, but it also is a bit like taking a boat on land.  I am also exploring the MVC pattern, which seems like a better fit for web development.  That is neither here nor there because to build a webpart, you have to think like a server control developer—something I haven’t done since the ASP.NET 1.0 era.

So, my challenge is to learn the SharePoint way and also learn ASP.NET, and learn how to make things pretty.  I had help on the last bit from the product group.  Here is a screenshot of the basic item browser (the browser is a browser of ideas, a topic for another post):


The browser webpart is configured to browse three items at a time (this is a personalization aspect of the webpart).   At the core of the browser is one method:

   1: public static SPListItemCollection GetListPage(SPList list,
   2:                                                uint pageSize,
   3:                                                int pageIndex,                                                        
   4:                                                string pagingInfo,
   5:                                                string fields,
   6:                                                string queryCaml,
   7:                                                out bool isEndOfList)
   8: {
  10:     SPQuery query = new SPQuery();
  12:     query.RowLimit = pageSize;
  13:     query.ViewFields = fields;
  14:     query.Query = queryCaml;
  16:     if (!string.IsNullOrEmpty(pagingInfo))
  17:     {
  18:         SPListItemCollectionPosition collectionPosition = new SPListItemCollectionPosition(pagingInfo);                
  19:         query.ListItemCollectionPosition = collectionPosition;
  20:     }
  22:     SPListItemCollection returnValue = list.GetItems(query);
  24:     isEndOfList = (((pageIndex - 1) * pageSize) + returnValue.Count) >= list.ItemCount;
  26:     return returnValue;
  27: }

First, I really dislike methods that take this many parameters, but it takes a lot to feed this little algorithm:

SPList list – The list

uint pageSize – The page size (number of ideas per page)

int pageIndex – The current page index                                                       
string pagingInfo – The paging info (more on this later)

string fields – The fields to bring back from the list (in CAML)

string queryCaml – The CAML query to execute

out bool isEndOfList – An out parameter which signals that the results are the end of the list

Here is the method in context:

   1: SPList list = web.Lists.GetList(listGuid, false);
   2: SPView view = list.GetView(viewGuid);
   4: List<string> fields = new List<string>();
   6: foreach (string field in view.ViewFields)
   7: {
   8:     fields.Add(field);
   9: }
  11: SPListItemCollection pageItems = Fx.GetListPage(list,
  12:                                                 (uint) IdeasPerPage,
  13:                                                 CurrentPage,
  14:                                                 PageInfo,                                                            
  15:                                                 Fx.GetFieldRefCaml(fields.ToArray()),
  16:                                                 caml,
  17:                                                 out _EndOfList);
  18: RenderIdeas(pageItems);

I get the list from the web and the default view.  I pack up the fields and send the whole shebang to GetListPage.  So, where does the CurrentPage and the PageInfo come from?

The key is to remember the current page’s first and last item and derive the PageInfo from that data.  Lets walk through this:

1.)  RenderIdeas – Capture the first and last ideas and send them along to AddNavigation

2.) AddNavigation – Use the first and last items, the list view GUID, and the current state to setup the navigation elements used for paging.

Okay, now for the code:

   1: private void RenderIdeas(SPListItemCollection pageItems, Guid viewGuid)
   2: {
   3:     if (CurrentPage == 1 && pageItems.Count == 0)
   4:     {
   5:         CreateNoIdeasWarning();
   7:         return;
   8:     }
  10:     SPListItem lastIdea = null;
  12:     foreach (SPListItem idea in pageItems)
  13:     {
  14:         Panel ideaContainer = CreateIdeaContainer(idea);
  16:         _ContainerPanel.Controls.Add(ideaContainer);
  18:         lastIdea = idea;
  19:     }
  21:     AddNavigation(pageItems[0], lastIdea, viewGuid);
  22: }

Pretty straightforward.  If the list is empty, show a warning; otherwise, render each idea in the list returned by GetListPage.   Now, for AddNavigation:
   1: private void AddNavigation(SPListItem firstIdea, SPListItem lastIdea, Guid viewGuid)
   2: {            
   3:     DateTime lastItemCreatedDate = (DateTime) lastIdea["Created"];
   4:     int lastItemId = (int) lastIdea["ID"];
   6:     string lastItemDateData = lastItemCreatedDate.ToUniversalTime().ToString("yyyyMMdd hh:mm:ss");
   8:     string nextPage = string.Format("Paged=TRUE~p_{0}={1}~p_ID={2}~View={3}~PageFirstRow={4}",
   9:                                     "Created",
  10:                                     SPEncode.UrlEncode(lastItemDateData),
  11:                                     lastItemId,
  12:                                     SPEncode.UrlEncode(viewGuid.ToString()),
  13:                                     firstIdea["ID"]);
  15:     string previousPage;
  17:     if (CurrentPage > 2)
  18:     {
  19:         previousPage = string.Format("Paged=TRUE~PagedPrev=TRUE~p_{0}={1}~p_ID={2}~View={3}~PageFirstRow={4}",
  20:                                      "Created",
  21:                                      SPEncode.UrlEncode(lastItemDateData),
  22:                                      lastItemId,
  23:                                      SPEncode.UrlEncode(viewGuid.ToString()),
  24:                                      firstIdea["ID"]);
  25:     }
  26:     else
  27:     {
  28:         previousPage = string.Format("Paged=TRUE~View={0}", SPEncode.UrlEncode(viewGuid.ToString()));
  29:     }
  32:     HyperLink previousPageLink = null;
  33:     if (CurrentPage > 1)
  34:     {
  35:         previousPageLink = new HyperLink();
  36:         previousPageLink.Text = "Previous Page";
  37:         previousPageLink.CssClass = "IdeaLeftArrow";
  38:         previousPageLink.NavigateUrl = string.Format("{0}?Page={1}&PageInfo={2}", Page.Request.Url.GetLeftPart(UriPartial.Path), CurrentPage - 1, previousPage);
  39:     }
  41:     HyperLink nextPageLink = null;
  43:     if (!_EndOfList)
  44:     {
  45:         nextPageLink = new HyperLink();
  46:         nextPageLink.Text = "Next Page";
  47:         nextPageLink.CssClass = "IdeaRightArrow";
  48:         nextPageLink.NavigateUrl = string.Format("{0}?Page={1}&PageInfo={2}", Page.Request.Url.GetLeftPart(UriPartial.Path), CurrentPage + 1, nextPage);
  49:     }
  51:     if (nextPageLink == null && previousPageLink == null)
  52:     {
  53:         return;
  54:     }
  56:     Panel navigationPanel = new Panel();
  58:     navigationPanel.CssClass = "IdeaNavigationPanel";
  60:     if (previousPageLink != null)
  61:     {
  62:         navigationPanel.Controls.Add(previousPageLink);
  63:     }
  64:     if (nextPageLink != null)
  65:     {
  66:         navigationPanel.Controls.Add(nextPageLink);
  67:     }
  69:     _ContainerPanel.Controls.Add(navigationPanel);
  70: }

This should look familiar because I am constructing two query strings similar to what was discussed in the beginning of the post.  The next page URL is pretty straightforward.  The previous page URL requires a bit more explanation.  If CurrentPage is great than 2, I have to insert the PagedPrev element, otherwise it is sufficient to just add the list GUID element.

When the user clicks one of the links, this paging information is parsed out of the query string and feeds the next pagination call (from the OnLoad event):

   1: if (Page.Request.QueryString.HasKeys())
   2: {
   3:     if (Page.Request.QueryString["Page"] != null)
   4:     {
   5:         int value;
   6:         if (int.TryParse(Page.Request.QueryString["Page"], out value))
   7:         {
   8:             CurrentPage = value;
   9:         }
  10:     }
  12:     if (Page.Request.QueryString["PageInfo"] != null)
  13:     {
  14:         _PageInfo = Page.Request.QueryString["PageInfo"];
  15:         _PageInfo = _PageInfo.Replace("~", "&");
  16:     }
  17: }
  19: EnsureChildControls();
  20: RenderIdeaList();

That’s it folks.  I hope this helps somebody because this took me about 5 hours to figure it out.

Comments (11)

  1. anchamparuthi says:

    Thanks for the great post. I’m still confused on one thing. Lets say we have 100 items in a list, if we are using pagination by your algorithm, say 10 items in one page, are we still querying all the 100 items everytime when i click the next page and displaying only 10, OR are we only querying 10 items in every next page click.


  2. Anonymous says:

    I believe the ID for the variable "p_ID" used in the variable "previousPage" should be the first item ID not the last item ID

  3. Anonymous says:

    Yes, I also believe the ID for the "p_ID"  in "previousPage" should be first item ID.

    previousPage = string.Format("Paged=TRUE~PagedPrev=TRUE~p_{0}={1}~p_ID={2}~View={3}~PageFirstRow={4}",








  4. Anonymous says:

    Hi all,

    I revealed that the {1} is the firstItemDateData, {2} should be the firstItemId.

    And View={3} and PageFirstRow={4} should not be here.

    With the suggestion, I believe it solve the backward paging.

    previousPage = string.Format("Paged=TRUE~PagedPrev=TRUE~p_{0}={1}~p_ID={2}",





  5. The XmlNode that is returned from GetListItems has a pointer node to the next page ONLY.  This node cane be used to create a QueryOptions node tp navigate to the next page.  However, previous page and page(X) is not possible.

    At one point we were using GetList and GetListItems to create and pupulate an ADO.Net DataTable with the entire List. Once we have the DataTable, we bind it to a GridView control on and ASP.Net web page. We gave the client the ability to Filter, Sort and Update the data. This is a much nicer interface than anything SharePoint has.

    However, when the List became too large, GetListItems would throw the Soap "System.OutOfMemoryException".

    We had one of our subs build us a C# Class Library that is basically a wrapper around GetList, GetListItems and UpdateListItems.

    This Wrapper works in the following manner: When the class is instantiated, the ListURL, ListName, RowsPerPage, Query and ViewFields need to be set. GetSPList will return the first page as an Ado.Net DataTable. It also has the PageNext, PagePrevious and GotoPage(X) methods that return an Ado.Net DataTable. Other read only properties include CurrentPage and TotalPages.

    If ViewFields, Query or RowsPerPage are reset, it will recalculate TotalPages. GetListItems is another read only property that holds the GetListItems XmlNode. This has worked perfectly for us and solved our problems.

    They told us this assembly was written in VS 2008 C# and was unit tested using WSS 3.0, MOSS 2007, IE 8.0 and Windows Server 2003 32bit.

    We purchased only the Assembly. Not the code. If you want, I can put you in touch with these people.

  6. Anonymous says:

    Guys, you have to reach me at colbyafrica.blogspot.com.  Left MSFT a while ago.

  7. Anonymous says:


    You can also visit my blog which explain how to apply paging with sorting,caml query, folder query on sharepoint list by using SPQuery and SPListItemCollectionPosition.

    <a href="sharepointdad.wordpress.com/…/" target="_blank">SharePoint List Pagination using SPListItemCollectionPosition</a>

  8. Anonymous says:

    When using DateTime I don't think you should format the string. I believe you should leave it as UTC string only. It appears when you sort on a column of type DateTime which contains rows with the same exact DateTime (ex: 2012-10-17 16:00:00 in multiple rows). Then the paging starts to act odd. Using just the UTC value fixed this for me.