Eu comecei a usar o ASP.NET Core para meu novo projeto REST API depois de usar o ASP.NET Web API regular por muitos anos. Eu não'não vejo uma boa maneira de lidar com exceções no ASP.NET Core Web API. Eu tentei implementar um filtro/atributo para o tratamento de exceções:
public class ErrorHandlingFilter : ExceptionFilterAttribute
{
public override void OnException(ExceptionContext context)
{
HandleExceptionAsync(context);
context.ExceptionHandled = true;
}
private static void HandleExceptionAsync(ExceptionContext context)
{
var exception = context.Exception;
if (exception is MyNotFoundException)
SetExceptionResult(context, exception, HttpStatusCode.NotFound);
else if (exception is MyUnauthorizedException)
SetExceptionResult(context, exception, HttpStatusCode.Unauthorized);
else if (exception is MyException)
SetExceptionResult(context, exception, HttpStatusCode.BadRequest);
else
SetExceptionResult(context, exception, HttpStatusCode.InternalServerError);
}
private static void SetExceptionResult(
ExceptionContext context,
Exception exception,
HttpStatusCode code)
{
context.Result = new JsonResult(new ApiResponse(exception))
{
StatusCode = (int)code
};
}
}
E aqui está o meu registo do filtro de arranque:
services.AddMvc(options =>
{
options.Filters.Add(new AuthorizationFilter());
options.Filters.Add(new ErrorHandlingFilter());
});
O problema que eu estava tendo é que quando a exceção ocorre no meu AuthorizationFilter
it'não está sendo tratado pelo ErrorHandlingFilter
. Eu estava esperando que ele fosse pego lá assim como funcionava com a antiga ASP.NET Web API.
Então como posso apanhar todas as excepções de aplicação, bem como quaisquer excepções dos Filtros de Acção?
Depois de muitas experiências com diferentes abordagens de tratamento de exceções, acabei usando middleware. Funcionou da melhor maneira para a minha aplicação ASP.NET Core Web API. Ele lida com exceções de aplicações, bem como exceções de filtros de ação e eu tenho controle total sobre o tratamento de exceções e resposta HTTP. Aqui está o meu middleware de tratamento de exceções:
public class ErrorHandlingMiddleware
{
private readonly RequestDelegate next;
public ErrorHandlingMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context /* other dependencies */)
{
try
{
await next(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception ex)
{
var code = HttpStatusCode.InternalServerError; // 500 if unexpected
if (ex is MyNotFoundException) code = HttpStatusCode.NotFound;
else if (ex is MyUnauthorizedException) code = HttpStatusCode.Unauthorized;
else if (ex is MyException) code = HttpStatusCode.BadRequest;
var result = JsonConvert.SerializeObject(new { error = ex.Message });
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)code;
return context.Response.WriteAsync(result);
}
}
Registre-o antes de MVC na classe Startup
:
app.UseMiddleware(typeof(ErrorHandlingMiddleware));
app.UseMvc();
Você pode adicionar rastreamento de pilha, nome do tipo de exceção, códigos de erro ou qualquer coisa que você queira. Muito flexível. Aqui está um exemplo de resposta a exceções:
{ "error": "Authentication token is not valid." }
Considere injetar IOptions<MvcJsonOptions>
ao método Invoke' para então utilizá-lo quando você serializar o objeto de resposta para utilizar o ASP.NET MVC's serialization settings in
JsonConvert.SerializeObject(errorObj, opts.Value.SerializerSettings)` para melhor consistência de serialização em todos os pontos finais.
Existe outra API não óbvia chamada `UseExceptionHandler' que funciona "ok" para cenários simples:
app.UseExceptionHandler(a => a.Run(async context =>
{
var feature = context.Features.Get<IExceptionHandlerPathFeature>();
var exception = feature.Error;
var result = JsonConvert.SerializeObject(new { error = exception.Message });
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(result);
}));
Esta não é uma forma muito óbvia, mas fácil de estabelecer um tratamento de exceções. Contudo, eu ainda prefiro a abordagem de middleware em vez dela, pois tenho mais controle com a capacidade de injetar as dependências necessárias.
A sua melhor aposta é usar o Middleware para conseguir fazer o seu registo're procurando. Você quer colocar seu registro de exceção em um middleware e depois lidar com suas páginas de erro exibidas para o usuário em um middleware diferente. Isso permite a separação da lógica e segue o design que a Microsoft estabeleceu com os 2 componentes do middleware. Aqui's um bom link para a documentação da Microsoft's: Tratamento de Erros no Núcleo ASP.Net
Para o seu exemplo específico, você pode querer usar uma das extensões no middleware StatusCodePage ou enrolar o seu próprio como isto.
Você pode encontrar aqui um exemplo de exceções de registro: ExceptionHandlerMiddleware.cs
public void Configure(IApplicationBuilder app)
{
// app.UseErrorPage(ErrorPageOptions.ShowAll);
// app.UseStatusCodePages();
// app.UseStatusCodePages(context => context.HttpContext.Response.SendAsync("Handler, status code: " + context.HttpContext.Response.StatusCode, "text/plain"));
// app.UseStatusCodePages("text/plain", "Response, status code: {0}");
// app.UseStatusCodePagesWithRedirects("~/errors/{0}");
// app.UseStatusCodePagesWithRedirects("/base/errors/{0}");
// app.UseStatusCodePages(builder => builder.UseWelcomePage());
app.UseStatusCodePagesWithReExecute("/Errors/{0}"); // I use this version
// Exception handling logging below
app.UseExceptionHandler();
}
Se você não'não gosta dessa implementação específica, então você também pode usar ELM Middleware, e aqui estão alguns exemplos: Elm Exception Middleware
public void Configure(IApplicationBuilder app)
{
app.UseStatusCodePagesWithReExecute("/Errors/{0}");
// Exception handling logging below
app.UseElmCapture();
app.UseElmPage();
}
Se isso não't funcionar para suas necessidades, você pode sempre rolar seu próprio componente Middleware olhando para suas implementações do ExceptionHandlerMiddleware e do ElmMiddleware para entender os conceitos para construir o seu próprio.
É importante adicionar o middleware de tratamento de exceções abaixo do middleware StatusCodePages mas acima de todos os seus outros componentes de middleware. Dessa forma o seu middleware Exception irá capturar a exceção, registrá-la e então permitir que a solicitação prossiga para o middleware StatusCodePage que irá exibir a página de erro amigável para o usuário.
Primeiro, configure o ASP.NET Core 2 Startup
para reexecutar para uma página de erro para quaisquer erros do servidor web e quaisquer exceções não-propagadas.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment()) {
// Debug config here...
} else {
app.UseStatusCodePagesWithReExecute("/Error");
app.UseExceptionHandler("/Error");
}
// More config...
}
A seguir, defina um tipo de exceção que lhe permitirá lançar erros com códigos de status HTTP.
public class HttpException : Exception
{
public HttpException(HttpStatusCode statusCode) { StatusCode = statusCode; }
public HttpStatusCode StatusCode { get; private set; }
}
Finalmente, no seu controlador para a página de erro, personalize a resposta com base no motivo do erro e se a resposta será vista diretamente por um usuário final. Este código assume que todas as URLs da API começam com /api/
.
[AllowAnonymous]
public IActionResult Error()
{
// Gets the status code from the exception or web server.
var statusCode = HttpContext.Features.Get<IExceptionHandlerFeature>()?.Error is HttpException httpEx ?
httpEx.StatusCode : (HttpStatusCode)Response.StatusCode;
// For API errors, responds with just the status code (no page).
if (HttpContext.Features.Get<IHttpRequestFeature>().RawTarget.StartsWith("/api/", StringComparison.Ordinal))
return StatusCode((int)statusCode);
// Creates a view model for a user-friendly error page.
string text = null;
switch (statusCode) {
case HttpStatusCode.NotFound: text = "Page not found."; break;
// Add more as desired.
}
return View("Error", new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier, ErrorText = text });
}
O ASP.NET Core registará o detalhe do erro para que possa depurar, pelo que um código de estado pode ser tudo o que deseja fornecer a um requerente (potencialmente não confiável). Se você quiser mostrar mais informações, você pode melhorar o HttpException' para fornecê-lo. Para erros de API, você pode colocar informações de erro codificadas em JSON no corpo da mensagem, substituindo
return StatusCode...por
return Json...`.