“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