處理 ASP.NET Core web Api 中的錯誤

本文說明如何使用 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

在 ASP.NET Core 3.0 和更新版本中,如果用戶端不要求 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 回應的摘錄:

在 ASP.NET Core 2.2 及更早版本中,開發人員例外狀況頁面會顯示 HTML 格式的回應。 例如,請考慮下列來自 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;
}

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

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();
        });
    }
    
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/error");
            app.UseHsts();
        }
    
        app.UseHttpsRedirection();
        app.UseMvc();
    }
    
  2. 設定控制器動作以回應 /error 路由:

    [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規範的承載傳送給用戶端。

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

  1. 在中 Startup.Configure ,註冊環境特定的例外狀況處理中介軟體實例:

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

    在上述程式碼中,會使用下列程式碼來註冊中介軟體:

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

    上述程式碼會呼叫ControllerBase來建立回應。 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;
            }
        }
    }
    

    上述篩選準則會指定 Order 最大整數值減10的。 這可讓其他篩選準則在管線結束時執行。

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

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

驗證失敗錯誤回應

針對 web API 控制器, ValidationProblemDetails 當模型驗證失敗時,MVC 會以回應類型回應。 MVC 使用的結果 InvalidModelStateResponseFactory 來建立驗證失敗的錯誤回應。 下列範例會使用 factory 將預設回應類型變更為 SerializableError 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;
    };
});

用戶端錯誤回應

錯誤結果 定義為 HTTP 狀態碼為400或更高的結果。 針對 web API 控制器,MVC 會將錯誤結果轉換成結果 ProblemDetails

重要

ASP.NET Core 2.1 會產生幾乎符合 RFC 7807 規範的問題詳細資料回應。 如果100% 合規性很重要,請將專案升級為 ASP.NET Core 2.2 或更新版本。

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

  1. 執行 ProblemDetailsFactory
  2. 使用 ApiBehaviorOptions. ClientErrorMapping

實作 ProblemDetailsFactory

MVC 用 Microsoft.AspNetCore.Mvc.Infrastructure.ProblemDetailsFactory 來產生和的所有 ProblemDetails 實例 ValidationProblemDetails 。 這包括用戶端錯誤回應、驗證失敗錯誤回應,以及 ControllerBase.ProblemControllerBase.ValidationProblem helper 方法。

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

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

The error response can be configured as outlined in the Use ApiBehaviorOptions.ClientErrorMapping section.

使用 ApiBehaviorOptions. ClientErrorMapping

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

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

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

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