Skip to content

Hypermedia configuration, container and appenders

Geoffrey Braaf edited this page Jul 30, 2014 · 1 revision

Hypermedia configuration, container and appenders

Hypermedia appenders are an alternative way, within WebApiHal, to append hypermedia (Link instances) to your Representation derived instances. The idea is that you configure a dedicated IHypermediaAppender<T> implementation for each of your representations. As a result, your Representation instances will only represent state, while the logic for determining and appending hypermedia is taken care of by a separate focused class.

public class BeerRepresentation : Representation
{
    public int Id { get; set; }
    public string Name { get; set; }

    public int? BreweryId { get; set; }
    public string BreweryName { get; set; }

    public int? StyleId { get; set; }
    public string StyleName { get; set; }

    [JsonIgnore]
    public List<int> ReviewIds { get; set; }
}

public class BeerHypermediaAppender : IHypermediaAppender<BeerRepresentation>
{
    public void Append(BeerRepresentation resource, IEnumerable<Link> configured)
    {
        foreach (var link in configured)
        {
            switch (link.Rel)
            {
                case "style":
                    if (resource.StyleId != null)
                        resource.Links.Add(link.CreateLink(new {id = resource.StyleId}));
                    break;
                case "brewery":
                    if (resource.BreweryId != null)
                        resource.Links.Add(link.CreateLink(new { id = resource.BreweryId }));
                    break;
                case "review":
                    if (resource.ReviewIds != null)
                    {
                        foreach (var reviewId in resource.ReviewIds)
                            resource.Links.Add(link.CreateLink(new {id = reviewId}));
                    }
                    break;
            }
        }
    }
}

Note that there might be more links configured than the ones that are caught in the above switch statement. Based on whether we know/have access to the appropriate parameters, we should add these as well.

// append as a template, given we do not have the parameters
resource.Links.Add(link); 

// append as a concrete link, given we do know the parameters
resource.Links.Add(link.CreateLink(/* parameters */)); 

Now that we have the hypermedia appending logic extracted out of our Representation classes, it is also possible to inject things into the constructor of our appenders, as needed. One possible use case could be: Only append the link for the brewery if it is a preferred brewery Given our BeerRepresentation does not hold that kind of information, we might need to query a data store of some sort to find that out.

While they can be very useful, use external dependencies with care! The Append method is called every time an instance (possibly embedded ) of a Representation is serialised and returned to the client. As such, actions like querying a data store upon serialisation might not be a good idea, considering performance consequences and error handling. It is highly recommended you use properties on your Representation to determine how to append hypermedia as much as possible. If that means you need certain pieces of information that you do not want to show up in your response, either decorate them with the [JsonIgnore] attribute or mark them as internal.

Hypermedia configuration

The logic behind hypermedia appenders is mostly implemented in the internal HypermediaContainer class. This class, besides implementing the public IHypermediaContainer interface, also implements the IHypermediaConfiguration interface. It is an instance of the latter interface which is injected into an overloaded constructor of the JsonHalMediaTypeFormatter.

You can simply roll your own implementation and inject that at application start if you like. Perhaps you do not like the concept of hypermedia appenders, or perhaps you'd prefer to directly interrogate a datastore for your hypermedia mappings. Just implement the interface and your are good to go.

//
// Implement this interface:

public interface IHypermediaConfiguration
{
    /// <summary>
    /// Configures the passed in resource by appending all the necesarry hypermedia to its 
    /// <see cref="IResource.Links"/> collection
    /// </summary>
    /// <param name="resource">Resource to configure</param>
    void Configure(IResource resource);
}

And then in your WebApiConfig.cs you'd write something like:

config.Formatters.Add(new JsonHalMediaTypeFormatter(new CustomHypermediaConfiguration()));

Hypermedia Container

If you decide to work with hypermedia appenders, then the hypermedia container is the central place where everything about your hypermedia is registered and resolved. Using the HypermediaContainerBuilder you configure all hypermedia and subsequently build an instance of IHypermediaContainer.

Having all there is to know about your hypermedia in a single central place we gain several advantages:

  • We can append CURIES links automatically
  • We have a single object we can inject into our business logic which knows about our hypermedia. A simple scenario where this would be useful, is when you ant to return a HTTP 302 response, redirecting the user to another resource.
  • We no longer need the static LinkTemplates class

A typical WebApiConfig.cs would look like this:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        ...
        
        var hypermediaConfiguration = BuildHypermediaConfiguration();

        config.Formatters.Add(new JsonHalMediaTypeFormatter(hypermediaConfiguration));
        config.Formatters.JsonFormatter.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
        config.Formatters.Remove(config.Formatters.XmlFormatter);

        return hypermediaConfiguration;
        ...
    }

    private static IHypermediaContainer BuildHypermediaConfiguration()
    {        
        var builder = new HypermediaContainerBuilder();

	//
	// Define the self-links
	
	var curie = new CuriesLink("beerco", "http://api.beerco.com/docs{?rel}");
	
	var rootLink = curie.CreateLink<RootRepresentation>("root", "~/");
	var breweryListLink = curie.CreateLink<BreweryListRepresentation>("breweries", "~/breweries");
	var breweryLink = curie.CreateLink<BreweryRepresentation>("brewery", "~/breweries/{id}");
	var beerListLink = curie.CreateLink<BeerListRepresentation>("beers", "~/beers");
	var beerLink = curie.CreateLink<BeerRepresentation>("beer", "~/beers/{id}");
	
	var helpLink = new Link("help", "http://www.iana.org/assignments/link-relations/link-relations.xhtml");
	
	//
	// Register things with the container
	
	builder.Register(rootLink, breweryListLink, beerListLink, helpLink);
	builder.Register(breweryListLink, rootLink, beerListLink);	builder.Register(beerListLink, rootLink, breweryListLink);
	builder.Register(breweryLink, new BreweryHypermediaAppender(), breweryListLink);
	builder.Register(beerLink, new BeerHypermediaAppender(), beerListLink, breweryLink);
	
	return builder.Build();	
}

There are several things of interest here.

  • Note how we deliberately remove XML support as there is no support for the central hypermedia configuration yet.
  • Note how the HyperMediaContainer, which implements the HypermediaConfiguration interface is injected into the MediaTypeFormatter.
  • Note how we first create a CURIES link and then create links of off it. This allows the hyper media appenders to figure out what CURIES link to add to your root Representation
  • Note how we mix CURIED and named links.
  • Note how we register the self-link, the appender, and any other links in one method call. This is achieved by some extension methods. You could separately register each class of hypermedia, but why would you!?!
  • Note how not all registrations have a dedicated hypermedia appender as their 2nd argument. This is because a default appender, which simply appends all configured links, without any further logic, will be registered automatically for you.

Important notes

  • The current implementation of both hypermedia appenders and the central hypermedia configuration have only been applied to the JsonHalMediaTypeFormatter and are not (yet) available in the XmlHalMediaTypeFormatter
  • Although the introduction of both the hypermedia appenders and the central hypermedia configuration occur at the same time, they are not mutually dependent. The implementation of hypermedia appenders relies on the possibility to instruct the JsonHalMediaTypeFormatter to use the centra hypermedia configuration, but not the other way around. If you chose to roll your own IHypermediaConfiguration instance, you do not need to bother with hypermedia appenders.
  • If you already have a working API that implements the CreateHypermedia method on your Representation classes you cannot benefit from either hypermedia appenders or the central hypermedia configuration. It is impossible to use both methods of appending hypermedia.