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:

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:

Best Practices for Global Exception Handling

Common Mistakes to Avoid

Real-World Example Flow

When to Use Global Exception Handling

Use it in:

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.