left-icon

ASP.NET Web API Succinctly®
by Emanuele DelBono

Previous
Chapter

of
A
A
A

CHAPTER 7

Content Negotiation

Content Negotiation


Formatting a resource

As we already know from Chapter 1, the client can ask for the resource in a specific format using the Accept header. With this, the server will try to serve the request to match the specific format and if the format is not supported, it returns a 406 Not Acceptable.

The Web API uses a content negotiation mechanism to support this functionality. We saw in the previous chapter that all the responses are served as JSON, but we can ask for XML format too.

Consider the Post example and build a request like this:

Data:Users:ema:Dropbox:Ideas:WebApiBook:images:GetXml.png

A GET that specifies the accept type

We simply add the Accept header and set it to text/xml. This means that the client asks for the Post with an Id equal to 1 in XML format. In fact, the server responds with an XML response with elements that match the attributes of the Post object.

This works because the ASP.NET Web API has the XmlFormatter included in the list of available formatters.

Formatters are objects that inherit from MediaTypeFormatter, an abstract class with the following interface:

public abstract class MediaTypeFormatter

{

    Collection<MediaTypeHeaderValue> SupportedMediaTypes { get; }

    Collection<Encoding> SupportedEncodings { get; }

    Collection<MediaTypeMapping> MediaTypeMappings { get; }

    IRequiredMemberSelector RequiredMemberSelector { get; set; }

    Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger);

    Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext);

    Encoding SelectCharacterEncoding(HttpContentHeaders contentHeaders);

    void SetDefaultContentHeaders(Type type, HttpContentHeaders headers, MediaTypeHeaderValue mediaType);

    MediaTypeFormatter GetPerRequestFormatterInstance(Type type, HttpRequestMessage request, MediaTypeHeaderValue mediaType);

    bool CanReadType(Type type);

    bool CanWriteType(Type type);

}

At first look, we could see that a formatter can read and write data. This makes sense because a formatter can parse the request in a specific format to build the parameters for the actions, and on the other side can convert the result of an action to another specific format.

Given that the most important members are:

  • SupportedMediaType: A collection of the types supported by this formatter. These correspond to the MIME types of the requests.
  • CanReadType: A Boolean that is true if the formatter is able to read the type from the request.
  • CanWriteType: A Boolean that is true if the formatter is able to write the type to the response.
  • ReadFromStreamAsync: This is the method that will be called to read from the input stream. It should return the instance of the resource.
  • WriteToStreamAsync: This is the method that will be called to write the resource to the output stream.
  • In the ASP.NET Web API, there are already four media type formatters out-of-the-box:
  • JsonMediaTypeFormatter: Format the requested resource in JSON format (this is the default formatter).
  • XmlMediaTypeFormatter: Format the requested resource in XML format.
  • FormUrlEncodedMediaTypeFormatter: This is used to manage the application/x-www-form-urlencoded requests.
  • JQueryMvcFormUrlEncodedFormatter: Derives from the FormURLEncodedMediaTypeFormatter and adds support for the JQuerySchema.

If we want, we can add our own formatters to match the API specification.

Let's look at an example to see what we can do with a media type formatter. Consider the following controller:

public class AuthorsControllerApiController

{

    // ...

    public IQueryable<Author> Get()

    {

        return _repository.GetAll();

    }

    public Author Get(int id)

    {

        return _repository.Get(id);

    }

}

It is a simple controller with two methods to get a list or a single author. The author class is something like this:

public class Author

{

    public int Id { get; set; }

    public string Name { get; set; }

    public string PhotoUrl { get; set; }

}

So if we request a single author, we see:

Data:Users:ema:Dropbox:Ideas:WebApiBook:images:GetAuthor.png

Getting a single author

What we want now is the photo of the author, but we would like to use the same URI we used to obtain the author info (/api/authors/1).

Therefore, we create a new GET request with the Accept header that specifies which format we want (for example, image/png).

Data:Users:ema:Dropbox:Ideas:WebApiBook:images:GetAuthorWihHeaders.png

A get request that specifies the accept type

As a response, we want a PNG image that represents Tolkien (the author with id 1).

To obtain this we need the help of a MediaTypeFormatter that takes the Author object from the controller and replaces it with his photo.

This is the complete implementation:

public class ImageFormatter : MediaTypeFormatter

{

    public ImageFormatter()

    {

        SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/png"));

    }

    public override bool CanReadType(Type type)

    {

        return false;

    }

    public override bool CanWriteType(Type type)

    {

        return type == typeof(Author);

    }

    public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext)

    {

        return Task.Factory.StartNew(() => WriteToStream(type, value, writeStream, content));

    }

    public void WriteToStream(Type type, object value, Stream stream, HttpContent content)

    {

        Author author = (Author)value;

        Image image = Image.FromFile(@".\Photos\" + author.Name + ".png");

        image.Save(stream, ImageFormat.Png);

        image.Dispose();

    }

}

This class inherits from MediaTypeFormatter, a base class for all the formatters. The runtime calls the WriteToStreamAsync, as noted previously. In this method, since it is asynchronous, we create a new Task and start it with the methods that actually read a file from the file system and write it to the output stream.

To add the newly created ImageFormatter media type formatter to the Web API pipeline, we need to register it by using the Formatters property on the HttpConfiguration object when the application starts:

public static class WebApiConfig

{

    public static void Register(HttpConfiguration config)

    {

        config.Formatters.Add(new ImageFormatter());

    }

}

The result when invoking the service is as follows:

Data:Users:ema:Dropbox:Ideas:WebApiBook:images:GetWithImage.png

The response in PNG format

The nice thing is that with the same URI (remember that the URI represents the ID of a resource), we have two different representations, and we can decide which one we want using different headers.

Summary

Content negotiation is an important part of a real REST API. It makes it possible to expose a resource in various formats and let the client decide which is best. We saw how to implement a custom formatter to transform a resource in a PNG format so that the image representation of the resource could be provided to the client.

Scroll To Top
Disclaimer
DISCLAIMER: Web reader is currently in beta. Please report any issues through our support system. PDF and Kindle format files are also available for download.

Previous

Next



You are one step away from downloading ebooks from the Succinctly® series premier collection!
A confirmation has been sent to your email address. Please check and confirm your email subscription to complete the download.