ASP.NET Core Web API のエラーを処理するHandle errors in ASP.NET Core web APIs

この記事では、ASP.NET Core Web API を使用したエラーの処理方法とエラー処理のカスタマイズ方法について説明します。This article describes how to handle and customize error handling with ASP.NET Core web APIs.

サンプル コードを表示またはダウンロードします (ダウンロード方法)。View or download sample code (How to download)

開発者例外ページDeveloper Exception Page

開発者例外ページは、サーバー エラーの詳しいスタック トレースを取得するために役立つツールです。The Developer Exception Page is a useful tool to get detailed stack traces for server errors. DeveloperExceptionPageMiddleware を使用して、HTTP パイプラインから同期および非同期例外をキャプチャし、エラー応答を生成します。It uses DeveloperExceptionPageMiddleware to capture synchronous and asynchronous exceptions from the HTTP pipeline and to generate error responses. これを説明するために、次のコントローラー アクションがあるとします。To illustrate, consider the following controller action:

[HttpGet("{city}")]
public WeatherForecast Get(string city)
{
    if (!string.Equals(city?.TrimEnd(), "Redmond", StringComparison.OrdinalIgnoreCase))
    {
        throw new ArgumentException(
            $"We don't offer a weather forecast for {city}.", nameof(city));
    }
    
    return GetWeather().First();
}

次の curl コマンドを実行して、上記のアクションをテストします。Run the following curl command to test the preceding action:

curl -i https://localhost:5001/weatherforecast/chicago

ASP.NET Core 3.0 以降では、クライアントにより HTML 形式の出力が要求されない場合、開発者例外ページにはテキスト形式の応答が表示されます。In ASP.NET Core 3.0 and later, the Developer Exception Page displays a plain-text response if the client doesn't request HTML-formatted output. 次のような出力が表示されます。The following output appears:

HTTP/1.1 500 Internal Server Error
Transfer-Encoding: chunked
Content-Type: text/plain
Server: Microsoft-IIS/10.0
X-Powered-By: ASP.NET
Date: Fri, 27 Sep 2019 16:13:16 GMT

System.ArgumentException: We don't offer a weather forecast for chicago. (Parameter 'city')
   at WebApiSample.Controllers.WeatherForecastController.Get(String city) in C:\working_folder\aspnet\AspNetCore.Docs\aspnetcore\web-api\handle-errors\samples\3.x\Controllers\WeatherForecastController.cs:line 34
   at lambda_method(Closure , Object , Object[] )
   at Microsoft.Extensions.Internal.ObjectMethodExecutor.Execute(Object target, Object[] parameters)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Logged|12_1(ControllerActionInvoker invoker)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

HEADERS
=======
Accept: */*
Host: localhost:44312
User-Agent: curl/7.55.1

代わりに HTML 形式の応答を表示するには、Accept HTTP 要求ヘッダーを text/html のメディアの種類に設定します。To display an HTML-formatted response instead, set the Accept HTTP request header to the text/html media type. 例 :For example:

curl -i -H "Accept: text/html" https://localhost:5001/weatherforecast/chicago

HTTP 応答からの次の抜粋を考えてみます。Consider the following excerpt from the HTTP response:

ASP.NET Core 2.2 以前では、開発者例外ページには HTML 形式の応答が表示されます。In ASP.NET Core 2.2 and earlier, the Developer Exception Page displays an HTML-formatted response. たとえば、HTTP 応答からの次の抜粋を考えてみます。For example, consider the following excerpt from the HTTP response:

HTTP/1.1 500 Internal Server Error
Transfer-Encoding: chunked
Content-Type: text/html; charset=utf-8
Server: Microsoft-IIS/10.0
X-Powered-By: ASP.NET
Date: Fri, 27 Sep 2019 16:55:37 GMT

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta charset="utf-8" />
        <title>Internal Server Error</title>
        <style>
            body {
    font-family: 'Segoe UI', Tahoma, Arial, Helvetica, sans-serif;
    font-size: .813em;
    color: #222;
    background-color: #fff;
}

HTML 形式の応答は、Postman などのツールを使用してテストするときに役立ちます。The HTML-formatted response becomes useful when testing via tools like Postman. 次の画面キャプチャは、Postman のテキスト形式と HTML 形式の両方の応答を示しています。The following screen capture shows both the plain-text and the HTML-formatted responses in Postman:

Postman での開発者例外ページのテスト

警告

アプリを開発環境で実行するときにのみ、開発者例外ページを有効にします。Enable the Developer Exception Page only when the app is running in the Development environment. アプリを実稼働環境で実行するときは、詳細な例外情報を公開しません。You don't want to share detailed exception information publicly when the app runs in production. 環境の構成について詳しくは、「ASP.NET Core で複数の環境を使用する」をご覧ください。For more information on configuring environments, see ASP.NET Core で複数の環境を使用する.

例外ハンドラーException handler

開発以外の環境では、例外処理ミドルウェアを使用してエラー ペイロードを生成できます。In non-development environments, Exception Handling Middleware can be used to produce an error payload:

  1. Startup.Configure で、UseExceptionHandler を呼び出してミドルウェアを使用します。In Startup.Configure, invoke UseExceptionHandler to use the middleware:

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/error");
        }
    
        app.UseHttpsRedirection();
        app.UseRouting();
        app.UseAuthorization();
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
    
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/error");
            app.UseHsts();
        }
    
        app.UseHttpsRedirection();
        app.UseMvc();
    }
    
  2. /error ルートに応答するようにコントローラー アクションを構成します。Configure a controller action to respond to the /error route:

    [ApiController]
    public class ErrorController : ControllerBase
    {
        [Route("/error")]
        public IActionResult Error() => Problem();
    }
    
    [ApiController]
    public class ErrorController : ControllerBase
    {
        [Route("/error")]
        public ActionResult Error([FromServices] IHostingEnvironment webHostEnvironment)
        {
            var feature = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
            var ex = feature?.Error;
            var isDev = webHostEnvironment.IsDevelopment();
            var problemDetails = new ProblemDetails
            {
                Status = (int)HttpStatusCode.InternalServerError,
                Instance = feature?.Path,
                Title = isDev ? $"{ex.GetType().Name}: {ex.Message}" : "An error occurred.",
                Detail = isDev ? ex.StackTrace : null,
            };
    
            return StatusCode(problemDetails.Status.Value, problemDetails);
        }
    }
    

前の Error アクションは、RFC 7807 準拠のペイロードをクライアントに送信します。The preceding Error action sends an RFC 7807-compliant payload to the client.

ローカル開発環境では、例外処理ミドルウェアによって、さらに詳しいコンテンツ ネゴシエーション結果も提供されます。Exception Handling Middleware can also provide more detailed content-negotiated output in the local development environment. 次の手順に従い、開発環境と運用環境で一貫性のあるペイロード形式を生成します。Use the following steps to produce a consistent payload format across development and production environments:

  1. Startup.Configure で、環境固有の例外処理ミドルウェア インスタンスを登録します。In Startup.Configure, register environment-specific Exception Handling Middleware instances:

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseExceptionHandler("/error-local-development");
        }
        else
        {
            app.UseExceptionHandler("/error");
        }
    }
    
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseExceptionHandler("/error-local-development");
        }
        else
        {
            app.UseExceptionHandler("/error");
        }
    }
    

    前のコードでは、ミドルウェアは次のように登録されます。In the preceding code, the middleware is registered with:

    • 開発環境では /error-local-development のルート。A route of /error-local-development in the Development environment.
    • 開発以外の環境では /error のルート。A route of /error in environments that aren't Development.
  2. 属性ルーティングをコントローラー アクションに適用します。Apply attribute routing to controller actions:

    [ApiController]
    public class ErrorController : ControllerBase
    {
        [Route("/error-local-development")]
        public IActionResult ErrorLocalDevelopment(
            [FromServices] IWebHostEnvironment webHostEnvironment)
        {
            if (webHostEnvironment.EnvironmentName != "Development")
            {
                throw new InvalidOperationException(
                    "This shouldn't be invoked in non-development environments.");
            }
    
            var context = HttpContext.Features.Get<IExceptionHandlerFeature>();
    
            return Problem(
                detail: context.Error.StackTrace,
                title: context.Error.Message);
        }
    
        [Route("/error")]
        public IActionResult Error() => Problem();
    }
    
    [ApiController]
    public class ErrorController : ControllerBase
    {
        [Route("/error-local-development")]
        public IActionResult ErrorLocalDevelopment(
            [FromServices] IHostingEnvironment webHostEnvironment)
        {
            if (!webHostEnvironment.IsDevelopment())
            {
                throw new InvalidOperationException(
                    "This shouldn't be invoked in non-development environments.");
            }
    
            var feature = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
            var ex = feature?.Error;
    
            var problemDetails = new ProblemDetails
            {
                Status = (int)HttpStatusCode.InternalServerError,
                Instance = feature?.Path,
                Title = ex.GetType().Name,
                Detail = ex.StackTrace,
            };
    
            return StatusCode(problemDetails.Status.Value, problemDetails);
        }
    
        [Route("/error")]
        public ActionResult Error(
            [FromServices] IHostingEnvironment webHostEnvironment)
        {
            var feature = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
            var ex = feature?.Error;
            var isDev = webHostEnvironment.IsDevelopment();
            var problemDetails = new ProblemDetails
            {
                Status = (int)HttpStatusCode.InternalServerError,
                Instance = feature?.Path,
                Title = isDev ? $"{ex.GetType().Name}: {ex.Message}" : "An error occurred.",
                Detail = isDev ? ex.StackTrace : null,
            };
    
            return StatusCode(problemDetails.Status.Value, problemDetails);
        }
    }
    

例外を使用して応答を変更するUse exceptions to modify the response

応答の内容は、コントローラーの外部で変更できます。The contents of the response can be modified from outside of the controller. ASP.NET 4.x Web API の場合、これを行う方法の 1 つが HttpResponseException 型の使用でした。In ASP.NET 4.x Web API, one way to do this was using the HttpResponseException type. ASP.NET Core には同等の型が含まれていません。ASP.NET Core doesn't include an equivalent type. HttpResponseException のサポートは以下の手順で追加することができます。Support for HttpResponseException can be added with the following steps:

  1. HttpResponseException という名前の一般的な例外の種類を作成します。Create a well-known exception type named HttpResponseException:

    public class HttpResponseException : Exception
    {
        public int Status { get; set; } = 500;
    
        public object Value { get; set; }
    }
    
  2. HttpResponseExceptionFilter という名前のアクション フィルターを作成します。Create an action filter named HttpResponseExceptionFilter:

    public class HttpResponseExceptionFilter : IActionFilter, IOrderedFilter
    {
        public int Order { get; set; } = int.MaxValue - 10;
    
        public void OnActionExecuting(ActionExecutingContext context) { }
    
        public void OnActionExecuted(ActionExecutedContext context)
        {
            if (context.Exception is HttpResponseException exception)
            {
                context.Result = new ObjectResult(exception.Value)
                {
                    StatusCode = exception.Status,
                };
                context.ExceptionHandled = true;
            }
        }
    }
    
  3. Startup.ConfigureServices に、フィルター コレクションに対するアクション フィルターを追加します。In Startup.ConfigureServices, add the action filter to the filters collection:

    services.AddControllers(options =>
        options.Filters.Add(new HttpResponseExceptionFilter()));
    
    services.AddMvc(options =>
            options.Filters.Add(new HttpResponseExceptionFilter()))
        .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
    
    services.AddMvc(options =>
            options.Filters.Add(new HttpResponseExceptionFilter()))
        .SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
    

検証失敗のエラー応答Validation failure error response

Web API コントローラーでは、モデルの検証が失敗すると、MVC が ValidationProblemDetails という応答の種類で応答します。For web API controllers, MVC responds with a ValidationProblemDetails response type when model validation fails. MVC は InvalidModelStateResponseFactory の結果を使用して、検証失敗に対するエラー応答を作成します。MVC uses the results of InvalidModelStateResponseFactory to construct the error response for a validation failure. 次の例では、SerializableError で、ファクトリを使用して応答の既定の種類を Startup.ConfigureServices に変更します。The following example uses the factory to change the default response type to SerializableError in Startup.ConfigureServices:

services.AddControllers()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.InvalidModelStateResponseFactory = context =>
        {
            var result = new BadRequestObjectResult(context.ModelState);

            // TODO: add `using System.Net.Mime;` to resolve MediaTypeNames
            result.ContentTypes.Add(MediaTypeNames.Application.Json);
            result.ContentTypes.Add(MediaTypeNames.Application.Xml);

            return result;
        };
    });
services.AddMvc()
    .SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
    .ConfigureApiBehaviorOptions(options =>
    {
        options.InvalidModelStateResponseFactory = context =>
        {
            var result = new BadRequestObjectResult(context.ModelState);

            // TODO: add `using System.Net.Mime;` to resolve MediaTypeNames
            result.ContentTypes.Add(MediaTypeNames.Application.Json);
            result.ContentTypes.Add(MediaTypeNames.Application.Xml);

            return result;
        };
    });
services.AddMvc()
    .SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

services.Configure<ApiBehaviorOptions>(options =>
{
    options.InvalidModelStateResponseFactory = context =>
    {
        var result = new BadRequestObjectResult(context.ModelState);

        // TODO: add `using using System.Net.Mime;` to resolve MediaTypeNames
        result.ContentTypes.Add(MediaTypeNames.Application.Json);
        result.ContentTypes.Add(MediaTypeNames.Application.Xml);

        return result;
    };
});

クライアントのエラー応答Client error response

"エラー結果" は、HTTP 状態コードが 400 以上の結果として定義されます。An error result is defined as a result with an HTTP status code of 400 or higher. Web API コントローラーの場合、MVC によってエラー結果が ProblemDetails を含む結果に変換されます。For web API controllers, MVC transforms an error result to a result with ProblemDetails.

重要

ASP.NET Core 2.1 では、RFC 7807 にほぼ準拠した、問題の詳しい応答が生成されます。ASP.NET Core 2.1 generates a problem details response that's nearly RFC 7807-compliant. 100% のコンプライアンスが重要な場合は、プロジェクトを ASP.NET Core 2.2 以降にアップグレードしてください。If 100 percent compliance is important, upgrade the project to ASP.NET Core 2.2 or later.

エラー応答は、次のいずれかの方法で構成できます。The error response can be configured in one of the following ways:

  1. ProblemDetailsFactory の実装Implement ProblemDetailsFactory
  2. ApiBehaviorOptions.ClientErrorMapping の使用Use ApiBehaviorOptions.ClientErrorMapping

ProblemDetailsFactory の実装Implement ProblemDetailsFactory

MVC は、Microsoft.AspNetCore.Mvc.ProblemDetailsFactory を使用して、ProblemDetailsValidationProblemDetails のすべてのインスタンスを生成します。MVC uses Microsoft.AspNetCore.Mvc.ProblemDetailsFactory to produce all instances of ProblemDetails and ValidationProblemDetails. これには、クライアント エラー応答および検証失敗エラー応答と、Microsoft.AspNetCore.Mvc.ControllerBase.Problem および ValidationProblem() ヘルパー メソッドが含まれます。This includes client error responses, validation failure error responses, and the Microsoft.AspNetCore.Mvc.ControllerBase.Problem and ValidationProblem() helper methods.

問題の詳しい応答をカスタマイズするには、ProblemDetailsFactoryStartup.ConfigureServices のカスタム実装を登録します。To customize the problem details response, register a custom implementation of ProblemDetailsFactory in Startup.ConfigureServices:

public void ConfigureServices(IServiceCollection serviceCollection)
{
    services.AddControllers();
    services.AddTransient<ProblemDetailsFactory, CustomProblemDetailsFactory>();
}

エラー応答は、「ApiBehaviorOptions.ClientErrorMapping の使用」セクションの説明に従って構成できます。The error response can be configured as outlined in the Use ApiBehaviorOptions.ClientErrorMapping section.

ApiBehaviorOptions.ClientErrorMapping の使用Use ApiBehaviorOptions.ClientErrorMapping

ClientErrorMapping の応答の内容を構成するには、ProblemDetails プロパティを使用します。Use the ClientErrorMapping property to configure the contents of the ProblemDetails response. たとえば、Startup.ConfigureServicesの次のコードにより、404 応答の type プロパティが更新されます。For example, the following code in Startup.ConfigureServices updates the type property for 404 responses:

services.AddControllers()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.SuppressConsumesConstraintForFormFileParameters = true;
        options.SuppressInferBindingSourcesForParameters = true;
        options.SuppressModelStateInvalidFilter = true;
        options.SuppressMapClientErrors = true;
        options.ClientErrorMapping[404].Link =
            "https://httpstatuses.com/404";
    });
services.AddMvc()
    .SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
    .ConfigureApiBehaviorOptions(options =>
    {
        options.SuppressConsumesConstraintForFormFileParameters = true;
        options.SuppressInferBindingSourcesForParameters = true;
        options.SuppressModelStateInvalidFilter = true;
        options.SuppressMapClientErrors = true;
        options.ClientErrorMapping[404].Link =
            "https://httpstatuses.com/404";
    });