JakeGinnivan/WebApi.Hal — pull request #57 by wis3guy (2014-05-14 11:35:27+00:00)
Title: Added Hypermedia configuration

*A second, better thought through attempt to add automatic curies support.*

##Main design goals:
* Create a central place to store/configure links
* Have curies be added to the root resource automatically if needed
* Extract hypermedia generation out of the representation. This is particularly useful when you need dependencies, like caches or repositories, to determine uri.
* Remain fully backwards compatible
*Add an extension point for alternative ways to generate and append hypermedia

##Disclaimers:
* This is a proposal that needs work to be finalized
* No support for configuration based XML serialization as of yet, simply because i have no experience with and knowledge of the desired output.
* Proposed code is saved in a separate "Proposed" namespace for the the sake of simplicity

##Concerns
* I wonder is the proposed solution - and the same goes for the status quo - is wise, in that hypermedia is generated as late as possible, when serializing the resource.
* I feel that the `Representation` class has too many responsibilities.
* I feel that the way the `Representation.Embedded` property is filled can be improved upon.

##Proposed approach
###In words
As a consumer of the library i create my resource objects, as simple poco's which derive from the `Representation` base class. Then, at application startup (where you would typically configure your IoC container) you new up an instance of `HypermediaConfigurationBuilder` and configure:

* Self links
* Other links
* Curies namespaces
* Hypermedia appenders

Particularly interesting are the `HypermediaAppender` derived classes here. Their purpose is, as the name implies, to append hypermedia to a resource.

That's all there is to it!

###In code
Create simple `Representation` derived classes:
```
public class ProductRepresentation : Representation
{
public int Id { get; set; }
public string Title { get; set; }
public double Price { get; set; }
public CategoryRepresentation Category { get; set; }
}

public class CategoryRepresentation : Representation
{
[JsonIgnore]
public int Id { get; set; }

public string Title { get; set; }
}
```

Create `HypermediaAppender` derived classes:

```
public class ProductRepresentationHypermediaAppender : HypermediaAppender
{
public override void Append(IEnumerable configured)
{
foreach (var link in configured)
{
switch (link.Rel)
{
case Link.RelForSelf:
Resource.Links.Add(link.CreateLink(new { id = Resource.Id }));
break;
case "example-namespace:category":
Resource.Links.Add(link.CreateLink(new {id = "Action Figures"}));
break;
case "example-namespace:related-product":
for (var i = 0; i < 3; i++)
Resource.Links.Add(link.CreateLink(new { id = string.Format("related-product-{0:00}", i) }));
break;
case "example-namespace:product-on-sale":
for (var i = 0; i < 3; i++)
Resource.Links.Add(link.CreateLink(new { id = string.Format("product-on-sale-{0:00}", i) }));
break;
default:
Resource.Links.Add(link); // append untouched ...
break;
}
}
}
}

public class CategoryRepresentationHypermediaAppender : HypermediaAppender
{
public override void Append(IEnumerable configured)
{
foreach (var link in configured)
{
switch (link.Rel)
{
case Link.RelForSelf:
Resource.Links.Add(link.CreateLink(new { id = Resource.Id }));
break;
default:
Resource.Links.Add(link); // append untouched ...
break;
}
}
}
}
```

Note that in this case we simply generate 3 related product links and sale links. In reality, we would probably want to consult an external API or a data store (likely cached) to generate these.

At application startup build your configuration:

```
var curiesLink = new CuriesLink("example-namespace", "http://api.example.com/docs/{rel}");
var productLink = new Link("example-namespace:product", "http://api.example.com/products/{id}");
var relatedProductLink = new Link("example-namespace:related-product", productLink.Href);
var saleProductLink = new Link("example-namespace:product-on-sale", "http://api.example.com/products/sale/{id}");
var categoryLink = new Link("example-namespace:category", "http://api.example.com/categories/{id}");

var builder = new HypermediaConfigurationBuilder();

builder.RegisterAppender(new ProductRepresentationHypermediaAppender());
builder.RegisterAppender(new CategoryRepresentationHypermediaAppender());
builder.RegisterCuries(curiesLink);
builder.RegisterSelf(productLink);
builder.RegisterSelf(categoryLink);
builder.RegisterLinks(relatedProductLink, saleProductLink);

config = builder.Build();

GlobalConfiguration.Configuration.Formatters.Add(new JsonHalMediaTypeFormatter(config));
GlobalConfiguration.Configuration.Formatters.Add(new XmlHalMediaTypeFormatter(config));
```

Of interest here is that the `MediaTypeFormatter` classes take a parameter of type `IHypermediaConfiguration`, which can easily be implemented to facilitate an alternative hypermedia creation mechanism.

Note that if you do not want to use the new approach, you can still suffice with:

```
GlobalConfiguration.Configuration.Formatters.Add(new JsonHalMediaTypeFormatter());
GlobalConfiguration.Configuration.Formatters.Add(new XmlHalMediaTypeFormatter());
```