Introduction

I already wrote about making custom errorpages in Nancy a few months ago. And I used some static htm pages for those and a stream to get them to show on your screen.

This was all fine and dandy but I’m sure I can do better.

Using RenderView

First of all I can make use of the Nancy View Rendereing and make my errorpages RazorViews like the rest of them.

So I moved 404.htm and 500.htm to the Views folder in a subfolder errorpages.

And then changed the CustomErros.vb file to this.

Imports Nancy
Imports Nancy.ErrorHandling
Imports Nancy.ViewEngines

Namespace ErrorPages

    Public Class CustomErrors
        Inherits DefaultViewRenderer
        Implements IStatusCodeHandler

        Private ReadOnly _errorPages As IDictionary(Of HttpStatusCode, String)
        Private ReadOnly _supportedStatusCodes As HttpStatusCode() = {HttpStatusCode.NotFound, HttpStatusCode.InternalServerError}

        Public Sub New(factory As IViewFactory)
            MyBase.New(factory)
            _errorPages = New Dictionary(Of HttpStatusCode, String) From
                {
                {HttpStatusCode.NotFound, "errorpages/404"},
                {HttpStatusCode.InternalServerError, "errorpages/500"}
                }
        End Sub

        Public Sub Handle(statusCode As HttpStatusCode, context As NancyContext) Implements IStatusCodeHandler.Handle
            Dim errorPage As String
            If Not _errorPages.TryGetValue(statusCode, errorPage) Then
                Return
            End If
            If String.IsNullOrEmpty(errorPage) Then
                errorPage = "500"
            End If

            Dim response = RenderView(context, errorPage)
            response.StatusCode = statusCode
            context.Response = response
        End Sub

        Public Function HandlesStatusCode(statusCode As HttpStatusCode, context As NancyContext) As Boolean Implements IStatusCodeHandler.HandlesStatusCode
            Return _supportedStatusCodes.Any(Function(s) s = statusCode)
        End Function

    End Class
End Namespace```
And that’s all the code you need.

The above will return a 404 if needed and a 500 on any other exception. YOu could probably make that better.

Now there is a problem with the above. It will always return a htm page even if the people on the other side request json.

I found that code [here][2] BTW. 

## Making the response return json if needed

I did the above because I [read this by Paul Stovell][3]

So first thing to do was to create my ErrorResponse Clas, like Paul does.

```vbnet
Imports Nancy
Imports Nancy.Responses

Namespace ErrorPages
    Public Class ErrorResponse
        Inherits JsonResponse

        Private ReadOnly _errorClass As ErrorClass

        Private Sub New(errorClass As ErrorClass)
            MyBase.New(errorClass, New DefaultJsonSerializer)
            _errorClass = errorClass
        End Sub

        Public ReadOnly Property ErrorMessage As String
            Get
                Return _errorClass.ErrorMessage
            End Get
        End Property

        Public ReadOnly Property FullException As String
            Get
                Return _errorClass.FullException
            End Get
        End Property

        Public Shared Function FromMessage(message As String) As ErrorResponse
            Dim response = New ErrorResponse(New ErrorClass With {.ErrorMessage = message})
            response.StatusCode = HttpStatusCode.InternalServerError
            Return response
        End Function

        Public Shared Function FromException(ex As Exception) As ErrorResponse
            Dim response = New ErrorResponse(New ErrorClass With {.ErrorMessage = ex.Message, .FullException = ex.ToString()})
            response.StatusCode = HttpStatusCode.InternalServerError
            Return Response
        End Function
    End Class
End Namespace```
And then we need to make sure our Errors are now in that Response format. For that I will Cut into the OnError pipleine via our custom bootstrapper and add something to that pipleine. Like so.

```vbnet
Imports BecareServices.Constants
Imports BecareServices.ErrorPages
Imports Nancy.Bootstrapper
Imports Nancy.Elmah
Imports Nancy
Imports Nancy.Security
Imports BecareServices.BeCareApi.Session
Imports Nancy.ViewEngines

Public Class BootStrapper
    Inherits DefaultNancyBootstrapper

    Protected Overrides Sub ApplicationStartup(container As TinyIoc.TinyIoCContainer, pipelines As Nancy.Bootstrapper.IPipelines)
        MyBase.ApplicationStartup(container, pipelines)
        ConfigurePipelines(pipelines)
    End Sub

    Public Shared Sub ConfigurePipelines(pipelines As IPipelines)
        pipelines.OnError.AddItemToEndOfPipeline(Function(x, ex)
                                                     Log.ErrorException(ex.Message, ex)
                                                     Return ErrorResponse.FromException(ex)
                                                 End Function)
    End Sub

End Class

Now why did I not use a lambda there, I hear you think? I’ll come to that in a minute. I first have to adapt my customerrors class again. But only the handle method.

```vbnet Public Sub Handle(statusCode As HttpStatusCode, context As NancyContext) Implements IStatusCodeHandler.Handle Dim errorPage As String If Not _errorPages.TryGetValue(statusCode, errorPage) Then Return End If If String.IsNullOrEmpty(errorPage) Then errorPage = “500” End If

        If statusCode = HttpStatusCode.NotFound Then
            context.Response = ErrorResponse.FromMessage("The resource you requested could not be found.").WithStatusCode(HttpStatusCode.NotFound)
        End If

        If context.Request.Headers.Accept.Count = 0 OrElse context.Request.Headers.Accept.Where(Function(x) x.Item1 = "text/html").Count = 1 Then
            Dim response = RenderView(context, errorPage, context.Response)
            response.StatusCode = statusCode
            context.Response = response
        Else
            Dim response = context.Response
            response.StatusCode = statusCode
            context.Response = response
        End If
    End Sub```

I now get html when asked or when the mediarange is empty and I get json in all other cases. At least now it is easy for the user of our services to see what the error was.

Now why did I make that ugly extra method in the bootstrapper.

Because I don’t want to write end to end tests to test if that works. I wanted to write some simpler tests to see if the above worked.

And I did. And I wanted to make sure I got the same pipeline as in my implementation.

And here are a bunch of tests.

```vbnet Imports BecareServices.ErrorPages Imports FakeItEasy Imports BecareServices.Test.Modules Imports Nancy.Localization Imports Nancy Imports Nancy.Responses.Negotiation Imports NUnit.Framework Imports Nancy.Testing Imports Nancy.ViewEngines.Razor

Namespace ErrorPages Public Class TestCustomErrors Private _browser As Browser

    <SetUp()>
    Public Sub FixtureSetup()
        Dim configuration = A.Fake(Of IRazorConfiguration)()
        Dim textResource = A.Fake(Of ITextResource)()
        Dim bootstrapper = New ConfigurableBootstrapper(Sub(config)
                                                            config.Module(Of ErrorModule)()
                                                            config.RootPathProvider(Of RootPathProvider)()
                                                            config.Dependency(Of ITextResource)(textResource)
                                                            config.ApplicationStartup(Sub(x, pipelines)
                                                                                          BeCareServices.BootStrapper.ConfigurePipelines(pipelines)
                                                                                      End Sub)
                                                            config.StatusCodeHandler(Of CustomErrors)()
                                                            config.ViewEngine(New RazorViewEngine(configuration))
                                                        End Sub)
        _browser = New Browser(bootstrapper)
    End Sub

    <Test>
    Public Sub IfGetErrorReturnsStatusCode500()
        Dim result = _browser.Get("/error", Sub(x)
                                                x.HttpRequest()
                                                x.Accept(New MediaRange() With {.Type = "application", .Subtype = "json"})
                                            End Sub)
        Assert.AreEqual(HttpStatusCode.InternalServerError, result.StatusCode)
    End Sub

    <Test>
    Public Sub IfGetErrorReturnsJsonWhenRequested()
        Dim result = _browser.Get("/error", Sub(x)
                                                x.HttpRequest()
                                                x.Accept(New MediaRange() With {.Type = "application", .Subtype = "json"})
                                            End Sub)
        Assert.IsTrue(result.Body.AsString.Contains("{""ErrorMessage"":""Testing"",""FullException"":"))
    End Sub

    <Test>
    Public Sub IfGetErrorReturnsViewWithErrorMessage()
        Dim result = _browser.Get("/error", Sub(x)
                                                x.HttpRequest()
                                            End Sub)
        Assert.AreEqual("Testing", result.Body("p").ToList(2).InnerText)
    End Sub

    <Test>
    Public Sub IfGetNotFoundReturnsStatusCode404()
        Dim result = _browser.Get("/notfound", Sub(x)
                                                   x.HttpRequest()
                                                   x.Accept(New MediaRange() With {.Type = "application", .Subtype = "json"})
                                               End Sub)
        Assert.AreEqual(HttpStatusCode.NotFound, result.StatusCode)
    End Sub

    <Test>
    Public Sub IfGetNotFoundReturnsJsonWhenRequested()
        Dim result = _browser.Get("/notfound", Sub(x)
                                                   x.HttpRequest()
                                                   x.Accept(New MediaRange() With {.Type = "application", .Subtype = "json"})
                                               End Sub)
        Assert.IsTrue(result.Body.AsString.StartsWith("{""ErrorMessage"":""The resource you requested could not be found."",""FullException"":"))
    End Sub

    <Test>
    Public Sub IfGetNotFoundReturnsViewWithErrorMessage()
        Dim result = _browser.Get("/notfound", Sub(x)
                                                   x.HttpRequest()
                                               End Sub)
        Assert.AreEqual("Page not found", result.Body("h2").ToList(0).InnerText)
    End Sub

    <Test>
    Public Sub IfGetMessageReturnsStatusCode500()
        Dim result = _browser.Get("/message", Sub(x)
                                                  x.HttpRequest()
                                                  x.Accept(New MediaRange() With {.Type = "application", .Subtype = "json"})
                                              End Sub)
        Assert.AreEqual(HttpStatusCode.InternalServerError, result.StatusCode)
    End Sub

    <Test>
    Public Sub IfGetMessageReturnsJsonWhenRequested()
        Dim result = _browser.Get("/message", Sub(x)
                                                  x.HttpRequest()
                                                  x.Accept(New MediaRange() With {.Type = "application", .Subtype = "json"})
                                              End Sub)
        Assert.IsTrue(result.Body.AsString.Contains("{""ErrorMessage"":""message"",""FullException"":"))
    End Sub

    <Test>
    Public Sub IfGetMessageReturnsViewWithErrorMessage()
        Dim result = _browser.Get("/message", Sub(x)
                                                  x.HttpRequest()
                                              End Sub)
        Assert.AreEqual("message", result.Body("p").ToList(2).InnerText)
    End Sub

    <Test>
    Public Sub IfGetFromExceptionReturnsStatusCode500()
        Dim result = _browser.Get("/fromexception", Sub(x)
                                                        x.HttpRequest()
                                                        x.Accept(New MediaRange() With {.Type = "application", .Subtype = "json"})
                                                    End Sub)
        Assert.AreEqual(HttpStatusCode.InternalServerError, result.StatusCode)
    End Sub

    <Test>
    Public Sub IfGetFromExceptionReturnsJsonWhenRequested()
        Dim result = _browser.Get("/fromexception", Sub(x)
                                                        x.HttpRequest()
                                                        x.Accept(New MediaRange() With {.Type = "application", .Subtype = "json"})
                                                    End Sub)
        Assert.IsTrue(result.Body.AsString.Contains("{""ErrorMessage"":""fromexception"",""FullException"":"))
    End Sub

    <Test>
    Public Sub IfGetFromExceptionReturnsViewWithErrorMessage()
        Dim result = _browser.Get("/fromexception", Sub(x)
                                                        x.HttpRequest()
                                                    End Sub)
        Assert.AreEqual("fromexception", result.Body("p").ToList(2).InnerText)
    End Sub

    Private Class ErrorModule
        Inherits NancyModule

        Public Sub New()
            [Get]("/error") = Function(parameters)
                                  Throw New Exception("Testing")
                              End Function
            [Get]("/notfound") = Function(parameters)
                                     Return HttpStatusCode.NotFound
                                 End Function
            [Get]("/message") = Function(parameters)
                                    Return ErrorResponse.FromMessage("message")
                                End Function
            [Get]("/fromexception") = Function(parameters)
                                          Return ErrorResponse.FromException(New Exception("fromexception"))
                                      End Function
        End Sub

    End Class

End Class

End Namespace``` It’s not extremely pretty but it works. And all tests pass.

Go ahead and tell me I’m doing it wrong.