Saltar al contenido

Manejo de excepciones de la API web de ASP.NET Core

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 Startup Configure(...) 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.Featuresy 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.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *