Tuesday, 8 October 2013

Create OData Web Services and Use Filter Expressions

OData (Open Data Protocol) lets you expose your resources and let the clients directly do the queries over it. Earlier when we used to use SOAP, you expose some methods, which performs some operations over the data returns back you the results, and if any client needs some specific data, then we used to changes our services by exposing down a new method which returns the needed output. But with OData also known as Resource Oriented architecture, we can just expose the resources or the data and let the client make the queries according to the requirement.

Here in this article of mine I will show you how to create a simple OData Service and let the client make queries just like SQL queries over it to retrieve the required data. Queries can be made by just specifying the filter expressions in the URL only.


So to start with I have created a WCF Service Application.


After creating we need to create a Data Service, which is done by just the following, some simple steps: Right click on the WCF application, and Add WCF Data Service as shown in the screenshot below.


As soon as it is done, you will see the generated service file:

    public class SampleWCFDataService : 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.V3;
        }
    }

I have highlighted in yellow, to get the service ready to be used we need to specify the Data Source Class, this can be achieved by simply providing Entity Framework Model for a particular database. So what this Data Source class should support is IQueryable properties over the exposed entities. Entity Framework model exposes all your entities as IQueryable properties. It allows directly exposing your tables and letting the client make queries over it by using Filter Expressions.

But for the sake of keeping it simple I am not creating Entity Framework model and will be creating a Data context class which have IQueryable properties.

So let’s look into how to create DataContext Class, before that we need to create some entities (Data Service Entities) which will be exposed as IQueryable entities via this DataContext class, here I will be creating a single entity User:

    [DataServiceKey("Id")]
    [DataServiceEntity]
    public class User
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }
    }

Attribute has been added specifying the User class as Data Service Entity. Now we will create a context class:

    public class SampleContext
    {
        #region Static Constructor

        /// <summary>
        /// In this static constructor i am creating some data which will be filled into users list, the purpose of creating static constructor is that it will be executed once
        /// on the creating of very first object and fills in some data into the users list which we can use to run through the demo.
        /// </summary>
        static SampleContext()
        {
            users = new List<User>();

            User user1 = new User();
            user1.Id = 1;
            user1.Name = "Abhishek1";
            user1.Age = 20;

            User user2 = new User();
            user2.Id = 2;
            user2.Name = "Abhishek2";
            user2.Age = 30;

            User user3 = new User();
            user3.Id = 3;
            user3.Name = "Abhishek3";
            user3.Age = 40;

            users.Add(user1);
            users.Add(user2);
            users.Add(user3);
        }

        #endregion

        #region Private Members

        static IList<User> users;

        #endregion

        #region Public Methods

        public IQueryable<User> Users
        {
            get
            {
                return users.AsQueryable();
            }
        }

        #endregion
    }

Now you can see that I have provided the Data Source for the Data Service we created earlier to this context class:


With this we are ready to use this service:


So above you can see that the Data Service is working, and the format in which it is displayed above is the Atom pub format. By default it doesn’t exposes the Data Context we created and set in the Data source in the service. For achieving so what we have to do is in the service code just uncomment the code highlighted in yellow, what this code do is, it defines the Entity access rule for the entities (Which all entities are needed to be exposed) so what we doing is we are exposing the entity Users below and are setting the Access level to Read, so we are only allowing Read operations.

    public class SampleWCFDataService : DataService<SampleContext>
    {
        // 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("Users", EntitySetRights.AllRead);
            // config.SetServiceOperationAccessRule("MyServiceOperation", ServiceOperationRights.All);
            config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
        }
    }

Now if you will access the Service again you will see something like below:


So here we get a new collection users, which we can use in the URL (Same as REST format) to get the users collection.


So here we are getting the Users Collection in the Atom PUB format shown above. So what’s the use of this is we can make a request to this URL (http://localhost/SampleWCFDataService.svc/Users) from a Web Page or a JavaScript file (Data can be returned in JSON format) can use the output i.e. Users list in the Web Page.

If we understand the format above we can see that it already provides the things to determine how we can query down the Data Source or can user filter expressions in it. Just for the sake of understanding I am formatting the output above in XML Format.

<?xml version="1.0" encoding="utf-8"?>
<feed xml:base="http://localhost:51937/SampleWCFDataService.svc/" xmlns="http://www.w3.org/2005/Atom" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
  <id>http://localhost:51937/SampleWCFDataService.svc/Users</id>
  <title type="text">Users</title>
  <updated>2013-10-08T14:44:06Z</updated>
  <link rel="self" title="Users" href="Users" />
  <entry>
    <id>http://localhost:51937/SampleWCFDataService.svc/Users(1)</id>
    <category term="SampleService.User" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
    <link rel="edit" title="User" href="Users(1)" />
    <title />
    <updated>2013-10-08T14:44:06Z</updated>
    <author>
      <name />
    </author>
    <content type="application/xml">
      <m:properties>
        <d:Id m:type="Edm.Int32">1</d:Id>
        <d:Name>Abhishek1</d:Name>
        <d:Age m:type="Edm.Int32">20</d:Age>
      </m:properties>
    </content>
  </entry>
  <entry>
    <id>http://localhost:51937/SampleWCFDataService.svc/Users(2)</id>
    <category term="SampleService.User" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
    <link rel="edit" title="User" href="Users(2)" />
    <title />
    <updated>2013-10-08T14:44:06Z</updated>
    <author>
      <name />
    </author>
    <content type="application/xml">
      <m:properties>
        <d:Id m:type="Edm.Int32">2</d:Id>
        <d:Name>Abhishek2</d:Name>
        <d:Age m:type="Edm.Int32">30</d:Age>
      </m:properties>
    </content>
  </entry>
  <entry>
    <id>http://localhost:51937/SampleWCFDataService.svc/Users(3)</id>
    <category term="SampleService.User" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
    <link rel="edit" title="User" href="Users(3)" />
    <title />
    <updated>2013-10-08T14:44:06Z</updated>
    <author>
      <name />
    </author>
    <content type="application/xml">
      <m:properties>
        <d:Id m:type="Edm.Int32">3</d:Id>
        <d:Name>Abhishek3</d:Name>
        <d:Age m:type="Edm.Int32">40</d:Age>
      </m:properties>
    </content>
  </entry>
</feed>

So if we type in the above highlighted command directly into the Address bar we will get something like below which is the very first user in the list:

<?xml version="1.0" encoding="utf-8"?>
<entry xml:base="http://localhost:51937/SampleWCFDataService.svc/" xmlns="http://www.w3.org/2005/Atom" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
  <id>http://localhost:51937/SampleWCFDataService.svc/Users(1)</id>
  <category term="SampleService.User" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
  <link rel="edit" title="User" href="Users(1)" />
  <title />
  <updated>2013-10-08T14:53:39Z</updated>
  <author>
    <name />
  </author>
  <content type="application/xml">
    <m:properties>
      <d:Id m:type="Edm.Int32">1</d:Id>
      <d:Name>Abhishek1</d:Name>
      <d:Age m:type="Edm.Int32">20</d:Age>
    </m:properties>
  </content>
</entry>

This is how the service works and we can actually directly consume the data into the Web Page where we need to. Now we will look into how to use filter expression directly while accessing the Data Service and retrieve the results. There are lot filter expressions available:

  • And
  • OR
  • Less Than(equal to)
  • Greater Than(equal to)
  • Not Equal
  • Ends with
  • Starts With
  • Length
  • Index Of
  • Replace
  • And many more…

How to actually use them, so if I consider the above scenario in which we are getting the Users collection, now if I want to get all the users which are actually having age greater than 22, then we can simply make a filter query(http://localhost/SampleWCFDataService.svc/Users?$filter=Age gt 22) and get the results.


<?xml version="1.0" encoding="utf-8"?>
<feed xml:base="http://localhost:51937/SampleWCFDataService.svc/" xmlns="http://www.w3.org/2005/Atom" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
  <id>http://localhost:51937/SampleWCFDataService.svc/Users</id>
  <title type="text">Users</title>
  <updated>2013-10-08T15:13:42Z</updated>
  <link rel="self" title="Users" href="Users" />
  <entry>
    <id>http://localhost:51937/SampleWCFDataService.svc/Users(2)</id>
    <category term="SampleService.User" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
    <link rel="edit" title="User" href="Users(2)" />
    <title />
    <updated>2013-10-08T15:13:42Z</updated>
    <author>
      <name />
    </author>
    <content type="application/xml">
      <m:properties>
        <d:Id m:type="Edm.Int32">2</d:Id>
        <d:Name>Abhishek2</d:Name>
        <d:Age m:type="Edm.Int32">30</d:Age>
      </m:properties>
    </content>
  </entry>
  <entry>
    <id>http://localhost:51937/SampleWCFDataService.svc/Users(3)</id>
    <category term="SampleService.User" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
    <link rel="edit" title="User" href="Users(3)" />
    <title />
    <updated>2013-10-08T15:13:42Z</updated>
    <author>
      <name />
    </author>
    <content type="application/xml">
      <m:properties>
        <d:Id m:type="Edm.Int32">3</d:Id>
        <d:Name>Abhishek3</d:Name>
        <d:Age m:type="Edm.Int32">40</d:Age>
      </m:properties>
    </content>
  </entry>
</feed>

So as it should be it is returning only two users, now let’s make it something like greater than 22 and less than 35(http://localhost/SampleWCFDataService.svc/Users?$filter=Age gt 22 and Age lt 35) which should return only one result. Now here we have seen the use of both Greater than, less than and the “and” expressions.


Similarly you can use the other available filter expressions. You can also download the sample application(From Here: Download) to see the working.