Zpracování chyb ve ASP.NET Core rozhraní API

Tento článek popisuje, jak zpracovávat a přizpůsobovat zpracování chyb pomocí ASP.NET Core rozhraní API.

Zobrazení nebo stažení ukázkového kódu (Stažení )

Stránka výjimky pro vývojáře

Stránka výjimky pro vývojáře je užitečný nástroj, který poskytuje podrobné trasování zásobníku pro chyby serveru. Používá k zachycení synchronních a asynchronních výjimek z kanálu HTTP a DeveloperExceptionPageMiddleware ke generování chybových odpovědí. Pro ilustraci zvažte následující akci kontroleru:

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

Spuštěním následujícího curl příkazu otestujte předchozí akci:

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

Ve ASP.NET Core 3.0 a novějších verzích se na stránce výjimky vývojáře zobrazí odpověď ve formátu prostého textu, pokud klient nepožádá o výstup ve formátu HTML. Objeví se následující výstup:

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

Pokud chcete místo toho zobrazit odpověď ve formátu HTML, nastavte hlavičku požadavku Accept HTTP na text/html typ média. Například:

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

Vezměte v úvahu následující úryvek z odpovědi HTTP:

V ASP.NET Core verze 2.2 a starší se na stránce Developer Exception Page zobrazí odpověď ve formátu HTML. Představte si například následující úryvek z odpovědi 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;
}

Odpověď ve formátu HTML se stává užitečnou při testování prostřednictvím nástrojů, jako je Postman. Následující snímek obrazovky ukazuje odpovědi ve formátu prostého textu i HTML v aplikaci Postman:

Testování stránky výjimky pro vývojáře v Postmanu

Upozornění

Stránku výjimky pro vývojáře povolte jenom v případě, že je aplikace spuštěná ve vývojovém prostředí. Pokud je aplikace spuštěná v produkčním prostředí, nesdílejte podrobné informace o výjimce veřejně. Další informace o konfiguraci prostředí najdete v tématu Používání více prostředí v ASP.NET Core .

Neo označení metody akce obslužné rutiny chyb atributy metody HTTP, například HttpGet . Explicitní příkazy brání některým požadavkům v dosažení metody akce. Pokud by se měla chyba zobrazit neověřeným uživatelům, povolte anonymní přístup k metodě.

Obslužná rutina výjimky

V jiných než vývojových prostředích lze middleware pro zpracování výjimek použít k vytvoření datové části chyby:

  1. V Startup.Configure souboru UseExceptionHandler vyvolat k použití middlewaru:

    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. Nakonfigurujte akci kontroleru tak, aby reagovala na /error trasu:

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

Předchozí akce odešle klientovi datovou část odpovídající specifikaci Error RFC 7807.

Middleware pro zpracování výjimek může také poskytovat podrobnější výstup vyjednaný obsahem v místním vývojovém prostředí. Následující postup použijte k vytvoření konzistentního formátu datové části v různých vývojových a produkčních prostředích:

  1. V Startup.Configure zaregistrujte instance middlewaru pro zpracování výjimek specifické pro prostředí:

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

    V předchozím kódu je middleware zaregistrován pomocí:

    • Trasa ve /error-local-development vývojovém prostředí.
    • Trasa v /error prostředích, která nejsou Vývoj.

  2. Použití směrování atributů na akce kontroleru:

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

    Předchozí kód volá ControllerBase.Problem k vytvoření ProblemDetails odpovědi.

Použití výjimek k úpravě odpovědi

Obsah odpovědi je možné upravit mimo kontroler. V ASP.NET rozhraní API verze 4.x bylo jedním ze způsobů použití HttpResponseException typu . ASP.NET Core neobsahuje ekvivalentní typ. Podporu HttpResponseException pro můžete přidat pomocí následujících kroků:

  1. Vytvořte známý typ výjimky s názvem HttpResponseException :

    public class HttpResponseException : Exception
    {
        public int Status { get; set; } = 500;
    
        public object Value { get; set; }
    }
    
  2. Vytvořte filtr akcí s názvem 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;
            }
        }
    }
    

    Předchozí filtr určuje maximální celočíselnou Order hodnotu minus 10. To umožňuje spuštění dalších filtrů na konci kanálu.

  3. V Startup.ConfigureServices souboru přidejte filtr akcí do kolekce filtrů:

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

Odpověď na chybu ověření

U kontrolerů webového rozhraní API odpoví MVC ValidationProblemDetails typem odpovědi, když se ověření modelu nezdaří. MVC používá výsledky k InvalidModelStateResponseFactory vytvoření chybové odpovědi pro selhání ověření. Následující příklad používá objekt pro vytváření ke změně výchozího typu odpovědi na SerializableError v 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;
    };
});

Odpověď na chybu klienta

Výsledek chyby je definován jako výsledek se stavový kód HTTP 400 nebo vyšší. V případě kontrolerů webového rozhraní API MVC transformuje výsledek chyby na výsledek pomocí ProblemDetails .

Důležité

ASP.NET Core 2.1 vygeneruje odpověď s podrobnostmi o problému, která téměř vyhovuje specifikaci RFC 7807. Pokud je 100% dodržování předpisů důležité, upgradujte projekt na ASP.NET Core 2.2 nebo novější.

Odpověď na chybu je možné nakonfigurovat jedním z následujících způsobů:

  1. Implementace ProblemDetailsFactory
  2. Použití ApiBehaviorOptions.ClientErrorMapping

Implementovat ProblemDetailsFactory

MVC používá k vytvoření všech instancí a Microsoft.AspNetCore.Mvc.Infrastructure.ProblemDetailsFactory ProblemDetails ValidationProblemDetails . Patří sem odpovědi na chyby klienta, chybové odpovědi při ověřování a metody ControllerBase.Problem ControllerBase.ValidationProblem a .

Pokud chcete přizpůsobit odpověď na podrobnosti problému, zaregistrujte vlastní implementaci v ProblemDetailsFactory Startup.ConfigureServices souboru :

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

Odpověď na chybu je možné nakonfigurovat tak, jak je uvedeno v části Use ApiBehaviorOptions.ClientErrorMapping.

Použití ApiBehaviorOptions.ClientErrorMapping

Ke ClientErrorMapping konfiguraci obsahu odpovědi použijte vlastnost ProblemDetails . Například následující kód v souboru Startup.ConfigureServices aktualizuje vlastnost pro odpovědi 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";
    });

Vlastní middleware pro zpracování výjimek

Výchozí hodnoty middlewaru pro zpracování výjimek fungují pro většinu aplikací dobře. U aplikací, které vyžadují specializované zpracování výjimek, zvažte přizpůsobení middlewaru pro zpracování výjimek.

Vytvoření datové části ProblemDetails pro výjimky

ASP.NET Core nevytváří standardizovanou datovou část chyby, když na serveru dojde k neošetřené výjimce. Ve scénářích, kde je žádoucí vrátit klientovi standardizovanou odpověď ProblemDetails, lze middleware ProblemDetails použít k mapování výjimek a 404 na datovou část ProblemDetails. Middleware pro zpracování výjimek lze použít také k vrácení datové ProblemDetails části pro neošetřené výjimky.