Last Updated: September 29, 2021
·
11.12K
· danhaller

Nancy content negotiation

Nancy, the lightweight .NET web framework, recently implemented automatic content negotiation in version 0.12.0. This means that you just need to return your model, and Nancy will do the serialising based upon the Accept header supplied with the request. Petty cool. The documentation hasn't quite caught up with the code yet, so I thought I'd write about one or two things that I had to discover to get my automatic negotiation working nicely.

How it works

Nancy registers a couple of classes (XmlProcessor, JsonProcessor) that implement IResponseProcessor. IResponseProcessors tell Nancy whether they can satisfy a particular Accept header (CanProcess), and how to satisfy that header (Process). When a request comes in, Nancy asks each IResponseProcessor whether it can satisfy the request, selects those that can, and uses the first to serialise the response.

Changing the default response content

I found that if an Accept header is not specified, Nancy will return JSON. I wanted this to be XML. What actually happens when an Accept header is not specified is that the Accept header is changed to "*/*", thus all of the IResponseProcessors signal that they can serialise the response. So it just comes down to the ordering then IResponseProcessor collection. Fortunately, like most things in Nancy, the IResponseProcessors can be configured.

By creating a custom bootstrapper, you can override the order of the ResponseProcessors:

public class CustomResponseProcessorBootstrapper: DefaultNancyBootstrapper
{
  protected override NancyInternalConfiguration InternalConfiguration 
  { 
    get
    {
      return NancyInternalConfiguration.WithOverrides((c) =>
      {                 
        c.ResponseProcessors.Remove(typeof (JsonProcessor));
        c.ResponseProcessors.Insert(c.ResponseProcessors.Count,       typeof(JsonProcessor));
      });
    } 
  }
}

Here, I have taken out the JsonProcessor which was at the top of the list, and moved it to the bottom.

edit 08/04/2013: For better protection from changes, it's probably better to clear the ResponseProcessors collection and explicitly add exactly which processors you want. I say this because since writing this post Nancy includes a ViewProcessor which is now the top processor in the list.

Now, when an accept doesn't have an Accept header, the XmlProcessor is picked first. You can also see the potential here for easily adding your own custom IResponseProcessors if XML or JSON aren't your thing. If you want to change anything in Nancy, the NancyInternalConfiguration is always a good place to start. As a bonus, you can do something similar to switch out the default Json serialiser (based on JavascriptSerializer) with Nancy.Serialization.JsonNet (based on JSON.Net):

public class CustomResponseProcessorBootstrapper: DefaultNancyBootstrapper
{
    protected override NancyInternalConfiguration InternalConfiguration 
    { 
        get
        {
            return NancyInternalConfiguration.WithOverrides((c) =>
                {
                    c.Serializers.Remove(typeof (DefaultJsonSerializer));
                    c.Serializers.Insert(c.Serializers.Count, typeof(JsonNetSerializer));

                    c.ResponseProcessors.Remove(typeof (JsonProcessor));
                    c.ResponseProcessors.Insert(c.ResponseProcessors.Count, typeof(JsonProcessor));
                });
        } 
    }
}

By the way, your custom bootstrapper will be picked up automatically.

Changing the response

Before automatic content negotiation you would return a response object, setting properties such as the http status code:

return new Response
{
    StatusCode = HttpStatusCode.OK
    ContentType = "text/html",
    Contents = StreamText(message)
};

By letting Nancy deal with the content negotiation by returning an object, you are also letting it deal with the response object. By default, the response code will be a 200 ok. You can override this though by calling the negotiator yourself:

return Negotiate
.WithModel(model)
.WithStatusCode(HttpStatusCode.BadRequest)

There are also a number of other methods which should allow you to modify the response to whatever you may want. Here's an example lifted from Nancy's demos:

public MainModule(IRouteCacheProvider routeCacheProvider)
{
  Get["/negotiated"] = parameters => {
     return Negotiate
       .WithModel(new RatPack {FirstName = "Nancy "})
       .WithMediaRangeModel("text/html", new RatPack {FirstName = "Nancy fancy pants"})
       .WithView("negotiatedview")
       .WithHeader("X-Custom", "SomeValue");
    };
}

I'm new to Nancy but I've enjoyed using it so far. You can get an API up and running very quickly, and you can extend or configure it when you need to. The automatic content negotiation fits right into that style.