-
Notifications
You must be signed in to change notification settings - Fork 5
Configuration
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
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.
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.
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 });
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.
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.
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.
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).