IMiddleware

“Middleware is software that’s assembled into an app pipeline to handle requests and responses.”

IMiddleware is an interface provided by the ASP.NET framework in the namespace Microsoft.AspNetCore.Http.Abstractions

Dependency injection

First we need to add our services to the DI container

1
2
services.AddScoped<LogCorrelationMiddleware>();
services.AddScoped<ExceptionMiddleware>();

Then configure the HTTP request pipeline (called by the runtime) to include our services using the extension UseMiddleware from Microsoft.AspNetCore.Http.Abstractions.

1
2
app.UseMiddleware<LogCorrelationMiddleware>();
app.UseMiddleware<ExceptionMiddleware>();

Exception Middleware

This can globally handle application exceptions, here Serilog context is being enriched but any logger can be injected into the constructor and used.

  • /Application/Middleware/ExceptionMiddleware.cs
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
using System;
using Microsoft.AspNetCore.Http;
using Serilog;

namespace SweetApp.Application.Middleware
{
public class ExceptionMiddleware : IMiddleware
{
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
try
{
await next(context);
}
catch (MyException1 exception)
{
Log.Warning(exception, $"App plowed: ErrorCode={exception.ErrorCode} ({exception.ErrorCode})");
}
catch (MyException2 exception)
{
Log.Error(exception, $"App plowed: ErrorCode={exception.ErrorCode} ({exception.ErrorCode})");
}
catch (Exception exception)
{
Log.Error(exception, "Unhandled exception occurred.");
}
}
}
}

Additionally for an API we would want to respond with a problem detail so each catch above could then call WriteToResponse

1
2
3
4
5
6
7
private async Task WriteToResponse(HttpContext context, MyProblemDetails details)
{
context.Response.ContentType = "application/problem+json";
context.Response.StatusCode = details.Status ?? StatusCodes.Status500InternalServerError;

await context.Response.WriteAsync(JsonSerializer.Serialize(details));
}

Where MyProblemDetails has a definition of

1
2
3
4
5
6
7
8
9
10
using Microsoft.AspNetCore.Mvc;

namespace SweetApp.Application.Exceptions
{
public class MyProblemDetails : ProblemDetails
{
public Guid CorrelationId { get; set; }
public Dictionary<string, List<string>> Errors { get; set; }
}
}

And to make your life easy you can create a MyProblemDetails instance using a factory, this can then be extended as your application grows.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
namespace SweetApp.Exceptions
{
public static class ProblemDetailsFactory
{
public static MyProblemDetails MapUnhandledException(string path, Guid correlationId)
{
var problem = InternalServerError();

problem.Instance = path;
problem.CorrelationId = correlationId;

return problem;
}
}
}

Log Correlation Middleware

Correlation ID’s are useful when you need to track down calls done across several micro services for the same transaction of work. This middleware can automagically enrich the serilog log context with your requests correlation ID.

  • /Application/Middleware/LogCorrelationMiddleware.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using System;
using Microsoft.AspNetCore.Http;

namespace SweetApp.Application.Middleware
{
public class LogCorrelationMiddleware : IMiddleware
{
private readonly IMyRequestContext _requestContext;

public LogCorrelationMiddleware(IMyRequestContext requestContext)
{
_requestContext = requestContext;
}

public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
using (LogContext.PushProperty("CorrelationId", _requestContext.GetCorrelationId()))
{
await next(context);
}
}
}
}

References