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

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read