7

I have a NancyContext and I need to get a Response with a body based on the correct content negotiator for the request. I think I can use Nancy's Negotiator class to add a model, set the status, and other things. But then, I need to return a subtype of Response. So, what can I use to build the response using the Negotiator?

Here's my method:

public Response ConvertToHttpResponse(Exception exception, NancyContext context)
{
    var negotiator = new Negotiator(context)
        .WithStatusCode(HttpStatusCode.BadRequest)
        .WithReasonPhrase(exception.Message);

    return ???;
}
andyp
  • 6,229
  • 3
  • 38
  • 55
Byron Sommardahl
  • 12,743
  • 15
  • 74
  • 131
  • Hi, have a look at the section 'Controlling the negotiation' [here](https://github.com/NancyFx/Nancy/wiki/Content-Negotiation). The 2nd paragraph suggests you just return the Negotiator itself. – andyp May 05 '14 at 18:25

2 Answers2

8

I personally prefer to use the Nancy negotiator to return "Happy Path" results only (i.e. the view/jsondto returns), and then return vanilla nancy Response objects for any errors that may occur.

One way of doing this would be to return the errors directly within your module, for e.g.:

public class ProductsModule : NancyModule
{
    public ProductsModule()
        : base("/products")
    {
        Get["/product/{productid}"] = _ => 
        {
            var request = this.Bind<ProductRequest>();

            var product = ProductRepository.GetById(request.ProductId);

            if (product == null)
            {
                var error = new Response();
                error.StatusCode = HttpStatusCode.BadRequest;
                error.ReasonPhrase = "Invalid product identifier.";
                return error;
            }

            var user = UserRepository.GetCurrentUser();

            if (false == user.CanView(product))
            {
                var error = new Response();
                error.StatusCode = HttpStatusCode.Unauthorized;
                error.ReasonPhrase = "User has insufficient privileges.";
                return error;
            }

            var productDto = CreateProductDto(product);

            var htmlDto = new {
              Product = productDto,
              RelatedProducts = GetRelatedProductsDto(product)
            };

            return Negotiate
                    .WithAllowedMediaRange(MediaRange.FromString("text/html"))
                    .WithAllowedMediaRange(MediaRange.FromString("application/json"))
                    .WithModel(htmlDto)  // Model for 'text/html'
                    .WithMediaRangeModel(
                          MediaRange.FromString("application/json"), 
                          productDto); // Model for 'application/json';
        }
    }
}

This can get pretty messy though. My preferred approach is to set up my error handling "once" within my Nancy module bootstrapper, and have it catch known/expected exceptions and return them with the appropriate response object.

A simple example of a bootrapper configuration for this could be:

public class MyNancyBootstrapper : DefaultNancyBootstrapper
{
    protected override void ApplicationStartup(
        TinyIoCContainer container, IPipelines pipelines)
    {
        base.ApplicationStartup(container, pipelines);

        // Register the custom exceptions handler.
        pipelines.OnError += (ctx, err) => HandleExceptions(err, ctx); ;
    }

    private static Response HandleExceptions(Exception err, NancyContext ctx)
    {
        var result = new Response();

        result.ReasonPhrase = err.Message;

        if (err is NotImplementedException)
        {
            result.StatusCode = HttpStatusCode.NotImplemented;
        }
        else if (err is UnauthorizedAccessException)
        {
            result.StatusCode = HttpStatusCode.Unauthorized;
        }
        else if (err is ArgumentException)
        {
            result.StatusCode = HttpStatusCode.BadRequest;
        }
        else
        {
            // An unexpected exception occurred!
            result.StatusCode = HttpStatusCode.InternalServerError;    
        }

        return result;
    }
}

Using this, you can refactor your module to simply throw the appropriate exception which will invoke the correct response type. You can start to create a nice set of standards for your API in this respect. An example of this would be:

public class ProductsModule : NancyModule
{
    public ProductsModule()
        : base("/products")
    {
        Get["/product/{productid}"] = _ => 
        {
            var request = this.Bind<ProductRequest>();

            var product = ProductRepository.GetById(request.ProductId);

            if (product == null)
            {
                throw new ArgumentException(
                    "Invalid product identifier.");
            }

            var user = UserRepository.GetCurrentUser();

            if (false == user.CanView(product))
            {
                throw new UnauthorizedAccessException(
                    "User has insufficient privileges.");
            }

            var productDto = CreateProductDto(product);

            var htmlDto = new {
              Product = productDto,
              RelatedProducts = GetRelatedProductsDto(product)
            };

            return Negotiate
                    .WithAllowedMediaRange(MediaRange.FromString("text/html"))
                    .WithAllowedMediaRange(MediaRange.FromString("application/json"))
                    .WithModel(htmlDto)  // Model for 'text/html'
                    .WithMediaRangeModel(
                          MediaRange.FromString("application/json"), 
                          productDto); // Model for 'application/json';
        }
    }
}

This feels slightly cleaner to me, and now I am introducing a set of standards into my modules. :)


Something else you could consider doing, which can be especially helpful during development would be to attach a full exception report to the Content result of your error Response objects.

A basic example of this would be:

result.Contents = responseStream =>
    {
        string errorBody = string.Format(
            @"<html>
                <head>
                    <title>Exception report</title>
                </head>
                <body>
                    <h1>{0}</h1>
                    <p>{1}</p>
                </body>
              </html>",
            ex.Message,
            ex.StackTrace);

        // convert error to stream and copy to response stream
        var byteArray = Encoding.UTF8.GetBytes(errorBody);
        using (var errorStream = new MemoryStream(byteArray))
        {
            errorStream.CopyTo(responseStream);
        }
    }

Again, this is just a very basic, illustrative example, and you would have to decide if it is appropriate to your solution and then expand upon it.

ctrlplusb
  • 12,847
  • 6
  • 55
  • 57
  • I agree with you 100%, @sean, about throwing exceptions. That's actually how I'm doing it, but I'm trying to extract my exception handler out to a service and I'm running into issues negotiating the content type of the error response. If the content-type is json, I want to return the error response as json. Same for html, or xml. So, that's why I was asking originally about the `Negotiator`. Although I like your answer, I can't accept it since it didn't answer the original question. – Byron Sommardahl Jun 06 '14 at 17:12
  • I'm just looking into this myself because I'm currently doing exactly this, throwing exceptions for bad requests, but I imagine as my API scales this could be bad, and if a client has a runaway process that is making a LOT of bad requests, maybe it could bring my server to its knees. So I'm trying to figure out an elegant way to do a similar thing but without throwing exceptions. – richard Sep 07 '15 at 16:30
3

Based on your code sample, here's one possible way:

public Response ConvertToHttpResponse(Exception exception, NancyContext context, IEnumerable<IResponseProcessor> processors, Nancy.Conventions.AcceptHeaderCoercionConventions coercionConventions)
{
    var negotiator = new Negotiator(context)
        .WithStatusCode(HttpStatusCode.BadRequest)
        .WithReasonPhrase(exception.Message);

    return new DefaultResponseNegotiator(processors, coercionConventions)
        .NegotiateResponse(negotiator, context);
}

Depending on your implementation, a better way may be to have processors and coercionConventions as parameters to the class constructor, and allow the IoC container to resolve them as normal. However, in my case, I resolved them in my bootstrapper, and gave them to an extension method I created for negotiating Exception instances to an XML or JSON response.

protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines)
{
    //  Resolving outside the lambda because no more components will be registered at this point.
    var responseProcessors = container.Resolve<IEnumerable<Nancy.Responses.Negotiation.IResponseProcessor>>();
    var coercionConventions = container.Resolve<AcceptHeaderCoercionConventions>();

    pipelines.OnError += (context, exception) => 
    {
        return exception.GetErrorResponse(context, responseProcessors, coercionConventions);
    };
}
Zack Martin
  • 373
  • 4
  • 8