OData

OData service with WCF and data in memory

May 20, 2012 C#, OData, Web 3 comments

In previous blog post I briefly touched OData protocol by showing quick usage of OData feed and then implemented ever simple WCF data service with using Entity Framework.

Now, let’s imagine that we have some data in memory and would like to expose it. Is it hard or easy?

Querying in memory data

If you can represent your data as IQueryable then, for most cases, you are fine. There is an extension method to IEnumerable called AsQueryable, so as long as your data can be accessed as IEnumerable you can make it IQueryable.

Sample data kept in memory

Now, let’s create some sample data:

private IList<Sport> _sports = new List<Sport>();
private IList<League> _leagues = new List<League>();

private void Populate()
{
    _sports = new List<Sport>();
    _leagues = new List<League>();

    _sports.Add(new Sport() { Id = 1, Name = "Swimming"});
    _sports.Add(new Sport() { Id = 2, Name = "Skiing"});

    var sport = new Sport() { Id = 3, Name = "Football"};
    var league = new League() { Id = 1, Name = "EURO2012", Region = "Poland&Ukraine" };
    sport.AddLeague(league);
    var league1 = new League() { Id = 2, Name = "UK Premier League", Region = "UK" };
    sport.AddLeague(league1);
    _sports.Add(sport);

    var league2 = new League() { Id = 3, Name = "Austria Premier League", Region = "Austria" };
    _leagues.Add(league);
    _leagues.Add(league1);
    _leagues.Add(league2);

    _sports.Add(new Sport() { Id = 4, Name = "Tennis"});
    _sports.Add(new Sport() { Id = 5, Name = "Volleyball"});
}

Absolutely nothing smart or difficult there, but at least to give you an idea about dataset we have.

Few things to make it happen

To expose sports and leagues we would need to add public properties to our data service implementation like below:

public IQueryable<Sport> Sports { get { return _sports.AsQueryable(); } }
public IQueryable<League> Leagues { get { return _leagues.AsQueryable(); } }

Another important thing about exposing data is that you have to indicate key for your entities with DataServiceKey attribute.

To make our service bit more realistic I’m going to add caching as well.

Complete source code of service
public class SportsWcfDataService : DataService<SportsData>
{
    public static void InitializeService(DataServiceConfiguration config)
    {
        config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
        config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
        config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
        // To know if there are issues with your data model
        config.UseVerboseErrors = true;
    }

    protected override void HandleException(HandleExceptionArgs args)
    {
        // Put breakpoint here to see possible problems while accessing data
        base.HandleException(args);
    }

    protected override SportsData CreateDataSource()
    {
        return SportsData.Instance;
    }

    protected override void OnStartProcessingRequest(ProcessRequestArgs args)
    {
        base.OnStartProcessingRequest(args);
        var cache = HttpContext.Current.Response.Cache;
        cache.SetCacheability(HttpCacheability.ServerAndPrivate);
        cache.SetExpires(HttpContext.Current.Timestamp.AddSeconds(120));
        cache.VaryByHeaders["Accept"] = true;
        cache.VaryByHeaders["Accept-Charset"] = true;
        cache.VaryByHeaders["Accept-Encoding"] = true;
        cache.VaryByParams["*"] = true;
    }
}

public class SportsData
{
    static readonly SportsData _instance = new SportsData();

    public static SportsData Instance
    {
        get { return _instance; }
    }

    private SportsData()
    {
        Populate();
    }

    private void Populate()
    {
        // Population of data
        // Above in this post 
    }

    private IList<Sport> _sports = new List<Sport>();
    public IQueryable<Sport> Sports { get { return _sports.AsQueryable(); } }

    private IList<League> _leagues = new List<League>();
    public IQueryable<League> Leagues { get { return _leagues.AsQueryable(); } }
}

[DataServiceKey("Id")]
public class Sport
{
    public int Id { get; set; }
    public string Name { get; set; }
    public void AddLeague(League league)
    {
        _leagues.Add(league);
    }

    private IList<League> _leagues = new List<League>();
    public IEnumerable<League> Leagues { get { return _leagues; } }
}

[DataServiceKey("Id")]
public class League
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Region { get; set; }
}
Some results of our work

With this URL http://localhost:49936/SportsService.svc/ service can be seen:

<service xml:base="http://localhost:49936/SportsService.svc/">
 <workspace>
  <atom:title>Default</atom:title>
  <collection href="Sports"><atom:title>Sports</atom:title></collection>
  <collection href="Leagues"><atom:title>Leagues</atom:title></collection>
 </workspace>
</service>

Now, you can access data via URL or by writing C# Linq queries if connected with client app or LinqPad. Following request:

http://localhost:49936/SportsService.svc/Leagues()?$filter=Name eq ‘EURO2012’&$select=Region

Would produce result containing this:

<d:Region>Poland&amp;Ukraine</d:Region>

What if you need to edit data?

If you need your data to be updatable, your SportsData would need to implement System.Data.Services.IUpdatable interface.

What if your datasource is not just few collections in memory?

What if you have very special data source, or you cannot simply keep your data in memory like collections of some data? This could be tricky or very tricky, depending on your data source, of course. But anyway you would need to implement interface IQueryable by hand if not provided by your data source.

Here is step-by-step walkthrough on msdn. Only by size of article you could imagine it is not trivial task to do. But if you managed to do it you can be proud, because you can implement your own Linq provider. (Do you remember NHibernate introducing support of Linq? It was big feature for product, roughly all they did was implementation of IQueryably by their NhQueryable.)

Why do I learn OData?

First of all I have to investigate if my team can use it. (Depending on outcome you might see more posts on this topic from me.) And another reason is that it is very exciting topic, about which I knew so little.

Very disappointing to admit that I start to really understand things when I touch them for reason and that reading dozen of blog posts on topic is useless unless I do something meaningful related to matters in those posts.

So, my Dear Reader, if you are reading this post and have read my previous post, but never tried OData I would be really pleased if you invest 10-30 or more minutes of your time to play with OData.


3 comments


ODATA

May 20, 2012 C#, OData, WCF, Web 3 comments

Let’s talk about OData.

At first glance

I would propose to start our OData journey with the best thing about it. Which is openness of data, easy of accessing and working with it. Client applications no longer need to depend on some specific service methods or formats if there is OData feed available. Consuming OData is simple and enjoyable.

For example, I want to know how many users with name ‘Andriy’ are there on StackOverflow with reputation higher than 500. No one at StackOverflow would develop special method in API which would allow me to request exactly this data.

But as StackOverflow exposes OData feed we can connect to it with LinqPad (get it here) and simply write normal C# linq query, like this one:

Users.Where(x=>x.DisplayName.Contains("Andriy") && x.Reputation > 500).OrderBy(x=>x.Reputation)

image

You can see same data if you use URL below. This URL was built by LinqPad to request data:

http://data.stackexchange.com/stackoverflow/atom/Users()?$filter=substringof(‘Andriy’,DisplayName) and (Reputation gt 500)&$orderby=Reputation

(View page source if you don’t like how your browser rendered that feed).

So, no magic. You just build special URL and get your data of interest in preferred format. You can use wide set of libraries both for client and server to implement and use OData.

Whenever you see this icon Datafeeds16 it is good indication that there is OData feed available. There are many applications/web sites that already utilize this protocol. Do you use Nuget? It works through OData. Know ebay? They expose its catalog via OData. Need more examples? Go to ecosystem page of OData.

So what is OData?

The Open Data Protocol (OData) is a Web protocol for querying and updating data that provides a way to unlock your data and free it from silos that exist in applications today. OData does this by applying and building upon Web technologies such as HTTP, Atom Publishing Protocol (AtomPub) and JSON to provide access to information from a variety of applications, services, and stores. The protocol emerged from experiences implementing AtomPub clients and servers in a variety of products over the past several years.  OData is being used to expose and access information from a variety of sources including, but not limited to, relational databases, file systems, content management systems and traditional Web sites.

…from http://www.odata.org/

Continue your reading about OData on its documentation page here.

If you have time, I would recommend to watch this “OData: There’s Feed for That” MIX10 video.

Let’s build our first OData service

As OData was initially introduced by Microsoft no wonder it is extremely easy to put it in place when you are on MS stack of technologies. If you are using EF there is almost nothing you have to do to make it happen.

Add “WCF Data Service” to your project.

image

You will get following code:

public class WcfDataService1 : 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;
    }
}

Assuming that you have your data model generated on Northwind db. All you would need is something like this:

public class WcfDataService1 : DataService<NorthwindContext>
{
    public static void InitializeService(DataServiceConfiguration config)
    {
        config.SetEntitySetAccessRule("*", EntitySetRights.All);
        config.SetServiceOperationAccessRule("*", ServiceOperationRights.All);
        config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
    }

    protected override NorthwindContext CreateDataSource()
    {
        return new NorthwindContext(ConfigurationManager.ConnectionStrings["NorthwindContext.EF.MsSql"].ConnectionString);
    }
}

And now clients can do whatever they like with your data. Of course, you can restrict them as you wish. OData is not about putting your database into web, you can control what you expose and to which extent. Also you can play with your service by adding caching, intercepting queries, changing behaviors and much-much more.

In next post I will show how we can build OData service for custom data you keep in memory.

In meantime you can checkout another video from NDC2011 by Vagif Abilov. Video is called “Practical OData with and without Entity Framework”. Follow this link to direct mp4 video file.


3 comments