次の方法で共有


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

Note

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

警告

このバージョンの ASP.NET Core はサポート対象から除外されました。 詳細については、「.NET および .NET Core サポート ポリシー」を参照してください。 現在のリリースについては、この記事の .NET 8 バージョンを参照してください。

重要

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

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

協力: David Acker

この記事では、Minimal API アプリでエラーを処理する方法について説明します。 コントローラーベースの API でのエラー処理の詳細については、「ASP.NET Core でのエラー処理」および「ASP.NET Core コントローラーベースの Web 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 アプリでは既定で開発者例外ページが有効になります。

以前のテンプレート、つまり WebHost.CreateDefaultBuilder を使用して作成されたアプリは、app.UseDeveloperExceptionPage を呼び出すことによって開発者例外ページを有効にすることができます。

警告

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

開発者例外ページには、例外と要求に関する次の情報が含まれている場合があります。

  • スタック トレース
  • クエリ文字列のパラメーター (ある場合)
  • Cookie (ある場合)
  • ヘッダー
  • エンドポイント メタデータ (存在する場合)

開発者例外ページで何らかの情報が提供されるとは限りません。 完全なエラー情報については、ログ記録に関するページを参照してください。

次の図は、タブと表示される情報を示すアニメーション付きのサンプルの開発者例外ページを示しています。

選択された各タブを表示するようにアニメーション化された開発者例外ページ。

Accept: text/plain ヘッダーを含む要求への応答で、開発者例外ページは HTML ではなくプレーン テキストを返します。 次に例を示します。

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

開発者例外ページを確認するには:

  • 開発環境でサンプル アプリを実行します。
  • /exception エンドポイントに移動します。

例外ハンドラー

開発以外の環境では、例外処理ミドルウェアを使用してエラー ペイロードを生成します。 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(IServiceCollection) 拡張メソッドは、既定の IProblemDetailsService 実装を登録します。

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

AddProblemDetails 拡張メソッドを使用することで、Minimal API アプリは、すべての 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(IServiceCollection) 拡張メソッドは、既定の 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 の使用について詳しくは、「問題の詳細」を参照してください。