Empecé a usar ASP.NET Core para mi nuevo proyecto de API REST después de usar la API Web ASP.NET normal durante muchos años. No veo una buena manera de manejar las excepciones en ASP.NET Core Web API. Traté de implementar el filtro/atributo de manejo de excepciones:
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
};
}
}
Y aquí está mi registro de filtro de inicio:
services.AddMvc(options =>
{
options.Filters.Add(new AuthorizationFilter());
options.Filters.Add(new ErrorHandlingFilter());
});
El problema que estaba teniendo es que cuando se produce una excepción en mi AuthorizationFilter
no está siendo manejado por ErrorHandlingFilter
. Esperaba que se capturara allí tal y como funcionaba con la antigua ASP.NET Web API.
Entonces, ¿cómo puedo atrapar todas las excepciones de la aplicación, así como las excepciones de los filtros de acción?
Después de muchos experimentos con diferentes enfoques de manejo de excepciones terminé usando middleware. Fue el que mejor funcionó para mi aplicación ASP.NET Core Web API. Maneja las excepciones de la aplicación así como las excepciones de los filtros de acción y tengo un control total sobre el manejo de la excepción y la respuesta HTTP. Aquí está mi middleware de manejo de excepciones:
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);
}
}
Registrarlo antes de MVC en la clase Startup
:
app.UseMiddleware(typeof(ErrorHandlingMiddleware));
app.UseMvc();
Puedes añadirle el stack trace, el nombre del tipo de excepción, los códigos de error o lo que quieras. Muy flexible. Aquí hay un ejemplo de respuesta de excepción:
{ "error": "Authentication token is not valid." }
Considere la posibilidad de inyectar IOptions<MvcJsonOptions>
al método Invoke
para luego utilizarlo cuando serialice el objeto de respuesta para utilizar la configuración de serialización de ASP.NET MVC'en JsonConvert.SerializeObject(errorObj, opts.Value.SerializerSettings)
para una mejor consistencia de serialización en todos los endpoints.
Hay otra API no obvia llamada UseExceptionHandler
que funciona "bien" para la escenificación simple:
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 es una forma no muy obvia pero fácil de configurar el manejo de excepciones. Sin embargo, sigo prefiriendo el enfoque del middleware ya que tengo más control con la capacidad de inyectar las dependencias necesarias.
Su mejor opción es usar un Middleware para lograr el registro que usted busca. Usted quiere poner su registro de excepciones en un middleware y luego manejar sus páginas de error mostradas al usuario en un middleware diferente. Eso permite la separación de la lógica y sigue el diseño que Microsoft ha establecido con los 2 componentes de middleware. Aquí's un buen enlace a la documentación de Microsoft's: Manejo de errores en ASP.Net Core
Para su ejemplo específico, puede utilizar una de las extensiones en el StatusCodePage middleware o rodar su propio como esto.
Puedes encontrar un ejemplo aquí para el registro de excepciones: 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();
}
Si no te gusta esa implementación específica, también puedes usar ELM Middleware, y aquí tienes algunos ejemplos: Elm Exception Middleware
public void Configure(IApplicationBuilder app)
{
app.UseStatusCodePagesWithReExecute("/Errors/{0}");
// Exception handling logging below
app.UseElmCapture();
app.UseElmPage();
}
Si eso no funciona para tus necesidades, siempre puedes hacer tu propio componente Middleware mirando sus implementaciones de ExceptionHandlerMiddleware y ElmMiddleware para entender los conceptos para construir el tuyo propio.
Es importante añadir el middleware de manejo de excepciones por debajo del middleware StatusCodePages pero por encima de todos sus otros componentes de middleware. De esta manera tu middleware de Excepción capturará la excepción, la registrará, y luego permitirá que la petición proceda al middleware StatusCodePage que mostrará la página de error amigable al usuario.
Lenguaje: C# -->
En primer lugar, configure el Startup
de ASP.NET Core 2 para reejecutar a una página de error para cualquier error del servidor web y cualquier excepción no manejada.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment()) {
// Debug config here...
} else {
app.UseStatusCodePagesWithReExecute("/Error");
app.UseExceptionHandler("/Error");
}
// More config...
}
A continuación, defina un tipo de excepción que le permita lanzar errores con códigos de estado HTTP.
public class HttpException : Exception
{
public HttpException(HttpStatusCode statusCode) { StatusCode = statusCode; }
public HttpStatusCode StatusCode { get; private set; }
}
Por último, en su controlador para la página de error, personalice la respuesta en función del motivo del error y de si la respuesta será vista directamente por un usuario final. Este código asume que todas las URLs de la API comienzan con /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 });
}
ASP.NET Core registrará el detalle del error para que pueda depurarlo, por lo que un código de estado puede ser todo lo que quiera proporcionar a un solicitante (potencialmente no fiable). Si quieres mostrar más información, puedes mejorar HttpException
para proporcionarla. Para los errores de la API, puedes poner información de error codificada en JSON en el cuerpo del mensaje sustituyendo return StatusCode...
por return Json...
.