Exposing Data via OData

In an earlier post I discussed what OData is all about and how you can consume OData services using plain HTTP requests. In this post I will talk about the other side of the story: exposing data using OData protocol.

With WCF Data Services (DS for short), providing data using OData is simple and easy too. Out of the box, DS can pull data in two ways and expose it as one or more feeds:

  1. ADO.NET Entity Framework – DS integrates with ADO.NET Entity Framework (EF) well and as such it can provide an OData layer on top of an existing EF model and expose data from the underlying relational database. In this way, each entity in the model may be given a unique URI by DS and exposed as a feed (in OData terms, Entity Set – collection of business entities). The advantage with this is that DS itself gives the plumbing for CRUD operations that you may want to perform on the entities exposed.
  2. Plain .NET classes – Lets you expose plain .NET classes as WCF Data Services. For example you can expose your plain-old-C#-class (POCO) libraries as OData feeds simply using DS. Behind the scenes, DS uses .NET reflection to detect IQueryable collections on a target class and exposes them as feeds. The advantage is that you have better control over the actual CRUD operations when invoked via HTTP. The downsides are many:
    1. Obviously, you have to write extra code to expose your object collection as IQueryable.
    2. DS supports only Select operation by default for this type of data source and Atom formatted output. If you want to provide Update, Delete and Insert operations as well on the feeds then you should implement IUpdatable interface.
    3. Only Atom-formatted results are supported. If you need JSON-formatted response, you have to again write more code.

Nevertheless, OData is platform neutral and as such WCF Data Services can provide data irrespective of its underlying physical storage or model. The above are just two options that come out of the box with WCF Data Services but you can expose data from anywhere: XML files, Excel data, file system, another data feed, etc. For this post, let me start with exposing POCO collection as OData feeds using DS because it is simple to implement (with Select operation alone).

The following code sample requires references to System.Data.Services.Client.dll and System.Data.Services.dll assemblies for WCF Data Services specific types.

Let us say we want to expose a feed for lawyer data. The following is a simple class to represent the lawyer data. In order for the DS to identify an object uniquely, we should specify which property of the class should be treated as the key using the attribute DataServiceKey:


[DataServiceKey ("LawyerID")]

public class LawyerInfo
{
    public int LawyerID {get; set;}
    public string Name {get; set;}
    public string OfficeCode {get; set;}
    public AddressInfo Address {get; set;}
}

The complex property AddressInfo is defined as follows:

[DataServiceKey ("AddressID")]
public class AddressInfo
{
    public int AddressID {get; set;}
    public string AddressLine {get; set;}
    public string City {get; set;}
    public string State {get; set;}
}

Now we need a container class to expose a collection of the above class as IQueryable; DS will at runtime perform reflection on this class to find properties of type IQueryable and expose them as feeds (the name of such properties will be the name of the feeds from the base service URI as we would see them later).


public class LawyerDataProvider
{
    private static List<LawyerInfo> _ds;
    public LawyerDataProvider ()
    {
        _ds = new List<LawyerInfo> ();
        _ds.Add (new LawyerInfo ()
        {
            LawyerID = 89,
            Name = "James Horner",
            OfficeCode = "NY921",
            Address = new AddressInfo ()
            {
                AddressID = 8,
                AddressLine = "100 Park Ave",
                City = "New York",
                State = "NY"
            }
        });
        _ds.Add (new LawyerInfo ()
        {
            LawyerID = 61,
            Name = "Mathew Perry",
            OfficeCode = "IL017",
            Address = new AddressInfo ()
            {
                AddressID = 15,
                AddressLine = "625 West Madison",
                City = "Chicago",
                State = "IL"
            }
        });
        _ds.Add (new LawyerInfo ()
        {
            LawyerID = 17,
            Name = "Samatha Mathis",
            OfficeCode = "NJJC",
            Address = new AddressInfo ()
            {
                AddressID = 35,
                AddressLine = "Journal Square",
                City = "Jersey City",
                State = "NJ"
            }
        });
    }

    public IQueryable<LawyerInfo> Lawyers
    {
        get {return _ds.AsQueryable<LawyerInfo>();}
    }
}

Given the above class, you get the lawyers list as returned by the Lawyer property using http://service_base_uri/Lawyers /Lawyers once the service is up and running.

The final step in creating the data service provider is to create a DataService instance (in loose terms, the Entity Framework’s ObjectContext counterpart in WCF Data Service) specifying the class that has the IQueryable properties:

public class LawyerDs : DataService<LawyerDataProvider>
{
    public static void InitializeService(DataServiceConfiguration config)
    {
        config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
        config.SetEntitySetAccessRule ("Lawyers", EntitySetRights.All);
    }
}

SetEntitySetAccessRule method sets desired permissions for an entity set, in the above case Lawyers. For now, let give all the access rights for Lawyers.

Now, we have to host this service at a known URI location for consumption. Logically, this step is same as hosting a normal WCF service but for simplicity sake I am going to host the service using IIS. So, create an ASP.NET web application (at say, http://localhost/wapcs) and a new WCF Data Service item – Lawyers.svc:

Add WCF Data Service Item

This will create a class definition file (Lawyers.cs) underneath the web application’s App_Code folder and a corresponding Lawyers.svc file at the root. Assuming you put all the entity, entity set provider and data service classes in a single source file (say LawyerDsp.cs), remove the auto-created class definition file and modify the .svc file to point to the data service class as below:

<%@ ServiceHost Language="C#" Factory="System.Data.Services.DataServiceHostFactory" Service="LawyerDs" %>

If everything compiles without error, open up Internet Explorer and navigate to http://localhost/wapcs/lawyers.svc and the browser should display this:

Service Output

This basically indicates that our service exposes one feed called Lawyers at location Lawyers. The location is relative to the service base URI http://localhost/wapcs/Lawyers.svc. Now, go to http://localhost/wapcs/Lawyers.svc/Lawyers and you would get the LawyerInfo entity set (for display purpose, I have expanded only the first item):

Entity Set Collection

You can now request a single entity by specifying the key in the request URI as in http://localhost/wapcs/Lawyers.svc/Lawyers(17):

Single Entity

You can do association navigation to see the Address details of a lawyer: http://localhost/wapcs/Lawyers.svc/Lawyers(17)/Address

Navigation Property 

Having shown all HTTP request operations from a browser, doing the same programmatically is not any different from consuming a typical web service or a WCF service. From Visual Studio’s Service Explorer, simply add a service reference to the service base URI:

Add OData Ref

Visual Studio will automatically create the required proxy classes as usual and you can straightway get into the business.

DS Client Code

If everything goes right, the above code will produce the following console output:

DS Client Console

You can apply other query string options such as filtering ($filter), paging ($top, $skip), etc. on the feed to get the desired results.

That’s it.

I know I have not shown adding support for update, delete and insert options for this POCO-based data service provider but I will reserve it for a future post. The purpose of this post is to show you how easy it is to develop an OData service using WCF Data Services infrastructure.