Becoming HATEOAS with ASP.NET Web API 2.2

The context

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

The problem

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.

The solution

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:

[
  {
    "Id": "dev1",
    "Origin": "origin1",
    "Desination": "destination1",
    "Status": "Good news, everyone!",
    "HandlerId": "fry",    
    "Links": [
      {
        "Href": "http://localhost:57009/api/deliveries/dev1",
        "Rel": "self",
        "Method": "GET"
      },
      {
        "Href": "http://localhost:57009/api/deliveries/dev1/status?status=delivered",
        "Rel": "status-delivered",
        "Method": "PUT"
      }
    ]
  },
  {
    "Id": "dev2",
    "Origin": "origin2",
    "Desination": "destination2",
    "Status": "Good news, everyone!",
    "HandlerId": "leela",    
    "Links": [
      {
        "Href": "http://localhost:57009/api/deliveries/dev2",
        "Rel": "self",
        "Method": "GET"
      },
      {
        "Href": "http://localhost:57009/api/deliveries/dev2/status?status=delivered",
        "Rel": "status-delivered",
        "Method": "PUT"
      }
    ]
  }
]

Here’s how this can be achieved using WebApi.

Step 1 - Define Deliveries controller using attribute routing and provide GET /api/deliveries

[RoutePrefix("api/deliveries")]
public class DeliveriesController : ApiController
{
	[HttpGet, Route("")]
    public IHttpActionResult Get()
    {
    	var deliveries = repository.GetDeliveries();	
    	return Ok(deliveries);
    }
}

The reponse at this point should look like:

[
  {
    "Id": "dev1",
    "Origin": "origin1",
    "Desination": "destination1",
    "Status": "Good news, everyone!",
    "HandlerId": "fry"
  },
  {
    "Id": "dev2",
    "Origin": "origin2",
    "Desination": "destination2",
    "Status": "Good news, everyone!",
    "HandlerId": "leela"
  }
]

Step 2 - Define method to change delivery status PUT /api/deliveries/1234/status?status=delivered

[HttpPut, Route("{id}/status", Name = "ChangeStatusById")]
public IHttpActionResult ChangeStatus(string id, [FromUri]string status)
{
    var delivery = repository.GetDelivery(id);
    if (delivery == default(Delivery))
    {
        return NotFound();
    }
    delivery.ChangeStatus(status);
    return Ok();
}

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 id

[HttpGet, Route("{id}", Name = "GetDeliveryById")]
public IHttpActionResult Get(string id)
{
    var delivery = repository.GetDelivery(id);
    if (delivery == default(Delivery))
    {
        return NotFound();
    }
    return Ok(delivery);
}

Again, the only thing worth noticing is named routing definition.

Step 4 - Create a class which represents a hyperlink to available action

public class Link
{
    public string Href { get; set; }
    public string Rel { get; set; }
    public string Method { get; set; }
}

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.

private IEnumerable<Link> CreateLinks(Delivery delivery)
{
    var links = new[]
    {
        new Link
        {
            Method = "GET",
            Rel = "self",
            Href = Url.Link("GetDeliveryById", new {id = delivery.Id})
        },
        new Link
        {
            Method = "PUT",
            Rel = "status-delivered",
            Href = Url.Link("ChangeStatusById", new {id = delivery.Id, status = "delivered"})
        }
    };
    return links;
}

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 "self") and PUT to change status of delivery to "delivered" ("status-delivered" Rel).

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:

[HttpGet, Route("")]
public IHttpActionResult Get()
{
    var deliveries = repository.GetDeliveries().Select(d =>
    {
        d.AddLinks(CreateLinks(d));
        return d;
    });

    return Ok(deliveries);
}

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.

Wrap-up

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.