Introducing the OData Library
This blog talks about a new feature delivered in the WCF Data Services October CTP that can be downloaded here.
WCF Data Services’ latest CTP includes a new stand-alone library for working directly with OData. The library makes public some underpinnings of WCF Data Services (the server and client library), and we made this library stand-alone to allow its use independent from WCF Data Services. The library provides a low-level implementation of some components needed to build an OData producer/consumer. Specifically, we focused on the core tasks of reading/writing OData from streams in the library’s first version, and in the future we hope to add more fundamental OData functionality (possibly OData Uri reading and writing). However, we haven’t made any final plans on what we will add, and we welcome your feedback.
I want to take a minute to explain this library’s relation to the existing WCF Data Services products; this library doesn’t replace WCF Data Services. If you want a great end-to-end solution for creating and exposing your data via an OData endpoint, then the WCF Data Services server library is (and will continue to be) the way to go. If you want a great OData Feed-consuming client with auxiliary support, like code generation and LINQ translation, then WCF Data Services’ client library is still your best bet. However, we also recognize that people are exploring creative possibilities with OData, and to help them build their own solutions from scratch we made the components we use as part of the WCF Data Services stack available as a stand-alone library.
We have published the OData library’s latest source code for the on codeplex (http://odata.codeplex.com) as shared source for developers on .NET and other platforms
The CodePlex source code includes the samples that I have attached to this blog post, and I’ll walk through a couple of those samples to illustrate reading and writing OData.
Writing a Single Entity
To hide the library’s details of stream-reading/writing the OData Library uses an abstraction called a Message, which consists of stream and header interfaces (IODataRequestMessage, IODataResponseMessage). The example below walks through the basics of single-entry writing using an implementation of the messages that work over HTTPClient (this implementation is available in the samples project).
The library uses a class called the ODataMessageWriter to write the actual body of a single ODataMessage (request or response). The ODataMessageWriter has a bunch of methods on it that can be used for writing small non-streaming payloads, such as single properties or individual complex-type values. For larger payloads (such as entities and collections of entities) the ODataMessageWriter has methods that create streaming writers for each payload type. The example shows how to use the ODataMessageWriter methods to create an ODataEntryWriter that can be used to write a single OData entry.
Finally, the sample goes on to use the ODataEntryWriter to write a single Customer entry along with four primitive properties and two deferred links. The samples project includes a few samples that show how to write an expanded navigation link as well.
Writing Full Sample
Reading a Single Entity
Let’s look at an example that shows OData deserialization via the library. The example method below demonstrates how to issue a request to the Netflix OData feed for the set of Genres and parse the response.
The example below makes use of the same ODataMessage classes as the previous example (the HTTPClientMessage), but first creates an HTTPClientRequestMessage that targets the Genres URL for the OData Netflix feeds, and then executes the request to get an HTTPClientResponseMessage that represents the response returned by the Netflix services. For readability, the example just outputs the data in the response to a text file afterwards.
The example below uses an IEdmModel not used in the writer example above. When the ODataMessageReader is created an IEdmModel is passed in as a parameter – the IEdmModel is essentially an in-memory representation of the metadata about the service that is exposed via the $metadata url. For a client component the easiest way to create the IEdmModel is to use the ReadMetadata method in the OData Library that creates an in-memory IEdmModel by parsing a $metadata document from the server. For a server, you would generally use the APIs included in the Edm Library (Microsoft.Edm.dll) to craft a model. Providing a model for OData parsing provides key benefits:
o The reader will validate that the entities and properties in the documents being parsed conform to the model specified
o Parsing is done with full type fidelity (i.e. that wire types are converted to the model types when parsed); this is especially important when parsing JSON because the JSON format only preserves 4 types and the OData protocol supports many more. There are configuration options to change how this is done, but I won’t discuss them here for space reasons.
o If the service defines feed customizations, the model contains their definitions and the readers (and writers) will only know to apply them correctly if provided a model.
o JSON can only be parsed when a model is provided (this is a limitation of the library and we may add JSON parsing without a model at some point in the future). ATOM parsing without a model is supported.
In the example below an ODataFeedReader is created out of the ResponseMessageReader to read the contents of the response stream. The reader works like the XmlReader in the System.XML library, with which many of you will be familiar. Calling the Read() method moves the reader through the document, and each time Read() is called the reader changes to a specific state that depends on what the reader is currently reading, which is represented by an “Item”. For instance, when the reader reads an entry in the feed, it will go to the StartEntry state, and the Item on the reader will be the ODataEntry being read –there are similar states for Feeds and Links. Importantly, when the reader is in a start state (StartEntry, StartFeed, StartLink, etc) the reader will have an Item it has created to hold the Entry/Feed/Link that it is reading, but the Item will be mostly empty because the reader has not actually read it yet. It’s only when the reader gets to the end states (EndEntry, EndFeed, EndLink) that the Item will be fully populated with data.
This is a quick introduction to the new OData Library included in this CTP. The post’s attached samples walk through the basics of OData feed creation and consumption via the library. We welcome any feedback you have on the library so don’t hesitate to contact us.
Program Manager – OData Team