It has been more than a year since the last time we discussed WebAPI with WebForms, and a lot has changed in that time.  Microsoft has released a 1.0 release of WebAPI and Telerik now supports using WebAPI as a client-side datasource for many of our controls.  In this article, I will show you how to configure WebAPI to work with one of Telerik’s most popular controls, our Grid for ASP.NET AJAX.  Without changing the appearance of the grid, I will convert the grid from server-side databinding to client-side databinding and show you how it supports sorting, filtering and paging with WebAPI.

Original Grid
Figure 1 - Original Server-Generated Grid

Configuring WebAPI for OData

There are several ways to configure WebAPI for OData use, as it is extremely flexible in its implementation.  If you are using Telerik OpenAccess with your project, you can use a built in wizard to construct an appropriate WebAPI for your domain model.  Otherwise, we can build one by hand with syntax that mimics that of an ASP.Net MVC controller.  I built a simple WebAPI for our ConferenceBuddy application in a prior post, and I am going to use that base API for this sample.

In that original API, we derived our API from ApiController:

[Authorize()]
public class EventsController : ApiController
{
 
    public IEnumerable<Event> Get()
    {
        return repository.GetEvents();
    }
}
Code Listing 1 - Original WebAPI for Getting Events

This configures WebAPI to interact and respond with a set of POCOs (Plain Old CLR Objects).  To allow WebAPI to interact with OData queries and return data properly shaped like an OData resultset, we have three choices with varying degrees of complexity and return on our coding investment.

1.    Queryable attribute

With the help of a NuGet package called Microsoft.AspNet.WebApi.OData we can decorate our ApiController or its methods with a Queryable attribute and they will be able to handle OData query commands.  I can modify the EventsController’s Get method signature to look like the following:


[Queryable]
public IQueryable<Event> Get()
{
Code Listing 2 - Method Signature to accept WebAPI Filtering

Our Get method can now handle requests like:  

/api/Events?$orderby=name%20asc&$filter=startswith(name,'Philly')& $top=10&$skip=0

Data will be returned as a collection of events, serialized in JSON, XML, or whatever format you may have configured your HTTP Request to accept.

The drawback of this approach is that it is not a complete implementation of OData query, and you are not going to receive information about the size of the resultset delivered.  The only OData query arguments supported by this implementation are $filter, $orderby, $skip, and $top.  A significant piece that is missing from this collection is the interpretation of the $inlinecount query argument.  As the data that is returned in our sample is a collection of Events, there is no logical place to deposit the count in the resultset.

This partial implementation will do funny things to your AJAX controls that you try to bind to the WebAPI service.  If you attempt to use paging or any other operation that needs access to the total recordcount, it will not report correct information and the control’s pager will not function properly.

2.     Return PageResult and Handle ODataQueryOptions

The next option in our list is to handle the OData query and return appropriately formatted data ourselves.  This is the “manual” approach to ensuring that data is accepted and returned properly.  To implement this option, we will need to change the signature of our Get method to accept a collection of ODataQueryOptions and return an OData PageResult:

public PageResult<Event> Get(ODataQueryOptions<Event> options)
{
Code Listing 3 - Method Signature to Handle and Return OData Queries

With this signature change in play, you will need to change how your data is returned as it can no longer be formatted as an IQueryable.  To convert it to a PageResult and apply the OData query, consider the following modification:

public PageResult<Event> Get(ODataQueryOptions<Event> options)
{
 
  var ctx = new CbContext();
  var events = ctx.Events.AsQueryable();
 
  var results = options.ApplyTo(events);
  return new PageResult<Event>(results as IEnumerable<Event>, Request.GetNextPageLink(), Request.GetInlineCount());
 
  }
Code Listing 4 - Complete Get Method to Return OData Formatted Results

On the last few lines, I have applied the ODataQueryOptions to the IQueryable.  This applies those query arguments that were submitted and returns another IQueryable.  To format that data as a PageResult, we pass in the IQueryable and generate NextPage links and the count of the elements being returned.  The resulting data is in a different shape than we originally saw, with output looking more like a standard OData output:

{"Items":[
{   "id":1,
"name":"CodeMash",
"city":"Sandusky",
"state":"OH",
"country":"USA"
}],
"NextPageLink":null,
"Count":48}
Code Listing 5 - Resulting JSON from OData Query

3.    Implement an ODataController

The clean most compliant way to implement OData query access is to use an ODataController instead of an ApiController in your WebAPI implementation.  There is a small amount of pre-configuration required prior to working with this type of controller.  From the routeconfig.cs file, I need to add some information about the data types that OData will be handling and add the appropriate route information:

var modelBuilder = new ODataConventionModelBuilder();
modelBuilder.EntitySet<Models.Event>("Event");
var model = modelBuilder.GetEdmModel();
 
GlobalConfiguration.Configuration.Routes.MapODataRoute(routeName: "OData", routePrefix: "odata", model: model);
GlobalConfiguration.Configuration.EnableQuerySupport();
Code Listing 6 - Configuration to support ODataControllers

By defining the model and route, our ODataController can now have the following syntax for the Get method:

public class EventController : ODataController
{
 
    [Queryable]
    public IQueryable<Event> Get()
    {
        var ctx = new CbContext();
        return ctx.Events;
    }
 
}
Code Listing 7 - ODataController Get method syntax

… and will render JSON requests in this format:

{
  "odata.metadata":"http://localhost:5436/odata/$metadata#Event",
  "odata.count":"48",
  "value":[
    {
      "Id":1,"Name":"CodeMash","City":"Sandusky","State":"OH","Country":"USA"
    }
  ]
}
Code Listing 8 - JSON Results from an OData query

Configuring the RadGrid for WebAPI access

We can now configure the RadGrid with client-side databinding, using our WebAPI.  This change means that the RadGrid will no longer deliver data formatted as HTML from the server, instead it will query and format all data with JavaScript and AJAX requests to the WebAPI service.  The steps to complete this conversion are as follows:

  1. Remove any DataSource, ItemType, and model-binding attributes from the RadGrid markup.
  2. Add a ClientSettings block that defines the databinding to the location of the API routes with a ResponseType of JSON:
<ClientSettings>
    <DataBinding Location="/api" CountPropertyName="Count"
             ResponseType="JSON" DataPropertyName="Items">
        <DataService Type="OData" TableName="Events" />
    </DataBinding>
</ClientSettings>
Code Listing 9 - Client-Side DataBinding syntax

Note that the CountPropertyName and the DataPropertyName are configured for second option of the WebAPI configuration I previously defined.  These attributes tell the control where to find the data in the API resultset.

  1. Finally, all columns need to be explicitly declared in the MasterTableView so that the client-side code knows how to format the grid.
<MasterTableView AutoGenerateColumns="false">
  <Columns>
    <telerik:GridBoundColumn DataField="name" HeaderText="Event Name" />
    <telerik:GridBoundColumn HeaderText="Start Date" DataField="startDate"/>
  </Columns>
</MasterTableView>
Code Listing 10 - RadGrid Column Formatting to support Client-Side Databinding

With these few changes in play, we can see our grid looks the same with a working pager, filter, and sorting all operating on the client side.

Fiddler verifies this configuration for us, by demonstrating that our query went through and data was returned in JSON format:

Fiddler monitoring WebAPI queries
Figure 2 - Fiddler monitoring the WebAPI query and results

Summary:

In this article, I showed you how to configure a WebAPI controller to allow interaction as an OData endpoint.  We then made a very small set of changes to a previously implemented RadGrid to allow rich and simple interactions with the server for only the data we need to re-populate the grid.  This slimmer interaction with the server will give you much better perceived performance for those with accessing your application with a low bandwidth connection.


About the Author

Jeffrey T. Fritz

Jeffrey T. Fritz is a Microsoft MVP in ASP.Net and an ASPInsider with more than a decade of experience writing and delivering large scale multi-tenant web applications. After building applications with ASP, ASP.NET and now ASP.NET MVC, he is crazy about building web sites for all sizes on any device. You can read more from Jeffrey on his personal blog or on Twitter at @csharpfritz. Google Profile


Related Posts

Comments