Navigating OData and WCF Data Services

WCF Data Services and Data Service Providers hide much of the Open Data Protocol (OData) implementation. While this arrangement allows a developer to focus on developing a service, it doesn't mean a developer is completely isolated from OData and free to completely live in the .NET world. Understanding OData conventions are important to building an OData client and testing an OData Endpoint. Of all the OData conventions the most important convention a WCF Data Services developer must understand are the OData URI conventions. This article explains how OData URI conventions map to code living inside of WCF Data Services.

WCF Data Services and OData Introduction

A complete introduction to OData and WCF Data Services is beyond the scope of this article. You will find a more complete introduction here: WCF Data Services Providers.

OData conventions are defined using common Web Standards like HTTP, AtomPub, and JavaScript Object Notation (JSON). Data retrieval and update operations are implemented with HTTP commands like GET and POST. AtomPub and JSON conventions describe a data format.

WCF Data Services is built on top of Windows Communication Foundation (WCF). WCF Data Services builds on WCF's HTTP capabilities. Data Services extend WCF HTTP based hosting and Bindings to implement OData.

Like any WCF Service; a WCF Data Service runs inside a Host.

Building a Host

The following code demonstrates a "Self-Hosted" WCF Data Service Host that utilizes the WCF Data Service Reflection Provider.

[System.ServiceModel.ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class TestItemsDataService : DataService<TestItems>
{
    public static void InitializeService(IDataServiceConfiguration
                                config)
    {
        config.SetEntitySetAccessRule("*", EntitySetRights.All);            
        config.UseVerboseErrors = true;
    }
 
}
 
Type serviceType = typeof(TestItemsDataService);
Uri baseAddress = new Uri("http://localhost:8000");
Uri[] baseAddresses = new Uri[] { baseAddress };
 
DataServiceHost host = new DataServiceHost(
    serviceType,
    baseAddresses);
 
 
host.Open();
 

Developers familiar with WCF will notice missing Binding and Contract parameters. DataServiceHost handles everything but the Service URI. DataServiceHost requires an object derived from DataService<T>. DataService<T> and the generic class declared in T is the heart of a WCF Data Service and the entry point into the Service.

Defining the Service

The sample application surfaces the following data structures.

[DataServiceKey("Id")]
public class TestItem
{
    private Dictionary<string, TestSubItem> _dict = new Dictionary<string, TestSubItem>();
 
    public TestItem(TestSubItem[] subItems)
    {
        Id = "defaultId";
        Payload = "defaultPayload";
 
        foreach(var itm in subItems)
        {_dict.Add(itm.SubId,itm);}
    }
 
    public string Id { get; set; }
    public string Payload { get; set; }
 
    public IEnumerable<TestSubItem> SubItems { get { return _dict.Values; } }
}
 
 
[DataServiceKey("SubId", "OperationOrderNum")]
public class TestSubItem
{
    public TestSubItem()
    {
        SubId = "defaultId";
        this.ComplexPayload = new TestComplexItem("ComplexPayload");
        this.OperationOrderNum = 0;
    }
 
    public string SubId { get; set; }
    public int OperationOrderNum { get; set; }
    public TestComplexItem ComplexPayload { get; set; }
}
 
public class TestComplexItem
{
    public TestComplexItem(string payload)
    { this.Payload = payload; }
 
    public string Payload { get; set; }
    public int PayloadLength { get { return this.Payload.Length; } }
}
 

TestItem has a collection of SubTestItems. SubTestItems include a TestComplexItem. The DataServiceKey will be discussed later in the article. For now, think of the string value matching the property on the class as you would a database key value. In the sample, Id property is what differentiates a class instance within the collection.

Most applications will include a parent-child relationship like the ones demonstrated in the samples above. As stated earlier the generic class declared in DataService<T> is essentially "THE" service.

WCF Data Services requires an IQueryable<T> property for any surfaced collection. That includes a collection surfaced in the child portion (TestSubItem) part of the sample. IQueryable<T> must appear on the DataService<T> generic class like in the following example.

public class TestItems
{
static Dictionary<string,TestItem> _items = null;
static Dictionary<string, TestSubItem> _subItems = null;
 
static TestItems()
{
    _items = new Dictionary<string,TestItem>();
    _subItems = new Dictionary<string, TestSubItem>();
 
    _subItems.Add("SubId_1", new TestSubItem()
    {
        SubId = "SubId_1"
        ,
        ComplexPayload = new TestComplexItem("Complex_1")
    }
    );
    _subItems.Add("SubId_2", new TestSubItem()
    {
        SubId = "SubId_2"
        ,
        ComplexPayload = new TestComplexItem("Complex_2")
    }
    );
    _subItems.Add("SubId_3", new TestSubItem()
    {
        SubId = "SubId_3"
        ,
        ComplexPayload = new TestComplexItem("Complex_3")
    }
    );
    _subItems.Add("SubId_4", new TestSubItem()
    {
        SubId = "SubId_4"
        ,
        ComplexPayload = new TestComplexItem("Complex_4")
    }
    );
 
 
    _items.Add("One", new TestItem(new TestSubItem[] { _subItems["SubId_1"], _subItems["SubId_2"] }) { Id = "One", Payload = "This is One" });
    _items.Add("Two", new TestItem(new TestSubItem[] { _subItems["SubId_3"], _subItems["SubId_4"] }) { Id = "Two", Payload = "This is Two" });
 
}
 
public IQueryable<TestItem> Items
{
    get { return _items.Values.AsQueryable(); }
}
 
public IQueryable<TestSubItem> SubItems
{
    get { return null; }
}
 
}

Notice how the SubItems property returns null. The SubItems property on TestItems supplies hints to the Reflection Provider. As long as SubItems are not accessed from the root the Property is never invoked. Instead when the application is accessed through the parent-child relationship the code accesses the SubItems through the property on TestItem class. Consider how complicated classes can become, with deep nesting and recursive relationships. Surfacing all public collections on the WCF Data Service may not be desirable behavior.

Setting the IncludeExceptionDetails InFaults and setting UseVerboseErrors will provide more information when debugging the service. The additional information is probably not something a developer would enable in a production environment.

The running sample exposes the following EndPoint.

http://localhost:8000/

Navigating the Service

OData supports a rich URI based query navigation experience. By default a WCF Data Service renders in AtomPub. Because the format doesn't follow Atom standards, set your browser to render Atom in XML.

The following code navigates to the Items property on the root of the service, surfacing all the data in the Items collection.

http://localhost:8000/Items/

Notice how the XML includes the following href

href="Items('One')/SubItems

Following the href code navigates into a single TestItem in the Items collection. Earlier in the article, the DataServiceKey attribute specified the Id property as the key. The full URI looks like the following example.

http://localhost:8000/Items('One')/

The URI returns the TestItem with an Id matching the value "One". Properties on the TestItem come from the TestItem class and appear in the following section inside the XML response.

<m:properties>
<d:Id>One</d:Id> 
      <d:Payload>This is One</d:Payload> 
</m:properties>
 

Single properties can be retrieved with a URI like the following.

http://localhost:8000/Items('One')/Id

Accessing a single property is useful in, for example, a Javascript function that must retrieve a single value without the overhead of parsing a large response. The URI can be further refined to return the value without the metadata formatting like in the following example.

http://localhost:8000/Items('One')/Id/$value

Parent-child relationships can also be handled. The following URI navigates to an item in the SubItems collection on the "One" item inside the Items collections.

http://localhost:8000/Items('One')/SubItems(OperationOrderNum=0,SubId='SubId_1')

The previous URI also demonstrates what would be called a compound key in the database world. Name value pairs OperationOrderNum and SubId are separated by commas.

Developers may also find it useful to collect just the links to a collection. The following URI demonstrates this action.

http://localhost:8000/Items('One')/$links/SubItems

The URI returns all the links (hrefs) to the TestSubItem classes in the SubItems property on the TestItem class.

OData also has a set of Web query options. Web Query options can further refine what is normally returned in the URI response. According to documentation not all options are supported by WCF Data Services. Here are Web query option examples.

http://localhost:8000/Items?$top=1

The example above restricts results to one element from the collection. Following is a filtering example that returns TestItem with a Payload property matching the text "This is Two".

http://localhost:8000/Items?$filter=Payload eq 'This is Two'

There is a wealth of filtering and math operations similar to the ANSI SQL specification. A complete review of all filtering operations is beyond the scope of this article, but a complete list can be found on the OData URI conventions site located here http://www.odata.org/developers/protocols/uri-conventions.

Conclusion

Developers building a WCF Data Service should understand OData URI conventions if they want to test and document an Endpoint. URI conventions accommodate sophisticated .NET class parent-child relationships and Web query options to refine a Web response.

Resources

http://www.odata.org/developers/protocols/uri-conventions

http://msdn.microsoft.com/en-us/library/dd728281.aspx



About the Author

Jeffrey Juday

Jeff is a software developer specializing in enterprise application integration solutions utilizing BizTalk, SharePoint, WCF, WF, and SQL Server. Jeff has been developing software with Microsoft tools for more than 15 years in a variety of industries including: military, manufacturing, financial services, management consulting, and computer security. Jeff is a Microsoft BizTalk MVP. Jeff spends his spare time with his wife Sherrill and daughter Alexandra.

Related Articles

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • By providing complete access control with granular permissions, deployment flexibility, mapped drive support, and ability to transfer large files, Egnyte provides a more robust, secure and an affordable file sharing solution for the business than Box

  • Hurricane Sandy was one of the most destructive natural disasters that the United States has ever experienced. Read this success story to learn how Datto protected its partners and their customers with proactive business continuity planning, heroic employee efforts, and the right mix of technology and support. With storm surges over 12 feet, winds that exceeded 90 mph, and a diameter spanning more than 900 miles, Sandy resulted in power outages to approximately 7.5 million people, and caused an estimated $50 …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds