onsdag, april 04, 2012

Ramone: Consuming Hyper-Media REST Services in C#

Hyper-media controls are one of the core features of the World Wide Web as we know it today - without them there would be no way to link web pages and no way to interact with them through web forms. One of the REST constraints is the famous HATEOAS - "Hyper-media As The Engine Of Application State" and in order to consume RESTful web services we should make hyper-media controls first class citizens of the client interface we work with.

Hyper-media controls can be classified in different ways (see for instance Mike Amundsen's classification), but in Ramone we distinguish between the two well-known hyper-media elements; links and key/value forms as we know them from HTML, ATOM and more. These are represented with the following C# interface definitions:

  public interface ILink
    Uri HRef { get; }
    string Title { get; }
    IEnumerable<string> RelationTypes { get; }
    MediaType MediaType { get; }

  public interface IKeyValueForm
    IKeyValueForm Value(string key, object value);
    IKeyValueForm Value(object value);
    Request Bind(string button = null);

The idea is to use some C# extension method to get an instance of one of the above interface from the resource representation in the current response. This means in HTML there are methods for extracting <a> elements, <link> elements and <form> elements and return them as instances of the hyper-media interfaces. As more media-types are added to Ramone so will more similar extension methods be added - this can be done as add-ons without touching the core code.

The following example is from Ramone's Blog test collection (please take a closer look at the Github code since it includes some more explanations). This example shows how to follow an "author" link from an HTML page that shows a list of blog entries. The HTML contains (non-standard) micro formats for identifying various elements of the blog (post, author, title).

The actual HTML can be found here: https://gist.github.com/2305777#file_ramone_blog_list_html.html (and the HTML for an author can be found here: https://gist.github.com/2305988#file_gistfile1.html)

Here we go:

  // Create request by binding well known path to session's base URL
  Request blogRequest = Session.Bind(BlogRootPath);

  // GET blog HTML represented as an HtmlDocument (from Html Agility Pack)
  Response<HtmlDocument> blog = blogRequest.Get<HtmlDocument>();

  // Select first HTML anchor node with rel="author" as a anchor link.
  // This uses HtmlDocument specific extension methods to convert from anchor to ILink
  ILink authorLink = blog.Body.DocumentNode.SelectNodes(@"//a[@rel=""author""]").First().Anchor(blog);

  // Follow author link and get HTML document representing the author
  HtmlDocument author = authorLink.Follow(Session).Get<htmldocument>().Body;

  // Check e-mail of author
  HtmlNode email = author.DocumentNode.SelectNodes(@"//a[@rel=""email""]").First();
  Assert.AreEqual("pp@ramonerest.dk", email.Attributes["href"].Value);

The two important pieces of code in this example are the .Anchor(...) and .Follow(...) parts that respectively instantiates a link and follows it.

Forms can be loaded and submitted in a similar fashion.

Hopefully this gives an understandable introduction to working with hyper-media in Ramone - and, even more important, I hope you find it relatively intuitive and easy to work with.

Ingen kommentarer:

Send en kommentar