Thursday, March 19, 2015

.Net OData Services

Intro

I've talked about Web API in previous articles, and I think my overall opinion is pretty clear: I really like the direction MS has taken with internet-based services. I find Web API to be a big improvement over SOAP services and WCF. They're simpler and more flexible, and I find them to be just plain fun. However there is a more powerful alternative out there that is still quite simple: OData. OData is an "open protocol to allow the creation and consumption of queryable and interoperable RESTful APIs in a simple and standard way." For .Net, OData is supported in Web APIs with just a few minor tweaks to your code. So in essence you get queryable RESTful APIs with very little effort on your part. OK everybody, time to show me your OData face and let's get crackin!




Howdie Doodie

Time to talk specifics. Pretend you have a list of animals. You want a RESTful service that allows the client to query your list of animals by name. OK cool, sounds easy enough. You might create a Web API that has a GET method with a single parameter, name. The user hits your api, maybe with the url /api/animals/timmy, and this would return Timmy the lion (we'll just assume all your animals have unique names). Now what if the client wants to retrieve a list of mammals? You could always create a new GET method in your API that has a single parameter for class and the client would hit /api/animals/mammal, but now you've got 2 conflicting API methods and they each need their own special path. Plus you're making some duplicated code here, as in essence you're still just querying your list of animals for specific animal(s). OData gives you an easier out. Let's create an OData service that allows querying as per the previous desires.

First, fire up Visual Studio. I'm using VS 2013 here. Create a new empty Web API project.


Now add a class to your Models folder. Name the class Animal.

Make your class look like this:

using System.ComponentModel.DataAnnotations;
namespace BlogOData.Models
{
    public class Animal
    {
        [Key]
        public string Name { get; set; }
        public string Type { get; set; }
        public string Class { get; set; }
        public int Height { get; set; }
        public int Weight { get; set; }
    }
}

Note: The Key attribute is merely used to identify which is the unique property of our class that identifies individual instances of an animal. In the real world names aren't unique, but this is a blog not the real world.

Now it's time for our OData controller. Go ahead and add one named Animals to your Controllers directory as per the following screenshots:



You've now got a rather large file named AnimalsController.cs in your Controllers folder. It has a lot of extra actions that we don't need since all we care about is retrieving an animal or list of animals based on criteria specified by the client. Replace the code of your new controller with the following:

using System.Collections.Generic;
using System.Linq;
using System.Web.Http.OData;
using BlogOData.Models;

namespace BlogOData.Controllers
{
    public class AnimalsController : ODataController
    {
        public List<Animal> Animals;

        public AnimalsController()
        {
            Animals = new List<Animal>();
            Animals.Add(new Animal() { Class = "mammal", Type = "lion", Name = "timmy", Height = 64, Weight = 495 });
            Animals.Add(new Animal() { Class = "reptile", Type = "box turtle", Name = "billy", Height = 4, Weight = 2 });
            Animals.Add(new Animal() { Class = "reptile", Type = "leopard gecko", Name = "marzipan", Height = 1, Weight = 1 });
            Animals.Add(new Animal() { Class = "invertebrate", Type = "worm", Name = "willy", Height = 1, Weight = 1 });
            Animals.Add(new Animal() { Class = "mammal", Type = "house cat", Name = "sushi", Height = 1, Weight = 12 });
        }

        [EnableQuery]
        public IQueryable<Animal> Get()
        {
            return Animals.AsQueryable();
        }
    }
}

The first thing that is noteworthy is your class declaration for AnimalsController. We're descending from ODataController. Next in line, our constructor. It's not really all that special, but you can see we're populating a list of animals. Lastly the method Get. This has a few interesting bits:
  1. The EnableQuery attribute. This tells the runtime that we are returning a result from this method that is queryable using OData. 
  2. The return type is IQueryable<Animal>. This lets the result set be queried automatically via the OData backend.
  3. Lastly, we return our list of Animals as a queryable object. 
With this incredibly small amount of code, we've now created an OData controller that lets clients tell the server to return only specific results.

And now is where I tell you there's one more piece left. As things stand at the moment, there is no route to your OData controller, which means clients can't actually call it. Open up your WebApiConfig.cs file in the App_Start folder. Looking at the below code sample, you'll need to add the 3 lines of code underneath the comment "//OData Routes":

using System.Web.Http;
using System.Web.Http.OData.Builder;
using System.Web.Http.OData.Extensions;
using BlogOData.Models;

namespace BlogOData
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API configuration and services

            // Web API routes
            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );

            //OData Routes
            ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
            builder.EntitySet<Animal>("animals");
            config.Routes.MapODataServiceRoute("odata", "odata", builder.GetEdmModel());

        }
    }
}


These 3 lines tell the runtime that we wish people to access our OData controller via the url /odata/animals (case-sensitive). That's it. Run this little sucker! When your browser first comes up, you'll probably see a 403 forbidden error like the following:


Not to worry though; that's not really the url you want. If you're debugging in IE, go ahead and open up a firefox window. It's easier to demo Web API and OData functionality with than IE. Leave the IE debugging window open though. Navigate to your localhost url plus "/odata/animals". You should now see this:


Cool! This is the JSON response from our OData controller showing the full list of animals. I hope you're now thinking "Geez Peeticus, that's cool! But hey, didn't you say the client could tell the server, via a query of some sort, which animals to return?". Well yes I did, and yes we can. OData has it's own special syntax that we can use for filtering, delivered via the $filter querystring parameter. Let's go ahead and use a basic query now...add this on to the url you already have in FireFox: "?$filter=Name eq 'timmy'" (note these filters are also case-sensitive, as are string values contained therein). Here's what your result looks like:

Try a few more queries. Maybe we want to see all the mammals with "?$filter=Class eq 'mammal'".

Holy poo this is cool! We didn't have to write a bunch of switch statements with dynamic querying, LINQ statements, or anything of the sort. All this filtering is handled automagically!


What's Next?

You have a lot more query options with OData than what I've shown you here today. Play around, see what all else you can do, and check out the OData website for more information. It's powerful stuff. You might also try creating a C# client to consume your OData service. Info on how to do that can be found here.

Resources

OData Home
Create an OData v4 Endpoint Using ASP.Net Web API 2.2

No comments:

Post a Comment