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
- Introduction
- Overview of OData Protocol
- Formatting Output
- Reading Data
- Projection of Fields
- Expansion
- Pagination
- Filtering
- Ordering Results
- Updating Data
- Creating OData Service using WCF Data Service
- Implementation of Data Provider
- WCF Service Customization
- Interceptors
- Service Operations
- Using OData Service
- LINQPad
- C# Code Sample
- JavaScript Code Sample
- Other Tools/Libraries
- Conclusion
- 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:
- What is OData protocol and how you can query data using the URL
- How you can create your own OData service using the WCF Data Services
- 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:
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:
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:
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:
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:
="1.0"="utf-8"="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:
{
"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:
{
"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:
{
"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:
public class MyWcfDataService : DataService< >
{
public static void InitializeService(DataServiceConfiguration config)
{
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:
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:
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:
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:
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:
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:
[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:
In order to expose entities from the Entity Framework model, you just need to put model class into the Service declaration:
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:
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:
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.:
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:
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:
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:
[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:
[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:
[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:
[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:
[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:
[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:
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:
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:
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:
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:
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:
var count = odataService.Categories.Count(c => c.ID == 430);
var cat = odataService.Categories.FirstOrDefault(c => c.ID == 430);
However, this query works fine:
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:
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:
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:
$.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:
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:
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:
$.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