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.