處理 ASP.NET Core Web API 中的錯誤

本文描述如何使用 ASP.NET Core Web API 來處理錯誤以及自訂錯誤處理。

開發人員例外狀況頁面

開發人員例外狀況頁面會顯示詳細的伺服器錯誤堆疊追蹤。 它會使用 DeveloperExceptionPageMiddleware 從 HTTP 管線中擷取同步和非同步例外狀況,並產生錯誤回應。 例如,請考慮下列控制器動作,而此動作會擲回例外狀況:

[HttpGet("Throw")]
public IActionResult Throw() =>
    throw new Exception("Sample exception.");

「開發人員例外狀況頁面」偵測到未處理的例外狀況時,會產生與下列範例類似的預設純文字回應:

HTTP/1.1 500 Internal Server Error
Content-Type: text/plain; charset=utf-8
Server: Kestrel
Transfer-Encoding: chunked

System.Exception: Sample exception.
   at HandleErrorsSample.Controllers.ErrorsController.Get() in ...
   at lambda_method1(Closure , Object , Object[] )
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync()
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync()

...

如果用戶端要求 HTML 格式化回應,則「開發人員例外狀況頁面」會產生與下列範例類似的回應:

HTTP/1.1 500 Internal Server Error
Content-Type: text/html; charset=utf-8
Server: Kestrel
Transfer-Encoding: chunked

<!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;
}

h1 {
    color: #44525e;
    margin: 15px 0 15px 0;
}

...

若要要求 HTML 格式化回應,請將 Accept HTTP 要求標頭設定為 text/html

警告

除非應用程式在開發環境中執行,否則請勿啟用 [開發人員例外狀況頁面]。 當應用程式在生產環境中執作時,請不要公開分享詳細的例外狀況資訊。 如需設定環境的詳細資訊,請參閱在 ASP.NET Core 中使用多個環境

例外處理常式

在非開發環境中,使用例外狀況處理中介軟體來產生錯誤承載:

  1. Program.cs 中,呼叫 UseExceptionHandler 以新增「例外狀況處理中介軟體」:

    var app = builder.Build();
    
    app.UseHttpsRedirection();
    
    if (!app.Environment.IsDevelopment())
    {
        app.UseExceptionHandler("/error");
    }
    
    app.UseAuthorization();
    
    app.MapControllers();
    
    app.Run();
    
  2. 設定控制器動作,以回應 /error 路由:

    [Route("/error")]
    public IActionResult HandleError() =>
        Problem();
    

上述 HandleError 動作會將符合 RFC 7807 規範的承載傳送至用戶端。

警告

請不要使用 HTTP 方法屬性 (如 HttpGet) 來標記錯誤處理常式動作方法。 明確的動詞可防止某些要求到達動作方法。

針對使用 Swagger/OpenAPI 的 Web API,請使用 [ApiExplorerSettings] 屬性來標記錯誤處理常式動作,並將其 IgnoreApi 屬性設定為 true。 此屬性設定會排除應用程式 OpenAPI 規格的錯誤處理常式動作:

[ApiExplorerSettings(IgnoreApi = true)]

如果未經驗證的使用者應該看到錯誤,則允許匿名存取方法。

「例外狀況處理中介軟體」也可以用於開發環境,以產生跨所有環境的一致承載格式:

  1. Program.cs 中,註冊環境特定的「例外狀況處理中介軟體」執行個體:

    if (app.Environment.IsDevelopment())
    {
        app.UseExceptionHandler("/error-development");
    }
    else
    {
        app.UseExceptionHandler("/error");
    }
    

    在上述程式碼中,會向下列項目註冊中介軟體:

    • 開發環境中 /error-development 的路由。
    • 非開發環境中 /error 的路由。

  2. 新增開發和非開發路由的控制器動作:

    [Route("/error-development")]
    public IActionResult HandleErrorDevelopment(
        [FromServices] IHostEnvironment hostEnvironment)
    {
        if (!hostEnvironment.IsDevelopment())
        {
            return NotFound();
        }
    
        var exceptionHandlerFeature =
            HttpContext.Features.Get<IExceptionHandlerFeature>()!;
    
        return Problem(
            detail: exceptionHandlerFeature.Error.StackTrace,
            title: exceptionHandlerFeature.Error.Message);
    }
    
    [Route("/error")]
    public IActionResult HandleError() =>
        Problem();
    

使用例外狀況來修改回應

使用自訂例外狀況和動作篩選,可以從控制器外部修改回應的內容:

  1. 建立名為 HttpResponseException 的已知例外狀況類型:

    public class HttpResponseException : Exception
    {
        public HttpResponseException(int statusCode, object? value = null) =>
            (StatusCode, Value) = (statusCode, value);
    
        public int StatusCode { get; }
    
        public object? Value { get; }
    }
    
  2. 建立名為 HttpResponseExceptionFilter 的動作篩選:

    public class HttpResponseExceptionFilter : IActionFilter, IOrderedFilter
    {
        public int Order => int.MaxValue - 10;
    
        public void OnActionExecuting(ActionExecutingContext context) { }
    
        public void OnActionExecuted(ActionExecutedContext context)
        {
            if (context.Exception is HttpResponseException httpResponseException)
            {
                context.Result = new ObjectResult(httpResponseException.Value)
                {
                    StatusCode = httpResponseException.StatusCode
                };
    
                context.ExceptionHandled = true;
            }
        }
    }
    

    上述篩選指定最大整數值減 10 的 Order。 此 Order 允許在管線結尾執行其他篩選。

  3. Program.cs 中,將動作篩選新增至 filters 集合:

    builder.Services.AddControllers(options =>
    {
        options.Filters.Add<HttpResponseExceptionFilter>();
    });
    

驗證失敗錯誤回應

針對 Web API 控制器,MVC 會在模型驗證失敗時回應 ValidationProblemDetails 回應類型。 MVC 會使用 InvalidModelStateResponseFactory 的結果來建構驗證失敗的錯誤回應。 下列範例會在 Program.cs 中將預設處理站取代為也支援將回應格式化為 XML 的實作:

builder.Services.AddControllers()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.InvalidModelStateResponseFactory = context =>
            new BadRequestObjectResult(context.ModelState)
            {
                ContentTypes =
                {
                    // using static System.Net.Mime.MediaTypeNames;
                    Application.Json,
                    Application.Xml
                }
            };
    })
    .AddXmlSerializerFormatters();

用戶端錯誤回應

「錯誤結果」定義為 HTTP 狀態碼為 400 或更高值的結果。 針對 Web API 控制器,MVC 會轉換錯誤結果,以產生 ProblemDetails

預設會啟用自動建立 ProblemDetails 錯誤狀態碼,但可以使用下列其中一種方式來設定錯誤回應:

  1. 使用問題詳細資料服務
  2. Implement ProblemDetailsFactory
  3. Use ApiBehaviorOptions.ClientErrorMapping

預設問題詳細資料回應

下列 Program.cs 檔案是由 API 控制器的 Web 應用程式範本所產生:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

請考慮下列控制器,其會在輸入無效時傳回 BadRequest

[Route("api/[controller]/[action]")]
[ApiController]
public class Values2Controller : ControllerBase
{
    // /api/values2/divide/1/2
    [HttpGet("{Numerator}/{Denominator}")]
    public IActionResult Divide(double Numerator, double Denominator)
    {
        if (Denominator == 0)
        {
            return BadRequest();
        }

        return Ok(Numerator / Denominator);
    }

    // /api/values2 /squareroot/4
    [HttpGet("{radicand}")]
    public IActionResult Squareroot(double radicand)
    {
        if (radicand < 0)
        {
            return BadRequest();
        }

        return Ok(Math.Sqrt(radicand));
    }
}

下列任一條件適用時,會使用上述程式碼來產生問題詳細資料回應:

  • 使用零分母來呼叫 /api/values2/divide 端點。
  • 使用小於零的弧度來呼叫 /api/values2/squareroot 端點。

預設問題詳細資料回應本文具有下列 typetitlestatus 值:

{
  "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
  "title": "Bad Request",
  "status": 400,
  "traceId": "00-84c1fd4063c38d9f3900d06e56542d48-85d1d4-00"
}

問題詳細資料服務

ASP.NET Core 支援使用 IProblemDetailsService 來建立 HTTP API 的問題詳細資料。 如需詳細資訊,請參閱問題詳細資料服務

下列程式碼會將應用程式設定為產生所有 HTTP 用戶端和伺服器錯誤回應的問題詳細資料回應,而這些錯誤回應「還沒有本文內容」

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddProblemDetails();

var app = builder.Build();

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

if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}

app.MapControllers();
app.Run();

請考慮上節中的 API 控制器,其會在輸入無效時傳回 BadRequest

[Route("api/[controller]/[action]")]
[ApiController]
public class Values2Controller : ControllerBase
{
    // /api/values2/divide/1/2
    [HttpGet("{Numerator}/{Denominator}")]
    public IActionResult Divide(double Numerator, double Denominator)
    {
        if (Denominator == 0)
        {
            return BadRequest();
        }

        return Ok(Numerator / Denominator);
    }

    // /api/values2 /squareroot/4
    [HttpGet("{radicand}")]
    public IActionResult Squareroot(double radicand)
    {
        if (radicand < 0)
        {
            return BadRequest();
        }

        return Ok(Math.Sqrt(radicand));
    }
}

下列任一條件適用時,會使用上述程式碼來產生問題詳細資料回應:

  • 提供無效的輸入。
  • URI 沒有相符的端點。
  • 發生未處理的例外狀況。

SuppressMapClientErrors 屬性設定為 true 時,系統會停用自動為錯誤狀態碼建立 ProblemDetails 的動作:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.SuppressMapClientErrors = true;
    });

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

如果使用上述程式碼,則在 API 控制器傳回 BadRequest 時,不會傳回 HTTP 400 回應狀態,而且沒有回應本文。 SuppressMapClientErrors 可防止建立 ProblemDetails 回應,即使針對 API 控制器端點呼叫 WriteAsync 時也是一樣。 本文稍後將說明 WriteAsync

下節顯示如何使用 CustomizeProblemDetails 來自訂問題詳細資料回應本文,以傳回更實用的回應。 如需更多自訂選項,請參閱自訂問題詳細資料

使用 CustomizeProblemDetails 來自訂問題詳細資料

下列程式碼會使用 ProblemDetailsOptions 來設定 CustomizeProblemDetails

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

builder.Services.AddProblemDetails(options =>
        options.CustomizeProblemDetails = (context) =>
        {

            var mathErrorFeature = context.HttpContext.Features
                                                       .Get<MathErrorFeature>();
            if (mathErrorFeature is not null)
            {
                (string Detail, string Type) details = mathErrorFeature.MathError switch
                {
                    MathErrorType.DivisionByZeroError =>
                    ("Divison by zero is not defined.",
                                          "https://wikipedia.org/wiki/Division_by_zero"),
                    _ => ("Negative or complex numbers are not valid input.",
                                          "https://wikipedia.org/wiki/Square_root")
                };

                context.ProblemDetails.Type = details.Type;
                context.ProblemDetails.Title = "Bad Input";
                context.ProblemDetails.Detail = details.Detail;
            }
        }
    );

var app = builder.Build();

app.UseHttpsRedirection();

app.UseStatusCodePages();

app.UseAuthorization();

app.MapControllers();

app.Run();

已更新的 API 控制器:

[Route("api/[controller]/[action]")]
[ApiController]
public class ValuesController : ControllerBase
{
    // /api/values/divide/1/2
    [HttpGet("{Numerator}/{Denominator}")]
    public IActionResult Divide(double Numerator, double Denominator)
    {
        if (Denominator == 0)
        {
            var errorType = new MathErrorFeature
            {
                MathError = MathErrorType.DivisionByZeroError
            };
            HttpContext.Features.Set(errorType);
            return BadRequest();
        }

        return Ok(Numerator / Denominator);
    }

    // /api/values/squareroot/4
    [HttpGet("{radicand}")]
    public IActionResult Squareroot(double radicand)
    {
        if (radicand < 0)
        {
            var errorType = new MathErrorFeature
            {
                MathError = MathErrorType.NegativeRadicandError
            };
            HttpContext.Features.Set(errorType);
            return BadRequest();
        }

        return Ok(Math.Sqrt(radicand));
    }

}

下列程式碼包含 MathErrorFeatureMathErrorType,其與上述範例搭配使用:

// Custom Http Request Feature
class MathErrorFeature
{
    public MathErrorType MathError { get; set; }
}

// Custom math errors
enum MathErrorType
{
    DivisionByZeroError,
    NegativeRadicandError
}

下列任一條件適用時,會使用上述程式碼來產生問題詳細資料回應:

  • 使用零分母來呼叫 /divide 端點。
  • 使用小於零的弧度來呼叫 /squareroot 端點。
  • URI 沒有相符的端點。

使用小於零的弧度來呼叫其中一個 squareroot 端點時,問題詳細資料回應本文會包含下列項目:

{
  "type": "https://en.wikipedia.org/wiki/Square_root",
  "title": "Bad Input",
  "status": 400,
  "detail": "Negative or complex numbers are not allowed."
}

檢視或下載範例程式碼

實作 ProblemDetailsFactory

MVC 會使用 Microsoft.AspNetCore.Mvc.Infrastructure.ProblemDetailsFactory 來產生所有 ProblemDetailsValidationProblemDetails 執行個體。 此處理站用於:

若要自訂問題詳細資料回應,請在 Program.cs 中註冊 ProblemDetailsFactory 的自訂實作:

builder.Services.AddControllers();
builder.Services.AddTransient<ProblemDetailsFactory, SampleProblemDetailsFactory>();

使用 ApiBehaviorOptions.ClientErrorMapping

使用 ClientErrorMapping 屬性可設定 ProblemDetails 回應的內容。 例如,Program.cs 中的下列程式碼會更新 404 回應的 Link 屬性:

builder.Services.AddControllers()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.ClientErrorMapping[StatusCodes.Status404NotFound].Link =
            "https://httpstatuses.com/404";
    });

其他資源

本文描述如何使用 ASP.NET Core Web API 來處理錯誤以及自訂錯誤處理。

開發人員例外狀況頁面

開發人員例外狀況頁面會顯示詳細的伺服器錯誤堆疊追蹤。 它會使用 DeveloperExceptionPageMiddleware 從 HTTP 管線中擷取同步和非同步例外狀況,並產生錯誤回應。 例如,請考慮下列控制器動作,而此動作會擲回例外狀況:

[HttpGet("Throw")]
public IActionResult Throw() =>
    throw new Exception("Sample exception.");

「開發人員例外狀況頁面」偵測到未處理的例外狀況時,會產生與下列範例類似的預設純文字回應:

HTTP/1.1 500 Internal Server Error
Content-Type: text/plain; charset=utf-8
Server: Kestrel
Transfer-Encoding: chunked

System.Exception: Sample exception.
   at HandleErrorsSample.Controllers.ErrorsController.Get() in ...
   at lambda_method1(Closure , Object , Object[] )
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync()
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync()

...

如果用戶端要求 HTML 格式化回應,則「開發人員例外狀況頁面」會產生與下列範例類似的回應:

HTTP/1.1 500 Internal Server Error
Content-Type: text/html; charset=utf-8
Server: Kestrel
Transfer-Encoding: chunked

<!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;
}

h1 {
    color: #44525e;
    margin: 15px 0 15px 0;
}

...

若要要求 HTML 格式化回應,請將 Accept HTTP 要求標頭設定為 text/html

警告

除非應用程式在開發環境中執行,否則請勿啟用 [開發人員例外狀況頁面]。 當應用程式在生產環境中執作時,請不要公開分享詳細的例外狀況資訊。 如需設定環境的詳細資訊,請參閱在 ASP.NET Core 中使用多個環境

例外處理常式

在非開發環境中,使用例外狀況處理中介軟體來產生錯誤承載:

  1. Program.cs 中,呼叫 UseExceptionHandler 以新增「例外狀況處理中介軟體」:

    var app = builder.Build();
    
    app.UseHttpsRedirection();
    
    if (!app.Environment.IsDevelopment())
    {
        app.UseExceptionHandler("/error");
    }
    
    app.UseAuthorization();
    
    app.MapControllers();
    
    app.Run();
    
  2. 設定控制器動作,以回應 /error 路由:

    [Route("/error")]
    public IActionResult HandleError() =>
        Problem();
    

上述 HandleError 動作會將符合 RFC 7807 規範的承載傳送至用戶端。

警告

請不要使用 HTTP 方法屬性 (如 HttpGet) 來標記錯誤處理常式動作方法。 明確的動詞可防止某些要求到達動作方法。

針對使用 Swagger/OpenAPI 的 Web API,請使用 [ApiExplorerSettings] 屬性來標記錯誤處理常式動作,並將其 IgnoreApi 屬性設定為 true。 此屬性設定會排除應用程式 OpenAPI 規格的錯誤處理常式動作:

[ApiExplorerSettings(IgnoreApi = true)]

如果未經驗證的使用者應該看到錯誤,則允許匿名存取方法。

「例外狀況處理中介軟體」也可以用於開發環境,以產生跨所有環境的一致承載格式:

  1. Program.cs 中,註冊環境特定的「例外狀況處理中介軟體」執行個體:

    if (app.Environment.IsDevelopment())
    {
        app.UseExceptionHandler("/error-development");
    }
    else
    {
        app.UseExceptionHandler("/error");
    }
    

    在上述程式碼中,會向下列項目註冊中介軟體:

    • 開發環境中 /error-development 的路由。
    • 非開發環境中 /error 的路由。

  2. 新增開發和非開發路由的控制器動作:

    [Route("/error-development")]
    public IActionResult HandleErrorDevelopment(
        [FromServices] IHostEnvironment hostEnvironment)
    {
        if (!hostEnvironment.IsDevelopment())
        {
            return NotFound();
        }
    
        var exceptionHandlerFeature =
            HttpContext.Features.Get<IExceptionHandlerFeature>()!;
    
        return Problem(
            detail: exceptionHandlerFeature.Error.StackTrace,
            title: exceptionHandlerFeature.Error.Message);
    }
    
    [Route("/error")]
    public IActionResult HandleError() =>
        Problem();
    

使用例外狀況來修改回應

使用自訂例外狀況和動作篩選,可以從控制器外部修改回應的內容:

  1. 建立名為 HttpResponseException 的已知例外狀況類型:

    public class HttpResponseException : Exception
    {
        public HttpResponseException(int statusCode, object? value = null) =>
            (StatusCode, Value) = (statusCode, value);
    
        public int StatusCode { get; }
    
        public object? Value { get; }
    }
    
  2. 建立名為 HttpResponseExceptionFilter 的動作篩選:

    public class HttpResponseExceptionFilter : IActionFilter, IOrderedFilter
    {
        public int Order => int.MaxValue - 10;
    
        public void OnActionExecuting(ActionExecutingContext context) { }
    
        public void OnActionExecuted(ActionExecutedContext context)
        {
            if (context.Exception is HttpResponseException httpResponseException)
            {
                context.Result = new ObjectResult(httpResponseException.Value)
                {
                    StatusCode = httpResponseException.StatusCode
                };
    
                context.ExceptionHandled = true;
            }
        }
    }
    

    上述篩選指定最大整數值減 10 的 Order。 此 Order 允許在管線結尾執行其他篩選。

  3. Program.cs 中,將動作篩選新增至 filters 集合:

    builder.Services.AddControllers(options =>
    {
        options.Filters.Add<HttpResponseExceptionFilter>();
    });
    

驗證失敗錯誤回應

針對 Web API 控制器,MVC 會在模型驗證失敗時回應 ValidationProblemDetails 回應類型。 MVC 會使用 InvalidModelStateResponseFactory 的結果來建構驗證失敗的錯誤回應。 下列範例會在 Program.cs 中將預設處理站取代為也支援將回應格式化為 XML 的實作:

builder.Services.AddControllers()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.InvalidModelStateResponseFactory = context =>
            new BadRequestObjectResult(context.ModelState)
            {
                ContentTypes =
                {
                    // using static System.Net.Mime.MediaTypeNames;
                    Application.Json,
                    Application.Xml
                }
            };
    })
    .AddXmlSerializerFormatters();

用戶端錯誤回應

「錯誤結果」定義為 HTTP 狀態碼為 400 或更高值的結果。 針對 Web API 控制器,MVC 會轉換錯誤結果,以產生 ProblemDetails

錯誤回應可以透過下列其中一種方式進行設定:

  1. Implement ProblemDetailsFactory
  2. Use ApiBehaviorOptions.ClientErrorMapping

實作 ProblemDetailsFactory

MVC 會使用 Microsoft.AspNetCore.Mvc.Infrastructure.ProblemDetailsFactory 來產生所有 ProblemDetailsValidationProblemDetails 執行個體。 此處理站用於:

若要自訂問題詳細資料回應,請在 Program.cs 中註冊 ProblemDetailsFactory 的自訂實作:

builder.Services.AddControllers();
builder.Services.AddTransient<ProblemDetailsFactory, SampleProblemDetailsFactory>();

使用 ApiBehaviorOptions.ClientErrorMapping

使用 ClientErrorMapping 屬性可設定 ProblemDetails 回應的內容。 例如,Program.cs 中的下列程式碼會更新 404 回應的 Link 屬性:

builder.Services.AddControllers()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.ClientErrorMapping[StatusCodes.Status404NotFound].Link =
            "https://httpstatuses.com/404";
    });

處理例外狀況的自訂中介軟體

例外狀況處理中介軟體中的預設值適用於大部分的應用程式。 針對需要特殊化例外狀況處理的應用程式,請考慮自訂例外狀況處理中介軟體

產生例外狀況的 ProblemDetails 承載

發生未處理的例外狀況時,ASP.NET Core 不會產生標準化錯誤承載。 針對想要將標準化 ProblemDetails 回應傳回給用戶端的案例,可以使用 ProblemDetails 中介軟體以將例外狀況和 404 回應對應至 ProblemDetails 承載。 例外狀況處理中介軟體也可以用來傳回未處理例外狀況的 ProblemDetails 承載。

其他資源

本文描述如何使用 ASP.NET Core Web API 來處理和自訂錯誤處理。

檢視或下載範例程式碼 (如何下載)

開發人員例外狀況頁面

開發人員例外狀況頁面是一個有用的工具,可取得詳細的伺服器錯誤堆疊追蹤。 它會使用 DeveloperExceptionPageMiddleware 從 HTTP 管線中擷取同步和非同步例外狀況,並產生錯誤回應。 若要說明,請考慮下列控制器動作:

[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 命令來測試上述動作:

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

如果用戶端未要求 HTML 格式化輸出,則「開發人員例外狀況頁面」會顯示純文字回應。 會出現下列輸出:

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 媒體類型。 例如:

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

請考慮 HTTP 回應中的下列摘錄:

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

透過 Postman 這類工具進行測試時,HTML 格式化回應會變得十分有用。 下列螢幕擷取畫面顯示 Postman 中的純文字和 HTML 格式化回應:

Test the Developer Exception Page in Postman.

警告

僅有當應用程式是在開發環境中執行時,才啟用開發人員例外狀況頁面。 當應用程式在生產環境中執作時,請不要公開分享詳細的例外狀況資訊。 如需設定環境的詳細資訊,請參閱在 ASP.NET Core 中使用多個環境

請不要使用 HTTP 方法屬性 (如 HttpGet) 來標記錯誤處理常式動作方法。 明確的動詞可防止某些要求到達動作方法。 如果未經驗證的使用者應該看到錯誤,則允許匿名存取方法。

例外處理常式

在非開發環境中,可以使用例外狀況處理中介軟體來產生錯誤承載:

  1. Startup.Configure 中,叫用 UseExceptionHandler 以使用中介軟體:

    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();
        });
    }
    
  2. 設定控制器動作,以回應 /error 路由:

    [ApiController]
    public class ErrorController : ControllerBase
    {
        [Route("/error")]
        public IActionResult Error() => Problem();
    }
    

上述 Error 動作會將符合 RFC 7807 規範的承載傳送至用戶端。

「例外狀況處理中介軟體」也可以在本機開發環境中提供更詳細的內容交涉輸出。 使用下列步驟,以產生跨開發和生產環境的一致承載格式:

  1. Startup.Configure 中,註冊環境特定的「例外狀況處理中介軟體」執行個體:

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

    在上述程式碼中,會向下列項目註冊中介軟體:

    • 開發環境中 /error-local-development 的路由。
    • 非開發環境中 /error 的路由。

  2. 將屬性路由套用至控制器動作:

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

    上述程式碼會呼叫 ControllerBase.Problem 來建立 ProblemDetails 回應。

使用例外狀況來修改回應

可以從控制器外部修改回應的內容。 在 ASP.NET 4.x Web API 中,執行此作業的其中一種方式是使用 HttpResponseException 類型。 ASP.NET Core 未包括對等的類型。 您可以使用下列步驟來新增 HttpResponseException 的支援:

  1. 建立名為 HttpResponseException 的已知例外狀況類型:

    public class HttpResponseException : Exception
    {
        public int Status { get; set; } = 500;
    
        public object Value { get; set; }
    }
    
  2. 建立名為 HttpResponseExceptionFilter 的動作篩選:

    public class HttpResponseExceptionFilter : IActionFilter, IOrderedFilter
    {
        public int Order { get; } = 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;
            }
        }
    }
    

    上述篩選指定最大整數值減 10 的 Order。 此 Order 允許在管線結尾執行其他篩選。

  3. Startup.ConfigureServices 中,將動作篩選新增至 filters 集合:

    services.AddControllers(options =>
        options.Filters.Add(new HttpResponseExceptionFilter()));
    

驗證失敗錯誤回應

針對 Web API 控制器,MVC 會在模型驗證失敗時回應 ValidationProblemDetails 回應類型。 MVC 會使用 InvalidModelStateResponseFactory 的結果來建構驗證失敗的錯誤回應。 下列範例會使用處理站以將預設回應類型變更為 Startup.ConfigureServices 中的 SerializableError

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

用戶端錯誤回應

「錯誤結果」定義為 HTTP 狀態碼為 400 或更高值的結果。 針對 Web API 控制器,MVC 會將錯誤結果轉換為具有 ProblemDetails 的結果。

錯誤回應可以透過下列其中一種方式進行設定:

  1. Implement ProblemDetailsFactory
  2. Use ApiBehaviorOptions.ClientErrorMapping

實作 ProblemDetailsFactory

MVC 會使用 Microsoft.AspNetCore.Mvc.Infrastructure.ProblemDetailsFactory 來產生所有 ProblemDetailsValidationProblemDetails 執行個體。 此處理站用於:

若要自訂問題詳細資料回應,請在 Startup.ConfigureServices 中註冊 ProblemDetailsFactory 的自訂實作:

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

Use ApiBehaviorOptions.ClientErrorMapping

使用 ClientErrorMapping 屬性可設定 ProblemDetails 回應的內容。 例如,Startup.ConfigureServices 中的下列程式碼會更新 404 回應的 type 屬性:

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

處理例外狀況的自訂中介軟體

例外狀況處理中介軟體中的預設值適用於大部分的應用程式。 針對需要特殊化例外狀況處理的應用程式,請考慮自訂例外狀況處理中介軟體

產生例外狀況的 ProblemDetails 承載

發生未處理的例外狀況時,ASP.NET Core 不會產生標準化錯誤承載。 針對想要將標準化 ProblemDetails 回應傳回給用戶端的案例,可以使用 ProblemDetails 中介軟體以將例外狀況和 404 回應對應至 ProblemDetails 承載。 例外狀況處理中介軟體也可以用來傳回未處理例外狀況的 ProblemDetails 承載。