At the BASTA Spring 2013 conference I had a session about developing custom OData providers. In this blog article I summarize the most important take aways and publish the source code. For completeness here is the abstract of the talk in German:
In seiner OData Session zeigt Rainer Stropek, wie man eigene OData-Provider entwickelt. In einem durchgängigen Beispiel demonstriert er, wie man erst einen LINQ-Provider und darauf aufbauend einen OData-konformen REST Service erstellt und von verschiedenen Programmiersprachen und Tools darauf zugreift. In der Session werden Grundkenntnisse von OData und LINQ vorausgesetzt.
Note that I have done multiple talks about custom OData providers in the past. Amongst others I did talks about a concrete application of a custom OData provider: Fan-out queries in Sharding-scenarios. You can read more about it in this blog article (2011).
OData is a great protocol for web-enabling CRUD scenarios. It is a platform-independent standard. You can get client and server libraries for a bunch of different platforms. For more information about the protocol see OData homepage. .NET contains support for OData for quite a long time. Recently Microsoft has begun to ship its latest OData implementation using Nuget packges. For our sample you need to get WCF Data Services Server and its dependencies.
Most of Microsoft's cloud services (e.g. Table Storage) support OData out of the box. You do not need to implement an OData server yourself. Unfortunately SQL Server does not support OData natively - neither on-premise nor in the cloud. However, that's not a big problem in practise. If you have an Entity Framework model, Microsoft's OData SDK can turn it into an OData feed with just a few lines of code. If you want to learn more about it, take a look at this how-to chapter in MSDN. We will not go into details on this in this session.
In our sample we want to start with a simple OData service backed by an in-memory collection of Customer objects. The following code snippets show the Customer class and a helper class used to generate demo data. This code is just infrastructure, it is not specific to OData.
Publishing generated customer demo data with OData is really simple. Here is the code you need for it:
You can immediately try your OData service in the browser (click to enlarge the image):
Your OData service will also support JSON. You can try that by specifying an appropriate accept-header in Fiddler (click to enlarge the image):
Custom LINQ Provider
Did you recognize the problem of the code shown above? The class CustomerReflectionContextalways generates 100 customer objects. An OData client can specify a $top clause as shown above. However, the server will still generate 100 objects. Somewhere in the OData stack the unwanted 99 customers will be thrown away. Imagine generating the customer objects would be a difficult and time-consuming process. In such a case an architecture that works like the classes shown above would not work.
A solution to this problem is a custom LINQ provider. A LINQ provider takes an expression tree and has to interpret it. In our case we will just recognize the Take and Skip methods of the expression tree. We will use them to limit the number of customers to generate. Implementing a custom LINQ provider from scratch is hard. Fortunately there are multiple helper libraries available. In my sample I use the IQToolkit library. With its help, implementing the custom LINQ provider is easy. Here is the code:
Of course you do not absolutely need OData to make use of the LINQ provider. You can use it directly in your C# app, too. You can easily try it e.g. in a unit test:
Using the LINQ Provider in OData
Now that we have the optimized LINQ provider, we can back our OData service with it:
It you query the OData service with a $top clause now, the service will only generated the request number of customer objects in the background.
Unfortunately we have lost functionality with our customer LINQ provider, too. If you decide to go for a customer provider, you have to deal with all possible LINQ functions yourself. In our case we only allow $top and $skip. In all other cases we throw an exception (click to enlarge image):
Building a Completely Customized OData Provider
The example shown above can already dynamically handle parameters specified in OData queries during runtime. However, there are cases in which you might need even more flexibility. Imagine that you do not know the available entities or properties during compile time. Our own SaaS product time cockpit allows power users to customize its data model. Users can add custom entities, add properties, define calculated properties with an easy-to-learn formula language. In such a case, writing a custom LINQ provider and backing an OData service is not sufficient. We have to dive deeper into Microsoft's OData SDK.
In the sample code shown above we implemented the class CustomerReflectionContext which contains a property of type IQueryable. Our OData service CustomerReflectionService derives from DataService<CustomerReflectionContext>. By specifying the type parameter, Microsoft's OData SDK will look for all IQueryable properties in the specified class. In the custom OData provider sample we want to implement now, we do not know during compile time which IQueryable properties we will have during runtime. Therefore we change from a class that contains a fixed number of properties to a class that can dynamically provide IQueryables during runtime:
Next we have to somehow inform the OData runtime about which types and properties our class can provide. This is done using OData's ResourceSet and ResourceType objects in combination with the interface IDataServiceMetadataProvider. Note that I have kept the following implementation consciously simple. It should demonstrate the concept without you having to worry about use-case-specific implementation details.
We are still missing a class that connects our CustomerServiceDataContext class with OData. This is done using OData's IDataServiceQueryProvider interface. It has to be able to generate an IQueryable for every resource set that we have added to our metadata (see above). Our sample implementation of IDataServiceQueryProvider is again kept simple.
That's it. We have all prerequisites to setup our fully customizable OData service. Note how the .NET interface IServiceProvider is used to link our service with the previously created implementations of IDataServiceMetadataProvider and IDataServiceQueryProvider.