Links, absolute URI's and JSON rewriting
Serialization
As part of the design for the resources being returned from our API we wanted to ensure they included some useful information:
- An absolute link to themselves
- Links to other entities
- List of available expansions, and expanded properties (covered in the next part of this series)
So we are looking to return entities that look like this:
{
"Id": "5b2b0ad0-5371-4abf-a661-9f410088925f",
"UserName": "joeb",
"Email": "joe.bloggs@test.com",
"FirstName": "Joe",
"LastName": "Bloggs",
"Expands": [
"Groups"
],
"Self": "http://localhost:29840/api/user/5b2b0ad0-5371-4abf-a661-9f410088925f",
"Links": [
{
"Title": "Group Memberships",
"Href": "http://localhost:29840/api/user/5b2b0ad0-5371-4abf-a661-9f410088925f/groups",
"Rel": "Groups"
}
]
}
This inevitably means creating some kind of view model that you return from your API as the representation of the underlying resource (entity, aggregate etc.)
After some experimentation we landed on the idea of leveraging the capabilities of JSON.Net to perform on-the-fly JSON rewriting of our serialized entities.
This meant deriving our view models from this base class, and implementing the abstract "Self" property (to return a link to the entity itself) as well as supporting the links and expansions.
public abstract class AbstractModel
{
public const string ExpansionsProperty = "__expansions__";
public const string SelfProperty = "__self__";
public const string LinksProperty = "__links__";protected AbstractModel()
{
Expansions = new Dictionary();
Links = new List();
}[JsonProperty(SelfProperty, NullValueHandling = NullValueHandling.Ignore)]
public abstract string Self { get; }[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string[] Expands { get; set; }[JsonProperty(ExpansionsProperty, NullValueHandling = NullValueHandling.Ignore)]
public IDictionary Expansions { get; set; }[JsonProperty(LinksProperty, NullValueHandling = NullValueHandling.Ignore)]
public IList Links { get; set; }
}
Each link, was then also represented by a LinkModel, which was a very simple class:
public class LinkModel
{
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string Title { get; set; }
public bool Inline { get; set; }
public string Href { get; set; }
public string Rel { get; set; }
}
If you look closely at the abstract model class above you will see the properties use the names __expansions__, __self__ and __links__ - so initially when JSON.Net is used to serialize the entity, that's the name we get.
We then extend the existing JsonMediaTypeFormatter to perform serialization to JToken and then rewrite the token. This is an abstract class:
public abstract class AbstractRewritingJsonMediaTypeFormatter : JsonMediaTypeFormatter
{
protected abstract JToken Rewrite(JToken content);public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext)
{
if (type == null) throw new ArgumentNullException("type");
if (writeStream == null) throw new ArgumentNullException("writeStream");if (UseDataContractJsonSerializer)
{
return base.WriteToStreamAsync(type, value, writeStream, content, transportContext);
}return TaskHelpers.RunSynchronously(() =>
{
Encoding effectiveEncoding = SelectCharacterEncoding(content == null ? null : content.Headers);JsonSerializer jsonSerializer = JsonSerializer.Create(SerializerSettings);
using (var tokenWriter = new JTokenWriter())
{
jsonSerializer.Serialize(tokenWriter, value);JToken token = tokenWriter.Token;
JToken rewrittenToken = Rewrite(token);
using (var jsonTextWriter = new JsonTextWriter(new StreamWriter(writeStream, effectiveEncoding)) {CloseOutput = false})
{
if (Indent)
{
jsonTextWriter.Formatting = Formatting.Indented;
}rewrittenToken.WriteTo(jsonTextWriter);
jsonTextWriter.Flush();
}
}
});
}
}
Which we then have a concrete implementation of:
public class JsonNetFormatter : AbstractRewritingJsonMediaTypeFormatter
{
readonly IUrlTransformer _urlTransformer;
readonly ExpandsRewriter _expandsRewriter;
readonly SelfRewriter _selfRewriter;
readonly LinksRewriter _linksRewriter;public JsonNetFormatter(IUrlTransformer urlTransformer)
{
if (urlTransformer == null) throw new ArgumentNullException("urlTransformer");
_urlTransformer = urlTransformer;
_expandsRewriter = new ExpandsRewriter();
_selfRewriter = new SelfRewriter(_urlTransformer);
_linksRewriter = new LinksRewriter(_urlTransformer);
}protected override JToken Rewrite(JToken token)
{
_expandsRewriter.Rewrite(token);_selfRewriter.Rewrite(token);
_linksRewriter.Rewrite(token);
return token;
}
}
By doing this we can then implement simple visitors which can rewrite the JSON on the fly looking for those special token names - so for example, in our links above we have a property
public bool Inline { get; set; }
If Inline is true, we actually "in-line" the link into the body of the representation (using Rel as the name of the property), but if the link is not inline, we include in the set of links.
This rewriting process also takes care of rewriting relative API URL's to be absolute, so controllers largely don't need to care about resolving absolute URLs within representations themselves.
JSON Rewriting does bring a cost with it, but so far we have found the cost to be very low (as we are not transforming JSON strings, but just serializing directly to tokens first, then converting the tokens to a string - this avoids the need to delve into reflection to achieve the same results.
Links
Our linking implementation is largely bespoke, but mirror's that of a hyperlink within a web page. Initially we just had a Rel and Href property, but after a while adopted a Title as well (So similar to The netflix links representation in XML).
Though REST as a term is used to describe the "type" of API that is implemented, in fact the API (like most) falls into the camp of "REST'ish" as opposed to RESTful - though personally a fan of HATEOAS, in this case it's a trait we would like to move closer towards, but is certainly not a constraint our API must fulfill before we make it available for consumption.
There are some standards/proposals out there for links within JSON responses, but largely the impact would have been mostly negative to the consumption of the API, by making the representations more internally inconsistent in naming style etc. and for little gain, as the proposed standards don't make implementing the client any simpler at this stage.
The Links collection can be populated by external resources, but largely we have the the model itself populate the set of available links upon construct.
Collection results
When returning collection results, if a collection was page (more on paging in a future post we we look at querying/OData) we also aimed to return the following links as part of the response:
* First page
* Last page
* Next page
* Previous page
The IANA provides a list of well-known link relationships including "first", "next", "last" and "prev" - so we adopted those values for the links in the API.
public class QueryResults: AbstractModel
{
public override string Self
{
get;
}public int? Skip { get; set; }
public int? Top { get; set; }
public int? Total { get; set; }
public IList
Items { get; set; } public QueryResults
SetSelfAndGenerateLinks(string self)
{
...
}public QueryResults
SetSelfAndGenerateLinks(Uri uri)
{
..
}protected void AddLink(string rel, Uri uri, int start)
{
..
}
}
Notice we have a method for setting the Self URL in this case - this is because in the case of a query result the code returning the set of query results may be decoupled from the controller where knowledge of the current request URI exists.
Within the call to SetSelfAndGenerateLinks we have this code for adding the links, based on where we are at in the set of results.
if (inMiddle || atStart)
{
AddLink(StandardRelations.Next, uri, Math.Max(0, Skip.Value + Top.Value));
AddLink(StandardRelations.Last, uri, Math.Max(0, Total.Value - Top.Value));
}if (inMiddle || atEnd)
{
AddLink(StandardRelations.Previous, uri, Math.Max(0, Skip.Value - Top.Value));
AddLink(StandardRelations.First, uri, 0);
}
And then when the request is rendered we might end up with a response that looks like this:
{
"Skip": 20,
"Top": 25,
"Total": 45,
"Items": [
...
],
"Self": "http://localhost:29840/api/search?tql=Name+~+test&$skip=20&$top=25",
"Links": [
{
"Href": "http://localhost:29840/api/search?tql=Name+~+test&$skip=0&$top=25",
"Rel": "prev"
},
{
"Href": "http://localhost:29840/api/search?tql=Name+~+test&$skip=0&$top=25",
"Rel": "first"
}
]
}
Providing links like this can really simplify the implementation of clients and allows us to potentially change URI structure without causing as many headaches for API consumers.
Next
Next, in part 2 of this series we take a look at the implementation of Expand in the API, and how we mapped our entities to View models for the API.