Dusted Codes

Programming adventures

Error Handling in ASP.NET Core

Almost two years ago I wrote a blog post on demystifying ASP.NET MVC 5 error pages and error logging, which became one of my most popular posts on this blog. At that time of writing the issue was that there were an awful lot of choices on how to deal with unhandled exceptions in ASP.NET MVC 5 and no clear guidance or recommendation on how to do it the right way.

Fortunately with ASP.NET Core the choices have been drastically reduced and there is also a much better documentation on the topic itself. However, there's still a few interesting things to consider which I wanted to point out in this follow up blog post and clear up any remaining questions on ASP.NET Core error handling.

ASP.NET Core <> MVC

First I thought it's worth mentioning that the relationship between ASP.NET Core and ASP.NET Core MVC hasn't changed much since what it used to be in Classic ASP.NET.

ASP.NET Core remains the main underlying platform for building web applications in .NET Core while MVC is still an optional web framework which can be plugged into the ASP.NET Core pipeline. It's basically a NuGet library which sits on top of ASP.NET Core and offers a few additional features for the Model-View-Controller design pattern.

What that means in terms of error handling is that any exception handling capability offered by MVC will be limited to MVC. This will become much more apparent when we look at the ASP.NET Core architecture.

ASP.NET Core middleware

ASP.NET Core is completely modular and the request pipeline is mainly defined by the installed middleware in an application.

For better demonstration let's create a new boilerplate MVC application and check out the void Configure(..) method inside the Startup.cs class file:

public void Configure(
    IApplicationBuilder app,
    IHostingEnvironment env,
    ILoggerFactory loggerFactory)
{
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseDatabaseErrorPage();
        app.UseBrowserLink();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
    }

    app.UseStaticFiles();

    app.UseIdentity();

    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
    });
}

Because that is a lot of boilerplate code for a simple web application I'll trim it down to the main points of interest:

app.UseExceptionHandler("/Home/Error");
app.UseStaticFiles();
app.UseIdentity();
app.UseMvc(routes => ...);

What you can see here is fairly self explanatory, but there's a few key things to understand from this code. The app.Use...() (extension-) method calls are enabling several middleware by registering them with the IApplicationBuilder object. Each middleware will be made responsible for invoking the next middleware in the request pipeline, which is why the order of the app.Use...() method calls matter.

For example this is a rough skeleton of the StaticFileMiddleware:

public StaticFileMiddleware(RequestDelegate next, ...)
{
    // Some stuff before

    _next = next;

    // Some stuff after
}

public Task Invoke(HttpContext context)
{
    // A bunch of code to see if this middleware can
    // serve a static file that matches the HTTP request...

    // If not the code will eventually reach this line:
    return _next(context);
}

I cut out some noise to highlight the usage of the RequestDelegate variable.

As you can see each middleware must accept a RequestDelegate object in the constructor and each middleware must implement a method of type Task Invoke(HttpContext context).

The RequestDelegate is, as its name suggest, a delegate which represents the next middleware in the lifecycle. ASP.NET Core defers the responsibility of invoking it to the current middleware itself. For example if the StaticFileMiddleware is not able find a static file which matches the incoming HTTP request then it will invoke the next middleware by calling return _next(context); at the end. On the other hand if it was able to find the requested static file then it will return it to the client and never invoke the next or any subsequent middleware anymore.

This is why the order of the app.Use...() method calls matter. When you think about it the underlying pattern can be seen a little bit like an onion:

aspnet-core-middleware-onion-architecture

A HTTP request will travel from the top level middleware down to the last middleware, unless a middleware in between can satisfy the request and return a HTTP response earlier to the client. In contrast an unhandled exception would travel from the bottom up. Beginning at the middleware where it got thrown it would bubble up all the way to the top most middleware waiting for something to catch it.

In theory a middleware could also attempt to make changes to the response after it has invoked the next middleware, but this is normally not the case and I would advise against it, because it could result in an exception if the other middleware already wrote to the response.

Error handling should be the first middleware

With that in mind it is clear that in order to catch any unhandled exception an error handling middleware should be the first in the pipeline. Only then it can guarantee a final catch if nothing else caught the exception before.

Because MVC is typically registered towards the end of the middleware pipeline it is also clear that exception handling features (like the infamous ExceptionFilters) within MVC will not be able to catch every exception.

For more information on middleware please check out the official documentation.

Custom Exception Handlers

Now that middleware and exception handling hopefully makes sense I also wanted to quickly show how to create your own global exception handler in ASP.NET Core.

Even though there is already quite a few useful exception handlers in the Microsoft.AspNetCore.Diagnostics NuGet package available, it still might make sense to create your own one as well. For example one might want to have an exception handler which logs critical exceptions to Sentry by using Sentry's Raven Client for .NET or one might want to implement an integration with a bug tracking tool and log a new ticket for every NullReferenceException that gets thrown. Another option would be an integration with elmah.io.

There is many good reasons why someone might want to create additional exception handlers and it might even be useful to have multiple exception handlers registered at once. For example the first exception handler logs a ticket in a bug tracking system and re-throws the original exception. Then the next exception handler could log the error in ELMAH and re-trigger the original exception again. The final exception handler might catch the exception and return a friendly error page to the client. By having each exception handler focusing on a single responsibility they automatically become more re-usable across multiple projects and it would also enable to use different combinations on different environments (think dev/staging/production).

A good example of writing your own exception handling middleware is the default ExceptionHandlerMiddleware in ASP.NET Core.

A default exception handler boilerplate would look like this:

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;

namespace SomeApp
{
    public sealed class CustomExceptionHandlerMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly ILogger _logger;

        public CustomExceptionHandlerMiddleware(
            RequestDelegate next,
            ILoggerFactory loggerFactory)
        {
            _next = next;
            _logger = loggerFactory.
                    CreateLogger<CustomExceptionHandlerMiddleware>();
        }

        public async Task Invoke(HttpContext context)
        {
            try
            {
                await _next(context);
            }
            catch (Exception ex)
            {
                try
                {
                    // Do custom stuff
                    // Could be just as simple as calling _logger.LogError

                    // if you don't want to rethrow the original exception
                    // then call return:
                    // return;
                }
                catch (Exception ex2)
                {
                    _logger.LogError(
                        0, ex2, 
                        "An exception was thrown attempting " + 
                        "to execute the error handler.");
                }

                // Otherwise this handler will
                // re -throw the original exception
                throw;
            }
        }
    }
}

In addition to the RequestDelegate the constructor also accepts an ILoggerFactory which can be used to instantiate a new ILogger object.

In the Task Invoke(HttpContext context) method the error handler basically does nothing other than immediately calling the next middleware. Only if an exception is thrown it will come into action by capturing it in the catch block. What you put into the catch block is up to you, but it would be good practice to wrap any non trivial code in a second try-catch block and default back to basic logging if everything else is falling apart.

I hope all of this made sense and that this blog post was useful again. Personally I find it extremely nice to see how well ASP.NET Core has evolved from its predecessor. If you have any more questions just drop me a comment below.

Comments

Running Suave in ASP.NET Core (and on top of Kestrel)

Ho ho ho, happy F# Advent my friends! This is my blog post for the F# Advent Calendar in English 2016. First a quick thanks to Yan Cui who has pointed out this calendar to me last year and a big thanks to Sergey Tihon who is organising this blogging event and was kind enough to reserve me a spot this year.

santa-suave

In this blog post I wanted to write about two technologies which I am particularly excited about: Suave and ASP.NET Core. Both are frameworks for building web applications, both are written in .NET and both are open source and yet they are very different. Suave is a lightweight web server written entirely in F# and belongs to the family of micro frameworks similar to NancyFx. ASP.NET Core is Microsoft's new cloud optimised web framework which has been built from the ground up on top of .NET Core and all of its goodness. Both are fairly new cutting edge technologies and both are extremely fun to work with.

What I like the most about Suave is that it's written in F# for F#. It is really well designed and embraces functional concepts like railway oriented programming in its core architecture. Lately I've been a big fan of functional programming and being able to build web applications in a functional way is not only very productive but also a heap of fun. ASP.NET Core is object oriented and closer related to C#, but nonetheless an extraodinary new web framework. After more than 14 years of developing (the old) ASP.NET stack Microsoft has completely revamped the platform and built something new which is extremely fast and flexible. I love Kestrel, I love how ASP.NET Core is completely modular and extendable (via middleware) and I love how it is cross platform compatible and supported by Microsoft (Mono you have served us well but I am glad to move on now). There's more than one good reason to go with either framework and that's why I really wanted to combine them together.

Ideally I would like to continue building web applications with Suave in F# and then plug them into the ASP.NET Core pipeline to run them on top of Kestrel and benefit from both worlds.

Suave inside ASP.NET Core in theory

In order to better understand Suave let's have a quick look at a simple web application:

open System
open Suave
open Suave.Successful
open Suave.Operators
open Suave.RequestErrors
open Suave.Filters

let simpleApp =
    choose [
        GET >=> choose [
            path "/" >=> OK "Hello world from Suave."
            path "/ping" >=> OK "pong"
        ]
        NOT_FOUND "404 - Not found."
    ]

[<EntryPoint>]
let main argv = 
    startWebServer defaultConfig simpleApp
    0

Even in this simple example you can clearly see the core concept behind Suave. An application is always an assemble of one or many web parts. A WebPart is a function which takes a HttpContext and returns an option of HttpContext wrapped in an async workflow. Through combinators such as choose or >=> (and many others) one can compose a complex web application with routing, model binding, view engines and anything else that someone might want to do. At the end there is one top level function of type WebPart which takes in a HttpContext and returns a HttpContext. In this example this function is called simpleApp.

In theory the one thing required to plug a Suave web app into ASP.NET Core would be to take an incoming HTTP request from ASP.NET Core and convert it into an HttpContext in Suave, execute the top level web part, and then translate the resulting HttpContext back into an ASP.NET Core response:

suave-in-aspnetcore-concept

The other thing which you get with Suave is a self hosted web server which is built into the framework and the traditional way of starting a Suave web application. The startWebServer function takes a SuaveConfig object and the top level WebPart as input parameters. The config object allows web server specific configuration such as HTTP bindings, request quotas, timeout limits and many more things to be set.

When putting a Suave app into ASP.NET Core then it would be running on a different web server under the hood (Kestrel by default) and it wouldn't necessarily make sense to use an existing SuaveConfig in this scenario. Considering that ASP.NET Core offers other natural ways of configuring server settings, I think it is fair to skip the SuaveConfig when merging Suave into ASP.NET Core and mainly focusing on a smooth WebPart integration.

Suave inside ASP.NET Core in practice

Taking theory into practise I thought I can make it happen, and when a programmer says that then it usually means to google for an existing solution first. I was lucky to discover Suave.Kestrel which is a super early alpha version of the above concept written by Krzysztof Cieslak. Krzysztof is the developer behind the Ionide project which makes F# development in Visual Studio Code even possible, and therefore a massive thanks to him and his great contributions as well!

Even though this project was a good start to begin with there was still loads of work left to do. First I started off by using the existing code and trying to extend it, but then I quickly realised that I was fighting more with the tools than writing any code, which lead me to the decision of creating an entirely new project written in C#. Why C#? Because the Visual Studio tooling for F# projects in .NET Core is non existent (at least at the moment). As much as I love F# if I cannot properly debug or reason with my code then I rather switch to C# and get the job done.

However as a seasoned C# developer that was not a big problem and in the end it wouldn't even matter if the library was written in C#, F# or VB.NET as long as it would allow an easy integration from an F# point of view. Moments like this make me really appreciate the flexibility of the .NET framework.

Introducing Suave.AspNetCore

After my initial start on the project it took me another 3 months (mainly because I had absolutely no time) to finally release the first version of Suave.AspNetCore and make it available. Since yesterday anyone can install Suave.AspNetCore as a NuGet package for a .NET Core application.

I decided to name the package Suave.AspNetCore because I thought it is a more representative name of what the NuGet package has to offer. While this library makes it perfectly possible to run Suave on top of Kestrel it is certainly not limited to it. Suave.AspNetCore gives a way of plugging a Suave WebPart into the ASP.NET Core pipeline and run it on any environment of someone's desire. In theory Suave can be run alongside NancyFx and ASP.NET Core MVC in the same ASP.NET Core application and let the middleware decide which framework is suited best to satisfy an incoming request.

Current release information

The current version should be able to deal with any incoming web request which can be handled by a Suave WebPart. One thing that is missing (but already in the works) is the support for Suave's web socket implementation.

I shall also note that Suave and F# itself don't have an official stable release for .NET Core yet and therefore the project as a whole should be taken with some caution.

Suave.AspNetCore in action

Ok enough of the talk and let's look at a demo.

First I'll start by using one of my existing F# ASP.NET Core project templates and upgrade it to .NET Core 1.1.

Then I add Suave, Suave.AspNetCore and Newtonsoft.Json to the dependencies:

"dependencies": {
    "Microsoft.FSharp.Core.netcore": "1.0.0-alpha-*",
    "Microsoft.AspNetCore.Diagnostics": "1.1.0",
    "Microsoft.AspNetCore.Server.Kestrel": "1.1.0",
    "Microsoft.Extensions.Logging.Console": "1.1.0",

    "Suave": "2.0.0-*",
    "Suave.AspNetCore": "0.1.0",
    "Newtonsoft.Json": "9.0.1"
  }

Next I move on to the Startup.fs file and create a Suave web application:

module App =
    let catchAll =
        fun (ctx : HttpContext) ->
            let json = 
                JsonConvert.SerializeObject(
                    ctx.request,
                    Formatting.Indented)
            OK json 
            >=> Writers.setMimeType "application/json"
            <| ctx

This app is very basic. You can see that it is a single web part which uses Json.NET to serialize the entire HttpContext and then later returns a successful response of the Json text with a mime type of application/json.

It is not hugely interesting but it is a nice function which will handle every incoming web request and output the resulting HttpContext in a more or less readable way. It's at least a good way of quickly verifying if the incoming web request has been correctly converted into a Suave HttpContext object.

Finally I go to the Startup class and hook up the Suave catchAll web app into the ASP.NET Core pipeline via a middleware:

type Startup() =
    member __.Configure (app : IApplicationBuilder)
                        (env : IHostingEnvironment)
                        (loggerFactory : ILoggerFactory) =
        app.UseSuave(App.catchAll) |> ignore

Save all, dotnet restore, dotnet build and dotnet run:

running-a-suave-aspnetcore-app

If everything is correct then going to http://localhost:5000/ should return a successful response like this:

suave-in-aspnetcore-simple-get-request-result

You can check out the sample app in GitHub and try it yourself!

Differences between vanilla Suave and Suave.AspNetCore

After running a few tests of my own I noticed a few minor differences.

First I noticed that the original Suave web server converts all HTTP headers into lower case values. For example Content-Type: text/html would be stored as content-type: text/html in Suave's HTTP header collection. In contrast ASP.NET Core preserves the original casing. When using the Suave.AspNetCore middleware then it will match the original Suave behaviour, but can be easily overriden by setting the preserveHttpHeaderCasing parameter to true in the UseSuave method:

app.UseSuave(App.catchAll, true) |> ignore

Another difference which I found was that Suave always sets the host variable to 127.0.0.1 for local requests, even when I explicitely call the API with http://localhost:5000/. I wasn't able to find out why or where this is happening and if there is a good reason for it. In this case I didn't align with the original Suave behaviour and kept the values provided by ASP.NET Core.

Other than this I haven't found any big differences and hope that it is (mostly) bug free. The project is open source and I am open for ideas, help or suggestions of any kind.

Merry Christmas everyone!

Comments

Older Posts Newer Posts