September 5, 2012

Generating Hypermedia links in ASP.NET Web API

Over the past few weeks I’ve been developing an API for fabrik, the portfolio and blogging platform I launched last year.

This has been a great way to learn both ASP.NET Web API (Microsoft’s new way for creating HTTP services) and REST.

One design principle of REST, or specifically Hypermedia that I wanted to adhere to was to include links in my resource representations. To quote Wikipedia:

RESTful interaction is driven by hypermedia, rather than out-of-band information.

This was also necessary as I wanted to support AtomPub which dictates each Atom entry must provide an “edit” link so that clients know how to update entries.

So how can we achieve this in ASP.NET Web API, especially as my default response format is JSON, which has no concept of hyperlinks.

First I created the following a base class for all my Resource representations:

public abstract class Resource
{
    private readonly List<Link> links = new List<Link>();

    [JsonProperty(Order = 100)]
    public IEnumerable<Link> Links { get { return links; } }

    public void AddLink(Link link)
    {
        Ensure.Argument.NotNull(link, "link");
        links.Add(link);
    }

    public void AddLinks(params Link[] links)
    {
        links.ForEach(link => AddLink(link));
    }
}

The Link class is an abstract class for defining link relations. Following an idea I borrowed from one of Darrel Miller’s projects, I created a concrete Link class for each representation type that I needed:

/// <summary>
/// A base class for relation links
/// </summary>
public abstract class Link
{
    public string Rel { get; private set; }
    public string Href { get; private set; }
    public string Title { get; private set; }

    public Link(string relation, string href, string title = null)
    {
        Ensure.Argument.NotNullOrEmpty(relation, "relation");
        Ensure.Argument.NotNullOrEmpty(href, "href");

        Rel = relation;
        Href = href;
        Title = title;
    }
}

/// <summary>
/// Refers to a resource that can be used to edit the link's context.
/// </summary>
public class EditLink : Link
{
    public const string Relation = "edit";

    public EditLink(string href, string title = null)
        : base(Relation, href, title)
    {
    }
}

I prefer this approach to using a generic Link class as the consumer doesn’t need to know the valid RFC 5988 relation types.

With this done, we can now add links to our responses like so:

public Post Get(int id) 
{
	var post = Session.Load<Domain.Post>(id);
	var response = Mapper.Map<Post>(post);
	response.AddLink(new EditLink(Url.Link(...)));
	return response;
}

I’ve ommitted the Url.Link parameters for brevity, since ASP.NET Web API makes link generation sooo long-winded.

Here’s an example response:

{
    "id": 65,
    "siteId": 1,
    "title": "Better routing in ASP.NET",
    "slug": "better-routing-in-aspnet",
    "contentType": "text",
    "tags": [
        {
            "value": "ASP.NET",
            "slug": "aspnet"
        },
        {
            "value": "Routing",
            "slug": "routing"
        },
        {
            "value": "Web API",
            "slug": "web-api"
        }
    ],
    "published": true,
    "lastUpdated": "2012-09-03T17:20:12.4636911Z",
    "publishDate": "2012-09-03T17:20:12.4046878Z",
    "media": [ ],
    "links": [
        {
            "rel": "self",
            "href": "http://localhost:64511/sites/1/posts/65"
        },
        {
            "rel": "edit",
            "href": "http://localhost:64511/sites/1/posts/65"
        },
        {
            "rel": "edit-media",
            "href": "http://localhost:64511/sites/1/posts/65/media"
        }
    ]
}

As I started to add links to all my API resources I found that my once tidy controllers were becoming quite bloated. Since I returned the same resource classes in a number of places, I also had a lot of duplicated code.

To begin with I created extension methods to limit the duplication but was still not happy. Ideally I would have AutoMapper create the links for me when it maps my domain objects to their API representation, but this is difficult when you need access to contextual information (namely, HttpRequestMessage).

What we needed to do was intercept the response before it is returned to the client and add any necessary relation links. My solution to this took inspiration from something I’ve done with ASP.NET MVC, enriching view models.

The principle is that whenever we return view models with meta-data (like a list of Countries) we could enrich them with the necessary data before they were returned. This was a great solution to when ModelState validation failed and we needed to repopulate all the select lists on a form.

So let’s apply the same concept to ASP.NET Web API.

IResponseEnricher is an interface for any class that wants to enrich a response:

public interface IResponseEnricher
{
    bool CanEnrich(HttpResponseMessage response);
    HttpResponseMessage Enrich(HttpResponseMessage response);
}

Note that we have full access to the HttpResponseMessage so manipulating resource representations is just one of the things you can do here.

The CanEnrich method is the way that an IResponseEnricher implementation can “Opt in” to enriching a response.

Since, in my case I wanted to add links to my concrete resource classes, the ObjectContentResponseEnricher base class takes care of getting the object from the response:

public abstract class ObjectContentResponseEnricher<T> : IResponseEnricher
{
    public virtual bool CanEnrich(Type contentType)
    {
        return contentType == typeof(T);
    }

    public abstract void Enrich(T content);
    
    bool IResponseEnricher.CanEnrich(HttpResponseMessage response)
    {
        var content = response.Content as ObjectContent;
        return (content != null && CanEnrich(content.ObjectType));
    }

    HttpResponseMessage IResponseEnricher.Enrich(HttpResponseMessage response)
    {           
        T content;
        if (response.TryGetContentValue(out content))
        {
            Enrich(content);
        }

        return response;
    }
}

So to create an enricher that adds links to a Post representation we create the following class:

public class PostResponseEnricher : ObjectContentResponseEnricher<Post>
{
    public override void Enrich(Post post)
    {   
        post.AddSelfAndEditLinks(GetUrlHelper().Link("SiteApi", new { controller = "posts", siteId = post.SiteId, id = post.Id }));
        post.AddLink(new EditMediaLink(GetUrlHelper().Link("PostMedia", new { controller = "postmedia", siteId = post.SiteId, postId = post.Id })));

        post.Media.ForEach(mediaItem => 
            mediaItem.AddLink(new EditLink(GetUrlHelper().Link("PostMedia", new { controller = "postmedia", siteId = post.SiteId, postId = post.Id, id = mediaItem.Id })))
        );
    }
}

So now we have a way of creating “enrichers”, how do we plug them into the Request/Response pipeline? A message handler is perfect for the job:

public class EnrichingHandler : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        return base.SendAsync(request, cancellationToken)
            .ContinueWith(task =>
            {
                var response = task.Result;
                var enrichers = request.GetConfiguration().GetResponseEnrichers();

                return enrichers.Where(e => e.CanEnrich(response))
                    .Aggregate(response, (resp, enricher) => enricher.Enrich(response));
            });
    }
}

This handler simply passes the response through all the enrichers that want to enrich it.

To set up the enrichers we have a few extension methods on GlobalConfiguration:

public static class ResponseEnricherConfigurationExtensions
{
    public static void AddResponseEnrichers(this HttpConfiguration config, params IResponseEnricher[] enrichers)
    {
        foreach (var enricher in enrichers)
        {
            config.GetResponseEnrichers().Add(enricher);
        }
    }
    
    public static Collection<IResponseEnricher> GetResponseEnrichers(this HttpConfiguration config)
    {
        return (Collection<IResponseEnricher>)config.Properties.GetOrAdd(
                typeof(Collection<IResponseEnricher>),
                k => new Collection<IResponseEnricher>()
            );
    }
}

We then execute the following when our application starts (WebApiConfig is a good place):

// enrichers
config.AddResponseEnrichers(
        new PageResponseEnricher(),
        new PostArchiveResponseEnricher(),
        new PostResponseEnricher(),
        new PostTagsResponseEnricher(),
        new ProjectCategoryResponseEnricher(),
        new ProjectResponseEnricher(),
        new ProjectTagsResponseEnricher(),
        new SiteResponseEnricher()
    );

I’m really pleased with this solution. It provides a nice way of adding addition meta-data to a response without bloating your controllers.

I’ll be adding the source code to GitHub in the next few days.

[Update]

Source code now available on GitHub.

© 2022 Ben Foster