Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Understanding OData v3 and WCF Data Services 5.x

4.86/5 (34 votes)
23 Dec 2012CPOL26 min read 185.6K  
Explains OData and WCF Data Services Beyond a typical developer's usage

Table of Contents

What is this Article About?

This is not your typical “expose an entity framework model as a WCF Data Service” kind of article; in fact I deliberately omitted Entity Framework from this discussion. Now don’t get me wrong, I have nothing against EF, but you will find a lot of great articles on how to set up a WCF Data Service using EF as data source.  

The discussion is around OData v3 and WCF Data Services 5.0 and 5.2. Of course there is no way I cover all new features or every API available, nor do I need so; I will point you to the resources where you can get all information you need. Instead my aim in this article is to discuss some of the topics that usually remain oblivious to the typical WCF Data Services developer. 

What are OData, Atom, and AtomPub? 

The Open Data Protocol (OData) is a protocol which standardizes the exposure and consumption of data. In times where data is being exposed at high rates and where consumers connect to more and more data endpoints, it’s important for clients to access these endpoints in a common way. 

OData builds on web standards such as Http, Atom, and JSON to provide REST-based access to these endpoints. Data is exposed as entities where each entity can be treated as an Http resource which makes it subject to CRUD (create, read, update, delete) operations. 

So how is OData related to Atom and AtomPub?

Atom is way to expose feeds much the same way RSS does. If you are wondering what are the differences between Atom and RSS you can check this site (http://en.wikipedia.org/wiki/Atom_(standard)#Atom_compared_to_RSS_2.0) 

Atom by itself allows only feed exposure. If you want to publish data, AtomPub (Atom publishing) provides this ability. AtomPub uses Http verbs GET, POST, PUT, and DELETE to enable data publishing. 

OData adds a set of extensions on top of AtomPub to enable more advanced and smart operations such as data retrieval filtration and typed values definition. For example, below are two queries that are made possible through the power of OData: 

  • http://server/service.svc/entity/$count
  • http://server/service.svc/entity?$filter=(entityID add 4) eq 8 

REST vs. SOAP: The Design Decisions 

You must have expected a REST vs. SOAP section in an OData article, haven’t you? After all the resources around this topic are really scarce. Ok, you got the joke.

If you search online for REST vs. SOAP you will undoubtedly get a lot of valuable resources. So instead of addressing the topic in a rather abstract approach, I will show you real world decisions I took at work while evaluating whether to go for REST or SOAP. 

So I work in the e-government sector (why I mentioned this will become clear shortly). When I first joined my current job, the first mission was creating an integration service bus that would eliminate any point-to-point integrations; which was the predominant case back then. This bus was also required to handle B2B communications with other e-government agencies over the internet. Examine the below diagram, and let’s see how each requirements was mapped into a (high level) design decision to whether I should use SOAP or REST-based services: 

Image 1

  • 1, 1.1, and 1.2: we had an initial list of governmental agencies that we needed to allow them to query our services for data retrieval. In addition, we knew that over time the list of agencies will increase. So we did not want to have an ever expanding list of endpoints that we share with our partners. Moreover, due to the crucial nature of government operations, we wanted the minimum amount of downtime required if we ever wanted to edit our schema internally. So Our best bet was to have some sort of addressing mechanism where all agencies get the same endpoint with a fixed schema that nearly never chances, yet based on some content on each request we would route the request into internally hosted services. This scenario is best handled using SOAP WS-Addressing extension. So we ended up implementing a WCF service configured to use WS-Addressing. (Note: when using WS-Addressing, there are some security roadblocks that you will hit, but that outside the scope of this discussion)  
  • 2: Another requirement was mutual authentication for every service that wanted to call into our service bus. Of course that’s very typical in government communications. REST-based services are typically secured via SSL. For any form of custom client certificate authentication and digital signatures, you’d be better of using WS-Security which we ended up using via the power of WCF.
  • 3: internally we have a BPM engine that requests data from a data store. Now since the communication is internal, we are relaxed about the security requirements. We just need username/password authentication and no encryption or signature is required. REST-based services in this case are the best bet due to its simplicity. These services we ended up building using WCF Data Services, were easily consumed by the BPM front-end which is heavily JQuery-based which made REST/JSON combination a perfect candidate. One more important thing was the size of payload. Using REST and JSON, the payload returned was in much reduced size when compared to the size of SOAP/XML payload.
  • 4: Finally, we needed transaction support from the BPM to the called service. This required WS-Transaction SOAP extension which made SOAP a must. REST based services do not support transactions. If you want to read more about transactions, I advise you to check my article: http://www.codeproject.com/Articles/35087/Truly-Understanding-NET-Transactions-and-WCF-Imple 

As you can see from the above discussion, the answer to the question “SOAP vs. REST” is the famous one: “it depends”. Hopefully the above scenarios will provide you a decent starting point to making similar decisions. Personally, I would go with REST simplicity whenever I can; sometimes however, it’s just that you cannot.

First Look at OData Queries

The best way to have a first look of OData in action is using some demo services offered by the OData web site. Browse to http://www.odata.org/ecosystem and scroll down to section “Sample Services”. Select the Read-Only OData Sample Service and click Browse. 

Note: the services offered in the OData web site might change over time, but basically the techniques you will learn here will stay mostly the same. Only entity names might change. 

Now take your first look on an OData service and let’s examine some important observations:

Image 2 

  1. First notice that the service URL ends with “.svc” extension. This indicates that it’s a WCF service; more specifically, that’s a WCF Data Service – which we will discuss in detail later.
  2. Also notice that the data is published via the Atom feed 
  3. Also notice that what you got is the first level of the set of entities offered by the service
  4. For each entity you get a href attribute telling you how to further query sub entities and related entities

What points 3 and 4 confirm is that true to the REST promise, each resource (entity) is being defined as a URI which is easily consumable. 

Now let’s play a bit with this service. Say you want to query all the Products offered. Here is how you do this:

Image 3 

Again, I will discuss some important highlighted items:

  • The URL is pointing to http://services.odata.org/OData/OData.svc/Products. Recall that “href=Products” from our first query informed me how to query all the products.
  • Each product is represented with an entry, and each entry has an id which is the URI of each product in the set of products. For example the entity id of the first product is http://services.odata.org/OData/OData.svc/Products(0). Where the value “0” is the entity primary key discussed in the next point.
  • The primary key of the product entity is specified in the ID element. This key uniquely identifies an entity and in some cases it could be a composite key; for example it could have been “/Products(0,1)” if products would allow composite primary keys.
  • There are relationships for the Product entity with other entities; namely Category and Supplier. To see the Category of Product of key 0 you would use “Products(0)/Category” and to examine the Suppliers of the same Product you would use “Products(0)/Supplier”.
  • The properties of each product are listed in the properties element. 

Here are some more queries:<o:p>

<o:p>

<o:p>

Keywords

You can use keywords to perform some advanced query operations.<o:p>

For example, for client tools wanting to consume the OData service, the metadata can be exposed using: http://services.odata.org/OData/OData.svc/$metadata. The “metadata” keyword generates the metadata that can then be used by proxy generation tools. 

<o:p>

Another example is the “value” keyword which strips the XML surrounding a property value and returns the value in its raw format. For example, http://services.odata.org/OData/OData.svc/Products(1)/Price returns the price wrapped with Atom XML: 

Image 4

Using the “value” keyword returns the raw format:

Image 5 

<o:p>

Another useful keyword is the “filter” keyword. For example the following query returns all products with price = 2.5: http://services.odata.org/OData/OData.svc/Products?$filter=Price%20eq%202.5 (note: the %20 encoding is added by the browser) 

Image 6

The following query http://services.odata.org/OData/OData.svc/Products?$filter=Price%20gt%202.5&$orderby=Price%20desc&$top=2 couples a combination of keywords:

  • the “filter” keyword uses the “greater than” operator to filter products of a price > 2.5
  • the keyword “orderby” orders the prices by descending values
  • the keyword “top” returns only the top 2 of the returned product set 

Formatting

Up until this point, we have been querying the service using the Atom XML format. The problem of Atom is the size of the response as its wrapped with XML tags which increases the entire payload size. 

<o:p>

Using the “format” keyword however you can return data using the JavaScript Object Notation (JSON) format:

Image 7

However, what you have seen above is the default “verbose JSON”. It’s called verbose because the size of the JSON returned is quite large (although much less than Atom). 

A new feature in OData V3 is the “light JSON” which strips unwanted information from the verbose JSON and presents a much reduced size one. If you examine again the verbose JSON data you will see a lot of information concerning OData. The new light JSON strips this data off and only presents a single metadata URI which consuming clients can then further query if they do care about the OData stripped information.<o:p>

<o:p>

You can specify if you want verbose or light JSON in the request header by using the “DataServiceVersion” header. If you use fiddler to examine a request to an OData service using the $format=JSON keyword, you will see the “DataServiceVersion: 1.0” response header: 

Image 8

<o:p>

You can then use Fiddler composer to compose a custom request for a light JSON using the following two headers:

Image 9

Note that in the Accept header we supplied two sets:

  • application/json;odata=light;q=1: to enable JSON light format
  • application/json;odata=verbose;q=0.5: a fall back option to verbose JSON if the client does not support JSON light. 

Now back to the OData service from the http://services.odata.org website. The light JSON can be reached using an experimental link as the feature is not yet supported by all clients. Navigate to http://services.odata.org/Experimental/OData/OData.svc/Products?$filter=Price%20gt%202.5&$orderby=Price%20desc&$top=2&$format=json to see the light JSON and compare its size to the verbose one:

Image 10

So when to use ATOM and XML vs. JSON formatting? The answer is usually governed by the type of client consuming an OData service. JSON is typically ideal for clients running at user agents such as Ajax and Jquery based clients. ATOM XML is usually more appropriate for server side applications calling into OData services.

OData Clients 

OData clients are the programs that can read and parse data returned by an OData service. Some of these clients can handle both ATOM and JSON formats while others can understand a single format. 

One client we have already seen in action is the browser. As you’ve seen before, I have used the browser to download data from an OData service in both ATOM and JSON formats. Browsers however are unlikely to be useful in real world applications where clients typically need to present data in more user-friendly format which typically means in a strongly types manner, as well as performing further processing and undertaking business decisions based on this data. 

<o:p>

This section shows other types of clients. 

Excel 2013 PowerPivot<o:p>

Excel has an add-on called PowerPivot that is cabamle of reading ATOM feeds form an OData service. Frist locate the PowerPivot add-on in Excel and do the following:

Image 11

<o:p>

In the resulting window, point to http://services.odata.org/Experimental/OData/OData.svc/Products, test the connection and finish the wizard. This will import the products from the service and lay them out in a human readable form:

Image 12

<o:p>

As far as I know, PowerPivot cannot read JSON formatted data. 

Visual Studio 2012<o:p>

We’ll spend much more time within Visual Studio 2012 later on, but for now let’s take a glance at how VS can consume an OData Service. 

VS 2012 includes the WCF Data Service client library which enables generation of a strongly-typed client proxy through the familiar Add Service Reference wizard. 

Launch VS 2012, create a new project (it can be Console or Web) and invoke the Add Service Reference wizard. Point the wizard to the URL of our demo service http://services.odata.org/OData/OData.svc/. You will get the list of top-level entities as shown below:

Image 13

Click OK to close the wizard. 

<o:p>

To inspect the strongly-types client proxy, select Show All Files from the Solution Explorer toolbar. Then open Reference.cs file. This file exposes the entities and their relationships as strongly-typed classes and OO relationships. You will also notice that the proxy has auto generated methods to perform updates. 

Other Clients<o:p>

You can see a full list of OData clients here: http://www.odata.org/ecosystem#consumers

Working with OData via WCF Data Services 

Creating a WCF Data Service with POCOs 

There are multiple ways to expose data in an OData service. The below are methods available to you using the .NET Framework:

  • WCF Data Service with Entity Framework as a data source
  • WCF Data Service with LINQ to SQL as a data source
  • WCF Data Service with custom .NET objects (POCOs)
  • WCF RIA Service
  • WCF Data Service with a custom provider if all the above options are not applicable 

Plain Old CLR Objects (POCOs) refer to objects that obey the Persistence Ignorance design principal which states that objects do not contain any persistence logic. In plain English, these are the CLR objects that you create using simple OO mechanisms. 

<o:p>

Let’s start by creating a WCF Data Service exposing 2 CLR objects. Using VS 2012 create a new ASP.NET web application. Add a reference to “System.Data”. Create a new class and add the following code inside it: 

Image 14

  • The DataServiceKey attribute specifies the ID of each entity to be exposed via the data service
  • The DataServiceEntity actually marks the class as an entity
  • There is a composition relationship between Course and Student entities, where each student has multiple courses
  • Class MyDataSource acts as the container of my entities as note that it exposes the entities as IQueryable. This is required so that WCF Data Service can identify and expose classes as entities. 
Now add a new item of type WCF Data Service to the project. Once you do you will get the default template of a WCF Data Service class:

Image 15

Let’s examine this class:

  • It inherits from class DataService
  • We need to supply the data source class in the place of “/* TODO: put your data source class name here */”. In our case that’ll be class “MyDataSource” which holds the IQueryable collections
  • We need to set access rules for the entities in our service. For each entity set we can specify whether we want to allow reads, updates or both. In our case, we want to allow all operations on all entities. So this will read “config.SetEntitySetAccessRule("*", EntitySetRights.All);”.
  • Similarly we can set the access rules for any service operations we might have defined. Service operations will be discussed later.
  • Finally we can set the protocol version of OData which as mentioned before is in V3 at the time of this writing. 

So the class will look like this after modification:

Image 16

<o:p>

Now run the application, and just like that you have created a WCF Data Service:

Image 17

Just as we have done before, you can start querying the service and examining the relation between the entities. Here some query examples:<o:p>

Behind the Scenes

<o:p>

So obviously a WCF Data Service is actually a WCF service afterall. However, taking a look at the web.config file of the sample we have just created shows nothing realted to WCF except a blank system.serviceModel section:

Image 18

<o:p>

As you can see there are no services nor ednpoints defined. WCF Data Services uses default configurations to do its job and these are configurations are hidden from you. So what are these configurations? The best way is to show you directly. I have edited the web.config file to read as follows:

Image 19

<o:p>

I have defined a service names “MyDataService” which – as you recall – in the name of the WCF Data Service I created in the previous example.

Now to the creame of WCF: the endpoint. If you have any previous experience in WCF you know that an endpoint is identified via an address, binding, and contract. Here I have supplied these values. 

The binding is webHttpBinding which is WCF binding used for REST operations<o:p>

I specified REST as my address, so from now on to query my service I will use http://localhost:19865/MyDataService.svc/REST/ to query my service instead of http://localhost:19865/MyDataService.svc/ which was used before. 

<o:p>

Finally, what about the contract System.Data.Services.IRequestHandler? A contract is the interface that a service implements. In our case the service is MyDataService which to remind you is defined as follows: 

Image 20

So this begs the question: why the endpoint contract is not DataService instead of System.Data.Services.IRequestHandler? To answer this question you need to understand what IRequestHandler is. In order to so I will use reflector to show you what’s inside this interface:

Image 21

<o:p>

Now examining the contents of IRequestHandler shows this:

Image 22

The handler is itself defined as a service contract. It contains one generic method which accepts a generic message stream and returns a Message type which is WCF’s generic message representation. This method is defined is tagged as a WCF operation and is tagged with WebInvokeAttribute which accepts any UriTemplate and any Method. If you’re new to .NET REST programming you will be like: what the heck did he just say? 

Ok so prior to WCF Data Services, if you wanted to create REST-based WCF services you would have to use REST starter kit. When doing so you have to define by hand all what WCF Data Service abstracts away. One of the things you had to do is to tag any operation that you want to expose via REST by one of two attributes:

  • WebGetAttribute: used to expose an operation using Get Http verb. Can be used only to retrieve data.
  • WebInvokeAttribute: used to expose an operation via any required Http verb. It defaults to POST but using the Method parameter you can specify any required verb. So WebInvokeAttribute can be used for both data updates and retrieval (note: it’s only a common practice to use WebGet for retrieves and WebInvoke for updated to stay consistent with Http specification, however, when WebInvoke(Method=”GET”) is used the metadata generated mimics those of WebGet)

<o:p>

<o:p>

UriTemplate allows you to specify the REST URI for a method. Recall that in REST, each resource can be specified using a unique URI. UriTemplate is the way to do so for a particular WCF operation – which in this case is the resource. 

One final thing; in reflector examine the Derived Types of the handler:

Image 23

You will notice that DataService<T> (i.e. DataService of generic type T) implements the interface. And recall one more time that our service “MyDataService” inherits from DataService<T>. 

<o:p>

So to summarize, IRequestHandler is WCF Data Service’s generic handler for all operations defined within the service. This abstracts away the need for us to define what we used to define in the REST starter kit by hand. 

Finally, browse the service using the new URL:

Image 24

You can now navigate the service as usual, for example:<o:p>

Enabling JSON Format through WCF Message Inspector

When I showed you the demo service provided by the odata.org web site, I was able to use the “format” keyword to display data using JSON format instead of the default ATOM format. You might be tempted then to apply the same technique on our created service, for example:<o:p>

http://localhost:19865/MyDataService.svc/REST/Students?&$format=json 

<o:p>

However, doing so will not give you want you’d expected. Instead you will get: 

Image 25

The reason you got this response is that out of the box (at the time of this writing), WCF Data Services does not provide a way to implicitly tell your service to expose data using JSON format. 

What we need is a way to intercept client requests and explicitly set the Accept header to “application/json” so WCF Data Service runtime knows to generate JSON responses instead of ATOM ones. Basically there are two ways to do so, either via Http Module or WCF Message Inspectors. I prefer the second approach. 

To fully understand WCF channel layer and extension points I advise you to read my post here:<o:p>

http://thedotnethub.blogspot.com/2012/01/creating-soaprest-based-wcf-service.html 

<o:p>

In a nutshell, WCF provides extension points where you can intercept requests both at client and service sides. In this example I will create an extension point at the service side to add the required JSON header. 

Step1: Create the Message Inspector and Behavior Extension<o:p>

Here is the code:

C#
public class JSONMessageInspector : IDispatchMessageInspector
{
    public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)
    {
HttpRequestMessageProperty requestMessage = request.Properties["httpRequest"] as HttpRequestMessageProperty;
        UriTemplateMatch jsonMatch = (UriTemplateMatch)request.Properties["UriTemplateMatchResults"];
        if (jsonMatch.QueryParameters["$format"] != null && jsonMatch.QueryParameters["$format"].ToLower() == "json")
        {
            jsonMatch.QueryParameters.Remove("$format");
            requestMessage.Headers["Accept"] = "application/json;odata=light,application/json;odata=verbose"; //do not worry. This will be //explained in the next section
           
        }    }
    public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState) { }
}
public class JSONEndpointBehavior : IEndpointBehavior
{
    public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters) { }
    public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime) { }
    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
    {
        endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new JSONMessageInspector());
    }
    public void Validate(ServiceEndpoint endpoint) { }
}
public class JSONBehaviorExtensionElement : BehaviorExtensionElement
{
    public JSONBehaviorExtensionElement() { }
    public override Type BehaviorType
    {
        get { return typeof(JSONEndpointBehavior); }
    }
    protected override object CreateBehavior()
    { return new JSONEndpointBehavior(); }
}

The AfterReceiveRequest method contains the important logic. It inspects the incoming request and checks if $format is set to “json”. If so it forces the accept header to “application/json” to instruct WCF to return JSON formatted response, and it removes $format from the list of query parameters in order to prevent the “The query parameter '$format' begins with a system-reserved '$' character but is not recognized” problem described earlier from appearing. 

<o:p>

Again, refer to my post to understand the mechanics behind this as I want to keep the article focused on OData rather than WCF architecture.

Step2: Register the Extension point in web.config  

XML
<system.serviceModel>
    
    <extensions>
      <behaviorExtensions>
        <add name="messageInspector" type="JSONBehaviorExtensionElement, TestOData, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
        </behaviorExtensions>
      </extensions>
    
    <behaviors>
          <endpointBehaviors>
      <behavior name="JSONBehavior">
        <messageInspector/>
        <webHttp helpEnabled="true" />
        </behavior>
      </endpointBehaviors>
    </behaviors>
    
      <services>
     <service name="MyDataService">
        <endpoint binding="webHttpBinding" contract="System.Data.Services.IRequestHandler" address="REST" behaviorConfiguration="JSONBehavior"></endpoint>
      </service>
    </services>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
  </system.serviceModel>

Now run the application and browse to http://localhost:19865/MyDataService.svc/REST/Students. You will see an ATOM formatted response:

<o:p>

Image 26

<o:p>

Now supply instruct the application into using the JSON format by using http://localhost:19865/MyDataService.svc/REST/Students?&$format=json. You will get the JSON formatted response:

Image 27

What about JSON light?

As explained before, JSON light is a new direction by OData in place of verbose JSON. Review section “Formatting” for more details.<o:p>

Back to the previous section, and there is one bit of detail that I missed. Examine this line again:<o:p>

requestMessage.Headers["Accept"] = "application/json;odata=light,application/json;odata=verbose"; 

Why did not I simply use: requestMessage.Headers["Accept"] = "application/json”? 

The answer resides in class MyDataService.svc.cs, in the following line:<o:p>

C#
config.DataServiceBehavior.MaxProtocolVersion =
DataServiceProtocolVersion.V3;<o:p> </o:p> 

V3 indicates that I am using OData V3 which defaults to JSON light when setting Accept header to “application/json”. To see this in action, edit the accept header in the message inspector to read:<o:p>

C#
requestMessage.Headers["Accept"] =
"application/json";

<o:p>

Now browse to http://localhost:19865/MyDataService.svc/REST/Students?&$format=json:

Image 28

The reason I get “Unsupported media type requested” is that my client (WCF Client Library client 5.0) does not yet support JSON light.<o:p>

Now edit the MxProtocolVersion in MyDataService.svc.cs to read:<o:p>

C#
config.DataServiceBehavior.MaxProtocolVersion =
DataServiceProtocolVersion.V3; 

<o:p>

Browse again to http://localhost:19865/MyDataService.svc/REST/Students?&$format=json

Image 29

This time JSON format was rendered because we are using OData V2 which defaults to verbose JSON. 

<o:p>

This is why when using DataServiceProtocolVersion.V3, I set the accept header to "application/json;odata=light,application/json;odata=verbose"; where verbose JSON acts a fall back option in case the client does not yet support JSON light. 

JSON Light in WCF Data Services 5.2.0

The latest version of Data Services 5.2.0 (both service and client) are released just couple of days ago. Let’s use NuGet to install both packages:

Image 30

Version 5.2.0 contains full support for JSON light. As you recall from the previous section “What about JSON light?”, back then I used WCF Data Services client V5.0 which had no support for JSON light. I then used a custom Http header to support verbose JSON as a fall back option when the client does not support JSON light. 

<o:p>

Now after updating to V5.2.0 lets remove the message inspector: 

Image 31

<o:p>

Now browse to http://localhost/TestOdata/MyDataService.svc/REST/Courses?&$format=json. You will see JSON light returned without even emitting a custom header: 

Image 32

<o:p>

This makes JSON light a first class citizen in V5.2.0.

Extending Data Service with Service Operations and Interceptors 

As we have seen, WCF Data Services exposes data as entities for clients. However, sometimes you want to expose data shaped by come custom logic; for example an entity which is the result of a merge of other entities. Service operations allow you to expose custom operations to your clients.<o:p>

You might also want to intercept requests and perform some logic prior to these requests being services by WCF Data Service. Interceptors come into play in this scenario.

Service Operations<o:p> 

<o:p>

To add a service operation to our example, simply add the following method to class MyDataService:

C#
[WebGet]
        public IQueryable<Student> GetCustomLogicStudents()
        {
            //simulate some logic. here i will just return the same result set
            return (new List<Student>()
        {
            new Student { NationalID = "01", Name = "Mohamad", Courses = null}
        }).AsQueryable();
        } 

Note that you need to apply the WebGetAttribute to this operation. Similarly you could’ve applied WeInvokeAttribute for service operations which perform updates (see the “Behind the Scenes” section for more information about WebGet and WebInvoke). 

You also need to access to the new service operation as follows:<o:p>

config.SetServiceOperationAccessRule("GetCustomLogicStudents", ServiceOperationRights.All); 

<o:p>

Now browse to http://localhost:19865/MyDataService.svc/REST/GetCustomLogicStudents to see the service operation in action. 

Query Interceptors<o:p>

To add a query interceptor to prevent course of id 1 to be returned, add the following method to class MyDataService:

C#
[QueryInterceptor("Courses")]
        public
Expression<Func<Course, bool>> CoursesQuery()
        {
            return
p => p.CourseID != "1";
        } 

<o:p>

<o:p>

<o:p>

<o:p>

 Now if you query for http://localhost:19865/MyDataService.svc/REST/Courses, you will see that course of id 1 will not be returned.

Metadata, EDM, and CSDL

Back in the “Keywords” section I mentioned that you can use “$metadata” to generate the metadata of a WCF Data Service. Let’s do that for our sample service:

Image 33 

The Schema element under the Edmx and DataServices elements is the Conceptual Schema Definition Language (CSDL) specification. CSDL defines the data model of OData which is none other than the Entity Data Model (EDM) of Microsoft which is a derivative of the Entity Relationship model. This exposes CSDL data as a set of entities and associations (relationships) between them; which we have been working with since the start of the article.<o:p>

Yep I know what you’re thinking, that’s the same edmx of Entity Framework; however, this has nothing to do with Entity Framework in this context.<o:p>

It’s the CSDL that VS 2012 Add Service Reference dialog uses to generate the “Reference.cs” file which is the strongly typed client proxy. 

<o:p>

Let’s go ahead and use VS to add a service reference to our sample service. Using CSDL the dialog was able to strongly-type identify the classes that should be generated: 

Image 34

If you examine the generated Referece.cs file you will see that the class generated is of name “MyDataService” which is the value of the “Namespace” attribute of the CSDL:

Image 35

Concurrency Updates with ETAG

Consider this scenario: a client application is calling into our WCF Data Service. It first retrieves a certain Course object, updates it, and then submit the changes back to the service. Now assume in between the moments the client retrieves the Course object and just before it submits the changes, the Course object back at the service was updated. This causes a concurrency issue because the client when performing the update possibly took the decision based on the last Course object it got from the service; however, this object has changed without the client ever knowing it. This can cause all sorts of business issues.<o:p>

There are two ways you deal with concurrency; either be optimistic or pessimistic about it. Being optimistic is like saying “ok let’s not bother with concurrency for now, let the client update the Customer object and if something wring happens then we worry about it”. Being pessimistic however is being overly protective, it’s like saying “better safe than sorry. I want to prevent the client from updating the Course object if the value was changed back at the service”.<o:p>

ETAGs provide the mechanism to check for concurrency issues when you want to being pessimistic about it.<o:p>

In order to see this in action, there are couple of updates that I will make to our Course class. Here is the full code:

<o:p>

<o:p>

[DataServiceKey("CourseID")]
    [DataServiceEntity]
    [ETag("CourseNum")]
    public class Course
    {
        public decimal CourseID { get; set; }
        public int CourseNum { get; set; }
        
        public static List<Course> GetCourses()
        {
            return (new List<Course>()
        {
            new Course() { CourseID=1, CourseNum=2},
            new Course() { CourseID=2, CourseNum=1}
        });
        }
    }
public class MyDataSource:IUpdatable
    {
        static List<Course> courses;
        static List<Student> students;
        static MyDataSource()
        {
            courses = Course.GetCourses();
            students = Student.GetStudents();
        }
        public IQueryable<Course> Courses
        {
            get { return (courses.AsQueryable()); }
        }
        public IQueryable<Student> Students
        {
            get { return (students.AsQueryable()); }
        }
        #region IUpdatable
        public object CreateResource(string containerName, string fullTypeName)
        {
            throw new NotImplementedException();
        }
        public void DeleteResource(object targetResource)
        {
            throw new NotImplementedException();
        }
        public object GetResource(IQueryable query, string fullTypeName)
        {
            object result = null;
            var enumerator = query.GetEnumerator();
            while (enumerator.MoveNext())
            {
                if (enumerator.Current != null)
                {
                    result = enumerator.Current;
                    break;
                }
            }
            if (fullTypeName != null && !fullTypeName.Equals(result.GetType().FullName))
            {
                throw new DataServiceException();
            }
            object resultClone = result;
            //TODO: Uncomment this line to see ETAG in action
            //((Course)resultClone).CourseNum = 100;
            return resultClone;
        }
        public object GetValue(object targetResource, string propertyName)
        {
            var targetType = targetResource.GetType();
            var targetProperty = targetType.GetProperty(propertyName);
            return targetProperty.GetValue(targetResource, null);
        }
        public void SetValue(object targetResource,
           string propertyName, object propertyValue)
        {
            Type targetType = targetResource.GetType();
            PropertyInfo property = targetType.GetProperty(propertyName);
            property.SetValue(targetResource, propertyValue, null);
        }
        public object ResolveResource(object resource)
        {
            return resource;
        }
        public void SaveChanges()
        {
            // no persistent medium
        }
        public void SetReference(object targetResource,
           string propertyName, object propertyValue)
        {
            throw new NotImplementedException();
        }
        public object ResetResource(object resource)
        {
            throw new NotImplementedException();
        }
        public void ClearChanges()
        {
            throw new NotImplementedException();
        }
        public void AddReferenceToCollection(object targetResource,
           string propertyName, object resourceToBeAdded)
        {
            throw new NotImplementedException();
        }
        public void RemoveReferenceFromCollection(object targetResource,
           string propertyName, object resourceToBeRemoved)
        {
            throw new NotImplementedException();
        } 
        #endregion
    }  

First thing to notice is that I decorated class Course with an ETAG attribute and I set CourseNum as the value to monitor (more about this in a moment).<o:p>

Next I made my custom data source implement IUpdatable. This is required for my data source to accept updates from clients (if the client only wanted to query my service, then no updates on my data source would have been required). 

Now create a Console application and a service reference to service:<o:p>

http://localhost/TestOdata/MyDataService.svc/REST/

<o:p>

Write the following code:

C#
MyDataSource proxy = new MyDataSource(new Uri("http://localhost/TestOdata/MyDataService.svc/REST/"));
            Course course = (from c in proxy.Courses
                             where c.CourseID == 1
                             select c).First();
            course.CourseID = 2;
            proxy.UpdateObject(course);
            DataServiceResponse res = proxy.SaveChanges(); 

The code first retrieves a certain Course object, updates it, and send the updates back to the service. Run the client and examine Fiddler:

Image 36 

Here you see that in the request the “If-Match” header is saying that it will check if the ETAG value is 2. The response actually comes with ETAG value of 2, so the invocation succeeds.<o:p>

Now let’s do a trick. Go back to the code of class MyDataSource and uncomment the following line:<o:p>

//((Course)resultClone).CourseNum = 100;<o:p>

In this line I am hardcoding a change to the CourseNum property (since I do not have a persistent medium such as EF). This way, the object that the client will try to update will not be the same as that it first retrieved. 

<o:p>

Run the client again. Examine Fiddler:  

Image 37

<o:p>

This time the invocation fails with an exception “the etag value in the request header does not match with the current etag value of the object”.

Resources 

http://www.odata.org/<o:p>

WCF Web Http Programming Model:<o:p>

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

WCF Data Services 5.0 on MSDN<o:p>

http://msdn.microsoft.com/en-us/library/hh487257(v=vs.103).aspx<o:p>

<o:p>

<o:p>

<o:p>

<o:p>

<o:p>

<o:p>

<o:p>

<o:p>

<o:p>

<o:p>

<o:p>

<o:p>

<o:p>

<o:p>

<o:p>

<o:p>

<o:p>

<o:p>

<o:p>

<o:p>

<o:p>

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)