NancyFX - Revisiting Content Negotiation & APIs (Part 1)

posted on 22 Apr 2013 | NancyFX

I thought I would revisit this topic since I don't believe I did it enough justice last time around, and I believe it really is important when creating an API that is going to be consumed not only by the public or client, but by you also!

When the browser asks for text/html its negotiating with the server. So really your website is an API, your Views are just an additional type of content that your API serves up when requested.

Example

Lets say you're building Twitter, the initial page shows a list of tweets, so the browser makes a call to /tweets and the server responds with a list of tweets rendered with using HTML.

Once the page has loaded, the client uses JavaScript to load new tweets, so it calls /tweets again, this time it returns a json result, and the client-side templating engine then renders and appends those to the top of the existing list, keeping the client up to date with the latest tweets.

What's nice is no new data needed to be written on the server!

Note: I'm not going to discuss RESTful API's, that would just create too many arguments.

Need code please

Right so lets see this in action for real. When I demonstrated this in my previous post, I showed a somewhat complicated Negotiate, this time I'm going to show a super simple scenario.

We have a ProductsModule, it can Get a single product, or it can Get a list of products.

public class ProductsModule : NancyModule
{
    public ProductsModule(IProductRepository productRepository)
        :base("products")
    {
        Get["/{id}"] = _ =>
        {
            var product = productRepository.Get((int)_.id);

            return product;
        };

        Get["/"] = _ =>
        {
            var products = productRepository.List();

            return products;
        };
    }
}

Using a Chrome Plugin called Postman which can be found on the Chrome Web Store.

We can invoke some calls to get some data. We specify the URL, the VERB, and add some Headers. In this case we will add a single header. Accept where we specify that we want application/xml.

When we invoke this, we get an XML result of our products:

When we do the same call but we specify the Accept header with application/json we get a JSON result.

Nice right? So far we haven't written any code other than returning some data to a endpoint we specified. Lets take a look at the first route, its a Get By Id route, and all we're returning is a single Product.

Now if we specify the Accept as something the browser would ask for, text/html we would expect rendered HTML.

Bam we get an error, but this is only because the view doesn't exist yet. By default NancyFX looks for a view of the same name of the type returned.

Since we returned a 'product', the view name that Nancy will look for is typeof(Product).Name or Product. So if we create a new product view to display some data:

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title></title>
</head>
<body>

    <div>
        Id: @Model.Id
        <br />
        Name: @Model.Name
        <br />
        Price: $@Model.Price
    </div>

</body>
</html>

Super super simple, now when we call the same route again we get:

Now we have a Single API that returns JSON, XML, or HTML.

Fiddly Issues

Here's where things get fiddly, if we want to return the collection with a view, NancyFX will attempt to convert the List<Product> to it's name, and look for the view. This ends up looking for a view by the name of List`1, which you may think will fail, but funnily enough you can actually create a view with the backquote.

So lets create a new file called List`1.sshtml and populate it with some basic HTML:

<!DOCTYPE html> 
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title></title>
</head>
<body>

    @Each
    <div>
        Id: @Current.Id
        <br />
        Name: @Current.Name
        <br />
        Price: $@Current.Price
    </div>
    <hr>
    @EndEach

</body>
</html>

Note: in case you're wondering, this isn't Razor, this is Nancy's Super Simple View Engine :)

Now if we run up the site again and hit /products we get:

Recap

So far we have created a single endpoint with very little code that can respond with JSON/XML/HTML. Next I'm going to show how Negotiate gives you more flexibility.

comments powered by Disqus