Middleware is a piece of software that handles requests and responses in the middle of application pipeline. An example of built-in ASP.NET Core middleware is authentication component and response caching component. In this article I’ll show you how to create global exception handler using custom middleware component.

This article will teach you how to create global exception handler which will save each uncaught exception into file. Later on, you can also use it to send exceptions to 3-rd party service like Raygun (but it is not included in this how-to) or to do anything else you want with that exception.

Basic middleware

It’s really easy to create basic middleware. It needs a constructor with a RequestDelegate parameter and an Invoke method which returns Task and which has a HttpContext parameter. Therefore our class should look like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public class ExceptionHandlerMiddleware
{
    private readonly RequestDelegate _next;
    
    public ExceptionHandlerMiddleware(RequestDelegate next)
    {
        _next = next;
    }
    
    public async Task Invoke(HttpContext httpContext)
    {
        await _next(httpContext);
    }
}

There is one more thing you need to do to make it part of an application pipeline. You need to add this line to your app configuration:

1
2
// Startup.cs inside Configure method
app.UseMiddleware<ExceptionHandlerMiddleware>();

Now, all requests are handled by this class. The Invoke method you saw earlier can perform additional actions before or after invocation of next pipeline component. The _next(httpContext) call runs other components.

Intercepting exceptions

Since we want to do something with all unhandled exceptions we need to add a try-catch on the _next(httpContext) call:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public class ExceptionHandlerMiddleware
{
    private readonly RequestDelegate _next;
    
    public ExceptionHandlerMiddleware(RequestDelegate next)
    {
        _next = next;
    }
    
    public async Task Invoke(HttpContext httpContext)
    {
        try
        {
            await _next(httpContext);
        }
        catch (Exception)
        {
            throw;
        }
    }
}

Whenever exception is raised we catch it, we also make sure that it is rethrown. Let’s save contents of the exception into a file. For now we’ll hardcode file path to c:/app_exceptions/:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public async Task Invoke(HttpContext httpContext)
{
    try
    {
        await _next(httpContext);
    }
    catch (Exception e)
    {
        Log(httpContext, e);
        throw;
    }
}

private void Log(HttpContext context, Exception exception)
{
    var savePath = @"C:/app_exceptions/";
    var now = DateTime.UtcNow;
    var fileName = $"{now.ToString("yyyy_MM_dd")}.log";
    var filePath = Path.Combine(savePath, fileName);

    // ensure that directory exists
    new FileInfo(filePath).Directory.Create();

    using (var writer = File.CreateText(filePath))
    {
        writer.WriteLine($"{now.ToString("HH:mm:ss")} {context.Request.Path}");
        writer.WriteLine(exception.Message);
    }
}

Middleware dependency injection

Most likely you will want to save your file somewhere within your app folder e.g. ~/logs. For this we’ll need to inject IHostingEnvironemnt dependency which exposes WebRootPath property.

In order to inject any dependency into middleware component you only have to add it as a parameter to the Invoke method. Here’s how the final version of our global exception handler should look like:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public async Task Invoke(HttpContext httpContext, IHostingEnvironment hostingEnvironment)
{
    try
    {
        await _next(httpContext);
    }
    catch (Exception e)
    {
        Log(httpContext, e, hostingEnvironment);
        throw;
    }
}

private void Log(HttpContext context, Exception exception, IHostingEnvironment hostingEnvironment)
{
    var savePath = hostingEnvironment.WebRootPath;
    var now = DateTime.UtcNow;
    var fileName = $"{now.ToString("yyyy_MM_dd")}.log";
    var filePath = Path.Combine(savePath, "logs", fileName);
    
     // ensure that directory exists
    new FileInfo(filePath).Directory.Create();

    using (var writer = File.CreateText(filePath))
    {
        writer.WriteLine($"{now.ToString("HH:mm:ss")} {context.Request.Path}");
        writer.WriteLine(exception.Message);
    }
}

Summary

You learned how to create basic middleware and how to use it to grab unhandled exception. You also know how to inject dependencies into middleware.