Tuesday 28 October 2014

Implementing Sorting and Paging in the Web API Using OData Queries


The client displaying data returned by a Web API may want to implement sorting or paging on the data being returned. Although there can be different ways to implement these features, a simple technique is to use OData support offered by the Web API. This article shows how to call a Web API by using a client-side script and also shows how to implement Ajax-driven sorting and paging.

Defining the Requirements

In the example discussed in this article, you will meet the following requirements:
  • The data to be displayed is returned by a Web API.
  • The data should be displayed in an HTML table.
  • The column headings should be clickable. Clicking a column should sort the data shown in the table.
  • The sorting should be persistent and bidirectional. Clicking a column header should toggle the sort order between ascending and descending. The sort order must be persistent even when the page index changes.
  • The bottom of the table should display a pager row with page numbers. Clicking a page should display only the records belonging to that page.
  • The sorting as well as paging should be implemented by using Ajax calls.
Building Multi-device Apps with Apache Cordova and Xamarin in Visual Studio
Figure 1 shows the final outcome of the example discussed in this article.
Paging1
Figure 1: The completed table
As shown in Figure 1, the column headers are clickable hyperlinks. They don't point to any specific resource. They are simply used to indicate a clickable heading. The same holds true for hyperlinks shown in the pager row of the table. The click event raised by a hyperlink is trapped by using jQuery code and the form is submitted to the server programmatically for further action.

Creating the Model

To begin developing this example, create a new ASP.NET Web Application by using a Web API project template (see Figure 2).
Paging2
Figure 2: Creating a new project
Then, right-click the Models folder and add a new ADO.NET entity data model for the Customers table of the Northwind database. Figure 3 shows the Customer entity class in the Visual Studio designer.
Paging3
Figure 3: The Customer entity class
In addition to the Customer entity class, you need an auxiliary class: SortingPagingInfo. This class contains all the settings used by the sorting and paging features, such as the field on which the data is to be sorted and the total number of pages. The SortingPagingInfo class is shown below:
  1. public class SortingPagingInfo
  2. {
  3. public string SortField { get; set; }
  4. public string SortDirection { get; set; }
  5. public int PageSize { get; set; }
  6. public int PageCount { get; set; }
  7. public int CurrentPageIndex { get; set; }
  8. }
As you can see, the SortingPagingInfo class consists of five properties: SortField, SortDirection, PageSize, PageCount, and CurrentPageIndex. These property names are self explanatory and therefore are not discussed here.

Creating the Web API

This example uses OData support provided in the Web API. Data protocol allows you to create RESTful data services by exposing URI end points. The ASP.NET Web API supports OData queries so that your data can be consumed by the client over HTTP with the help of OData query options. If you are not familiar with this feature, you may consider reading this article first.
Once the model is ready, right-click the References folder and select the "Manage NuGet Packages" option. In the Manage NuGet Packages dialog, search for WebApi.OData and install the "Microsoft ASP.NET Web API 2.2 for OData v1.3" package. Along with the installation, other dependencies also will be added for you.
Paging4
Figure 4: Installing the "Microsoft ASP.NET Web API 2.2 for OData v1.3" package
Then, add a new Web API controller in the Controllers folder and name it CustomerController. This controller class is shown below:
  1. public class CustomerController : ApiController
  2. {
  3. [EnableQuery]
  4. public IQueryable<Customer> GetCustomers()
  5. {
  6. NorthwindEntities db = new NorthwindEntities();
  7. eturn db.Customers;
  8. }
  9. }
As you can see, the CustomerController class contains only one method: GetCustomers(). This method is invoked from the client through a GET request and returns the required data. Notice that the GetCustomers() method is decorated with the [EnableQuery] attribute. The [EnableQuery] attribute enables OData support for the Web API. The return type of the method is IQueryable of Customer objects. Inside, it simply returns all the data from the Customers DbSet back to the caller. This is all you need to do in the Web API.

Creating the HomeController

Open the HomeController from the Controllers folder and modify the Index() action method as shown below. The Index() action sets the default values for the sorting and paging features.
  1. public class HomeController : Controller
  2. {
  3. public ActionResult Index()
  4. {
  5. using (NorthwindEntities db = new NorthwindEntities())
  6. {
  7. SortingPagingInfo info = new SortingPagingInfo();
  8. info.SortField = "CustomerID";
  9. info.SortDirection = "asc";
  10. info.PageSize = 10;
  11. info.PageCount = Convert.ToInt32(Math.Ceiling((double)(db.Customers.Count()
  12. / info.PageSize)));
  13. info.CurrentPageIndex = 0;
  14. ViewBag.SortingPagingInfo = info;
  15. return View();
  16. }
  17. }
  18. }
The Index() action method creates an instance of the SortingPagingInfo class created earlier and sets its properties, such as SortField, SortDirection, PageSize, PageCount, and CurrentPageIndex. The SortingPagingInfo object is passed to the view by using a ViewBag property so that these values can be utilized on the view.

Creating the Index View

Now comes the important part: consuming the Web API and implementing the sorting and paging features. Add the Index view and write the following code in it:
  1. <h1>List of Customers</h1>
  2. @{
  3. SortingPagingInfo info = ViewBag.SortingPagingInfo;
  4. }
  5. @using (Html.BeginForm("Index", "Home", FormMethod.Post))
  6. {
  7. @Html.Hidden("SortField", info.SortField)
  8. @Html.Hidden("SortDirection", info.SortDirection)
  9. @Html.Hidden("PageCount", info.PageCount)
  10. @Html.Hidden("PageSize", info.PageSize)
  11. @Html.Hidden("CurrentPageIndex", info.CurrentPageIndex)
  12. <table border="1" cellpadding="10">
  13. <tr class="headerRow">
  14. <th><a href="#" data-sortfield="CustomerID"
  15. class="header">CustomerID</a></th>
  16. <th><a href="#" data-sortfield="CompanyName"
  17. class="header">CompanyName</a></th>
  18. <th><a href="#" data-sortfield="ContactName"
  19. class="header">ContactName</a></th>
  20. <th><a href="#" data-sortfield="Country"
  21. class="header">Country</a></th>
  22. </tr>
  23. <tr class="pagerRow">
  24. <td colspan="4">
  25. @for (var i = 0; i < info.PageCount; i++)
  26. {
  27. <a href="#" data-pageindex="@i"
  28. class="pager">@(i + 1)</a>
  29. }
  30. </td>
  31. </tr>
  32. </table>
  33. }
The preceding code grabs the SortingPagingInfo object passed through the ViewBag and transfers its properties into five hidden fields. This is required because these values need to be accessed from jQuery code (discussed later). An HTML table is used to display the data. Notice how the table headers are shown. Each column header consists of an anchor element whose data-sortfield custom data attribute is set to the column name it represents. This data-sortfield attribute is used to decide the sorting order when a user clicks on the header. Also, notice that all the header anchor links have a header CSS class associated with them. This class is used as a selector in the jQuery code.
The pager row simply displays anchor elements based on the PageCount. Notice that each pager link stores its index in a data-pageindex attribute and that the table row has the pagerRow CSS class.
Next, add a script reference to the jQuery library and also add an empty script block.
  1. <script src="~/Scripts/jquery-1.10.2.min.js"></script>
  2. <script type="text/javascript">
  3. ...
  4. </script>
Then, add a JavaScript function, GetData(), inside the <script> block. This function calls the Web API and retrieves the required data.
  1. function GetData()
  2. {
  3. var sortfield = $("#SortField").val();
  4. var sortdirection = $("#SortDirection").val();
  5. var currentpageindex = $("#CurrentPageIndex").val();
  6. var pagesize = $("#PageSize").val();
  7. var url = "/api/customer";
  8. var query = "";
  9. switch (sortfield) {
  10. case "CustomerID":
  11. query = (sortdirection == "asc" ? "$orderby=CustomerID asc" :
  12. "$orderby=CustomerID desc");
  13. break;
  14. case "CompanyName":
  15. query = (sortdirection == "asc" ? "$orderby=CompanyName asc" :
  16. "$orderby=CompanyName desc");
  17. break;
  18. case "ContactName":
  19. query = (sortdirection == "asc" ? "$orderby=ContactName asc" :
  20. "$orderby=ContactName desc");
  21. break;
  22. case "Country":
  23. query = (sortdirection == "asc" ? "$orderby=Country asc" :
  24. "$orderby=Country desc");
  25. break;
  26. }
  27. query += "&$skip=" + (currentpageindex * pagesize) + "&$top=" + pagesize;
  28. url += "?" + query;
  29. $.getJSON(url, function (data) {
  30. $("table").find('tr').slice(1, -1).remove();
  31. for (var i = 0;i<data.length;i++)
  32. {
  33. var html = '<tr>';
  34. html += '<td>' + data[i].CustomerID + '</td>';
  35. html += '<td>' + data[i].CompanyName + '</td>';
  36. html += '<td>' + data[i].ContactName + '</td>';
  37. html += '<td>' + data[i].Country + '</td>';
  38. html += '</tr>';
  39. $(".pagerRow").before(html);
  40. }
  41. });
  42. }
The GetData() function grabs the values from the SortField, SortDirection, CurrentPageIndex, and PageSize hidden fields. As mentioned earlier, you will be sending an OData query to the Web API to retrieve data. This query needs to have three parameters: $orderby, $skip, and $top.
The switch block determines and adds the $orderby parameter of the OData query. Notice how the "asc" and "desc" clause is being added. After the switch block, the $skip parameter is added by calculating its value as (currentpageindex * pagesize). Finally, the $top parameter is set to the value of the pagesize variable. This way, the Web API has everything it needs to sort and page the data under consideration.
The actual Ajax call to the Web API is made by using $.getJSON() of jQuery. The first parameter of $.getJSON() is the URL pointing to the Web API: /api/customer. The second parameter is a callback function that gets invoked when the call is complete.
The callback function receives the Customer data in the form of an array. Before adding this data to the table, the existing data (if any) is removed by slicing the table. This is done by using the slice() method. A for loop iterates through the data and adds a table row filled with the Customer data to the table by using the before() method.
The GetData() function is called from the ready() method of jQuery. This is shown below:
  1. $(document).ready(function () {
  2. GetData();
  3. $(".header").click(function (evt) {
  4. var sortfield = $(evt.target).data("sortfield");
  5. if ($("#SortField").val() == sortfield) {
  6. if ($("#SortDirection").val() == "asc") {
  7. $("#SortDirection").val("desc");
  8. }
  9. else {
  10. $("#SortDirection").val("asc");
  11. }
  12. }
  13. else {
  14. $("#SortField").val(sortfield);
  15. $("#SortDirection").val("asc");
  16. }
  17. evt.preventDefault();
  18. GetData();
  19. });
  20. $(".pager").click(function (evt) {
  21. var pageindex = $(evt.target).data("pageindex");
  22. $("#CurrentPageIndex").val(pageindex);
  23. evt.preventDefault();
  24. GetData();
  25. });
  26. });
As soon as the page loads in the browser, you need to display the first page of data. So, a call to GetData() is made immediately inside the ready() method.
A class selector is used to retrieve all the header anchor elements (header CSS class) and a click event handler is wired to them. The click event handler basically checks the header element that was clicked and accordingly sets the SortField hidden field. This is done by using the data-sortfield custom data attribute of the anchor element under consideration. Notice that if a header element is clicked multiple times, the SortDirection hidden field toggles its value between asc and desc. Then, GetData() is called to get the data as per the new sort order.
On similar lines, the click event handler of pager hyperlinks grabs the data-pageindex custom data attribute and sets the CurrentPageIndex hidden field. GetData() is called again so that the new page of data can be displayed.
That's it! Run the application and test whether sorting and paging works as expected.

Summary

The data returned from the Web API is often displayed in a tabular format. You may need to provide sorting and paging for such a table. By using OData support offered by the Web API, client-side script, and Ajax calls, you easily can implement persistent and bidirectional sorting as well as paging. This article illustrated how this can be accomplished.

Extra Reading Links


No comments:

Post a Comment

Angular Tutorial (Update to Angular 7)

As Angular 7 has just been released a few days ago. This tutorial is updated to show you how to create an Angular 7 project and the new fe...