Implementing Hypermedia Links with OpenRasta
Posted in OpenRasta, REST on July 25th, 2012 by Emily Skitek – 2 CommentsFor this month’s innovation time, I wanted to experiment with implementing hypermedia links using OpenRasta. Links are used in REST to tell the client how to transition from one state to the next. This is something we don’t do (yet) in our API. It’s considered to be a feature of a level 3 REST API.
Martin Fowler explains: ”The point of hypermedia controls is that they tell us what we can do next, and the URI of the resource we need to manipulate to do it. Rather than us having to know where to post our appointment request, the hypermedia controls in the response tell us how to do it…. One obvious benefit of hypermedia controls is that it allows the server to change its URI scheme without breaking clients… A further benefit is that it helps client developers explore the protocol. The links give client developers a hint as to what may be possible next.”
- Put the links in the response LinkHeader
- Put the links in the body of the response. Something like:
<order>
<id>156</id>
<status>Pending Payment</status>
<items>
<sku>12345</sku>
<quantity>1</quantity>
</items>
<links>
<link rel="order" url="http://api.mycompany.com/orders/156" />
<link rel="invoice" url="http://api.mycompany.com/payments/156" />
<link rel="payment" url="http://api.mycompany.com/invoices/156" />
</links>
</order>
There didn’t seem to be a clear recommendation on which way to go. Some people recommend option (1) because you can get to the links without having to parse the response body. However, I ended up going for option (2), because option (1) would get messy/unwieldy in scenarios where a list of items was returned in the response and you wanted to associate links with specific items in the list. To put it another way, option 1 would work well for response-level links, but not so well for resource-level links. Thanks to my colleagues Greg and Matt for the opinions/insight on this!
To spike this, I created a very simple OpenRasta app that has a single API method for creating a basket. It actually doesn’t do anything except for to new up a BasketResource and return it in the OperationResult’s ResponceResource, but that’s sufficient for our purposes.
Before returning the BasketResource, the handler sets a “self” LinkResource on the newly created BasketResource using OpenRasta’s UriResolver.CreateUriFor method, which handily constructs the link based on the routing you’ve defined in the ConfigurationSource. The handler also sets the “RedirectLocation” on the OperationResult, which sets the Location header on the response.
There are several OpenRasta extension methods that make it easier to create URIs for resources (ex: resource.CreateUri()), but these were problematic to unit test so I settled on the slightly more cumbersome (but testable) CreateUriFor method in the code sample below. The logic for constructing links should ultimately be pushed out of the BasketHandler and into another object that only is responsible for making links, but I haven’t gotten around to that refactoring bit yet.
using System;
using System.Collections.Specialized;
using OpenRasta.Api.Basket.Resources;
using OpenRasta.Web;
namespace OpenRasta.Api.Basket.Handlers
{
public class BasketHandler
{
private readonly IUriResolver _uriResolver;
private readonly ICommunicationContext _communicationContext;
public BasketHandler(IUriResolver uriResolver, ICommunicationContext communicationContext)
{
_uriResolver = uriResolver;
_communicationContext = communicationContext;
}
[HttpOperation(HttpMethod.POST)]
public OperationResult Create()
{
var basketResource = new BasketResource { Id = 1 };
var getBasketUri = CreateGetBasketUri(basketResource);
var selfLink = CreateSelfLink(getBasketUri);
basketResource.SelfLink = selfLink;
return new OperationResult.Created
{
RedirectLocation = getBasketUri,
ResponseResource = basketResource
};
}
private static LinkResource CreateSelfLink(Uri getBasketLink)
{
return new LinkResource{ Relation = "self", Uri = getBasketLink.AbsoluteUri };
}
private Uri CreateGetBasketUri(BasketResource basketResource)
{
return _uriResolver.CreateUriFor(
_communicationContext.ApplicationBaseUri,
basketResource.GetType(),
null,
new NameValueCollection { { "id", basketResource.Id.ToString() } }
);
}
}
}
<link rel="cancel" uri="http://myapi.com/basket/123" />
This is the same uri as the “get” uri used to retrieve the basket – the only difference is that to “cancel”, the client must use the DELETE method (instead of GET). I found this same question asked on StackOverflow. Some said that the API documentation should provide this sort of detail, but someone else recommended including another attribute on the link to specify the method.
One of my colleagues, Phil, also brought up the point that you could use the OPTIONS method to tell the client which http methods can be used on a resource. However, the down-side to this is that the client would have to make an additional API call to find out this information.
<link rel="cancel" method="delete" uri="http://myapi.com/basket/123" />
I haven’t implemented this bit of functionality yet, but it should be easy enough to do.