Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

OData Services

4.96/5 (93 votes)
8 Jun 2012CPOL29 min read 409.7K   7.3K  
How to create OData services using the WCF Data Services and use them
In this article, you will learn what OData services are, how to create them using the WCF Data Services, and how to use them.

Table of Contents

  1. Introduction
  2. Overview of OData Protocol
    1. Formatting Output
    2. Reading Data
    3. Projection of Fields
    4. Expansion
    5. Pagination
    6. Filtering
    7. Ordering Results
    8. Updating Data
  3. Creating OData Service using WCF Data Service
    1. Implementation of Data Provider
    2. WCF Service Customization
    3. Interceptors
    4. Service Operations
  4. Using OData Service
    1. LINQPad
    2. C# Code Sample
    3. JavaScript Code Sample
    4. Other Tools/Libraries
  5. Conclusion
  6. History

Introduction

A standard web service enables you to create some functions or procedures that return or update data. The base idea is that you enable consumers to access these functions via web using HTTP protocol. This is base for the service-oriented architecture (SOA). However, the service oriented architecture has some drawbacks. You can never predict what kind of queries/services will be required by your consumers, therefore you will always need to add new services, add parameters or return values to existing services just because some consumer needs something specific.

Another approach is so called resource-oriented architecture (ROA) where you expose resources and enable users to run various ad-hoc queries against the resources. This is similar to the approach in the database systems where you have tables that represent data and ability to create various SQL queries. The only difference is that in ROA, you create queries via URL.

OData is a protocol that specifies characteristics of web services that exposes data. In this article, I will show you what the OData protocol is, and how you can implement web services compliant to this protocol using the WCF Data Service technology. In the following sections, I will explain the following:

  1. What is OData protocol and how you can query data using the URL
  2. How you can create your own OData service using the WCF Data Services
  3. How you can call OData services using the various tools and code samples

Overview of OData Protocol

As it is mentioned above, OData services are web services that expose some resources. You can access these resources via URL. OData protocol specifies how you can access data via HTTP queries. The basic idea is that you can open some URL with parameters that will execute queries against the resources.

Currently, there are lot of public OData services you can use. You can find a complete list on the OData site [^]- some of them are:

Therefore, I will describe OData protocol using the real queries that will be executed against the real public OData services. In the first example, I will show you how you can find all resources that OData web service exposes. If you open base URL of some OData, e.g., http://services.odata.org/OData/OData.svc/, you will see a list of resources it exposes:

Image 1

Here, you can see three resource collections that are exposed by the service. In order to access these resources, you will need to add names of the resources in the URL:

These queries will return complete lists of resources in the Xml-Atom format. Example of the response returned for the Products query is shown below:

Image 2

You can either load and parse this XML or use some RSS feed tool that reads it.

Formatting Data

Instead of the default Xml-Atom format, you can use other formats - currently is supported JSON format. You can return data in JSON format if you add parameter $format=json to the URL as in the following examples:

At the time of writing this article, URLs above return so called "Verbose JSON" where a lot of metadata information are placed in the response. There is an initiative to change the format to the "Light JSON" where returned results will have just a minimal set of metadata and decrease size of the response. This is still in experimental phase but you might see how it looks on the following URLs:

In the future, the current "Verbose JSON" format will still be available but it needs to be explicitly requested with $format=verbosejson as in the following examples:

However, note that this is still experimental and not officially released. Once this experimental version become official (or dropped), these experimental URLs will not be available anymore.

Using these URL queries you can take complete list of data; however, OData enables you to slice, filter and take subset of data. In the following sections, you can find more examples about some usages of OData services.

Reading Data

Instead of taking all information, you can create URL queries that find information about the some specific resources. As an example, you can find resources by id if you add id value in the brackets. The following example shows how you can return only category with id 1:

Also, you can take related information for some object if you add /Resource suffix. Some examples are:

Using these queries, you can find any information relative to some entities.

Projection of Fields

By default, all fields in the resource are returned; however, you can specify subset of fields that you want to be returned in the query. As an example, if you want to get only ID and name of product, you will use the following query:

This also works if you want to return a complex subtype of resource. As an example, if supplier has Address property with Address, Town, State, and Postcode sub-fields, you can specify that the Address object should be returned using the following query:

When you select subtype, you will get all fields of address object. Unfortunately, there is no way to select just one property of sub-object so query that would return just a State field from the Address resource will break:

Expansion

When you query for the resource, only direct fields are returned without related entities. As an example, if you take a product by id, information about the related supplier and category will not be included. Example of response in the verbose JSON format is shown in the following figure:

Image 3

As you can see, it tells you that Category and suppliers objects are deferred and that you have a link that you can use to take these resources. If you want this information to be returned along with the original data, you will need to use $expand system parameter. Expansion is opposite to the projection. Instead of restricting field list, using the $expand you are including additional related entities. Examples of queries that return Product and associated Supplier and Category are:

For the second URL you will be able to see that supplier information are included if you use verbose JSON format as output:

Image 4

As you can see, supplier object is included but not supplier products. In order to include them too, you will need to change $expand value - /Products(1)?$format=json&$expand=Supplier/Products . Note that depth of the expand levels might be constrained on the server-side.

Pagination

Instead of returning the whole set of results, you can divide them in blocks and implement some kind of pagination. There are following parameters you can use to implement pagination:

  • $top – specifies how many results should be returned in the current response
  • $skip – specifies how many records that satisfy the current criterion should be skipped
  • $skiptoken – specifies that all results from the start to the some id should be skipped
  • $inlinecount – if it is set to allpages requires that total number of records should be returned

Example of the URL query that returns records from 20 to 30 with returned total count is shown in the following example:

If the results are returned as Atom-xml feed, you will get the additional information in the <m:count> tag as it is shown in the following example:

XML
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<feed>
    <title type="text">Products</title>
    <id>http://services.odata.org/Northwind/Northwind.svc/Products</id>
    <updated>2012-05-23T15:59:18Z</updated>
    <link rel="self" title="Products" href="Products" />
    <m:count>77</m:count>
    ...
</feed> 

In the JSON (verbose) format, you will get additional __count property where a total number of entities is placed:

JavaScript
{
"d" : {
        "results": [

            <<results>>

        ],
        "__count": "77"
     }
}  

System parameter $skiptoken is another way to partition your results. If you specify id of the entity in the $skiptoken=<<ID>> part, everything from the start to entity with the key <<ID>> will be skipped.

Note that some of the OData web services will by default return you only up to (N) results and give you a link to the next page. As an example, if you want to take all customers from the Northwind OData service in the json format, e.g., /Customers?$format=json, you will get a limited number of results in the one response, and link to the next set of resources in the __next property as it is shown in the following example:

JavaScript
{
"d" : {
        "results": [

            <<results>>

        ],
        "__next": 
        "http://services.odata.org/Northwind/Northwind.svc/Customers?$skiptoken='ERNSH'"
}
}  

If you follow the link, you will get next top (N) number of results. Note that this is server side limitation and it is not dependent on your value in the $top parameter. If you try to run the following query on the Northwind OData service /Products?$top=100&$inlinecount=allpages&$format=json where you require 100 results to be returned, you will get 20 results in the following format:

JavaScript
{
"d" : {
        "results": [
 
               <<20 results>>

        ],
        "__count": "77",
        "__next": "http://services.odata.org/Northwind/Northwind.svc/
                   Products?$inlinecount=allpages&$top=80&$skiptoken=20"
     }
}

As you can see, although the total number of all resources (77) is less than the required $top value (100), Northwind service will return you just 20 entries with a link to the next partition. This is server-side setting that you cannot control via URL query unless if you create your own OData web service.

Filtering

In the previous example, you have seen how you can filter resources by ID, e.g., /Products(1). However, the same query can be created using the $filter parameter where you explicitly specify that you want to filter products by id, e.g., /Products?$filter=ID eq 1. System parameter $filter enables you to specify any condition by other properties. As an example, you can filter products by name as in the following examples /Products?$filter=Name eq 'Milk'. In this section, you can find various usages of $filter system option.

Relational Operators

In the previous example, you have seen how you can use eq operator for filtering. OData protocol enables you to use other relational operators such as not equal (ne), less than (lt), less or equal (le), greater than (gt), greater or equal (ge). Some examples are:

Logical Operators

You can create complex queries using the logical operators and, or, not and brackets. One example of complex query is shown in the following example:

Arithmetical Operations

You can apply standard operators to add (add), subtract (sub), multiply (mul), divide(div), or find remainder(mod). Example of query that returns all products where a total value in stock (unit price * units in stock) is less that 45 (with condition that there are some items in stock) is:

Note that currently, you can use arithmetical functions only in $filter condition but not in the $select.

Numerical Functions

If your properties are numbers, you can apply floor, ceiling, and round functions. Example of the query that uses these functions is /Products?$filter=floor(Price) eq 3 or ceiling(Price) eq 3.

String Functions

There are a lot of string functions you can use in your filter expressions - some of them (with examples) are:

Date Functions

If you have datetime properties in the resources, you can use several date part functions such as year(), month(), day(), hour(), minute(), and second(). As an example, URL query that returns all orders with OrderDate in 1996 is:

Ordering Results

Another usable feature is ordering. You can order results by some property or function using the $orderby query option. Default order is ascending but you can change it. Some examples are:

Updating Data

OData services are REST services so data modification operations are also allowed. In the REST services, a type of data access operation is defined using the type of HTTP request that is sent using the following rules:

  • HTTP GET request is used to read data
  • HTTP POST request is used to update existing entity
  • HTTP PUT request is used to create a new entity
  • HTTP DELETE request is used to delete entity

HTTP GET protocol is default protocol when you open URL via browser so I have used it to show various examples to read data. However, other request we cannot run via browser (without some form) so usually they are executed via some client side library with API that allows you to update data. As you will never use HTTP protocols directly, and as each data modification request depends on the library you are using, I will not show examples here. However, you should be aware that this is possible and that you can easily execute it using any client side library you are using.

Note that in the section about using OData services with C# code, you can find example how OData resources can be updated using the C# code. That code is directly translated in the HTTP queries that updates data in the service.

Creating OData Web Service using the WCF Data Services

In the previous section, you saw how you can use some public web services that are compliant with the OData protocol. However, if you know OData specification, you can create your own service - it just needs to provide everything OData specification requires. In this section, I will show how you can create your own OData compliant web service using the WCF Data Services.

WCF Data Services (formerly known as ADO.NET Services) enables you to create resource oriented web services. However, you must understand that WCF Data Services are not OData services – they are very close to the OData spec however they are not 100% compliant. Most of the features provided by the WCF Data Service implementation match OData specification, however there are some tweaks you might do manually in order to align WCF service with OData specification.

Creating WCF Service is simple – just go to Add new Item > Web > WCF Data Service, Enter the name (e.g. MyWcfDataService) and Visual Studio will generate the following source code:

C#
public class MyWcfDataService : DataService< /* TODO: put your data source class name here */ >
{
	// This method is called only once to initialize service-wide policies.
	public static void InitializeService(DataServiceConfiguration config)
	{
		// TODO: set rules to indicate which entity sets and service operations are visible, 
        // updatable, etc.
		// Examples:
		// config.SetEntitySetAccessRule("MyEntityset", EntitySetRights.AllRead);
		// config.SetServiceOperationAccessRule
        // ("MyServiceOperation", ServiceOperationRights.All);

		config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
	}
} 

This is a template that exposes resources in the Data Source Provider class. Now you will need to set Data source provider in the < > part. Data source provider is some class that contains properties that represent resources that should be exposed by the service. Types of these properties either implement IQueryable (for read-only entities), IUpdateable (for writable entities) or both interfaces. Example of Data Source Provider class is shown in the following listing:

C#
public class MyDataSourceProvider{

	public IQueriable<Product> Products { get; }

	public IQueriable<Supplier> Suppliers { get; }

	public IUpdateable<Category> Categories { get; }
}

This data source provider enables you to expose Products and Categories as read-only properties, and Categories as writable resource. If you put MyDataSourceProvider class in the definition of the service, you will be able to expose resources from the provider:

C#
public class MyWcfDataService : DataService< MyDataSourceProvider >
{

}

Once you associate data source provider to the WCF service, you will need to define access rights in the service initialization method. The following code enables full access to suppliers, read access to products, and write access to categories:

C#
public static void InitializeService(DataServiceConfiguration config)
{
	config.SetEntitySetAccessRule("Suppliers", EntitySetRights.All)
	config.SetEntitySetAccessRule("Products", EntitySetRights.AllRead);
	config.SetEntitySetAccessRule("Categories", EntitySetRights.AllWrite);

	config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
} 

In the following sections, we will see how you can define customize this WCF Data Service.

Implementation of Data Source Provider

Data source provider is the most important part of the service because it will act as a data proxy for querying and updating data. In this section, you will see how you can implement data source provider to the plain list of in memory objects and database.

Reflection Data Source Provider

We can create provider to any data source – even a static list of in-memory objects. That kind of class is shown in the following example:

C#
public class DataRepository
{
    public static List<Product> Products = new List<Product>();

    public static List<Supplier> Suppliers = new List<Supplier>();
} 

Here, we can see one static class that contains list of products and suppliers. I don't care how these lists are populated. In order to expose these objects using the WCF data service, we would need to create provider that has IQueryable properties – example is shown in the following listing:

C#
public class MyWCFDataSource
{
    public IQueryable<Product> MyProducts
    {
        get
        {
            return DataRepository.Products.AsQueryable();
        }
    }

    public IQueryable<Supplier> MySuppliers
    {
        get
        {
            return DataRepository.Suppliers.Where(s => !s.IsArchived).AsQueryable();
        }
    }

    public MyWCFDataSource()
    {
        this.MyLondonSuppliers = DataRepository.Suppliers
                                    .Where(s=>s.County == "London")
                                    .Select(supplier => new LondonSupplier()
                                    {
                                        ID = supplier.SupplierID,
                                        Name = supplier.Name,
                                        Address = supplier.Address
                                    })
                                    .AsQueryable();
    }

    public IQueryable<LondonSupplier> MyLondonSuppliers
    {
        get;
        private set;
    }
} 

Here, I have placed standard IQueryable property that is created when Products are converted to Queryable object. AsQueryable() method will create query-able object that can execute various LINQ queries against the collection of Product objects. WCF Service will convert URL queries described in the OData overview to the LINQ queries and this interface will return results that match the criterion.

In this example, Suppliers are not plain conversion from the list of suppliers in data source. Before they are converted to IQueryable, one filter is applied that exposes just suppliers that are not archived. As you can see, you can apply any operation to the original collection before you return it to the WCF service.

The third property is derived from the list of suppliers in order to create suppliers from London. In this case, a new class LondonSuppliers is created that represents a new resource in the data source. WCF service does not care how you had provided set of resources as long as you return it as IQueryable object. Instead of the property body, code is placed in the constructor and assigned to the property using setter.

Product and supplier classes can be plain classes - you do not need to inherit some base class, set attributes etc. The only thing you might want to do is to set what field will be used as key attribute. Otherwise, reflection provider will choose one of them using the following rules:

  • If the class has a property called ID, it's an entity with the ID as the only key property.
  • If the class is called for example Customer and it has a property called CustomerID, this property will be used as a key.

If your key property does not follow this convention, you will need to specify data key name as in the following listing:

C#
[DataServiceKey("Name")]
public class LondonSupplier
{
    public int ID { get; set; }
    public string Name { get; set; }
    public string Address { get; set; }
    public string Email { get; set; }
} 

Here, I have explicitly defined that Name property will be used as a key instead of the ID property. This code is everything you need to create fully functional WCF service. Instead of the plain list, you can use any method to take data, and just convert it to IQueryable in the data source provider.

Entity Framework Data Source Provider

The easiest way to create data source provider is to create Entity Framework model and pass it directly to the WCF Service. An example of Entity Framework model is shown in the following figure:

Image 5

In order to expose entities from the Entity Framework model, you just need to put model class into the Service declaration:

C#
public class MyWcfDataService : DataService< MyEntityFrameworkModel >
{

}

Each entity in the entity framework model will be recognized as one resource that can be exposed. These resources implement both IQueryable and IUpdateable interfaces, therefore resources can be read and updated. The only thing you need to do is to define access rights as in the previous case. Example of the access rights definition is shown in the following code:

C#
public static void InitializeService(DataServiceConfiguration config)
{
	config.SetEntitySetAccessRule("Companies", EntitySetRights.All);
	config.SetEntitySetAccessRule("Products", EntitySetRights.AllRead);
	config.SetEntitySetAccessRule("Categories", EntitySetRights.AllWrite);

	config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
}    

That is everything you need to do in order to implement WCF Data Service. The only thing you will need to be aware of is naming convention. If you pluralize and singularize entities in the model, you will see names in singular in the model diagram, however in the configuration, you need to put names in plural. If you miss the name, service will fail to initialize.

If you do not want directly to bind Entity Framework model to the service, you can create custom data source provider that will expose entities via IQueryable properties. Example is shown in the following listing:

C#
public class MyWCFDataSource
{
    public MyWCFDataSource()
    {
        var ctx = new OData.Models.ODataDemoEntities();

        this.MyLondonSuppliers = ctx.Companies.Select(c => new LondonSupplier()
        {
                ID = c.CompanyID,
                Name = c.Name,
                Address = c.Address
        });
    }

    public IQueryable<Supplier> MyLondonSuppliers 
    {
        get;
        private set;
    }
}  

This code is equivalent to the code in the previous section. Instead of the plain list as in previous example, I have used entities taken from the Entity Model. This way is useful if you need to merge entities from the several data sources and return them as single collection.

WCF Service Customization

There are lot of possibilities to modify your WCF service. The majority of settings is placed in the InitializeService method of the WCF service. Some of the properties you can set are:

  • MaxResultSetsPerCollection – defines a maximum number of objects that are returned in one query
  • SetEntitySetPageSize(<entity-set>, limit) – defines a maximum result set for individual entity set. It cannot be used with MaxResultsSetPerCollection() property.
  • MaxExpandCount – defines a maximum number of subresources in the $expand parameter. As an example, if it is set to 2, you cannot use the URL query /Products(1)?$expand=Supplier,Category,ProductItems because it requires three expanded properties.
  • MaxExpandDepth – defines a maximum depth of path in the $expand option. As an example, if it is set to 2, you cannot run /Products(1)?$expand=Supplier/Category/Products query because it has three levels in the path.
  • DataServiceBehaviour.AcceptCountRequests – defines are the queries with $count and $inlinecount allowed. Default true.
  • DataServiceBehaviour.AcceptProjectionRequests – defines are the queries with $select system parameter allowed. Default true.
  • DataServiceBehaviour.InvokeInterceptorsOnLinkDelete – defines should the change interceptor be executed on delete operation.

There properties are configured in the InitializeService method of the WCF service, e.g.:

C#
public static void InitializeService(DataServiceConfiguration config)
{       
    config.MaxResultSetPerCollection = 100;
    config.MaxExpandDepth = 2;
    config.DataServiceBehaviour.AcceptCountRequests = false;          
}  

I have mentioned above that if you send a query to the Northwind OData service, it will restrict a number of results to 20 regardless what you have set as $top option. Here, you can see how this setting is overridden on the server-side.

You can set the event handlers that are executed before request is started and when error occurs. These are two service methods that you can override as it is shown in the following listing:

C#
protected override void OnStartProcessingRequest(ProcessRequestArgs args)
{
    var method = args.OperationContext.RequestMethod;
    base.OnStartProcessingRequest(args);
}
protected override void HandleException(HandleExceptionArgs args)
{
    base.HandleException(args);
} 

Each time service is called, it will call a constructor of the data source class. If you want to control how data source is created, you can override this behavior in the CreateDataSource method as it is shown in the following listing:

C#
protected override MyWCFDataSource CreateDataSource()
{
    return base.CreateDataSource();
}

Note that this method is called before OnStartProcessingRequest.

I have mentioned above that WCF Service is not 100% compliant to the OData service. One difference is that in WCF service are not supported $format and $callback options, and this is a problem when you create JSON based clients. By default, you cannot pass $format=json to the WCF service – the only way to take JSON data is to set Http request header accept parameter to “application/json”. To fix this, you will need to manually create some attribute (let us call it JSONPSupportBehaviourAttribute) that removes $format parameter from the request and sets request header value. In the attached code is already included example of that kind of attribute. The only thing you need to do is to set attribute in the WCF service that should be able to return JSON as it is shown in the following listing:

C#
[JSONPSupportBehaviourAttribute]
public class MyWcfDataService : DataService< MyDataSourceClass >
{

} 

This modification enables you use $format and $callback parameters.

Interceptors

You can create methods that will be executed before resources are read or updated. The only thing you need to do is to add methods in the WCF service and mark it as QueryInterceptor or ChangeInterceptor for some resource set.

QueryInterceptors return some condition that should be satisfied by all results that are returned. By modifying this condition, you can filter resources that are returned by the current query. Example of the method that intercepts read requests to the products is shown in the following listing:

C#
[QueryInterceptor("MyProducts")]
public Expression<Func<Product, bool>> FilterProducts()
{
     if (HttpContext.Current.Session["UserID"] != null)
         return p => p.ModifiedBy == Convert.ToInt32(HttpContext.Current.Session["UserID"]);
     else
         return p => p.ModifiedBy == 0;
}

This interceptor is placed in the WCF service and it checks whether the UserID is in the session. In that case, it returns only subset of products that belong to the current user. Otherwise, it returns “public” products (the ones with ModifiedBy = 0).

ChangeInterceptors intercepts create/update/delete requests and allow you to modify data. Example of interceptor that checks whether the current user has modified product and sets date modified to the current date is shown in the following listing:

C#
[ChangeInterceptor("MyProducts")]
public void OnChangeProducts(Product product, UpdateOperations operations)
{
    if (Convert.ToInt32(HttpContext.Current.Session["UserID"]) != product.ModifiedBy)
        throw new Exception("Product cannot be updated");
    if (operations == UpdateOperations.Delete)
        throw new Exception("Product cannot be deleted");
    product.ModifiedOn = DateTime.Now;
}

Interceptors enable you to implement fine grained control over the resources. In the InitializeService, you can set the global rules that define whether client can write data or whether it can read all records. However, you cannot define can some specific user write the data or should some user see just a subset of all data.

With Interceptors, you can add your own custom logic that can check access rights and do some custom processing based on the information about the current user, cookies, etc. This is just a simulation of some access rules logic but in the real-world scenario, you can easily implement some role based security, authentication, etc.

Service Operations

Although WCF Data Services are resource-oriented, it does not mean that you cannot add your own services for accessing data. In the WCF service, you can add your own operations that returns custom resource sets. This might be helpful if you need to provide pre-compiled queries on the service side.

An example of service operation that returns list of suppliers by city is shown in the following listing:

C#
[WebGet]
public IQueryable<Supplier> GetSuppliersByCity(string city)
{
     return DataRepository.Suppliers.Where(s => s.Town.Contains(city)).AsQueryable();
}

You can use this service operation the same way as any other resource – just call it by name, supply parameters and add any service parameters you need. Some examples are:

  • /GetSuppliersByCity?city='Manchester'&$orderby=Name desc
  • /GetSuppliersByCity?city='Cardiff'&$top=1

You do not need to use any parameters in the service operation. The following example shows how you can implement resource of London suppliers with service operation:

C#
[WebGet]
public IQueryable<Supplier> LondonSuppliers()
{
      return DataRepository.Suppliers.Where(s => s.County == "London").AsQueryable();
}

This is identical to the previous example with MyLondonSuppliers where the same logic is placed in the data source provider. There is no difference between the resources from data sources and service operations so you can create standard URL query such as:

  • /LondonSuppliers?$skip=10&$take=5

You can also create service operations that do not return results just update entities. That kind of operation is shown in the following listing:

C#
[WebGet]
public string UpdateProductName(int ID, string Name)
{
    DataRepository.Products.First(p => p.ProductID == ID).Name = Name;
    return "ok";
} 

This is just a simulation but in the real case you might update database information. You can invoke this operation using the following URL /UpdateProductName?ID=2&Name='My Product' and product with id 2 will be updated. The response of the service operation will be wrapped in XML like <UpdateProductName>ok</UpdateProductName>. Instead of the [WebGet], you might set attribute [WebInvoke] in order to deny get request and allow only to be invoked via POST, PUT or some other Http operation.

The only thing you need to add is setting the service operation access level in the InitializeService method of the WCF service. Example is shown in the following listing:

C#
public static void InitializeService(DataServiceConfiguration config)
{            
    config.SetServiceOperationAccessRule("GetSuppliersByCity", ServiceOperationRights.AllRead);
    config.SetServiceOperationAccessRule("LondonSuppliers", ServiceOperationRights.AllRead);
    config.SetServiceOperationAccessRule("UpdateProductName", ServiceOperationRights.All);

    config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;          
} 

Here, I have allowed read access to GetSupplierByCity and LondonSuppliers, and full access to the UpdateProductName service operation.

Using OData Service

Now when we have seen where you can find public OData services and how you can build your own service, I will show you how you can use services. As you have already seen, OData queries are simple Http requests executed via URL, therefore the only thing you need is a web browser. However in practice, you will use other tools for accessing OData services.

LINQPad

LINQPad is one excellent tool that can help you to directly send queries to the WCF service.

In order to connect to WCF service, you will need the latest LINQPad (version 4 at least). There, you can see “Add connection” link that will enable you to choose type of connection (choose WCF Data Services (OData)), then enter URI where service is placed (e.g., http://services.odata.org/OData/OData.svc/ ), optionally enter username and password if service is not public, and create connection. If you succeed, you will see in the left hand side all resources that are provided by OData service you have added. Now you can create some LINQ query on the resources and execute it. Once you execute query, you can see results or generated URL query. Example is shown in the following figure:

Image 6

LINQPad is an excellent tool if you want to learn OData queries. Just put any LINQ query you would use and see what equivalent generated URL query is.

C# code via Web Reference

WCF Services can be used from the C# code as any other web services. Just right-click on the solution, add a new service reference and enter the URL of the OData service as it is shown in the following figure:

Image 7

If URL is correct, you will see all resources provided by the OData service (Categories, Products, and Suppliers in the example above). Now, just type the namespace for the Service reference and you will be able to send queries to the service.

Reading Data

Querying OData service from the code is easy – just create instance of service reference using the Uri and run some LINQ query. Example is shown in the following listing:

C#
var odataService = new DemoService(new Uri("http://services.odata.org/OData/OData.svc/"));
var products = odataService.Products.Where
               (p=>p.Name.Length<10).OrderBy(p=>p.Name).Skip(3).Take(4); 

However, you need to be aware that this is just a translation od the LINQ to OData URL queries. Therefore, you might have some problems if you create LINQ queries that are not supported. Imagine that you want to check if there is a category with ID 430. You might want to find a number of these categories and check is the count 0 or 1 as in the following example:

C#
var count = (odataService.Categories.Where(c => c.ID == 430)).Count();

However, if you run this query, you will get the following message, "Unhandled Exception: System.NotSupportedException: Cannot specify query options (orderby, where, take, skip) on single resource.". Problem is in the fact that during translation is recognized that c.ID is identifier so following query is sent /Categories(430). That query returns a single entity, and you cannot apply Count() on the single entity (although it is not mentioned in the error message). Also, Count, First, and FirstOrDefault are not supported so the following queries will fail with not supported message:

C#
var count = odataService.Categories.Count(c => c.ID == 430);
var cat = odataService.Categories.FirstOrDefault(c => c.ID == 430);

However, this query works fine:

C#
var cat = odataService.Categories.Where(c => c.ID == 430).FirstOrDefault();

In this code, first is executed LINQ to Http query that has translated Where part to the URL query. Then, FirstOrDefault is applied on the in-memory result. As you can see, you should carefully use these queries. Note that this fails in the current service library I'm using, so it might be possible that it will be fixed in the future releases. It would be best to try LINQ queries in the LINQPad first if you are not sure whether they are supported or not.

If your query is valid, you can easily inspect it and find what query will be sent to the OData server if you inspect returned object. Example is shown in the following figure:

Image 8

Here, I have created LINQ query that skips 2 and takes 3 products. If you inspect returned object in the debug mode, you might see that $skip and $top parameters are added. This is similar to the LINQPad functionality.

Updating Data

Beside queries, C# code is ideal when you need to update data via service.

In this case, I will use writable version of one public OData service to show you how updates are done. Read-write demo OData service enables you update data but just within your session. For each session, you have unique URL in the format http://services.odata.org/(S(SESSION_ID))/OData/OData.svc/ . In this example, I will randomly choose session d21qfurvulln1hs3cjn5Tiur as a session id, so URL that I will use to create service will be something like http://services.odata.org/(S(d21qfurvulln1hs3cjn5Tiur))/OData/OData.svc/. In the following code, you might see how you can implement updates:

C#
var odataService = new DemoService(
new Uri("http://services.odata.org/%28S%28d21qfurvulln1hs3cjn5Tiur%29%29/OData/OData.svc/"));
           
var music = new Category() { ID = 430, Name = "Music"};
foreach (var p in odataService.Products.Take(2))
    music.Products.Add(p);
odataService.AddToCategories(music);
odataService.SaveChanges();

var cat = odataService.Categories.Where(c=>c.ID == 430).First();

foreach (var p in cat.Products)
{
    Console.WriteLine(p.Name + "(" + p.ID + ")");
}

odataService.DeleteObject(cat);
odataService.SaveChanges();

Here, I have created reference to the Demo service, created new category, taken two products and associated them to the category, saved changes, fetched category again, listed products and deleted category. As you might see, LINQ operations over the OData are identical to the standard LINQ operations applied on the Entity Framework.

JavaScript Code Sample

OData service is called via URL and can return JSON therefore it is easy to call it via JavaScript AJAX. In the following example, how you can send AJAX request using the JQuery call is shown:

C#
$.ajax({
    url: "WcfDataService2.svc/MySuppliers",
    dataType: "json",
    cache: "false",
    success: function (response) {
        alert("Returned " + response.d.length + " suppliers")
    }
}) 

Example of the response traced in the FireBug is shown in the following figure:

Image 9

You can use this JSON object and display any data directly via JavaScript. JSON output format is perfect for the JavaScript code because there you can use JS templating engines that convert JSON data using some template to the HTML output as it is shown in the following figure:

Image 10

In that case, you will need to prepare some HTML template and when you load JSON from the service, you can load it into the template and generate actual HTML. Example with loadJSON template engine is shown in the following listing:

C#
$.ajax({
    url: "WcfDataService2.svc/MySuppliers",
    dataType: "json",
    cache: "false",
    success: function (response) {
        $("#template").loadJSON(response.d);
    }
})  

Details about the usage of template engines is out of scope of this article, but you can see more details in the jQuery Templates/View Engines in ASP.NET MVC article where I have shown how you can use various template engines in MVC applications that return JSON response. In this case, instead of the controllers that return JSON, you have an OData service that returns JSON format.

Other Tools/Libraries

There is a big list of applications that can consume OData services – you can find a full list on the OData site. Also, you can find a lot of libraries that can help you to use OData services from Java, JavaScript, PHP, etc – full list of libraries is listed on the OData site.

Conclusion

OData services are great way to implement your web services. Instead of traditional way that exposes particular methods in this approach, you expose entire entities and enable your clients to create any query they need. If you are using it from the .NET client, you are able to use standard LINQ that will be used for queries – therefore access is easier. Also, you have possibilities to control access, customize your queries and everything else you had in the standard web service so you do not lose anything, but you are able to create more flexible web services. For me, OData web services are always better choice comparing to the standard web services, so I recommend that you try to use them.

History

  • 1st June, 2012: Initial version

License

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