Solución:
Usar middleware de manejo de excepciones integrado
Paso 1. En su inicio, registre su ruta de manejo de excepciones:
// It should be one of your very first registrations
app.UseExceptionHandler("/error"); // Add this
app.UseEndpoints(endpoints => endpoints.MapControllers());
Paso 2. Cree un controlador que manejará todas las excepciones y producirá una respuesta de error:
[ApiExplorerSettings(IgnoreApi = true)]
public class ErrorsController : ControllerBase
{
[Route("error")]
public MyErrorResponse Error()
{
var context = HttpContext.Features.Get<IExceptionHandlerFeature>();
var exception = context.Error; // Your exception
var code = 500; // Internal Server Error by default
if (exception is MyNotFoundException) code = 404; // Not Found
else if (exception is MyUnauthException) code = 401; // Unauthorized
else if (exception is MyException) code = 400; // Bad Request
Response.StatusCode = code; // You can use HttpStatusCode enum instead
return new MyErrorResponse(exception); // Your error model
}
}
Algunas notas y observaciones importantes:
-
[ApiExplorerSettings(IgnoreApi = true)]
es necesario. De lo contrario, puede romper tu arrogancia Swashbuckle. - De nuevo,
app.UseExceptionHandler("/error");
tiene que ser uno de los primeros registros en su StartupConfigure(...)
método. Probablemente sea seguro colocarlo en la parte superior del método. - El camino en
app.UseExceptionHandler("/error")
y en el controlador[Route("error")]
debería ser el mismo, para permitir que el controlador maneje las excepciones redirigidas desde el middleware del controlador de excepciones.
La documentación de Microsoft para este tema no es tan buena pero tiene algunas ideas interesantes. Dejaré el enlace aquí.
Modelos de respuesta y excepciones personalizadas
Implemente su propio modelo de respuesta y excepciones. Este ejemplo es solo un buen punto de partida. Cada servicio necesitaría manejar las excepciones a su manera. Pero con este código, tiene total flexibilidad y control sobre el manejo de excepciones y la devolución de un resultado adecuado a la persona que llama.
Un ejemplo de modelo de respuesta a errores (solo para darle algunas ideas):
public class MyErrorResponse
{
public string Type { get; set; }
public string Message { get; set; }
public string StackTrace { get; set; }
public MyErrorResponse(Exception ex)
{
Type = ex.GetType().Name;
Message = ex.Message;
StackTrace = ex.ToString();
}
}
Para servicios más simples, es posible que desee implementar una excepción de código de estado http que se vería así:
public class HttpStatusException : Exception
{
public HttpStatusCode Status { get; private set; }
public HttpStatusException(HttpStatusCode status, string msg) : base(msg)
{
Status = status;
}
}
Esto se puede lanzar así:
throw new HttpStatusCodeException(HttpStatusCode.NotFound, "User not found");
Entonces su código de manejo podría simplificarse a:
if (exception is HttpStatusException httpException)
{
code = (int) httpException.Status;
}
¿Por qué tan poco obvio? HttpContext.Features.Get<IExceptionHandlerFeature>()
?
Los desarrolladores de ASP.NET Core adoptaron el concepto de middlewares donde diferentes aspectos de la funcionalidad como Auth, Mvc, Swagger, etc.se separan y ejecutan secuencialmente procesando la solicitud y devolviendo la respuesta o pasando la ejecución al siguiente middleware. Con esta arquitectura, MVC en sí, por ejemplo, no podría manejar los errores que ocurren en Auth. Entonces, se les ocurrió un middleware de manejo de excepciones que detecta todas las excepciones que ocurren en middlewares registrados en la tubería, empuja los datos de excepción a HttpContext.Features
y vuelve a ejecutar la canalización para la ruta especificada (/error
), permitiendo que cualquier middleware maneje esta excepción, y la mejor manera de manejarla es mediante nuestros Controladores para mantener una negociación de contenido adecuada.
Hay un middleware integrado que lo hace más fácil que escribir uno personalizado.
Asp.Net Core 5
versión:
app.UseExceptionHandler(a => a.Run(async context =>
{
var exceptionHandlerPathFeature = context.Features.Get<IExceptionHandlerPathFeature>();
var exception = exceptionHandlerPathFeature.Error;
await context.Response.WriteAsJsonAsync(new { error = exception.Message });
}));
Versiones anteriores (no tenían WriteAsJsonAsync
extensión):
app.UseExceptionHandler(a => a.Run(async context =>
{
var exceptionHandlerPathFeature = context.Features.Get<IExceptionHandlerPathFeature>();
var exception = exceptionHandlerPathFeature.Error;
var result = JsonConvert.SerializeObject(new { error = exception.Message });
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(result);
}));
Debería hacer más o menos lo mismo, solo que escribir un poco menos de código.
Importante: Recuerda agregarlo antes UseMvc
(o UseRouting
en .Net Core 3) ya que el orden es importante.
Su mejor opción es utilizar middleware para lograr el registro que está buscando. Desea poner su registro de excepciones en un middleware y luego manejar las páginas de error que se muestran al usuario en un middleware diferente. Eso permite la separación de la lógica y sigue el diseño que Microsoft ha presentado con los 2 componentes de middleware. Aquí hay un buen enlace a la documentación de Microsoft: Manejo de errores en ASP.Net Core
Para su ejemplo específico, es posible que desee utilizar una de las extensiones en el middleware StatusCodePage o lanzar la suya propia de esta manera.
Puede encontrar un ejemplo aquí para registrar 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 le gusta esa implementación específica, también puede usar ELM Middleware, y aquí hay 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 sus necesidades, siempre puede implementar su propio componente de Middleware mirando sus implementaciones de ExceptionHandlerMiddleware y ElmMiddleware para comprender los conceptos para construir el suyo propio.
Es importante agregar el middleware de manejo de excepciones debajo del middleware StatusCodePages pero sobre todos los demás componentes de middleware. De esa manera, su middleware de excepción capturará la excepción, la registrará y luego permitirá que la solicitud proceda al middleware de StatusCodePage, que mostrará la página de error amigable al usuario.