LEARN BUILD GROW TOGETHER
Practical knowledge for developers
DEVELOPER LEARNING HUB
Learn. Build. Grow Together.

Practical tutorials, real-world examples and interview preparation.

.NETC#SQL SERVERANGULAR
Home /.NET Core /Global Exception Handling in .NET Web API – Complete Guide

Global Exception Handling in .NET Web API – Complete Guide

 

Introduction

In Day 12, we explored Code First and Database First approaches in Entity Framework Core and understood how both approaches help us manage database structures.

Now, let's understand another important concept in .NET Web API development: Global Exception Handling.

While developing APIs, errors can occur for different reasons, such as invalid operations, database failures, missing resources, or unexpected application issues.

Instead of writing try-catch blocks in every controller action, we can handle exceptions centrally using global exception handling.

In this article, we'll understand how exception handling works and how to implement it in a .NET Web API application.

1. What Is Exception Handling?

An exception is an unexpected event that occurs while an application is running and interrupts the normal execution of the program.

For example:

  • A database connection fails.

  • A requested employee does not exist.

  • An invalid operation is performed.

  • A null reference occurs.

  • An external API request fails.

Consider this simple example:

public IActionResult GetEmployee(int id)
{
    int result = 10 / 0;

    return Ok(result);
}

This code throws a DivideByZeroException because division by zero is not allowed.

Without proper exception handling, the API may return an unexpected error response.

Exception handling helps us catch errors and return meaningful responses to API consumers.

2. What Is Global Exception Handling?

Global exception handling is a centralized mechanism for handling unhandled exceptions across an application.

Instead of handling exceptions separately in every controller, we configure a common exception-handling mechanism.

For example, imagine an application with multiple controllers:

  • EmployeeController

  • DepartmentController

  • ProductController

  • OrderController

If an unexpected exception occurs in any of these controllers, the global exception handler can process it.

Without Global Exception Handling

Each controller may contain repetitive try-catch blocks.

[HttpGet("{id}")]
public IActionResult GetEmployee(int id)
{
    try
    {
        var employee = _employeeService.GetById(id);

        return Ok(employee);
    }
    catch (Exception ex)
    {
        return StatusCode(500, ex.Message);
    }
}

Repeating this approach across multiple controllers creates unnecessary code duplication.

With Global Exception Handling

We configure exception handling once in the application pipeline.

Controllers can focus on their business logic, while unhandled exceptions are processed centrally.

3. What Is Middleware in .NET?

Middleware is a component that is executed as part of the HTTP request pipeline.

Each middleware component can:

  • Process an incoming HTTP request.

  • Pass the request to the next middleware.

  • Process the outgoing HTTP response.

  • Handle or modify errors when appropriate.

For example, a typical request pipeline may look like this:

Client Request
      |
      v
Exception Handling Middleware
      |
      v
HTTPS Redirection
      |
      v
Authentication
      |
      v
Authorization
      |
      v
Controller / Endpoint
      |
      v
HTTP Response
      |
      v
Client

If an unhandled exception occurs in downstream middleware or an endpoint, the exception-handling middleware can catch it and generate an appropriate response.

Important: Middleware order matters. Exception handling should be placed early enough in the pipeline to catch exceptions from the components that follow it.

4. Implement Global Exception Handling Using IExceptionHandler

Modern .NET provides the IExceptionHandler interface to implement centralized exception handling.

Let's implement it step by step.

Step 1: Create a Custom Exception Handler

Create a folder named Exceptions and add a class named GlobalExceptionHandler.cs.

using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Mvc;

public class GlobalExceptionHandler : IExceptionHandler
{
    private readonly ILogger<GlobalExceptionHandler> _logger;

    public GlobalExceptionHandler(
        ILogger<GlobalExceptionHandler> logger)
    {
        _logger = logger;
    }

    public async ValueTask<bool> TryHandleAsync(
        HttpContext httpContext,
        Exception exception,
        CancellationToken cancellationToken)
    {
        _logger.LogError(
            exception,
            "An unexpected error occurred.");

        var problemDetails = new ProblemDetails
        {
            Status = StatusCodes.Status500InternalServerError,
            Title = "An unexpected error occurred.",
            Detail = "An error occurred while processing your request."
        };

        httpContext.Response.StatusCode =
            StatusCodes.Status500InternalServerError;

        await httpContext.Response.WriteAsJsonAsync(
            problemDetails,
            cancellationToken);

        return true;
    }
}

Let's understand the important parts:

  • IExceptionHandler: Interface used to implement a custom exception handler.

  • TryHandleAsync(): Method called when an exception is encountered.

  • ILogger: Used to log exception details.

  • ProblemDetails: Provides a standard structure for API error responses.

  • return true: Indicates that the exception has been handled.

The actual exception is logged internally, while the client receives a generic error message.

Step 2: Register the Exception Handler

Open Program.cs and register the handler.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

builder.Services.AddExceptionHandler<GlobalExceptionHandler>();

builder.Services.AddProblemDetails();

var app = builder.Build();

Here:

  • AddExceptionHandler<GlobalExceptionHandler>() registers our custom exception handler.

  • AddProblemDetails() registers services for generating Problem Details responses.

Step 3: Add Exception Handling Middleware

Add the exception-handling middleware before other middleware that you want it to monitor.

app.UseExceptionHandler();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Now, unhandled exceptions from downstream middleware and controller actions can be processed by our global exception handler.

5. Create a Custom Business Exception

Sometimes, we need to handle specific business errors differently.

For example, if an employee does not exist, we should return 404 Not Found instead of 500 Internal Server Error.

Let's create a custom exception.

public class NotFoundException : Exception
{
    public NotFoundException(string message)
        : base(message)
    {
    }
}

Now update the exception handler to handle this exception separately.

using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Mvc;

public class GlobalExceptionHandler : IExceptionHandler
{
    private readonly ILogger<GlobalExceptionHandler> _logger;

    public GlobalExceptionHandler(
        ILogger<GlobalExceptionHandler> logger)
    {
        _logger = logger;
    }

    public async ValueTask<bool> TryHandleAsync(
        HttpContext httpContext,
        Exception exception,
        CancellationToken cancellationToken)
    {
        var statusCode = exception switch
        {
            NotFoundException =>
                StatusCodes.Status404NotFound,

            ArgumentException =>
                StatusCodes.Status400BadRequest,

            _ =>
                StatusCodes.Status500InternalServerError
        };

        if (statusCode >= 500)
        {
            _logger.LogError(
                exception,
                "An unexpected server error occurred.");
        }
        else
        {
            _logger.LogWarning(
                exception,
                "A request could not be processed.");
        }

        var problemDetails = new ProblemDetails
        {
            Status = statusCode,
            Title = statusCode switch
            {
                404 => "Resource Not Found",
                400 => "Bad Request",
                _ => "Internal Server Error"
            },
            Detail = statusCode == 500
                ? "An unexpected error occurred."
                : exception.Message
        };

        httpContext.Response.StatusCode = statusCode;

        await httpContext.Response.WriteAsJsonAsync(
            problemDetails,
            cancellationToken);

        return true;
    }
}

Now our exception handler can return different HTTP status codes based on the exception type.

Note: In a production application, define explicit custom exception types for different business scenarios. Avoid exposing internal exception details or sensitive information to clients.

6. Use the Custom Exception in Employee Service

Suppose we have an Employee Service.

We can throw NotFoundException when an employee does not exist.

public Employee GetById(int id)
{
    var employee = _context.Employees
        .FirstOrDefault(e => e.Id == id);

    if (employee == null)
    {
        throw new NotFoundException(
            $"Employee with ID {id} was not found.");
    }

    return employee;
}

Our controller can remain simple:

[HttpGet("{id}")]
public IActionResult GetEmployee(int id)
{
    var employee = _employeeService.GetById(id);

    return Ok(employee);
}

Notice that we don't need a try-catch block in the controller.

If the employee is not found, the service throws NotFoundException, and the global exception handler processes it.

7. What Is ProblemDetails?

ProblemDetails is a standard format for representing errors in HTTP API responses.

It helps API developers return consistent error information to clients.

A typical ProblemDetails response looks like this:

{
  "type": "about:blank",
  "title": "Resource Not Found",
  "status": 404,
  "detail": "Employee with ID 10 was not found."
}

Important properties include:

PropertyDescription
typeIdentifies the problem type
titleShort description of the error
statusHTTP status code
detailAdditional information about the error
instanceIdentifies the specific request or resource

Using ProblemDetails makes API error responses easier to understand and handle consistently.

8. How to Test Global Exception Handling

Let's test our implementation using Swagger or Postman.

Test 1: Employee Exists

Request:

GET /api/employee/1

Expected response:

200 OK

The API returns employee details.

Test 2: Employee Does Not Exist

Request:

GET /api/employee/10

Expected response:

404 Not Found

Response:

{
  "title": "Resource Not Found",
  "status": 404,
  "detail": "Employee with ID 10 was not found."
}

Test 3: Unexpected Server Exception

If an unexpected exception occurs, the global handler returns:

500 Internal Server Error

Response:

{
  "title": "Internal Server Error",
  "status": 500,
  "detail": "An unexpected error occurred."
}

The detailed exception information should be available in server logs, not exposed to the API consumer.

9. Global Exception Handling Best Practices

Here are some important practices to follow in real-world applications:

  1. Implement centralized exception handling instead of repeating try-catch blocks.

  2. Use meaningful custom exceptions for business scenarios.

  3. Return appropriate HTTP status codes.

  4. Use ProblemDetails for consistent error responses.

  5. Log unexpected exceptions using ILogger.

  6. Never expose database credentials, stack traces, or internal implementation details in API responses.

  7. Keep exception-handling middleware early in the request pipeline.

  8. Avoid using exceptions for normal business control flow.

  9. Include a correlation or trace identifier when investigating production errors.

  10. Test both expected business exceptions and unexpected server failures.

10. Key Takeaways

  • Exception handling helps manage errors during application execution.

  • Global exception handling centralizes the handling of unhandled exceptions.

  • Middleware is part of the .NET HTTP request pipeline.

  • IExceptionHandler provides a structured way to implement custom exception handling.

  • ProblemDetails helps return consistent error responses.

  • Custom exceptions allow us to handle specific business scenarios.

Conclusion

Global Exception Handling is an important part of building reliable .NET Web API applications.

Instead of writing repetitive try-catch blocks in every controller, we can use centralized exception handling through middleware and IExceptionHandler.

By combining custom exceptions, structured error responses, and proper logging, we can make our APIs easier to maintain and troubleshoot.

What's Next?

Day 14 – Build CRUD API (Complete Example)

In the next article, we'll bring together the concepts we've learned and build a complete CRUD API using .NET Web API, Entity Framework Core, and SQL Server. We'll implement Create, Read, Update, and Delete operations with a practical Employee Management example.


Skills Tutorials – Learn • Build • Grow Together

Continue learning with our 30-day .NET Core Web API series.

No comments:

Post a Comment