Peter Goodman bio photo

Peter Goodman

A software engineer and leader living in Auckland building products and teams. Originally from Derry, Ireland.

Twitter Google+ LinkedIn Github
TL;DR: How to create a client Linq api for querying IQueryable ASP.Net Web API rest services in very few lines of code using JSON.Net and Linq2Rest.

 

The ASP.Net MVC4 Beta was released recently and included the Beta release of ASP.Net Web API, formally known as the WCF Web API. Lots of people have already talked about the benefits of the Web API and how it has been built with simplicity, transparency, extensibility and testability in mind so I’ll not go into too many details about that here.

Using ASP.Net Web Api instead of WCF Data Services

One of the features that excites me about Web Api is the ability to expose an IQueryable on a Rest service and have Web Api automatically map queries in oData uri format to actual linq queries on the source i expose. For example, if my Web API controller looks like this:

public class PeopleController : ApiController {
    public IQueryable<Person> GetModels() {

        return people.AsQueryable();
    }

    private readonly Person[] people = new Person[] {
            new Person {
                Id = 1,
                FirstName = "Peter",
                LastName = "Goodman"
            }, 
            new Person {
                Id = 2,
                FirstName = "Peter",
                LastName = "Skeeter"
            }, 
            new Person {
                Id = 3,
                FirstName = "Tony",
                LastName = "Stark"
            }, 
            new Person {
                Id = 4,
                FirstName = "Frank",
                LastName = "Grimes"
            }, 
            new Person {
                Id = 5,
                FirstName = "Doug",
                LastName = "Kettle"
            }, 
            new Person {
                Id = 6,
                FirstName = "Finbar",
                LastName = "Coole"
            }, 
        };
}

 

Web Api will now let me access this resource and query it using oData syntax e.g.:

/api/People?$filter=FirstName eq ‘Peter'&$orderby=LastName desc

This of course is just like the kind of thing you get out of WCF Data Services with a couple of very important differences.

  • Web API does not currently support all of the features of oData, e.g. projection and expand
  • Web API is highly configurable and extensible.
  • Web API does not currently have a formatter Atom+Xml format, although an implementation is on the roadmap
  • There currently is no built in .Net client api for issuing Linq queries to Web API rest services

One of the pains of WCF Data Services has been the inability to configure or extend the implementation, for example it is very difficult or impossible to add your own formatter/serializer. With Web API, this kind of stuff is almost trivial.

The WCF Data Services client API (DataServiceContext) only allows you to use Atom+Xml which for a lot of purposes is extremely bloated in comparison to something like JSON or protobuf.

Unfortunately there also is no equivalent to the DataServiceContext that allows you to query the above service in Web API, therefore you cannot use LINQ to query the above service. This is what we are going to build.

Setting up the project

Make sure you have ASP.Net MVC4 Beta installed and create a new ASP.Net MVC 4 application project. When you get prompted for the Project Template make sure to choose “Web API” and hit OK.

Under models add a new class called Person and add the following code.

public class Person {
    public int Id { get; set; }

    public string FirstName { get; set; }

    public string LastName { get; set; }
}

Change the name of the ValuesController in the Controllers folder to PeopleController and add the code at the start of this article.

Run your project and in the browser change the url to something like the following remembering to use the hostname and port of your own project:

http://localhost:21449/api/People

You should see a list of the people we have defined.

Now we should be able to change this to the following to see only the ‘Peter’s

http://localhost:21449/api/People?$filter=FirstName = 'Peter'

You will notice that the output is in Xml. We want to customize this to allow Json

Creating the JSON.Net formatter

To create a json formatted output for our service we are going to use JSON.Net, the Web API team have already said that they are going to ship with JSON.Net as the default JSON formatter when it goes RTM. Therefore you can skip this section if you are working with the RTM version.

Firstly, using Nuget package manager, add a package dependency to your project on JSON.Net

Create a folder in your web project called Infrastructure and add a class called JsonNetFormatter. Paste in the code for this class only from Henrik’s article on the subject.

In the global.asax.cs file in your project add the following code in the RegisterRoutes method to register the JSON.Net formatter before the current default one.

JsonSerializerSettings serializerSettings = new JsonSerializerSettings();
serializerSettings.Converters.Add(new IsoDateTimeConverter());

HttpConfiguration configuration = GlobalConfiguration.Configuration;

configuration.Formatters.Insert(0, new JsonNetFormatter(serializerSettings));

You can test that works in fiddler2 by starting fiddler, re-running the previous query in the browser (you may need to replace the hostname with ipv4.fiddler to see it), drag the session into the composer tab and change the accept header to

Accept: application/json

You should now see that we have JSON output.

Creating the client application and api

Add a new console application project to your solution named something sensible. This is going to be our .Net client.

To create our client side code we are going to need something that can take a LINQ query and convert it to the oData URI format. In another project I am working on I have recently been using the excellent Linq2Rest library written by Jacob Reimers. He has written Linq2Rest to solve the problem of converting between IQueryables and OData URIs. Unfortunately the client and server pieces for Linq2Rest are in the same assembly so you will need to make your console app targets .Net 4.0 instead of the client profile in the project properties in order to support the dependency on System.Web.

Add a nuget package reference in your console app to Linq2Rest and Json.Net.

Next we need to create a matching JSON.Net deserializer for the Linq2Rest library so that it will match our server side implementation. This is just to ensure compatibility, you could of course reference the same common serializer settings etc. Create a folder in your console app called Infrastructure and add the following code for the JsonNetSerializerFactory and it’s serializer.

public class JsonNetSerializerFactory : ISerializerFactory {
    public ISerializer<T> Create<T>() {
        return new JsonNetSerializer<T>();
    }

    public class JsonNetSerializer<T> : ISerializer<T> {
        public T Deserialize(string input) {
            return JsonConvert.DeserializeObject<T>(input);
        }

        public IList<T> DeserializeList(string input) {
            return JsonConvert.DeserializeObject<IList<T>>(input);
        }
    }
}

 

Now we are ready to create our equivalent of the DataServiceContext. Add a new class to that project called PeopleContext and fill it out with the following code.

public class PeopleContext {
    private readonly RestContext<Person> restContext;

    public PeopleContext(Uri uri, Format format) {
        restContext = new RestContext<Person>(GetRestClient(uri, format), GetSerializerFactory(format));
    }

    public enum Format {
        Pox,
        Json
    }

    public static IRestClient GetRestClient(Uri uri, Format format) {
        switch (format) {
            case Format.Pox:
                return new XmlRestClient(uri);
            case Format.Json:
                return new JsonRestClient(uri);
            default:
                throw new NotImplementedException();
        }
    }

    public static ISerializerFactory GetSerializerFactory(Format format) {
        switch (format) {
            case Format.Pox:
                return new XmlSerializerFactory(knownTypes);
            case Format.Json:
                return new JsonNetSerializerFactory();

            default:
                throw new NotImplementedException();
        }
    }

    private static readonly IEnumerable<Type> knownTypes = new[] {
        typeof (Person)
    };

    public IQueryable<Person> People {
        get { return restContext.Query; }
    }

}

Now we are ready to consume our new fancy pants context class. In the Program.cs add the following code to query the service and display the results. Remember to replace the hostname and port with your settings. I’ve used the ipv4.fiddler address to force fiddler to log the session, this of course won’t work if you don’t have fiddler running.

class Program {
    static void Main(string[] args) {
        PrintQuery(PeopleContext.Format.Pox);
        PrintQuery(PeopleContext.Format.Json);

        Console.ReadKey(true);
    }

    private static void PrintQuery(PeopleContext.Format wireFormat) {
        Console.WriteLine();
        Console.WriteLine("*** Querying in {0} format ***", wireFormat);
        Console.WriteLine();

        // Perform Query
        new PeopleContext(new Uri("http://ipv4.fiddler:14061/api/People"), wireFormat)
            .People
            .Where(model => model.FirstName.StartsWith("Pe"))
            .ToList()
            .ForEach(item => {
                Console.WriteLine("-----------------------------------");
                Console.WriteLine("Id:{0}", item.Id);
                Console.WriteLine("First Name:{0}", item.FirstName);
                Console.WriteLine("Last Name:{0}", item.LastName);
            });
        Console.WriteLine("-----------------------------------");
    }
}

 

Run the console app and you should see:


*** Querying in Pox format ***

-----------------------------------

Id:1

First Name:Peter

Last Name:Goodman

-----------------------------------

Id:2

First Name:Peter

Last Name:Skeeter

-----------------------------------

*** Querying in Json format ***

-----------------------------------

Id:1

First Name:Peter

Last Name:Goodman

-----------------------------------

Id:2

First Name:Peter

Last Name:Skeeter

-----------------------------------

 

And we’re done. You should be able to see in fiddler now that we have issued a request in each format (Xml and Json) and received the appropriate format in your response. I have even used this approach to implement a protobuf-net version for even greater speed and reduced payload.

You can get the source for this sample on GitHub

https://github.com/PeteGoo/WebApiContext.Sample