Introduction
Handling errors properly is one of the most important parts of building a robust ASP.NET Core Web API. If exceptions are not handled correctly, your application may crash, expose sensitive data, or provide a poor user experience.
Global exception handling allows you to manage all errors in one place instead of writing try-catch blocks in every controller or service. This makes your code cleaner, easier to maintain, and more secure.
In this article, you will learn how to implement global exception handling in ASP.NET Core Web API step by step using simple language and real-world examples.
What is Global Exception Handling?
Global exception handling is a centralized way to catch and handle all runtime errors (exceptions) in your application.
Instead of writing this everywhere:
try
{
// logic
}
catch(Exception ex)
{
// handle error
}
You define a single place that handles all exceptions automatically.
Benefits:
Types of Exceptions in ASP.NET Core
Common types of exceptions you may encounter:
System exceptions (NullReferenceException, DivideByZeroException)
Custom exceptions (business logic errors)
Validation errors
Unauthorized access errors
Understanding these helps you handle them properly.
Approach 1: Using Built-in Exception Handling Middleware
ASP.NET Core provides a built-in middleware to handle global exceptions.
Step 1: Configure Exception Handling in Program.cs
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/error");
}
This redirects all exceptions to a specific endpoint.
Step 2: Create Error Controller
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Mvc;
[ApiController]
public class ErrorController : ControllerBase
{
[Route("/error")]
public IActionResult HandleError()
{
var context = HttpContext.Features.Get<IExceptionHandlerFeature>();
var exception = context?.Error;
return Problem(
detail: exception?.Message,
title: "An error occurred",
statusCode: 500
);
}
}
This ensures a consistent response format.
Approach 2: Custom Middleware for Global Exception Handling
This is the most recommended and flexible approach.
Step 1: Create Custom Middleware
Create a new file: ExceptionMiddleware.cs
using System.Net;
using System.Text.Json;
public class ExceptionMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ExceptionMiddleware> _logger;
public ExceptionMiddleware(RequestDelegate next, ILogger<ExceptionMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
_logger.LogError(ex, "Unhandled Exception");
await HandleExceptionAsync(context, ex);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
{
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
var response = new
{
StatusCode = context.Response.StatusCode,
Message = "Something went wrong",
Detailed = exception.Message
};
return context.Response.WriteAsync(JsonSerializer.Serialize(response));
}
}
Step 2: Register Middleware in Program.cs
app.UseMiddleware<ExceptionMiddleware>();
Place it early in the pipeline (before other middlewares).
Handling Different Exception Types
You can customize responses based on exception types.
Example:
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
{
HttpStatusCode status;
string message;
switch (exception)
{
case UnauthorizedAccessException:
status = HttpStatusCode.Unauthorized;
message = "Unauthorized access";
break;
case ArgumentException:
status = HttpStatusCode.BadRequest;
message = "Invalid request";
break;
default:
status = HttpStatusCode.InternalServerError;
message = "Server error";
break;
}
context.Response.StatusCode = (int)status;
var result = JsonSerializer.Serialize(new
{
StatusCode = context.Response.StatusCode,
Message = message
});
return context.Response.WriteAsync(result);
}
Creating Custom Exceptions
Custom exceptions help represent business logic errors.
public class NotFoundException : Exception
{
public NotFoundException(string message) : base(message) { }
}
Usage:
if (user == null)
{
throw new NotFoundException("User not found");
}
Handle it in middleware for better responses.
Standard API Error Response Format
Use a consistent structure:
{
"statusCode": 500,
"message": "Error message",
"details": "Optional details"
}
This improves frontend integration.
Logging Exceptions
Logging is critical for debugging and monitoring.
ASP.NET Core provides built-in logging:
_logger.LogError(exception, "Error occurred");
You can also integrate tools like:
Serilog
NLog
Application Insights
Best Practices for Global Exception Handling
Do not expose sensitive details in production
Use structured logging
Handle known exceptions separately
Return proper HTTP status codes
Keep response format consistent
Common Mistakes to Avoid
Real-World Example Flow
When to Use Global Exception Handling
Use it in:
REST APIs
Microservices
Enterprise applications
It ensures reliability and maintainability.
Summary
Global exception handling in ASP.NET Core Web API helps you manage all errors in a centralized way. By using built-in middleware or creating custom middleware, you can ensure consistent error responses, better logging, and improved application security. This approach reduces code duplication and makes your application more professional and production-ready.