Minimal API アプリでエラーを処理する方法

Note

これは、この記事の最新バージョンではありません。 現在のリリースについては、この記事の .NET 8 バージョンを参照してください。

重要

この情報はリリース前の製品に関する事項であり、正式版がリリースされるまでに大幅に変更される可能性があります。 Microsoft はここに示されている情報について、明示か黙示かを問わず、一切保証しません。

現在のリリースについては、この記事の .NET 8 バージョンを参照してください。

協力: David Acker

この記事では、Minimal API アプリでエラーを処理する方法について説明します。

例外

Minimal API アプリには、未処理の例外を処理するための 2 つの異なる組み込みの一元化されたメカニズムがあります。

このセクションでは、次の Minimal API アプリを参照して、例外を処理する方法を示します。 エンドポイント /exception が要求されると、例外がスローされます。

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();

開発者例外ページ

開発者例外ページには、サーバー エラーの詳しいスタック トレースが示されています。 DeveloperExceptionPageMiddleware を使用して、HTTP パイプラインから同期および非同期例外をキャプチャし、エラー応答を生成します。

次の両方が当てはまる場合、ASP.NET Core アプリでは既定で開発者例外ページが有効になります。

ミドルウェアの構成の詳細については、「Minimal API アプリのミドルウェア」を参照してください。

上記の Minimal API アプリを使用すると、Developer Exception Page で未処理の例外が検出されると、次の例のような既定のプレーンテキスト応答が生成されます。

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

警告

アプリを開発環境で実行しない限り、開発者例外ページを有効にしないでください。 アプリを運用環境で実行するときは、詳細な例外情報を公開しないでください。 環境の構成について詳しくは、「ASP.NET Core で複数の環境を使用する」を参照してください。

例外ハンドラー

開発以外の環境では、例外処理ミドルウェアを使用してエラー ペイロードを生成します。 Exception Handler Middleware を構成するには、UseExceptionHandler を呼び出します。

たとえば、次のコードは、RFC 7807 準拠のペイロードでクライアントに応答するようにアプリを変更します。 詳細については、「問題の詳細」セクションを参照してください。

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();

クライアントとサーバーのエラー応答

次の Minimal API アプリについて考えてみましょう。

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);

/users エンドポイントは、id0 より大きい場合は Userjson 表現を使用し、それ以外の場合は応答本文のない 400 BAD REQUEST 状態コードを使用して 200 OK を生成します。 応答の作成の詳細については、「Minimal API アプリで応答を作成する」を参照してください。

Status Code Pages middleware は、すべての HTTP クライアント (400-499) またはサーバー (500 -599) 応答に対して、空の場合に共通の本文コンテンツを生成するように構成できます。 ミドルウェアは、UseStatusCodePages 拡張メソッドを呼び出すことによって構成されます。

たとえば、次の例では、ルーティング エラー ( など) を含むすべてのクライアントとサーバーの応答に対して、RFC 7807404 NOT FOUND 準拠のペイロードでクライアントに応答するようにアプリを変更します。 詳細については、「問題の詳細」セクションを参照してください。

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);

問題の詳細

問題の詳細は、HTTP API エラーを記述する唯一の応答形式ではありませんが、一般的に HTTP API のエラーを報告するために使用されます。

問題の詳細サービスは、IProblemDetailsService インターフェイスを実装し、これにより、ASP.NET Core での問題の詳細の作成がサポートされます。 IServiceCollectionAddProblemDetails 拡張メソッドは、既定の IProblemDetailsService 実装を登録します。

ASP.NET Core アプリでは、次のミドルウェアによって、AddProblemDetails が呼び出されたときに問題の詳細 HTTP 応答が生成されます。ただし、Accept 要求 HTTP ヘッダーに、登録された IProblemDetailsWriter (既定値: application/json) によってサポートされるいずれかのコンテンツ タイプが含まれていない場合を除きます。

Minimal API アプリは、AddProblemDetails 拡張メソッドを使用して、"本文のコンテンツがまだない" すべての HTTP クライアントとサーバーのエラー応答について問題の詳細の応答を生成するように構成できます。

次のコードでは、問題の詳細を生成するようにアプリを構成します。

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);

AddProblemDetails の使用について詳しくは、「問題の詳細」を参照してください。

IProblemDetailsService フォールバック

次のコードの httpContext.Response.WriteAsync("Fallback: An error occurred.") は、IProblemDetailsService の実装で 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();

上記のコードでは次の操作が行われます。

  • problemDetailsServiceProblemDetails を書き込めない場合は、フォールバック コードでエラー メッセージを書き込みます。 たとえば、DefaulProblemDetailsWriter がサポートしていないメディアの種類を Accept 要求ヘッダーで指定するエンドポイントなどです。
  • 例外ハンドラー ミドルウェアを使います。

次の例は、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);

この記事では、Minimal API アプリでエラーを処理する方法について説明します。

例外

Minimal API アプリには、未処理の例外を処理するための 2 つの異なる組み込みの一元化されたメカニズムがあります。

このセクションでは、次の Minimal API アプリを参照して、例外を処理する方法を示します。 エンドポイント /exception が要求されると、例外がスローされます。

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

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

app.Run();

開発者例外ページ

開発者例外ページには、サーバー エラーの詳しいスタック トレースが示されています。 DeveloperExceptionPageMiddleware を使用して、HTTP パイプラインから同期および非同期例外をキャプチャし、エラー応答を生成します。

次の両方が当てはまる場合、ASP.NET Core アプリでは既定で開発者例外ページが有効になります。

ミドルウェアの構成の詳細については、「Minimal API アプリのミドルウェア」を参照してください。

上記の Minimal API アプリを使用すると、Developer Exception Page で未処理の例外が検出されると、次の例のような既定のプレーンテキスト応答が生成されます。

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

警告

アプリを開発環境で実行しない限り、開発者例外ページを有効にしないでください。 アプリを運用環境で実行するときは、詳細な例外情報を公開しないでください。 環境の構成について詳しくは、「ASP.NET Core で複数の環境を使用する」を参照してください。

例外ハンドラー

開発以外の環境では、例外処理ミドルウェアを使用してエラー ペイロードを生成します。 Exception Handler Middleware を構成するには、UseExceptionHandler を呼び出します。

たとえば、次のコードは、RFC 7807 準拠のペイロードでクライアントに応答するようにアプリを変更します。 詳細については、「問題の詳細」セクションを参照してください。

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();

クライアントとサーバーのエラー応答

次の Minimal API アプリについて考えてみましょう。

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);

/users エンドポイントは、id0 より大きい場合は Userjson 表現を使用し、それ以外の場合は応答本文のない 400 BAD REQUEST 状態コードを使用して 200 OK を生成します。 応答の作成の詳細については、「Minimal API アプリで応答を作成する」を参照してください。

Status Code Pages middleware は、すべての HTTP クライアント (400-499) またはサーバー (500 -599) 応答に対して、空の場合に共通の本文コンテンツを生成するように構成できます。 ミドルウェアは、UseStatusCodePages 拡張メソッドを呼び出すことによって構成されます。

たとえば、次の例では、ルーティング エラー ( など) を含むすべてのクライアントとサーバーの応答に対して、RFC 7807404 NOT FOUND 準拠のペイロードでクライアントに応答するようにアプリを変更します。 詳細については、「問題の詳細」セクションを参照してください。

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);

問題の詳細

問題の詳細は、HTTP API エラーを記述する唯一の応答形式ではありませんが、一般的に HTTP API のエラーを報告するために使用されます。

問題の詳細サービスは、IProblemDetailsService インターフェイスを実装し、これにより、ASP.NET Core での問題の詳細の作成がサポートされます。 IServiceCollectionAddProblemDetails 拡張メソッドは、既定の IProblemDetailsService 実装を登録します。

ASP.NET Core アプリでは、次のミドルウェアによって、AddProblemDetails が呼び出されたときに問題の詳細 HTTP 応答が生成されます。ただし、Accept 要求 HTTP ヘッダーに、登録された IProblemDetailsWriter (既定値: application/json) によってサポートされるいずれかのコンテンツ タイプが含まれていない場合を除きます。

Minimal API アプリは、AddProblemDetails 拡張メソッドを使用して、"本文のコンテンツがまだない" すべての HTTP クライアントとサーバーのエラー応答について問題の詳細の応答を生成するように構成できます。

次のコードでは、問題の詳細を生成するようにアプリを構成します。

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();

AddProblemDetails の使用について詳しくは、「問題の詳細」を参照してください。