Hypermedia is an important aspect of REST. It allows you to build services that decouple client and server and allow them to evolve independently. This is achieved by returning representations of resources having links that indicate associated resources and way to interact with them.
For example, lets assume a package delivery service, which lists available deliveries and allows changing status of particular item to delivered. In other words, we expect client of delivery service to follow this flow:
GET /api/deliveries PUT /api/deliveries/1234/status?status=delivered
To achieve HATEOAS constraint, the client should not construct status change request on its own. Instead, the information required for request should be returned as part of response to previous request.
Exact representation of available operations in responses can vary as neither plain json nor xml contain hypermedia support. The most popular solution is to borrow href standard from HTML or use other standardized format which covers this, like HAL for example, and use it instead pure json or xml. Lets assume requesting
GET /api/deliveries should response the following:
Here’s how this can be achieved using WebApi.
Step 1 - Define
Deliveries controller using attribute routing and provide
The reponse at this point should look like:
Step 2 - Define method to change delivery status
The method is quite straightforward, nothing fancy. The only thing, that stands out is that the route has a name assigned (which is important).
Step 3 - Add a method to retrieve a delivery by
Again, the only thing worth noticing is named routing definition.
Step 4 - Create a class which represents a hyperlink to available action
That’s probably the simplest implementation possible. We might want to make this immutable, but that’s not the point of this example anyway.
Step 5 - Introduce a method which returns available hyperlinks associated with particular delivery.
Now things get interesting. We’re creating available actions basing on a state of particular delivery.
Method property defines which HTTP methods are available for an action,
Rel identifies type of action (should be used by clients to find exact hyperlink among others) and
Href contains the hyperlink to be invoked to execute this action, with all the parameters already in place if possible. This allows the client to just use what’s provided without figuring out what goes where and what are the names of request params. But instead of constructing entire Href by ourselves, we’re letting WebApi create it for us basing on defined routings. To achieve this, we’re invoking
Link method defined on UrlHelper class which is accessed by
Url property of ApiController class. Of course WebApi needs to know which route should be used - that’s where names assigned to routes come in. The second parameter is an anonymous type containing names (and it’s important!) that match parameters of invoked methods.
In this example, for each delivery listed we’re generating two available operations -
GET to self as a way of retrieving single delivery (
Rel set to
PUT to change status of delivery to
Step 6 - Enrich
GET /api/deliveries with generated links
To make use of generated links, we need to add them to the result returned by
Get methods. For example:
Type of delivery, its properties and methods is not important for the sake of this example - we just need to make sure it can serialize provided links to json (or any other required format). The easiest way it just add:
public IEnumerable<Link> Links;
as part of contract.
In this example we’ve achieved two important things. We’ve made our API fulfill HATEOAS constraint thus enabling clients to base state transitions only through dynamically generated by server links to actions. This means that the actions can be altered or even changed entirely on server-side without the need of changing the client code. Client just needs to be aware of type of action (identified by
Rel property of response) which it wants to invoke and look for it in the collection of returned links.
The other benefit is that actual
Hrefs to actions don’t need to be manually concatenated - WebApi generates them basing on
Routes, so that’s the only place that needs to be altered when introducing changes.