Skip to content

Configuration

biscuit314 edited this page Oct 9, 2014 · 3 revisions

As you know, you can configure your Web API service in a number of ways. This section deals with configuring the Media Type Formatters that come wtih PointW.WebApi.ResourceModel.

Remember the goal:

  • You model your resources without thinking (much) about media types
  • You configure the server to determine which media types are available
  • Clients use content negotiation to select which type they want.

As of this writing, configuration is all or nothing. That is either you support HAL or you don't. In later versions you can be more selective (e.g. these resource can be HAL and/or Collection+Json, that resource is Siren, etc.) This page will be updated when that functionality is available.

For more details about how configuration and content negotiation work together, look at the tests in PointW.WebApi.MediaTypeFormatters.Config.Tests

HttpConfiguration

If you are already know where your HttpConfiguration object is, you may skip this section.

There are many ways to structure a Web API service. You can self-host, host in IIS, be part of a larger MVC project, etc. No matter which way you host, you eventually use an HttpConfiguration object to configure your service. You may instantiate it directly, it may be passed to your Register() method, or you may get a reference to a global config object. Either way, somewhere in your service's code an HttpConfiguration object is operated on and (directly or indirectly) used to inform the HTTP server how your service works.

You verly likely have seen this object - even if you've only used it to configure routing in your Web API service.

If your service uses convention-based routing, you have something like this:

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

If your service uses attribute routing, you have something like this:

config.MapHttpAttributeRoutes();

In the sections that follow, I will simply use an object named config which is of type HttpConfiguration to demonstrate how to manipulate the formatters.

Formatters

If you do nothing, the config object already has four built-in formatters all ready to go.

  • JsonMediaTypeFormatter - serializes/deserializes your objects to/from application/json
  • XmlMediaTypeFormatter - serializes/deserializes your objects to/from application/xml
  • FormUrlEncodedMediaTypeFormatter - for handling HTML form URL-encoded data
  • JQueryMvcFormUrlEncodedFormatter - deserializes application/x-www-form-urlencoded requests to your objects

These formatters are powerful and have some configuration options. But none provide hypermedia - at least not in a standard way. That's where this project comes in.

To take control of formatting, first clear all the existing formatters. Then you can add one or more formatters as you wish:

config.Formatters.Clear();
config.Formatters.Add(new HalJsonMediaTypeFormatter());

Now, the only formatter your service will use is the HAL formatter, and that will be your default media type for all requests/responses. Any object you return that inherits from Resource or that implements IResource will be properly formatted as HAL. Any requests, no matter what the client puts in the Accept: header, or even if they do not provide an Accept: header, will be formatted in HAL.

Make it pretty

The JSON based formatters (HAL, Collection+JSON, and the built in JsonMediaTypeFormatter) allow you to specifiy indenting. With indenting off you get this:

{"name":"Pat Smith","_links":{"self":{"href":"http://example.org/api/person/1"}}}

This is good for machine-to-machine production systems as it contains fewer bytes.

But if the response body is going to be handled by a human (say debugging, or in QA, or in a machine-to-human system) then indenting makes it easier to read.

To format, say HAL, with indenting:

config.Formatters.Clear();
config.Formatters.Add(new HalJsonMediaTypeFormatter{ Indent = true });

Now you get this:

{
  "name": "Pat Smith",
  "_links": {
    "self": {
      "href": "http://example.org/api/person/1"
    }
  }
}

If you want indenting for debug builds and not-indented for release builds:

var indent = false;

#if DEBUG
    indent = true;
#endif

config.Formatters.Clear();
config.Formatters.Add(new HalJsonMediaTypeFormatter{ Indent = indent });

Combinations

If you want to support both HAL and Collection+JSON:

config.Formatters.Clear();
config.Formatters.Add(new HalJsonMediaTypeFormatter());
config.Formatters.Add(new CollectionJsonMediaTypeFormatter());

Now if the client doesn't pick a media type, they get HAL. This is because HAL is configured first. The service will provide Collection+JSON on request. A client requests Collection+JSON by providing and accept header in the request, like this:

Accept: application/vnd.collection+json

If you would rather serve Collection+JSON by default unless the client asks specifically for HAL:

config.Formatters.Clear();
config.Formatters.Add(new CollectionJsonMediaTypeFormatter());
config.Formatters.Add(new HalJsonMediaTypeFormatter());

In other words, order matters.

Strictly speaking

What if you want to only respond for media types that you strictly support? That is if the client asks for anything but what you've configured, you want to tell them "No can do" with a 406 Not Acceptible, instead of trying to respond with a different media type. If they don't pick anything, no problem - supply the default. But you don't want to offer this media type if they asked for that.

You can tell the config object to use strict matching on content negotiation by replacing the loose IContentNegotiator with a strict one. It's easier to show you the code than to talk about it:

config.Services.Replace(typeof(IContentNegotiator),new DefaultContentNegotiator(excludeMatchOnTypeOnly: true));

So now if your formatter configuration looks like this...

config.Services.Replace(typeof(IContentNegotiator),new DefaultContentNegotiator(excludeMatchOnTypeOnly: true));
config.Formatters.Clear();
config.Formatters.Add(new HalJsonMediaTypeFormatter());
config.Formatters.Add(new CollectionJsonMediaTypeFormatter());

...then a client must either

  • supply Accept: application/hal+json to make it explict the client wants HAL
  • supply Accpet: application/vnd.collection+json to pick Collection+JSON
  • not supply an Accept: header at all - they'll get HAL by default (because it is configured first)

Any other media-type in the Accept: header will result in a 406 Not Acceptible response.

Loosen up

So we can be strict if you need to be, but we can be loose as well. If you want to supply a HAL formatted response even if the client asks for only application/json:

config.Formatters.Clear();
config.Formatters.Add(new HalJsonMediaTypeFormatter(asJson: true));

This tells the HAL formatter "you handle both application/hal+json and plain ol' application/json". You can do the same for all JSON-based media type formatters.

Mix and match

Probably a likely configuration will be something like this:

config.Formatters.Clear();
config.Formatters.Add(new HalJsonMediaTypeFormatter(asJson: true));
config.Formatters.Add(new CollectionJsonMediaTypeFormatter());
config.Formatters.Add(new JQueryMvcFormUrlEncodedFormatter(config));

Which lets clients request JSON (which will format as HAL), HAL (default), Collection+JSON (if asked) for GETs, plus lets them POST or PUT application/x-www-form-urlencoded Very convenient.

Finally, you can still wire up any other formatters - including the original four (or even skip the Clear() step, though that's not advisable if want your service to focus on hypermedia).

Clone this wiki locally