Wed, 12 Nov 2008

Paging Over Data in Windows Azure Tables

[UPDATE 4/28/2010] This post is now woefully out of date.  Scott Densmore posted how to do this with the current StorageClient library.

This is my eleventh post to this blog, and until this post, all entries on my blog appeared on the front page.  I figured now was a good time to introduce the concept of paging to my blog.

I’ve created a sample application called Paging Windows Azure Tables on the MSDN Code Gallery which shows a simple paging example, and here I’ll share the code change I made to my blog engine to support paging.

Continuation Tokens

Paging in Windows Azure tables is based on continuation tokens.  The concept is that if you retrieve only a subset of the data in a particular query, the response from the table service will include a continuation token.  That continuation token can be passed in the next time you do the query, and the results you retrieve will start from that point onward.

In Windows Azure, there are actually two continuation tokens, one for partitions and one for rows.  When you perform a query and don’t retrieve all the results, two headers will be set in the HTTP response called x-ms-continuation-NextPartitionKey and x-ms-continuation-NextRowKey.  When you pass them in during the next query, use the query string parameters NextPartitionKey and NextRowKey.

Tokens in Practice

Here’s the code in the Index action of my blog code that finds a continuation token in the ct query parameter if it exists, and uses that to modify the table query.


    var query = (DataServiceQuery<Models.BlogEntry>)
        (new Models.BlogDataServiceContext().BlogEntryTable.Take(5));
    var continuation = Request["ct"];
    if (continuation != null)
    {
        // ct param looks like "<partition>/<row>"
        string[] tokens = continuation.Split('/');
        var partitionToken = tokens[0];
        var rowToken = tokens[1];

        // These end up as query parameters in the HTTP request.
        query = query
            .AddQueryOption("NextPartitionKey", partitionToken)
            .AddQueryOption("NextRowKey", rowToken);
    }

Firing up Fiddler, I can see that the HTTP request looks like this:

GET /BlogEntryTable()?$top=5&NextPartitionKey=smarx&NextRowKey=2521770338506922693%20show-off-your-windows-azure-application

and the headers in the HTTP response look like this:

HTTP/1.1 200 OK 
Cache-Control: no-cache 
Transfer-Encoding: chunked 
Content-Type: application/atom+xml;charset=utf-8 
Server: Table Service Version 1.0 Microsoft-HTTPAPI/2.0 
x-ms-request-id: 91e00f79-7b87-4733-ab71-aa7b71a06c58 
x-ms-continuation-NextPartitionKey: smarx 
x-ms-continuation-NextRowKey: 2521776077664033749 pdc-teaser 
Date: Wed, 12 Nov 2008 08:54:19 GMT

Here’s the code I use to extract those continuation tokens and pass them on to my view via the ViewData dictionary:

    var res = query.Execute();
    var qor = (QueryOperationResponse)res;
    string nextPartition = null;
    string nextRow = null;
    qor.Headers.TryGetValue("x-ms-continuation-NextPartitionKey", out nextPartition);
    qor.Headers.TryGetValue("x-ms-continuation-NextRowKey", out nextRow);

    if (nextPartition != null && nextRow != null)
    {
        ViewData["continuation"] = string.Format("{0}/{1}", nextPartition, nextRow);
    }
    return View(res);

Finally, in my view, I create a link to the next page if there is one:

    <% if (ViewData["continuation"] != null) { %>
        <div class="pagelink">
            <a href='?ct=<%= ViewData["continuation"] %>'>Older Entries &raquo;</a>
        </div>
    <% } %>

Download the Sample

Please check out the sample code on code.msdn.