In ASP.NET Core, handling exceptions globally ensures you don’t scatter
try-catch blocks everywhere and still return consistent responses.There are two main approaches:
1. Built-in:
app.UseExceptionHandler("/error")
What it does:
This is a built-in middleware that catches unhandled exceptions and redirects to a specific endpoint.
Example
In
Program.cs:app.UseExceptionHandler("/error");
Error endpoint:
[Route("/error")]
public IActionResult HandleError()
{
return Problem("Something went wrong!");
}
How it works:
- Exception occurs anywhere in pipeline
- Middleware catches it
- Redirects to
/error - You return a response (JSON, view, etc.)
Pros:
- Very simple to use
- Built-in and reliable
- Good for basic/global handling
Cons:
- Less control
- Harder to customize per exception type
- Redirect-based (less flexible for APIs)
2. Custom Exception Middleware (Recommended)
What it is:
You write your own middleware to handle exceptions exactly how you want.
Example
public class ExceptionMiddleware
{
private readonly RequestDelegate _next;
public ExceptionMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
context.Response.StatusCode = 500;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(
$"Error: {ex.Message}");
}
}
}
Register it:
app.UseMiddleware<ExceptionMiddleware>();
How it works:
- Wraps entire request pipeline
- Catches exceptions
- Returns custom response directly
Pros:
- Full control
- Can handle different exception types differently
- Best for APIs (JSON responses)
- Can add logging, correlation IDs, etc.
Cons:
- More code
- You must maintain it
Key Differences
| Feature | UseExceptionHandler | Custom Middleware |
|---|---|---|
| Setup | Very easy | Requires code |
| Flexibility | Limited | Full control |
| Custom responses | Basic | Advanced |
| API-friendly | Moderate | Excellent |
| Exception-specific | Hard | Easy |
Real-World Recommendation
Use
UseExceptionHandler when:- Simple app
- MVC with views
- Quick setup needed
Use Custom Middleware when:
- Building REST APIs
- Need structured JSON errors
- Want logging + monitoring
Best Practice (Common in real apps)
👉 Combine both:
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/error");
}
app.UseMiddleware<ExceptionMiddleware>();
Bonus: Handling Specific Exceptions
catch (KeyNotFoundException ex)
{
context.Response.StatusCode = 404;
}
catch (UnauthorizedAccessException ex)
{
context.Response.StatusCode = 401;
}
Simple Analogy
-
UseExceptionHandler= Default customer support desk - Custom middleware = Your own trained support team
Final Takeaway
👉 For Real projects:
- Mention both
- Prefer custom middleware for APIs
- Add logging (Serilog, etc.) in middleware
--------------------------------------------------------------------------------------
Production-Ready Exception Middleware
using System.Net;
using System.Text.Json;
public class GlobalExceptionMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<GlobalExceptionMiddleware> _logger;
public GlobalExceptionMiddleware(RequestDelegate next, ILogger<GlobalExceptionMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
_logger.LogError(ex, "Unhandled exception occurred");
await HandleExceptionAsync(context, ex);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception ex)
{
var statusCode = ex switch
{
KeyNotFoundException => HttpStatusCode.NotFound,
UnauthorizedAccessException => HttpStatusCode.Unauthorized,
ArgumentException => HttpStatusCode.BadRequest,
_ => HttpStatusCode.InternalServerError
};
var response = new
{
success = false,
message = ex.Message,
statusCode = (int)statusCode
};
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)statusCode;
var json = JsonSerializer.Serialize(response);
return context.Response.WriteAsync(json);
}
}
Register Middleware
app.UseMiddleware<GlobalExceptionMiddleware>();
Place it early in the pipeline (before other middlewares).
Sample JSON Response
{
"success": false,
"message": "Product not found",
"statusCode": 404
}
Enhancements (Real-World Level)
1. Hide sensitive errors in Production
message = env.IsDevelopment() ? ex.Message : "Something went wrong"
2. Add Trace ID (VERY IMPORTANT for debugging)
traceId = context.TraceIdentifier
3. Use custom exception classes
public class NotFoundException : Exception
{
public NotFoundException(string message) : base(message) { }
}
Then:
NotFoundException => HttpStatusCode.NotFound
4. Structured Logging (Serilog)
Works great with:
- Serilog
- ELK / Seq dashboards
Best Practice Architecture
In real enterprise apps:
- Controllers → throw exceptions
- Middleware → handles everything
- Logging → centralized
- Response → consistent JSON
Summarized Paragraph:
👉 “I use custom global exception middleware in ASP.NET Core to handle exceptions centrally. It logs errors, maps exceptions to proper HTTP status codes, and returns structured JSON responses. For simple apps, UseExceptionHandler can be used, but middleware provides more flexibility.”
No comments:
Post a Comment