Thursday, 14 June 2012

ASP NET Web API Series - Part 9: MediaTypeFormatter on the client


Introduction

[Level T3] In part 5 we talked about MediaTypeFormatter on the server. We have another side of the story - perhaps not as big - on the client. MediaTypeFormatters will helps us converting our ViewModels/DTOs to and from HTTP content. HttpClient exposes full goodness of server side formatting as we have seen in part5. This post is going to be short and simple - especially if you have read the server-side post.

Why should I care?

You should care if you want to do anything useful consuming an HTTP API from a .NET client - and not Javascript. 

As we discussed on the server-side post, MediaTypeFormatter marries the world of HTTP (headers and body) to the world of .NET (strongly-typed objects). It will be very likely that you will be sending some strongly typed objects across the wire and expecting the same back. MediaTypeFormatter will help you achieve that.

ObjectContent and ObjectContent<T>

ASP.NET Web API abstracts the HTTP message's body and its body-related headers as HttpContent class - this applies to both request and response. So what are body-related headers? Here is a list:
  • Allow
  • Content-Disposition
  • Content-Encoding
  • Content-Language
  • Content-Length
  • Content-Location
  • Content-MD5
  • Content-Range
  • Content-Type
  • Expires
  • Last-Modified
As we have highlighted, Content-Type is the key header here - as for the media type formatting. These are in fact the same headers that can appear in each part of a Multipart message. As we covered briefly before, in a multi-part message, each part gets its own share of the body and body-related headers.

While HttpContent generally abstracts HTTP body as a stream, ObjectContent which inherits from HttpContent abstracts it as a strongly typed object - especially the generic type ObjectContent<T>

Creating an ObjectContent<T>

ObjectContent<T> has two constructors:

public ObjectContent(T value, MediaTypeFormatter formatter) : this(value, formatter, null)
{
}

public ObjectContent(T value, MediaTypeFormatter formatter, string mediaType) : base(typeof(T), value, formatter, mediaType)
{
}

We are supposed to pass the value of generic type T and pass the actual MediaTypeFormatter we want to format the content. The content then can be used to be sent to the server, for example in a post:

var content = new ObjectContent<foo>(new Foo(), new JsonMediaTypeFormatter);
var httpClient = new HttpClient();
httpClient.PostAsync(url, content);

Since it is the client who initiates the HTTP call, it would know what format to send the request body in. So the content-negotiation does not apply here. Having said that, I would have liked to be able to POST a request without having to decide on a MediaTypeFormatter and let Web API to choose a default formatter for me. I have not been able to find this in the framework yet.

Choosing a default media type formatter is not that hard in fact. All we have to do is to create a new MediaTypeFormatterCollection and then choose the JsonFormatter or XmlFormatter property.

MediaTypeFormatterCollection

ASP.NET Web API comes with a non-generic collection for MediaTypeFormatter. Unlike most collections which are a bag/list of their items, MediaTypeFormatterCollection has some knowledge and understanding of the formatters.

For starter, constructor of this collection creates the collection with these three default formatters:

public MediaTypeFormatterCollection() : this(CreateDefaultFormatters())
{
}

private static IEnumerable<MediaTypeFormatter> CreateDefaultFormatters()
{
    return new MediaTypeFormatter[] { new JsonMediaTypeFormatter(), 
          new XmlMediaTypeFormatter(), 
          new FormUrlEncodedMediaTypeFormatter() };
}

Also it has three properties: JsonFormatter, XmlFormatter and FormUrlEncodedFormatter. With using the parameterless constructor, each of these properties will point to one of the formatters constructed. However, if we create our own IEnumerable<MediaTypeFormatter> and pass to constructor, collection will find these formatters and assign the properties. I call these common formatters since they are bread and butter of the web.

Formatting objects into HTTP content (Request)

As we saw earlier, we can directly create an ObjectContent<T> or do that as part of POST/PUT:

client.PostAsync<Foo>(uri, new Foo(),
 new MediaTypeFormatterCollection().JsonFormatter);

This is not really much more complex than that.

Reading object from HTTP content (response)

This is where ASP.NET Web API provides on the client its full media type formatter selection on the server. Normally you would use ReadAsAsync<T> on the HttpResponseMessage. For example:

Foo result = client.GetAsync(uri)
 .Result.Content.ReadAsAsync<Foo>().Result;

As you can see, no media type formatter had to be passed and we used the organic media type formatter selection within the framework. How? Well, parameterless ReadAsAsync<T> is in fact an extension method that creates a new MediaTypeFormatterCollection (as we saw above) and pass as a set of formatters:

public static Task<T> ReadAsAsync<T>(this HttpContent content)
{
    return content.ReadAsAsync<T>(new MediaTypeFormatterCollection());
}

This in turn will call other methods and in the end, FindReader method of the MediaTypeFormatterCollection will be used to select a suitable formatter based on type and Contet-Type header.

Example: using Rotten Tomatoes web API

Rotten Tomatoes is a popular website for movie reviews. So if you are a movies fan like me, and you are into Web API, you might be tempted to expose the search movie search functionality in your application/mashup.

So what you need is an API key. It is free to acquire and you just need a Mashery ID and register for an API key in Rotten Tomatoes here. After you got your api key, you need to append this key as a query string parameter to all your calls.

So let's imagine we want to search films having "matrix" word in them. I have done that and here is the JSON.

Next step is to create classes to represent your JSON. You are welcome to do that by hand but there is a useful online tool there that can do that for you: json2csharp.

So here is what it has generated:

public class ReleaseDates
{
    public string theater { get; set; }
    public string dvd { get; set; }
}

public class Ratings
{
    public string critics_rating { get; set; }
    public int critics_score { get; set; }
    public string audience_rating { get; set; }
    public int audience_score { get; set; }
}

public class Posters
{
    public string thumbnail { get; set; }
    public string profile { get; set; }
    public string detailed { get; set; }
    public string original { get; set; }
}

public class AbridgedCast
{
    public string name { get; set; }
    public string id { get; set; }
    public List<string> characters { get; set; }
}

public class AlternateIds
{
    public string imdb { get; set; }
}

public class Links
{
    public string self { get; set; }
    public string alternate { get; set; }
    public string cast { get; set; }
    public string clips { get; set; }
    public string reviews { get; set; }
    public string similar { get; set; }
}

public class Movy
{
    public string id { get; set; }
    public string title { get; set; }
    public int year { get; set; }
    public string mpaa_rating { get; set; }
    public int runtime { get; set; }
    public string critics_consensus { get; set; }
    public ReleaseDates release_dates { get; set; }
    public Ratings ratings { get; set; }
    public string synopsis { get; set; }
    public Posters posters { get; set; }
    public List<AbridgedCast> abridged_cast { get; set; }
    public AlternateIds alternate_ids { get; set; }
    public Links links { get; set; }
}

public class Links2
{
    public string self { get; set; }
    public string next { get; set; }
}

public class RootObject
{
    public int total { get; set; }
    public List<Movy> movies { get; set; }
    public Links2 links { get; set; }
    public string link_template { get; set; }
}

As you can see, it does a very good job in converting them but there are some naming conventions that you might have to go back and re-visit (In C# we use Pascal-Casing). Also Movy really needs to be Movie but for our purpose it is just fine.

So let's open Visual Studio 2010 and create a new console application project. Then add a code file and paste the codes above in there.

Then in the "Package Manager Console", type

PM> Install-Package System.Net.Http.Formatting
This should add all client libraries you need. Then we just need to write

var httpClient = new HttpClient();
   var rootObject = httpClient.GetAsync("http://api.rottentomatoes.com/api/public/v1.0/movies.json?q=matrix&page_limit=2&page=1&apikey=[YOUR_API_KEY]")
    .Result.Content.ReadAsAsync<RootObject>();

You should have the data correctly deserialised back to your objects. Well, not quite, there is a problem since the API uses legacy content-type "text/javascript" for the JSON instead of  "application/json". With the technique we learnt back in Part 7 and 8, we can create a message handler to change the legacy content type to the correct one and add it to the pipeline:

public class LegacyJsonMediaTypeConverterDelegatingHandler : DelegatingHandler
{
 protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
 {
    
  return base.SendAsync(request, cancellationToken)
     
   .ContinueWith(task =>
             {
                 var httpResponseMessage = task.Result;
        if (httpResponseMessage.Content.Headers.ContentType.MediaType == "text/javascript")
         httpResponseMessage.Content.Headers.ContentType.MediaType = "application/json";
                 return httpResponseMessage;
             });
 }
}

Now all we have to do is to add the handler to the pipeline and make the call:

static void Main(string[] args)
{

 var httpClient = new HttpClient(new LegacyJsonMediaTypeConverterDelegatingHandler() { InnerHandler = new HttpClientHandler()});
   
 var rootObject = httpClient.GetAsync("http://api.rottentomatoes.com/api/public/v1.0/movies.json?q=matrix&page_limit=2&page=1&apikey=YOUR_API_KEY")
  .Result.Content.ReadAsAsync<RootObject>();


 Console.Read();
}


Conclusion

ObjectContent<T> is an important class in ASP.NET Web API which abstracts the HTTP content (or a part within a multipart message) which includes its body and body-related headers.

MediaTypeFormatter on the client is as important as on the server. For sending requests, since client is the initiator of the request, it needs to explicitly set the formatter. For processing response data, however, a suitable formatter is selected based on type of the object to be read and content-type.

1 comment:

  1. Great article! but it would be great to have a downloadable version of this code.

    ReplyDelete