Error Handling

15 questions found

What is the purpose of global exception handling middleware in an ASP.NET Core application?

Beginner
Global exception handling middleware catches unhandled exceptions thrown anywhere downstream in the pipeline, preventing them from crashing the request or leaking a raw stack trace to clients, and instead converting them into a consistent, well-formed error response -- centralizing error-handling logic in one place instead of scattering try/catch blocks throughout every controller action or endpoint.
app.UseExceptionHandler("/error");

app.Map("/error", (HttpContext context) => Results.Problem(title: "An error occurred", statusCode: 500));
Real-world example Without global exception handling, a single unhandled null reference exception deep in business logic would return a raw ASP.NET Core error page instead of the consistent JSON error format all API consumers expect.

Common follow-ups: How does UseExceptionHandler differ from the newer IExceptionHandler interface in .NET 8?;What information should and shouldn't be included in an error response for security reasons?

ASP.NET Core Middleware & Request Pipeline;RESTful Web APIs & Controllers

How does the IExceptionHandler interface (introduced in .NET 8) provide a more structured alternative to the UseExceptionHandler lambda approach?

Intermediate
IExceptionHandler defines TryHandleAsync(HttpContext, Exception, CancellationToken), implemented as a proper, testable, DI-injectable class -- multiple exception handlers can be registered and are tried in order until one returns true, enabling different handlers for different exception types in a clean, composable way rather than one large if/else chain in a single inline delegate.
public class ValidationExceptionHandler : IExceptionHandler {
    public async ValueTask<bool> TryHandleAsync(HttpContext context, Exception exception, CancellationToken ct) {
        if (exception is not ValidationException ve) return false;
        context.Response.StatusCode = 400;
        await context.Response.WriteAsJsonAsync(new { errors = ve.Errors }, ct);
        return true;
    }
}

builder.Services.AddExceptionHandler<ValidationExceptionHandler>();
Real-world example An API registers a specific ValidationExceptionHandler that formats validation errors distinctly, chained with a catch-all GlobalExceptionHandler for everything else, replacing what used to be a large if/else chain inside a single exception-handling lambda.

Common follow-ups: What determines the order multiple registered IExceptionHandlers are tried in?;How does this integrate with ProblemDetails for standardized error responses?

Global Exception Handling & Middleware;API Documentation with Swagger/OpenAPI

What is ProblemDetails (RFC 7807/9457), and how does ASP.NET Core support it for standardized error responses across an API?

Advanced
ProblemDetails is a standardized JSON format for HTTP API error responses, including fields like type, title, status, detail, and instance, providing a consistent, interoperable error structure. ASP.NET Core has built-in support via Results.Problem(), AddProblemDetails(), and automatic ProblemDetails generation for validation failures and unhandled exceptions when configured, reducing the need to hand-roll a custom error response shape.
builder.Services.AddProblemDetails();

app.UseExceptionHandler();  // now produces RFC-compliant ProblemDetails automatically

return Results.Problem(title: "Insufficient stock", statusCode: 409, detail: "Only 2 units remaining");
Real-world example A public API adopts ProblemDetails universally so that third-party integrators consuming errors from any endpoint always see the same consistent, well-documented JSON error shape.

Common follow-ups: What are the standard vs extension members of the ProblemDetails format?;How do you add custom extension fields to a ProblemDetails response?

RESTful Web APIs & Controllers;API Versioning

How do you map different custom exception types to different HTTP status codes in a global exception handler?

Intermediate
Inside your exception handler, inspect the caught exception's type (or a custom base exception hierarchy) and map it to the semantically correct HTTP status code -- a NotFoundException maps to 404, a ValidationException to 400, an UnauthorizedAccessException to 403 -- rather than defaulting every unhandled exception to a generic 500.
public class GlobalExceptionHandler : IExceptionHandler {
    public async ValueTask<bool> TryHandleAsync(HttpContext context, Exception exception, CancellationToken ct) {
        var (statusCode, title) = exception switch {
            NotFoundException => (404, "Resource not found"),
            ValidationException => (400, "Validation failed"),
            _ => (500, "An unexpected error occurred")
        };
        context.Response.StatusCode = statusCode;
        await context.Response.WriteAsJsonAsync(new { title }, ct);
        return true;
    }
}
Real-world example An API's global handler correctly returns 404 for a custom ProductNotFoundException and 400 for a custom InvalidOrderStateException, giving API consumers semantically accurate status codes instead of a blanket 500 for every kind of failure.

Common follow-ups: What custom exception hierarchy design makes this mapping cleanest to maintain?;Should business logic ever throw exceptions for expected failure cases, or use a Result pattern instead?

Global Exception Handling & Middleware;RESTful Web APIs & Controllers

What information should be excluded from error responses in production to avoid leaking sensitive implementation details?

Advanced
Production error responses should never include raw exception messages, stack traces, internal file paths, database connection details, or anything revealing internal architecture, since these can aid an attacker -- instead, log the full exception server-side while returning a generic, safe message to the client, optionally including a correlation/trace ID that lets support staff look up full details in logs without exposing them directly.
public async ValueTask<bool> TryHandleAsync(HttpContext context, Exception exception, CancellationToken ct) {
    var traceId = context.TraceIdentifier;
    _logger.LogError(exception, "Unhandled exception. TraceId: {TraceId}", traceId);
    await context.Response.WriteAsJsonAsync(new { title = "An unexpected error occurred", traceId }, ct);
    return true;
}
Real-world example A penetration test flags an API that was returning full .NET stack traces (including internal file paths and a database connection string fragment) in error responses -- fixed by ensuring the developer exception page is strictly Development-only.

Common follow-ups: How does a correlation/trace ID help support teams without exposing sensitive data?;What's the risk specifically of exposing stack traces regarding internal architecture?

Authentication;Logging

How do you handle exceptions specifically within minimal API endpoints, given they don't use the traditional MVC exception filter pipeline?

Intermediate
Minimal APIs don't support MVC-style exception filter attributes, instead relying on the same pipeline-level exception handling middleware (UseExceptionHandler/IExceptionHandler) that applies globally across the whole application including minimal API endpoints, or using endpoint filters (IEndpointFilter) to wrap specific endpoints with custom try/catch logic beyond the global handler.
app.MapGet("/products/{id}", (int id, IProductService service) => {
    var product = service.GetById(id) ?? throw new NotFoundException($"Product {id} not found");
    return Results.Ok(product);
});
// Relies on global exception handling to catch NotFoundException and convert it to 404
Real-world example A minimal API project relies entirely on global exception handling middleware for consistent error responses across all its endpoints, since there's no per-action exception filter attribute mechanism available like there would be in a traditional MVC controller-based API.

Common follow-ups: How do endpoint filters provide finer-grained exception handling than the global handler?;What's the migration path for exception filter logic when moving from MVC controllers to minimal APIs?

Filters;Controllers vs Minimal APIs

How does exception handling middleware interact with model validation errors, and why do they typically produce two entirely separate error response paths?

Advanced
Model validation errors (from [ApiController]'s automatic model state validation) typically don't throw exceptions at all -- [ApiController] automatically short-circuits with a 400 Bad Request and a ValidationProblemDetails response before the action method even executes, entirely bypassing exception-handling middleware since no exception was ever thrown, which is why validation errors and unhandled exceptions often need separate consideration when designing a consistent overall error response strategy.
[ApiController]
public class ProductsController : ControllerBase {
    [HttpPost]
    public IActionResult Create([Required] ProductDto dto) {
        // If dto fails validation, this method body never executes --
        // [ApiController] already returned a 400 ValidationProblemDetails automatically
        return Ok();
    }
}
Real-world example A team designing their unified error response format realizes validation errors (automatic 400 responses) and unhandled exceptions (caught by exception middleware) are two entirely separate code paths that both need to produce a consistent ProblemDetails shape.

Common follow-ups: How do you customize the automatic validation error response format via InvalidModelStateResponseFactory?;How would you unify both paths to guarantee identical response shapes?

Model Binding & Validation;RESTful Web APIs & Controllers

How should a global exception handler differentiate between exceptions meant for client-facing details versus exceptions that should always show a generic message?

Intermediate
A common pattern defines a custom base exception class (like ApiException with a public Message and StatusCode meant for client display) that business logic throws for expected, user-facing error conditions, while any exception NOT inheriting from this base type is treated as an internal error given only a generic message -- this explicit opt-in design prevents accidentally leaking internal exception details for exception types never designed to be shown to end users.
public class ApiException(string message, int statusCode = 400) : Exception(message) {
    public int StatusCode { get; } = statusCode;
}

var (status, message) = exception is ApiException apiEx
    ? (apiEx.StatusCode, apiEx.Message)
    : (500, "An unexpected error occurred");
Real-world example A checkout flow throws a custom InsufficientStockException : ApiException with a clear customer-facing message, while an unrelated database timeout exception (not inheriting ApiException) is automatically shown only as a generic 'something went wrong' message.

Common follow-ups: How do you ensure developers consistently use the ApiException base class for new user-facing errors?;What's the risk of accidentally making ApiException too broadly applicable?

Global Exception Handling & Middleware;RESTful Web APIs & Controllers

How do you handle exceptions that occur during response streaming, after the response has already partially started sending to the client?

Advanced
Once HttpResponse.HasStarted is true (typically after the first write to the response body, common in streaming scenarios), you can no longer modify the response status code or headers -- exception handling middleware must check HasStarted and, if true, can only log the error and abruptly terminate the connection, making it critical to validate and prepare everything possible before any response writing begins.
public async ValueTask<bool> TryHandleAsync(HttpContext context, Exception exception, CancellationToken ct) {
    if (context.Response.HasStarted) {
        _logger.LogError(exception, "Exception after response started, cannot modify response");
        return true;
    }
    context.Response.StatusCode = 500;
    await context.Response.WriteAsJsonAsync(new { title = "An error occurred" }, ct);
    return true;
}
Real-world example A large file-export endpoint that fails midway through streaming can't return a clean error response (since headers were already sent) -- the team adds thorough upfront validation to catch as many failure conditions as possible before streaming begins.

Common follow-ups: How would a client detect and handle a connection that was abruptly terminated mid-stream?;What upfront validation strategies minimize the risk of mid-stream failures?

File Uploads & Streaming Large Files;gRPC Services

What role does the [ApiController] attribute play in automatically handling certain error scenarios without explicit exception handling code?

Intermediate
[ApiController] enables several automatic API-friendly behaviors: automatic 400 responses for invalid model state, automatic binding source inference, and automatic problem details for certain error responses -- reducing boilerplate that would otherwise require manual exception handling or explicit checks in every action for common scenarios like malformed request bodies or missing required parameters.
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase {
    [HttpPost]
    public IActionResult Create(ProductDto dto) {
        // No need to manually check ModelState.IsValid --
        // [ApiController] already returned 400 automatically if dto was invalid
        return Ok(_service.Create(dto));
    }
}
Real-world example A team migrating older MVC controllers (which required manual ModelState.IsValid checks in every action) to use [ApiController] removes dozens of duplicated validation-checking lines across their controllers.

Common follow-ups: What other automatic behaviors does [ApiController] enable besides validation short-circuiting?;How would you customize or disable this automatic behavior for specific scenarios?

Model Binding & Validation;Diagnostics & Performance

Showing 1–10 of 15