Paging With OData And ASP.NET Web API

Introduction

OData (Open Data Protocol) is an open protocol for sharing the data. There is built-in support of OData in Web API. OData URI is also support the filter expression.

One of the common requirements for web application is displaying partial data from a large result set. For example user may able to browse the 50 records from 1 lakh of records and the next 50 records will be populated when the user is in the next page. If we display all records in single page, it would affect performance significantly.

We can implement Paging in two ways. Web API has built-in support for both option client and server driven paging.

Client Driven Paging

The client decides how many records contained by the page and tell it to server for page of size.

Server Driven Paging

Here client is just sending requests for the collection of entities and server returns partial results like which is used to retrieve more results

Client - Driven Paging

As we know, we can define the parameters with OData URI to modify the OData query. Client sends these parameters in the query string of URI. Client driven paging can be implemented with the OData query.

OData Query Options

Following OData query options supported by Web API.

Options Description
$orderby Used for sorting
$select Select which properties are included in result set
$skip How many record wants to skip
$top How many record wants to retrieve
$filter Filter the result set
$expand Expand the related entity
$inlinecount Used to include total record count in response

As query option has keyword $top and $skip. $top is used tell server how many records client wants. We can turn on the client query support with "Queryable" or "EnableQuery" attribute defined on top of action method of Web API.

Example

I have taken same example which was used in my previous article. In this example I have created in-memory data source and configured the end point. In data source, I have added some more entries.

  1. /// <summary>  
  2. /// In memory Data source  
  3. /// </summary>  
  4. /// <returns></returns>  
  5. public List<TestData> CreateTestData()  
  6. {  
  7.     List<TestData> data = new List<TestData>();  
  8.     data.Add(new TestData { Id = 1, Name = "Jignesh", Role = "Project Manager" });  
  9.     data.Add(new TestData { Id = 2, Name = "Tejas", Role = "Architect" });  
  10.     data.Add(new TestData { Id = 3, Name = "Rakesh", Role = "Lead" });  
  11.     data.Add(new TestData { Id = 4, Name = "Hiren", Role = "Developer" });  
  12.     data.Add(new TestData { Id = 5, Name = "Pooja", Role = "Developer" });  
  13.     data.Add(new TestData { Id = 6, Name = "Keyur", Role = "Developer" });  
  14.     data.Add(new TestData { Id = 7, Name = "Ashish", Role = "Developer" });  
  15.     data.Add(new TestData { Id = 8, Name = "Parth", Role = "Developer" });  
  16.     data.Add(new TestData { Id = 9, Name = "Manish", Role = "QA" });  
  17.     data.Add(new TestData { Id = 10, Name = "Manisha", Role = "QA" });  
  18.     data.Add(new TestData { Id = 11, Name = "Urmi", Role = "QA" });  
  19.     data.Add(new TestData { Id = 12, Name = "Bharat", Role = "QA" });  
  20.     data.Add(new TestData { Id = 13, Name = "Varun", Role = "QA" });  
  21.     data.Add(new TestData { Id = 14, Name = "Komal", Role = "QA" });  
  22.     data.Add(new TestData { Id = 15, Name = "Dhaval", Role = "QA" });  
  23.     data.Add(new TestData { Id = 16, Name = "Chirag", Role = "Project Manager" });  
  24.     data.Add(new TestData { Id = 17, Name = "Devid", Role = "QA" });  
  25.     data.Add(new TestData { Id = 18, Name = "Vivek", Role = "Developer" });  
  26.     data.Add(new TestData { Id = 19, Name = "Purvi", Role = "Architect" });  
  27.     data.Add(new TestData { Id = 20, Name = "Denish", Role = "Developer" });  
  28.   
  29.     return data;  
  30. }  
I have tried following URI, it returns top five records.

URI: http://localhost:24367/TestData?$top=5

Output :
  1. {  
  2.   "@odata.context":"http://localhost:24367/$metadata#TestData","value":[  
  3.     { "Id":1,"Name":"Jignesh","Role":"Project Manager"   },  
  4.     { "Id":2,"Name":"Tejas","Role":"Architect"    },  
  5.     { "Id":3,"Name":"Rakesh","Role":"Lead"    },  
  6.     { "Id":4,"Name":"Hiren","Role":"Developer"    },  
  7.     { "Id":5,"Name":"Pooja","Role":"Developer"    }  
  8.   ]  
  9. }  
If we want the next 5 records, we can use $skip query option to ignore the first five records. So I have tried following URI now.

URI: http://localhost:24367/TestData?$skip=5&$top=5

Output:
  1. {  
  2.   "@odata.context":"http://localhost:24367/$metadata#TestData","value":[  
  3.     {      "Id":6,"Name":"Keyur","Role":"Developer"    },  
  4.     {      "Id":7,"Name":"Ashish","Role":"Developer"   },  
  5.     {      "Id":8,"Name":"Parth","Role":"Developer"    },  
  6.     {      "Id":9,"Name":"Manish","Role":"QA"    },  
  7.     {      "Id":10,"Name":"Manisha","Role":"QA"    }  
  8.   ]  
  9. }  
We can also use $filter query option with above URI to narrowing the records.

URI: http://localhost:24367/TestData?$filter=Role eq 'QA'&$skip=5&$top=5

Output:
  1. {  
  2.   "@odata.context":"http://localhost:24367/$metadata#TestData","value":[  
  3.     { "Id":14,"Name":"Komal","Role":"QA"    },  
  4.     { "Id":15,"Name":"Dhaval","Role":"QA"   },  
  5.     { "Id":17,"Name":"Devid","Role":"QA"    }  
  6.   ]  
  7. }  
Server driven Paging

In client driven paging, client always sends a number that indicates how many records are wanted. This number is sent to the server by using $top query option. In Server driven paging, server has control on page size. We are able to pass page size to EnableQuery or Queryable attribute. When we do the server driven paging, server always returns next page link to the response body.

So now Web API method is rewritten to:
  1. [EnableQuery(PageSize=5)]  
  2. public IHttpActionResult Get()  
  3. {  
  4.     var result = CreateTestData().AsQueryable();  
  5.     return Ok(result);  
  6. }  
URL: http://localhost:24367/TestData?$filter=Role eq 'QA'

Output
  1. {  
  2.   "@odata.context":"http://localhost:24367/$metadata#TestData","value":[  
  3.     { "Id":9,"Name":"Manish","Role":"QA"    },  
  4.   
  5.     { "Id":10,"Name":"Manisha","Role":"QA"    },  
  6.     { "Id":11,"Name":"Urmi","Role":"QA"    },  
  7.     { "Id":12,"Name":"Bharat","Role":"QA"    },  
  8.     {"Id":13,"Name":"Varun","Role":"QA"    }  
  9.   ],"@odata.nextLink":"http://localhost:24367/TestData?$filter=Role%20eq%20%27QA%27&$skip=5"  
  10. }  
As we have seen in above example, server will return data along with next URI. Next link is generated based on $skip.

$inlinecount query option

In server driven paging, client has easy navigation to the next page by using next link and in the same way client can navigate to previous page by remembering URI associated with current page while navigate to next page.

$inlinecount query option can be applied to any query to get total number of records. If we have total records and page size than we can easy create next and previous URI.

Currently, $inlinecount query option is supported by OData but not supported by Web API. In feature release, it can be added.

Summary

We can implement Paging in two ways in Web API using OData.
  • Client driven paging
  • Server driven paging

Hope this will help you.

Read more articles on ASP.NET: