DEV Community

davinceleecode
davinceleecode Subscriber

Posted on

๐ŸŒ Global Error Handling in ASP.NET Core with Custom Middleware and Serilog

Error handling is a fundamental part of building reliable APIs. In this post, Iโ€™ll walk you through how to implement global error handling in ASP.NET Core using custom middleware โ€” with clean JSON error responses and Serilog for structured logging.


๐Ÿง  Why Use Global Error Handling?
Instead of wrapping every controller action in a try-catch, we can centralize error handling using middleware. This gives us:

โœ… Clean, consistent error responses
โœ… Less duplicated code
โœ… Easy error tracing with unique IDs
โœ… Integration with logging libraries like Serilog


๐Ÿ”ง Step 1: Create the Middleware Class

using System.Net; namespace MyApp.Middlewares { public class ExceptionHandlerMiddleware { private readonly ILogger<ExceptionHandlerMiddleware> _logger; private readonly RequestDelegate _next; public ExceptionHandlerMiddleware(ILogger<ExceptionHandlerMiddleware> logger, RequestDelegate next) { _logger = logger; _next = next; } public async Task InvokeAsync(HttpContext httpContext) { try { await _next(httpContext); } catch (Exception ex) { var errorId = Guid.NewGuid(); // Log the error with a unique identifier _logger.LogError(ex, $"[{errorId}] Unhandled exception: {ex.Message}"); httpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError; httpContext.Response.ContentType = "application/json"; var error = new { Id = errorId, ErrorMessage = "Something went wrong. Please contact support with the error ID." }; await httpContext.Response.WriteAsJsonAsync(error); } } } } 
Enter fullscreen mode Exit fullscreen mode

๐Ÿ“ This middleware:

  • Catches any unhandled exceptions
  • Logs them with a unique Guid
  • Returns a friendly error message in JSON format

โš™๏ธ Step 2: Register the Middleware in Program.cs
Make sure it's registered early in the request pipeline:

var app = builder.Build(); // Add our global exception handler middleware app.UseMiddleware<ExceptionHandlerMiddleware>(); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); 
Enter fullscreen mode Exit fullscreen mode

๐Ÿงช Step 3: Throw an Error in a Controller to Test

[HttpGet] public IActionResult Crash() { throw new Exception("Simulated crash!"); } 
Enter fullscreen mode Exit fullscreen mode

Call this endpoint. You should see:

  • A clean JSON response with an error ID
  • An error logged in console/file (if Serilog is configured)

โœ๏ธ Bonus: Integrate Serilog for Logging
Add Serilog via NuGet:

dotnet add package Serilog.AspNetCore dotnet add package Serilog.Sinks.Console dotnet add package Serilog.Sinks.File 
Enter fullscreen mode Exit fullscreen mode

Then, update your Program.cs:

using Serilog; Log.Logger = new LoggerConfiguration() .MinimumLevel.Information() .WriteTo.Console() .WriteTo.File("Logs/log.txt", rollingInterval: RollingInterval.Day) .CreateLogger(); builder.Logging.ClearProviders(); builder.Logging.AddSerilog(); 
Enter fullscreen mode Exit fullscreen mode

Now all _logger.LogError() calls in your middleware (and anywhere else) will log to both the console and Logs/log.txt.


๐Ÿงผ Best Practices

  • โ— Keep your middleware early in the pipeline.
  • โœ… Always return consistent JSON error structures.
  • โž• Add contextual info like UserId, RequestPath, etc., if needed.
  • ๐Ÿ“‹ Consider logging HTTP context data (cautiously) in production.
  • ๐Ÿ’ก Use log enrichment with Serilog for deeper insights.

โœ… Final Thoughts
Global exception handling with custom middleware is a clean and scalable way to handle API errors. It improves developer experience, user experience, and observability โ€” especially when paired with Serilog.

Top comments (1)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.