How to handle errors in Minimal API apps

Note

This isn't the latest version of this article. For the current release, see the .NET 8 version of this article.

Warning

This version of ASP.NET Core is no longer supported. For more information, see .NET and .NET Core Support Policy. For the current release, see the .NET 8 version of this article.

Important

This information relates to a pre-release product that may be substantially modified before it's commercially released. Microsoft makes no warranties, express or implied, with respect to the information provided here.

For the current release, see the .NET 8 version of this article.

With contributions by David Acker

This article describes how to handle errors in Minimal API apps. For information about error handling in controller-based APIs, see Handle errors in ASP.NET Core and Handle errors in ASP.NET Core controller-based web APIs.

Exceptions

In a Minimal API app, there are two different built-in centralized mechanisms to handle unhandled exceptions:

This section refers to the following sample app to demonstrate ways to handle exceptions in a Minimal API. It throws an exception when the endpoint /exception is requested:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/exception", () => 
{
    throw new InvalidOperationException("Sample Exception");
});

app.MapGet("/", () => "Test by calling /exception");

app.Run();

Developer Exception Page

The Developer Exception Page displays detailed information about unhandled request exceptions. It uses DeveloperExceptionPageMiddleware to capture synchronous and asynchronous exceptions from the HTTP pipeline and to generate error responses. The developer exception page runs early in the middleware pipeline, so that it can catch unhandled exceptions thrown in middleware that follows.

ASP.NET Core apps enable the developer exception page by default when both:

Apps created using earlier templates, that is, by using WebHost.CreateDefaultBuilder, can enable the developer exception page by calling app.UseDeveloperExceptionPage.

Warning

Don't enable the Developer Exception Page unless the app is running in the Development environment. Don't share detailed exception information publicly when the app runs in production. For more information on configuring environments, see Use multiple environments in ASP.NET Core.

The Developer Exception Page can include the following information about the exception and the request:

  • Stack trace
  • Query string parameters, if any
  • Cookies, if any
  • Headers
  • Endpoint metadata, if any

The Developer Exception Page isn't guaranteed to provide any information. Use Logging for complete error information.

The following image shows a sample developer exception page with animation to show the tabs and the information displayed:

Developer exception page animated to show each tab selected.

In response to a request with an Accept: text/plain header, the Developer Exception Page returns plain text instead of HTML. For example:

Status: 500 Internal Server Error
Time: 9.39 msSize: 480 bytes
FormattedRawHeadersRequest
Body
text/plain; charset=utf-8, 480 bytes
System.InvalidOperationException: Sample Exception
   at WebApplicationMinimal.Program.<>c.<Main>b__0_0() in C:\Source\WebApplicationMinimal\Program.cs:line 12
   at lambda_method1(Closure, Object, HttpContext)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)

HEADERS
=======
Accept: text/plain
Host: localhost:7267
traceparent: 00-0eab195ea19d07b90a46cd7d6bf2f

To see the Developer Exception Page:

Exception handler

In non-development environments, use the Exception Handler Middleware to produce an error payload. To configure the Exception Handler Middleware, call UseExceptionHandler.

For example, the following code changes the app to respond with an RFC 7807-compliant payload to the client. For more information, see the Problem Details section later in this article.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.UseExceptionHandler(exceptionHandlerApp 
    => exceptionHandlerApp.Run(async context 
        => await Results.Problem()
                     .ExecuteAsync(context)));

app.MapGet("/exception", () => 
{
    throw new InvalidOperationException("Sample Exception");
});

app.MapGet("/", () => "Test by calling /exception");

app.Run();

Client and Server error responses

Consider the following Minimal API app.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/users/{id:int}", (int id) 
    => id <= 0 ? Results.BadRequest() : Results.Ok(new User(id)));

app.MapGet("/", () => "Test by calling /users/{id:int}");

app.Run();

public record User(int Id);

The /users endpoint produces 200 OK with a json representation of User when id is greater than 0, otherwise a 400 BAD REQUEST status code without a response body. For more information about creating a response, see Create responses in Minimal API apps.

The Status Code Pages middleware can be configured to produce a common body content, when empty, for all HTTP client (400-499) or server (500 -599) responses. The middleware is configured by calling the UseStatusCodePages extension method.

For example, the following example changes the app to respond with an RFC 7807-compliant payload to the client for all client and server responses, including routing errors (for example, 404 NOT FOUND). For more information, see the Problem Details section.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.UseStatusCodePages(async statusCodeContext 
    => await Results.Problem(statusCode: statusCodeContext.HttpContext.Response.StatusCode)
                 .ExecuteAsync(statusCodeContext.HttpContext));

app.MapGet("/users/{id:int}", (int id) 
    => id <= 0 ? Results.BadRequest() : Results.Ok(new User(id)) );

app.MapGet("/", () => "Test by calling /users/{id:int}");

app.Run();

public record User(int Id);

Problem details

Problem Details are not the only response format to describe an HTTP API error, however, they are commonly used to report errors for HTTP APIs.

The problem details service implements the IProblemDetailsService interface, which supports creating problem details in ASP.NET Core. The AddProblemDetails(IServiceCollection) extension method on IServiceCollection registers the default IProblemDetailsService implementation.

In ASP.NET Core apps, the following middleware generates problem details HTTP responses when AddProblemDetails is called, except when the Accept request HTTP header doesn't include one of the content types supported by the registered IProblemDetailsWriter (default: application/json):

Minimal API apps can be configured to generate problem details response for all HTTP client and server error responses that don't have body content yet by using the AddProblemDetails extension method.

The following code configures the app to generate problem details:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseExceptionHandler();
app.UseStatusCodePages();

app.MapGet("/users/{id:int}", (int id) 
    => id <= 0 ? Results.BadRequest() : Results.Ok(new User(id)));

app.MapGet("/", () => "Test by calling /users/{id:int}");

app.Run();

public record User(int Id);

For more information on using AddProblemDetails, see Problem Details

IProblemDetailsService fallback

In the following code, httpContext.Response.WriteAsync("Fallback: An error occurred.") returns an error if the IProblemDetailsService implementation isn't able to generate a ProblemDetails:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseExceptionHandler(exceptionHandlerApp =>
{
    exceptionHandlerApp.Run(async httpContext =>
    {
        var pds = httpContext.RequestServices.GetService<IProblemDetailsService>();
        if (pds == null
            || !await pds.TryWriteAsync(new() { HttpContext = httpContext }))
        {
            // Fallback behavior
            await httpContext.Response.WriteAsync("Fallback: An error occurred.");
        }
    });
});

app.MapGet("/exception", () =>
{
    throw new InvalidOperationException("Sample Exception");
});

app.MapGet("/", () => "Test by calling /exception");

app.Run();

The preceding code:

  • Writes an error message with the fallback code if the problemDetailsService is unable to write a ProblemDetails. For example, an endpoint where the Accept request header specifies a media type that the DefaulProblemDetailsWriter does not support.
  • Uses the Exception Handler Middleware.

The following sample is similar to the preceding except that it calls the Status Code Pages middleware.

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseStatusCodePages(statusCodeHandlerApp =>
{
    statusCodeHandlerApp.Run(async httpContext =>
    {
        var pds = httpContext.RequestServices.GetService<IProblemDetailsService>();
        if (pds == null
            || !await pds.TryWriteAsync(new() { HttpContext = httpContext }))
        {
            // Fallback behavior
            await httpContext.Response.WriteAsync("Fallback: An error occurred.");
        }
    });
});

app.MapGet("/users/{id:int}", (int id) =>
{
    return id <= 0 ? Results.BadRequest() : Results.Ok(new User(id));
});

app.MapGet("/", () => "Test by calling /users/{id:int}");

app.Run();

public record User(int Id);

This article describes how to handle errors in Minimal API apps.

Exceptions

In a Minimal API app, there are two different built-in centralized mechanisms to handle unhandled exceptions:

This section refers to the following Minimal API app to demonstrate ways to handle exceptions. It throws an exception when the endpoint /exception is requested:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Map("/exception", () 
    => { throw new InvalidOperationException("Sample Exception"); });

app.Run();

Developer Exception Page

The Developer Exception Page shows detailed stack traces for server errors. It uses DeveloperExceptionPageMiddleware to capture synchronous and asynchronous exceptions from the HTTP pipeline and to generate error responses.

ASP.NET Core apps enable the developer exception page by default when both:

For more information on configuring middleware, see Middleware in Minimal API apps.

Using the preceding Minimal API app, when the Developer Exception Page detects an unhandled exception, it generates a default plain-text response similar to the following example:

HTTP/1.1 500 Internal Server Error
Content-Type: text/plain; charset=utf-8
Date: Thu, 27 Oct 2022 18:00:59 GMT
Server: Kestrel
Transfer-Encoding: chunked

    System.InvalidOperationException: Sample Exception
    at Program.<>c.<<Main>$>b__0_1() in ....:line 17
    at lambda_method2(Closure, Object, HttpContext)
    at Microsoft.AspNetCore.Routing.EndpointMiddleware.Invoke(HttpContext httpContext)
    --- End of stack trace from previous location ---
    at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)
HEADERS
=======
Accept: */*
Connection: keep-alive
Host: localhost:5239
Accept-Encoding: gzip, deflate, br

Warning

Don't enable the Developer Exception Page unless the app is running in the Development environment. Don't share detailed exception information publicly when the app runs in production. For more information on configuring environments, see Use multiple environments in ASP.NET Core.

Exception handler

In non-development environments, use the Exception Handler Middleware to produce an error payload. To configure the Exception Handler Middleware, call UseExceptionHandler.

For example, the following code changes the app to respond with an RFC 7807-compliant payload to the client. For more information, see Problem Details section.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.UseExceptionHandler(exceptionHandlerApp 
    => exceptionHandlerApp.Run(async context 
        => await Results.Problem()
                     .ExecuteAsync(context)));

app.Map("/exception", () 
    => { throw new InvalidOperationException("Sample Exception"); });

app.Run();

Client and Server error responses

Consider the following Minimal API app.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Map("/users/{id:int}", (int id) 
    => id <= 0 ? Results.BadRequest() : Results.Ok(new User(id)) );

app.Run();

public record User(int Id);

The /users endpoint produces 200 OK with a json representation of User when id is greater than 0, otherwise a 400 BAD REQUEST status code without a response body. For more information about creating a response, see Create responses in Minimal API apps.

The Status Code Pages middleware can be configured to produce a common body content, when empty, for all HTTP client (400-499) or server (500 -599) responses. The middleware is configured by calling the UseStatusCodePages extension method.

For example, the following example changes the app to respond with an RFC 7807-compliant payload to the client for all client and server responses, including routing errors (for example, 404 NOT FOUND). For more information, see the Problem Details section.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.UseStatusCodePages(async statusCodeContext 
    =>  await Results.Problem(statusCode: statusCodeContext.HttpContext.Response.StatusCode)
                 .ExecuteAsync(statusCodeContext.HttpContext));

app.Map("/users/{id:int}", (int id) 
    => id <= 0 ? Results.BadRequest() : Results.Ok(new User(id)) );

app.Run();

public record User(int Id);

Problem details

Problem Details are not the only response format to describe an HTTP API error, however, they are commonly used to report errors for HTTP APIs.

The problem details service implements the IProblemDetailsService interface, which supports creating problem details in ASP.NET Core. The AddProblemDetails(IServiceCollection) extension method on IServiceCollection registers the default IProblemDetailsService implementation.

In ASP.NET Core apps, the following middleware generates problem details HTTP responses when AddProblemDetails is called, except when the Accept request HTTP header doesn't include one of the content types supported by the registered IProblemDetailsWriter (default: application/json):

Minimal API apps can be configured to generate problem details response for all HTTP client and server error responses that don't have a body content yet by using the AddProblemDetails extension method.

The following code configures the app to generate problem details:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseExceptionHandler();
app.UseStatusCodePages();

app.Map("/users/{id:int}", (int id) 
    => id <= 0 ? Results.BadRequest() : Results.Ok(new User(id)) );

app.Map("/exception", () 
    => { throw new InvalidOperationException("Sample Exception"); });

app.Run();

For more information on using AddProblemDetails, see Problem Details