WebAPI: Calculating NextPageLink for ODataResult<>

Fall update of Web API introduced ODataResult<> which is used to implement OData features like Server Driven Paging and $inlinecount. I blogged about it here 

If you look at the definition of ODataResult<>, it has following properties,

1. Items Collection
2. NextPageLink property
3. Count

Essentially calculating Items and Count are no brainer once you have access to IQueryable however NextPageLink can be interesting as there is no default implementation.

In this post, I’m going to describe how to calculate NextPageLink property. Feel free to modify the code as per your requirement, it is merely one way of achieving the goal.

Basics:

If you think about Paging support, you are thinking of two possible parameters, Batch Size & Page Number.

Usually queries are done like pageNumber=1 & batchSize=2 for first page, pageNumber=2 & batchSize=2 for the next page and so on, resulting in request made for page from 1..N but your batchSize does not change.

Here is the complete code sample which shows you how to calculate the NextPageLink.

 public static Uri CalculateNextPageLink(
            HttpRequestMessage request,
            int resultLimit)
        {
            if (resultLimit <= 0)
            {
                return null;
            }

            int topValue;
            int skipValue;

            var topValueRaw = request.GetQueryNameValuePairs()
                                     .FirstOrDefault(x => x.Key == "$top");
            if (!Int32.TryParse(
                topValueRaw.Value,
                out topValue))
            {
                return null;
            }

            var skipValueRaw = request.GetQueryNameValuePairs()
                                      .FirstOrDefault(x => x.Key == "$skip");

            if (!Int32.TryParse(
                skipValueRaw.Value,
                out skipValue))
            {
                return null;
            }

            if ((topValue > resultLimit) || (topValue <= 0 || skipValue < 0))
            {
                //return the same url
                return null;
            }

            //logic
            //skip=pageIndex*pageSize
            //top=pageSize
            var stringBuilder = new StringBuilder();
            var bothSkipAndTopSupplied = (topValue != -1 && skipValue != -1);
            var lastPageIndex = skipValue/topValue;
            //int lastPageNumber = lastPageIndex+1;

            foreach (var keyValue in request.GetQueryNameValuePairs())
            {
                switch (keyValue.Key)
                {
                    case "$top":
                        stringBuilder.AppendFormat("$top={0}",(bothSkipAndTopSupplied)? topValueRaw.Value: keyValue.Value);
                        break;
                    case "$skip":
                        if ((bothSkipAndTopSupplied))
                        {
                            stringBuilder.AppendFormat("$skip={0}",((lastPageIndex + 1)*topValue));
                        }
                        else
                        {
                            stringBuilder.AppendFormat("$skip={0}",keyValue.Value);
                        }
                        break;
                    default:
                        stringBuilder.Append(Uri.EscapeUriString(keyValue.Key));
                        stringBuilder.Append("=");
                        stringBuilder.Append(Uri.EscapeDataString((keyValue.Value)));
                        break;
                }

                stringBuilder.Append("&");
            }

            stringBuilder.Remove(
                stringBuilder.Length - 1,
                1);

            return new UriBuilder(request.RequestUri)
            {
                Query = stringBuilder.ToString()
            }.Uri;
        }