Controlar errores en ASP.NET Core

Nota

Esta no es la versión más reciente de este artículo. Para la versión actual, consulte la versión .NET 8 de este artículo.

Importante

Esta información hace referencia a un producto en versión preliminar, el cual puede sufrir importantes modificaciones antes de que se publique la versión comercial. Microsoft no proporciona ninguna garantía, expresa o implícita, con respecto a la información proporcionada aquí.

Para la versión actual, consulte la versión .NET 8 de este artículo.

Por Tom Dykstra

Este artículo trata sobre los métodos comunes para controlar errores en aplicaciones web ASP.NET Core. Consulte también Control de errores en API web de ASP.NET Core y Control de errores en aplicaciones de API mínimas.

Página de excepciones para el desarrollador

En la Página de excepciones para el desarrollador se muestra información detallada sobre las excepciones de la solicitud no controladas. De forma predeterminada, las aplicaciones de ASP.NET Core habilitan la página de excepciones del desarrollador cuando se cumplen las dos condiciones siguientes:

La página de excepciones del desarrollador se ejecuta al principio de la canalización del middleware, de modo que puede capturar las excepciones no controladas que se producen en el middleware que sigue.

La información detallada de las excepciones no debería mostrarse públicamente cuando la aplicación se ejecuta en el entorno de producción. Para más información sobre la configuración de los entornos, consulte Uso de varios entornos en ASP.NET Core.

La página de excepciones para el desarrollador puede incluir la siguiente información sobre la excepción y la solicitud:

  • Seguimiento de la pila
  • Parámetros de cadena de consulta (si existen)
  • Cookie (si existen)
  • Encabezados
  • Metadatos del punto de conexión, si los hay

En la imagen siguiente se muestra una página de excepción de desarrollador de ejemplo con Enrutamiento seleccionado y metadatos de punto de conexión mostrados:

Página de excepciones del desarrollador con enrutamiento seleccionado y metadatos del punto de conexión mostrados

No se garantiza que la página de excepciones del desarrollador proporcione alguna información. Use el registro obtener información completa sobre el error.

Página del controlador de excepciones

Para configurar una página de control de errores personalizada para el entorno de producción, llame a UseExceptionHandler. Este middleware de control de excepciones:

  • Captura y registra las excepciones no controladas.
  • Vuelve a ejecutar la solicitud en una canalización alternativa con la ruta de acceso indicada. La solicitud no se vuelve a ejecutar si se ha iniciado la respuesta. El código generado por la plantilla vuelve a ejecutar la solicitud mediante la ruta de acceso /Error.

Advertencia

Si la canalización alternativa produce una excepción propia, el middleware de control de excepciones vuelve a producir la excepción original.

Dado que este middleware puede volver a ejecutar la canalización de solicitudes:

  • Los middleware deben controlar la reentrada con la misma solicitud. Normalmente, esto significa limpiar su estado después de llamar a _next o almacenar en caché su procesamiento en HttpContext para evitar volver a hacerlo. Al tratar con el cuerpo de la solicitud, esto significa almacenar en búfer o almacenar en caché los resultados como el lector de formularios.
  • En el caso de la sobrecarga UseExceptionHandler(IApplicationBuilder, String) que se usa en las plantillas, solo se modifica la ruta de acceso de la solicitud y se borran los datos de ruta. Los datos de la solicitud, como los encabezados, los métodos y los elementos, se reutilizan tal cual.
  • Los servicios con ámbito siguen siendo los mismos.

En el ejemplo siguiente, UseExceptionHandler agrega middleware de control de excepciones en entornos que no son de desarrollo:

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

La plantilla de aplicación de Razor Pages proporciona una página de error (.cshtml) y una clase PageModel (ErrorModel) en la carpeta Pages. Para una aplicación de MVC, la plantilla de proyecto incluye un método de acción de Error y una vista del error para el controlador de Home.

El middleware de control de excepciones vuelve a ejecutar la solicitud mediante el método HTTP original. Si un punto de conexión de controlador de errores está restringido a un conjunto específico de métodos HTTP, solo se ejecuta para esos métodos HTTP. Por ejemplo, una acción de controlador de MVC que usa el atributo [HttpGet] solo se ejecuta para solicitudes GET. Para asegurarse de que todas las solicitudes lleguen a la página de control de errores personalizada, no las restrinja a un conjunto específico de métodos HTTP.

Para controlar las excepciones de manera diferente en función del método HTTP original:

  • En el caso de Razor Pages, cree varios métodos de control. Por ejemplo, use OnGet para controlar las excepciones GET y use OnPost para controlar las excepciones POST.
  • Para MVC, aplique los atributos de verbo HTTP a varias acciones. Por ejemplo, use [HttpGet] para controlar las excepciones GET y use [HttpPost] para controlar las excepciones POST.

Para permitir que los usuarios no autenticados vean la página de control de errores personalizada, asegúrese de que admite el acceso anónimo.

Acceso a la excepción

Use IExceptionHandlerPathFeature para acceder a la ruta de acceso de la solicitud original y a la excepción en un controlador de errores. En el ejemplo siguiente se usa IExceptionHandlerPathFeature para obtener más información sobre la excepción que se produjo:

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
[IgnoreAntiforgeryToken]
public class ErrorModel : PageModel
{
    public string? RequestId { get; set; }

    public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);

    public string? ExceptionMessage { get; set; }

    public void OnGet()
    {
        RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;

        var exceptionHandlerPathFeature =
            HttpContext.Features.Get<IExceptionHandlerPathFeature>();

        if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
        {
            ExceptionMessage = "The file was not found.";
        }

        if (exceptionHandlerPathFeature?.Path == "/")
        {
            ExceptionMessage ??= string.Empty;
            ExceptionMessage += " Page: Home.";
        }
    }
}

Advertencia

No proporcione información de errores confidencial a los clientes. Proporcionar información de los errores es un riesgo para la seguridad.

Lambda del controlador de excepciones

Una alternativa a una página del controlador de excepciones personalizada es proporcionar una expresión lambda en UseExceptionHandler. Usar una expresión lambda permite acceder al error antes de devolver la respuesta.

En el código siguiente se usa una expresión lambda para el control de excepciones:

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler(exceptionHandlerApp =>
    {
        exceptionHandlerApp.Run(async context =>
        {
            context.Response.StatusCode = StatusCodes.Status500InternalServerError;

            // using static System.Net.Mime.MediaTypeNames;
            context.Response.ContentType = Text.Plain;

            await context.Response.WriteAsync("An exception was thrown.");

            var exceptionHandlerPathFeature =
                context.Features.Get<IExceptionHandlerPathFeature>();

            if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
            {
                await context.Response.WriteAsync(" The file was not found.");
            }

            if (exceptionHandlerPathFeature?.Path == "/")
            {
                await context.Response.WriteAsync(" Page: Home.");
            }
        });
    });

    app.UseHsts();
}

Advertencia

No proporcione información de errores confidencial a los clientes. Proporcionar información de los errores es un riesgo para la seguridad.

IExceptionHandler

IExceptionHandler es una interfaz que proporciona al desarrollador una devolución de llamada para controlar excepciones conocidas en una ubicación central.

Las implementaciones de IExceptionHandler se registran mediante una llamada de IServiceCollection.AddExceptionHandler<T>. La duración de una instancia IExceptionHandler es singleton. Se pueden agregar varias implementaciones y se llaman en el orden registrado.

Si un controlador de excepciones controla una solicitud, puede devolver true para detener el procesamiento. Si un controlador de excepciones no controla una excepción, el control vuelve al comportamiento predeterminado y a las opciones del middleware. Se emiten diferentes métricas y registros para excepciones controladas frente a excepciones no controladas.

En el siguiente ejemplo se muestra una implementación de IExceptionHandler:

using Microsoft.AspNetCore.Diagnostics;

namespace ErrorHandlingSample
{
    public class CustomExceptionHandler : IExceptionHandler
    {
        private readonly ILogger<CustomExceptionHandler> logger;
        public CustomExceptionHandler(ILogger<CustomExceptionHandler> logger)
        {
            this.logger = logger;
        }
        public ValueTask<bool> TryHandleAsync(
            HttpContext httpContext,
            Exception exception,
            CancellationToken cancellationToken)
        {
            var exceptionMessage = exception.Message;
            logger.LogError(
                "Error Message: {exceptionMessage}, Time of occurrence {time}",
                exceptionMessage, DateTime.UtcNow);
            // Return false to continue with the default behavior
            // - or - return true to signal that this exception is handled
            return ValueTask.FromResult(false);
        }
    }
}

En el ejemplo siguiente se muestra cómo registrar una implementación de IExceptionHandler para la inserción de dependencias:

using ErrorHandlingSample;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddRazorPages();
builder.Services.AddExceptionHandler<CustomExceptionHandler>();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

// Remaining Program.cs code omitted for brevity

Cuando el código anterior se ejecuta en el entorno de desarrollo:

  • Se llama primero al CustomExceptionHandler para controlar una excepción.
  • Tras registrar la excepción, el método TryHandleException devuelve false, por lo que se muestra la página de excepción del desarrollador.

En otros entornos:

  • Se llama primero al CustomExceptionHandler para controlar una excepción.
  • Tras registrar la excepción, el método TryHandleException devuelve false, por lo que se muestra la página de /Error.

UseStatusCodePages

Una aplicación ASP.NET Core no proporciona de forma predeterminada ninguna página de códigos de estado para los códigos de estado HTTP, como 404 - No encontrado. Cuando la aplicación establece un código de estado de error HTTP 400-599 que no tiene un cuerpo, devuelve el código de estado y un cuerpo de respuesta vacío. Para habilitar los controladores de solo texto predeterminados para los códigos de estado de error comunes, llame a UseStatusCodePages en Program.cs:

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePages();

Llame a UseStatusCodePages antes del middleware de control de solicitudes. Por ejemplo, llame a UseStatusCodePages antes del middleware de archivos estáticos y el middleware de puntos de conexión.

Cuando no se usa UseStatusCodePages, al navegar a una dirección URL sin punto de conexión se devuelve un mensaje de error dependiente del explorador que indica que no se encuentra el punto de conexión. Cuando se llama a UseStatusCodePages, el explorador devuelve la siguiente respuesta:

Status Code: 404; Not Found

UseStatusCodePages no se utiliza normalmente en producción, ya que devuelve un mensaje que no es útil para los usuarios.

Nota

El middleware de páginas de códigos de estado no detecta excepciones. Para proporcionar una página de control de errores personalizada, use la página del controlador de excepciones.

UseStatusCodePages con cadena de formato

Para personalizar el texto y el tipo de contenido de la respuesta, use la sobrecarga de UseStatusCodePages que adopta una cadena de tipo de contenido y formato:

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

// using static System.Net.Mime.MediaTypeNames;
app.UseStatusCodePages(Text.Plain, "Status Code Page: {0}");

En el código anterior, {0} es un marcador de posición para el código de error.

UseStatusCodePages con una cadena de formato no se utiliza normalmente en producción, ya que devuelve un mensaje que no es útil para los usuarios.

UseStatusCodePages con una expresión lambda

Para especificar el código de escritura de respuesta y control de errores personalizado, use la sobrecarga de UseStatusCodePages que adopta una expresión lambda:

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePages(async statusCodeContext =>
{
    // using static System.Net.Mime.MediaTypeNames;
    statusCodeContext.HttpContext.Response.ContentType = Text.Plain;

    await statusCodeContext.HttpContext.Response.WriteAsync(
        $"Status Code Page: {statusCodeContext.HttpContext.Response.StatusCode}");
});

UseStatusCodePages con una expresión lambda no se utiliza normalmente en producción, ya que devuelve un mensaje que no es útil para los usuarios.

UseStatusCodePagesWithRedirects

Método de extensión UseStatusCodePagesWithRedirects:

  • Envía un código de estado 302 - Encontrado al cliente.
  • Redirige el cliente al punto de conexión de control de errores proporcionado en la plantilla de dirección URL. El punto de conexión de control de errores suele mostrar información de error y devuelve HTTP 200.
var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePagesWithRedirects("/StatusCode/{0}");

La plantilla de dirección URL puede incluir un marcador de posición {0} para el código de estado, tal y como se muestra en el código anterior. Si la plantilla de dirección URL comienza con ~ (tilde), la ~ se sustituye por el elemento PathBase de la aplicación. Si especifica un punto de conexión en la aplicación, cree una vista de MVC o una página de Razor para ese punto de conexión.

Este método se usa normalmente cuando la aplicación:

  • Debe redirigir al cliente a un punto de conexión diferente, normalmente en casos en los que una aplicación diferente procesa el error. En el caso de aplicaciones web, la barra de direcciones del explorador del cliente refleja el punto de conexión redirigido.
  • No debe conservar ni devolver el código de estado original con la respuesta de redirección inicial.

UseStatusCodePagesWithReExecute

Método de extensión UseStatusCodePagesWithReExecute:

  • Genera el cuerpo de respuesta, para lo cual vuelve a ejecutar la canalización de solicitud mediante una ruta de acceso alternativa.
  • No modifica el código de estado antes o después de volver a ejecutar la canalización.

La nueva ejecución de canalización puede modificar el código de estado de la respuesta, ya que la nueva canalización tiene un control absoluto del código de estado. Si la nueva canalización no modifica el código de estado, se envía el código de estado original al cliente.

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePagesWithReExecute("/StatusCode/{0}");

Si se especifica un punto de conexión en la aplicación, cree una vista de MVC o una página de Razor para ese punto de conexión.

Este método se usa normalmente cuando la aplicación debe:

  • Procesar la solicitud sin redirigirla a un punto de conexión diferente. En el caso de aplicaciones web, la barra de direcciones del explorador del cliente refleja el punto de conexión solicitado originalmente.
  • Conservar y devolver el código de estado original con la respuesta.

La plantilla de dirección URL debe empezar con / y puede incluir un marcador de posición {0} para el código de estado. Para pasar el código de estado como un parámetro de cadena de consulta, pase un segundo argumento a UseStatusCodePagesWithReExecute. Por ejemplo:

var app = builder.Build();  
app.UseStatusCodePagesWithReExecute("/StatusCode", "?statusCode={0}");

El punto de conexión que procesa el error puede obtener la dirección URL original que generó el error, como se muestra en el ejemplo siguiente:

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public class StatusCodeModel : PageModel
{
    public int OriginalStatusCode { get; set; }

    public string? OriginalPathAndQuery { get; set; }

    public void OnGet(int statusCode)
    {
        OriginalStatusCode = statusCode;

        var statusCodeReExecuteFeature =
            HttpContext.Features.Get<IStatusCodeReExecuteFeature>();

        if (statusCodeReExecuteFeature is not null)
        {
            OriginalPathAndQuery = string.Join(
                statusCodeReExecuteFeature.OriginalPathBase,
                statusCodeReExecuteFeature.OriginalPath,
                statusCodeReExecuteFeature.OriginalQueryString);
        }
    }
}

Dado que este middleware puede volver a ejecutar la canalización de solicitudes:

  • Los middleware deben controlar la reentrada con la misma solicitud. Normalmente, esto significa limpiar su estado después de llamar a _next o almacenar en caché su procesamiento en HttpContext para evitar volver a hacerlo. Al tratar con el cuerpo de la solicitud, esto significa almacenar en búfer o en caché los resultados como el lector de formularios.
  • Los servicios con ámbito siguen siendo los mismos.

Deshabilitar las páginas de códigos de estado

Para deshabilitar las páginas de códigos de estado de un método de acción o controlador MVC, use el atributo [SkipStatusCodePages].

Para deshabilitar las páginas de códigos de estado de solicitudes específicas de un método de controlador de Razor Pages o un controlador MVC, use IStatusCodePagesFeature:

public void OnGet()
{
    var statusCodePagesFeature =
        HttpContext.Features.Get<IStatusCodePagesFeature>();

    if (statusCodePagesFeature is not null)
    {
        statusCodePagesFeature.Enabled = false;
    }
}

Código de control de excepciones

El código de las páginas de control de excepciones también puede iniciar excepciones. Las páginas de errores de producción se deben probar minuciosamente y se debe tener especial cuidado para evitar que inicien sus propias excepciones.

Encabezados de respuesta

Una vez enviados los encabezados de una respuesta:

  • La aplicación no puede cambiar el código de estado de la respuesta.
  • No se pueden ejecutar páginas o controladores de excepciones. Deberá completarse la respuesta o anularse la conexión.

Control de excepciones del servidor

Además de la lógica de control de excepciones de la aplicación, la implementación del servidor HTTP puede controlar algunas excepciones. Si el servidor almacena en caché una excepción antes de que se envíen los encabezados de respuesta, envía una respuesta 500 - Internal Server Error sin cuerpo. Si el servidor almacena en caché una excepción después de que se envían los encabezados de respuesta, cierra la conexión. El servidor controla las solicitudes que no controla la aplicación. El control de excepciones del servidor controla cualquier excepción que se produzca cuando el servidor controle la solicitud. Las páginas de error personalizadas, el middleware de control de excepciones y los filtros de la aplicación no afectan este comportamiento.

Control de excepciones de inicio

Solo el nivel de hospedaje puede controlar las excepciones que tienen lugar durante el inicio de la aplicación. El host puede configurarse para capturar errores de inicio y capturar errores detallados.

La capa de hospedaje puede mostrar una página de error para un error de inicio capturado solo si este se produce después del enlace de puerto/dirección del host. Si se produce un error de enlace:

  • La capa de hospedaje registra una excepción crítica.
  • El proceso de dotnet se bloquea.
  • No se muestra ninguna página de error si el servidor HTTP es Kestrel.

Si se ejecuta en IIS, en Azure App Service o en IIS Express, el módulo ASP.NET Core devuelve un error de proceso 502.5 si el proceso no se puede iniciar. Para más información, consulte Solución de problemas de ASP.NET Core en Azure App Service e IIS.

Página de error de la base de datos

El filtro de excepciones AddDatabaseDeveloperPageExceptionFilter de la página del desarrollador de bases de datos captura las excepciones relacionadas con la base de datos que se pueden resolver mediante migraciones de Entity Framework Core. Cuando se producen estas excepciones, se genera una respuesta HTML con los detalles de las acciones posibles para resolver el problema. Esta página debe habilitarse solo en el entorno de desarrollo. El código siguiente agrega el filtro de la página de excepciones del desarrollador:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddRazorPages();

Filtros de excepciones

En las aplicaciones de MVC, los filtros de excepciones se pueden configurar globalmente, o bien por controlador o por acción. En las aplicaciones de Razor Pages, se pueden configurar a nivel global o por modelo de página. Estos filtros controlan todas las excepciones no controladas que se hayan producido durante la ejecución de una acción de controlador o de otro filtro. Para más información, consulte Filtros en ASP.NET Core.

Los filtros de excepciones son útiles para interceptar las excepciones que se producen en las acciones de MVC, pero no son tan flexibles como el middleware de control de excepciones integrado, UseExceptionHandler. Se recomienda usar UseExceptionHandler, a no ser que tenga que realizar el control de errores de otra forma según la acción de MVC elegida.

Errores de estado del modelo

Para obtener información sobre cómo controlar los errores de estado de los modelos, vea Enlace de modelos y Validación de modelos.

Detalles del problema

Los detalles del problema no son el único formato de respuesta para describir un error de la API HTTP; sin embargo, se usan normalmente para notificar errores para las API HTTP.

El servicio de detalles del problema implementa la interfaz IProblemDetailsService, que admite la creación de detalles del problema en ASP.NET Core. El método de extensión AddProblemDetails de IServiceCollection registra la implementación IProblemDetailsService predeterminada.

En aplicaciones de ASP.NET Core, el middleware siguiente genera respuestas HTTP de detalles del problema cuando se llama a AddProblemDetails, excepto cuando el encabezado HTTP de solicitud Accept no incluye uno de los tipos de contenido admitidos por el IProblemDetailsWriter registrado (valor predeterminado: application/json):

El código siguiente configura la aplicación para generar una respuesta de detalles del problema para todas las respuestas de error de servidor y cliente HTTP que aún no tienen contenido en el cuerpo:

builder.Services.AddProblemDetails();

var app = builder.Build();        

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler();
    app.UseHsts();
}

app.UseStatusCodePages();

En la sección siguiente se muestra cómo personalizar el cuerpo de la respuesta de detalles del problema.

Personalización de los detalles del problema

La creación automática de un objeto ProblemDetails se puede personalizar mediante las siguientes opciones:

  1. Use ProblemDetailsOptions.CustomizeProblemDetails
  2. Uso de una IProblemDetailsWriter personalizada
  3. Llamada a IProblemDetailsService en un middleware

Operation de CustomizeProblemDetails:

Los detalles del problema generado se pueden personalizar mediante CustomizeProblemDetails, y las personalizaciones se aplican a todos los detalles del problema generados automáticamente.

El código siguiente usa ProblemDetailsOptions para establecer CustomizeProblemDetails:

builder.Services.AddProblemDetails(options =>
    options.CustomizeProblemDetails = ctx =>
            ctx.ProblemDetails.Extensions.Add("nodeId", Environment.MachineName));

var app = builder.Build();        

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler();
    app.UseHsts();
}

app.UseStatusCodePages();

Por ejemplo, un resultado del punto de conexión HTTP Status 400 Bad Request genera el siguiente cuerpo de respuesta de detalles del problema:

{
  "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
  "title": "Bad Request",
  "status": 400,
  "nodeId": "my-machine-name"
}

IProblemDetailsWriter personalizado

Se puede crear una implementación de IProblemDetailsWriter para personalizaciones avanzadas.

public class SampleProblemDetailsWriter : IProblemDetailsWriter
{
    // Indicates that only responses with StatusCode == 400
    // are handled by this writer. All others are
    // handled by different registered writers if available.
    public bool CanWrite(ProblemDetailsContext context)
        => context.HttpContext.Response.StatusCode == 400;

    public ValueTask WriteAsync(ProblemDetailsContext context)
    {
        // Additional customizations.

        // Write to the response.
        var response = context.HttpContext.Response;
        return new ValueTask(response.WriteAsJsonAsync(context.ProblemDetails));
    }
}

Nota: Cuando se usa un elemento personalizado IProblemDetailsWriter, el elemento personalizado IProblemDetailsWriter se debe registrar antes de llamar a AddRazorPages, AddControllers, AddControllersWithViews o AddMvc:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddTransient<IProblemDetailsWriter, SampleProblemDetailsWriter>();

var app = builder.Build();

// Middleware to handle writing problem details to the response.
app.Use(async (context, next) =>
{
    await next(context);
    var mathErrorFeature = context.Features.Get<MathErrorFeature>();
    if (mathErrorFeature is not null)
    {
        if (context.RequestServices.GetService<IProblemDetailsWriter>() is
            { } problemDetailsService)
        {

            if (problemDetailsService.CanWrite(new ProblemDetailsContext() { HttpContext = context }))
            {
                (string Detail, string Type) details = mathErrorFeature.MathError switch
                {
                    MathErrorType.DivisionByZeroError => ("Divison by zero is not defined.",
                        "https://en.wikipedia.org/wiki/Division_by_zero"),
                    _ => ("Negative or complex numbers are not valid input.",
                        "https://en.wikipedia.org/wiki/Square_root")
                };

                await problemDetailsService.WriteAsync(new ProblemDetailsContext
                {
                    HttpContext = context,
                    ProblemDetails =
                    {
                        Title = "Bad Input",
                        Detail = details.Detail,
                        Type = details.Type
                    }
                });
            }
        }
    }
});

// /divide?numerator=2&denominator=4
app.MapGet("/divide", (HttpContext context, double numerator, double denominator) =>
{
    if (denominator == 0)
    {
        var errorType = new MathErrorFeature
        {
            MathError = MathErrorType.DivisionByZeroError
        };
        context.Features.Set(errorType);
        return Results.BadRequest();
    }

    return Results.Ok(numerator / denominator);
});

// /squareroot?radicand=16
app.MapGet("/squareroot", (HttpContext context, double radicand) =>
{
    if (radicand < 0)
    {
        var errorType = new MathErrorFeature
        {
            MathError = MathErrorType.NegativeRadicandError
        };
        context.Features.Set(errorType);
        return Results.BadRequest();
    }

    return Results.Ok(Math.Sqrt(radicand));
});

app.Run();

Detalles del problema del middleware

Un enfoque alternativo para usar ProblemDetailsOptions con CustomizeProblemDetails es establecer ProblemDetails en middleware. Una respuesta de detalles del problema se puede escribir llamando a IProblemDetailsService.WriteAsync:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStatusCodePages();

// Middleware to handle writing problem details to the response.
app.Use(async (context, next) =>
{
    await next(context);
    var mathErrorFeature = context.Features.Get<MathErrorFeature>();
    if (mathErrorFeature is not null)
    {
        if (context.RequestServices.GetService<IProblemDetailsService>() is
                                                           { } problemDetailsService)
        {
            (string Detail, string Type) details = mathErrorFeature.MathError switch
            {
                MathErrorType.DivisionByZeroError => ("Divison by zero is not defined.",
                "https://en.wikipedia.org/wiki/Division_by_zero"),
                _ => ("Negative or complex numbers are not valid input.", 
                "https://en.wikipedia.org/wiki/Square_root")
            };

            await problemDetailsService.WriteAsync(new ProblemDetailsContext
            {
                HttpContext = context,
                ProblemDetails =
                {
                    Title = "Bad Input",
                    Detail = details.Detail,
                    Type = details.Type
                }
            });
        }
    }
});

// /divide?numerator=2&denominator=4
app.MapGet("/divide", (HttpContext context, double numerator, double denominator) =>
{
    if (denominator == 0)
    {
        var errorType = new MathErrorFeature { MathError =
                                               MathErrorType.DivisionByZeroError };
        context.Features.Set(errorType);
        return Results.BadRequest();
    }

    return Results.Ok(numerator / denominator);
});

// /squareroot?radicand=16
app.MapGet("/squareroot", (HttpContext context, double radicand) =>
{
    if (radicand < 0)
    {
        var errorType = new MathErrorFeature { MathError =
                                               MathErrorType.NegativeRadicandError };
        context.Features.Set(errorType);
        return Results.BadRequest();
    }

    return Results.Ok(Math.Sqrt(radicand));
});

app.MapControllers();

app.Run();

En el código anterior, los puntos de conexión de API mínimas /divide y /squareroot devuelven la respuesta del problema personalizada esperada en la entrada de error.

Los puntos de conexión del controlador de API devuelven la respuesta del problema predeterminada en la entrada de error, no la respuesta del problema personalizada. Se devuelve la respuesta del problema predeterminada porque el controlador de API ha escrito en el flujo de respuesta, Detalles del problema para los códigos de estado de error, antes de llamar a IProblemDetailsService.WriteAsync y la respuesta no se vuelve a escribir.

El siguiente ValuesController devuelve BadRequestResult, que escribe en el flujo de respuesta y, por lo tanto, impide que se devuelva la respuesta del problema personalizado.

[Route("api/[controller]/[action]")]
[ApiController]
public class ValuesController : ControllerBase
{
    // /api/values/divide/1/2
    [HttpGet("{Numerator}/{Denominator}")]
    public IActionResult Divide(double Numerator, double Denominator)
    {
        if (Denominator == 0)
        {
            var errorType = new MathErrorFeature
            {
                MathError = MathErrorType.DivisionByZeroError
            };
            HttpContext.Features.Set(errorType);
            return BadRequest();
        }

        return Ok(Numerator / Denominator);
    }

    // /api/values/squareroot/4
    [HttpGet("{radicand}")]
    public IActionResult Squareroot(double radicand)
    {
        if (radicand < 0)
        {
            var errorType = new MathErrorFeature
            {
                MathError = MathErrorType.NegativeRadicandError
            };
            HttpContext.Features.Set(errorType);
            return BadRequest();
        }

        return Ok(Math.Sqrt(radicand));
    }

}

El siguiente Values3Controller devuelve ControllerBase.Problem para que se devuelva el resultado esperado del problema personalizado:

[Route("api/[controller]/[action]")]
[ApiController]
public class Values3Controller : ControllerBase
{
    // /api/values3/divide/1/2
    [HttpGet("{Numerator}/{Denominator}")]
    public IActionResult Divide(double Numerator, double Denominator)
    {
        if (Denominator == 0)
        {
            var errorType = new MathErrorFeature
            {
                MathError = MathErrorType.DivisionByZeroError
            };
            HttpContext.Features.Set(errorType);
            return Problem(
                title: "Bad Input",
                detail: "Divison by zero is not defined.",
                type: "https://en.wikipedia.org/wiki/Division_by_zero",
                statusCode: StatusCodes.Status400BadRequest
                );
        }

        return Ok(Numerator / Denominator);
    }

    // /api/values3/squareroot/4
    [HttpGet("{radicand}")]
    public IActionResult Squareroot(double radicand)
    {
        if (radicand < 0)
        {
            var errorType = new MathErrorFeature
            {
                MathError = MathErrorType.NegativeRadicandError
            };
            HttpContext.Features.Set(errorType);
            return Problem(
                title: "Bad Input",
                detail: "Negative or complex numbers are not valid input.",
                type: "https://en.wikipedia.org/wiki/Square_root",
                statusCode: StatusCodes.Status400BadRequest
                );
        }

        return Ok(Math.Sqrt(radicand));
    }

}

Producción de una carga de ProblemDetails para excepciones

Considere la aplicación siguiente:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseExceptionHandler();
app.UseStatusCodePages();

if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}

app.MapControllers();
app.Run();

En entornos que no son de desarrollo, cuando se produce una excepción, lo siguiente es una respuesta ProblemDetails estándar que se devuelve al cliente:

{
"type":"https://tools.ietf.org/html/rfc7231#section-6.6.1",
"title":"An error occurred while processing your request.",
"status":500,"traceId":"00-b644<snip>-00"
}

Para la mayoría de las aplicaciones, el código anterior es todo lo que se necesita para las excepciones. Sin embargo, en la sección siguiente se muestra cómo obtener respuestas a problemas más detalladas.

Una alternativa a una página del controlador de excepciones personalizada es proporcionar una expresión lambda en UseExceptionHandler. El uso de una expresión lambda permite acceder al error y escribir una respuesta de detalles del problema con IProblemDetailsService.WriteAsync:

using Microsoft.AspNetCore.Diagnostics;
using static System.Net.Mime.MediaTypeNames;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseExceptionHandler();
app.UseStatusCodePages();

if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler(exceptionHandlerApp =>
    {
        exceptionHandlerApp.Run(async context =>
        {
            context.Response.StatusCode = StatusCodes.Status500InternalServerError;
            context.Response.ContentType = Text.Plain;

            var title = "Bad Input";
            var detail = "Invalid input";
            var type = "https://errors.example.com/badInput";

            if (context.RequestServices.GetService<IProblemDetailsService>() is
                { } problemDetailsService)
            {
                var exceptionHandlerFeature =
               context.Features.Get<IExceptionHandlerFeature>();

                var exceptionType = exceptionHandlerFeature?.Error;
                if (exceptionType != null &&
                   exceptionType.Message.Contains("infinity"))
                {
                    title = "Argument exception";
                    detail = "Invalid input";
                    type = "https://errors.example.com/argumentException";
                }

                await problemDetailsService.WriteAsync(new ProblemDetailsContext
                {
                    HttpContext = context,
                    ProblemDetails =
                {
                    Title = title,
                    Detail = detail,
                    Type = type
                }
                });
            }
        });
    });
}

app.MapControllers();
app.Run();

Advertencia

No proporcione información de errores confidencial a los clientes. Proporcionar información de los errores es un riesgo para la seguridad.

Un enfoque alternativo para generar detalles del problema es usar el paquete NuGet de terceros Hellang.Middleware.ProblemDetails, que se puede usar para asignar excepciones y errores de cliente a los detalles del problema.

Recursos adicionales

Por Tom Dykstra

Este artículo trata sobre los métodos comunes para controlar errores en aplicaciones web ASP.NET Core. Consulte también Control de errores en API web de ASP.NET Core y Control de errores en aplicaciones de API mínimas.

Página de excepciones para el desarrollador

En la Página de excepciones para el desarrollador se muestra información detallada sobre las excepciones de la solicitud no controladas. De forma predeterminada, las aplicaciones de ASP.NET Core habilitan la página de excepciones del desarrollador cuando se cumplen las dos condiciones siguientes:

La página de excepciones del desarrollador se ejecuta al principio de la canalización del middleware, de modo que puede capturar las excepciones no controladas que se producen en el middleware que sigue.

La información detallada de las excepciones no debería mostrarse públicamente cuando la aplicación se ejecuta en el entorno de producción. Para más información sobre la configuración de los entornos, consulte Uso de varios entornos en ASP.NET Core.

La página de excepciones para el desarrollador puede incluir la siguiente información sobre la excepción y la solicitud:

  • Seguimiento de la pila
  • Parámetros de cadena de consulta (si existen)
  • Cookie (si existen)
  • encabezados

No se garantiza que la página de excepciones del desarrollador proporcione alguna información. Use el registro obtener información completa sobre el error.

Página del controlador de excepciones

Para configurar una página de control de errores personalizada para el entorno de producción, llame a UseExceptionHandler. Este middleware de control de excepciones:

  • Captura y registra las excepciones no controladas.
  • Vuelve a ejecutar la solicitud en una canalización alternativa con la ruta de acceso indicada. La solicitud no se vuelve a ejecutar si se ha iniciado la respuesta. El código generado por la plantilla vuelve a ejecutar la solicitud mediante la ruta de acceso /Error.

Advertencia

Si la canalización alternativa produce una excepción propia, el middleware de control de excepciones vuelve a producir la excepción original.

Dado que este middleware puede volver a ejecutar la canalización de solicitudes:

  • Los middleware deben controlar la reentrada con la misma solicitud. Normalmente, esto significa limpiar su estado después de llamar a _next o almacenar en caché su procesamiento en HttpContext para evitar volver a hacerlo. Al tratar con el cuerpo de la solicitud, esto significa almacenar en búfer o almacenar en caché los resultados como el lector de formularios.
  • En el caso de la sobrecarga UseExceptionHandler(IApplicationBuilder, String) que se usa en las plantillas, solo se modifica la ruta de acceso de la solicitud y se borran los datos de ruta. Los datos de la solicitud, como los encabezados, los métodos y los elementos, se reutilizan tal cual.
  • Los servicios con ámbito siguen siendo los mismos.

En el ejemplo siguiente, UseExceptionHandler agrega middleware de control de excepciones en entornos que no son de desarrollo:

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

La plantilla de aplicación de Razor Pages proporciona una página de error (.cshtml) y una clase PageModel (ErrorModel) en la carpeta Pages. Para una aplicación de MVC, la plantilla de proyecto incluye un método de acción de Error y una vista del error para el controlador de Home.

El middleware de control de excepciones vuelve a ejecutar la solicitud mediante el método HTTP original. Si un punto de conexión de controlador de errores está restringido a un conjunto específico de métodos HTTP, solo se ejecuta para esos métodos HTTP. Por ejemplo, una acción de controlador de MVC que usa el atributo [HttpGet] solo se ejecuta para solicitudes GET. Para asegurarse de que todas las solicitudes lleguen a la página de control de errores personalizada, no las restrinja a un conjunto específico de métodos HTTP.

Para controlar las excepciones de manera diferente en función del método HTTP original:

  • En el caso de Razor Pages, cree varios métodos de control. Por ejemplo, use OnGet para controlar las excepciones GET y use OnPost para controlar las excepciones POST.
  • Para MVC, aplique los atributos de verbo HTTP a varias acciones. Por ejemplo, use [HttpGet] para controlar las excepciones GET y use [HttpPost] para controlar las excepciones POST.

Para permitir que los usuarios no autenticados vean la página de control de errores personalizada, asegúrese de que admite el acceso anónimo.

Acceso a la excepción

Use IExceptionHandlerPathFeature para acceder a la ruta de acceso de la solicitud original y a la excepción en un controlador de errores. En el ejemplo siguiente se usa IExceptionHandlerPathFeature para obtener más información sobre la excepción que se produjo:

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
[IgnoreAntiforgeryToken]
public class ErrorModel : PageModel
{
    public string? RequestId { get; set; }

    public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);

    public string? ExceptionMessage { get; set; }

    public void OnGet()
    {
        RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;

        var exceptionHandlerPathFeature =
            HttpContext.Features.Get<IExceptionHandlerPathFeature>();

        if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
        {
            ExceptionMessage = "The file was not found.";
        }

        if (exceptionHandlerPathFeature?.Path == "/")
        {
            ExceptionMessage ??= string.Empty;
            ExceptionMessage += " Page: Home.";
        }
    }
}

Advertencia

No proporcione información de errores confidencial a los clientes. Proporcionar información de los errores es un riesgo para la seguridad.

Lambda del controlador de excepciones

Una alternativa a una página del controlador de excepciones personalizada es proporcionar una expresión lambda en UseExceptionHandler. Usar una expresión lambda permite acceder al error antes de devolver la respuesta.

En el código siguiente se usa una expresión lambda para el control de excepciones:

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler(exceptionHandlerApp =>
    {
        exceptionHandlerApp.Run(async context =>
        {
            context.Response.StatusCode = StatusCodes.Status500InternalServerError;

            // using static System.Net.Mime.MediaTypeNames;
            context.Response.ContentType = Text.Plain;

            await context.Response.WriteAsync("An exception was thrown.");

            var exceptionHandlerPathFeature =
                context.Features.Get<IExceptionHandlerPathFeature>();

            if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
            {
                await context.Response.WriteAsync(" The file was not found.");
            }

            if (exceptionHandlerPathFeature?.Path == "/")
            {
                await context.Response.WriteAsync(" Page: Home.");
            }
        });
    });

    app.UseHsts();
}

Advertencia

No proporcione información de errores confidencial a los clientes. Proporcionar información de los errores es un riesgo para la seguridad.

IExceptionHandler

IExceptionHandler es una interfaz que proporciona al desarrollador una devolución de llamada para controlar excepciones conocidas en una ubicación central.

Las implementaciones de IExceptionHandler se registran mediante una llamada de IServiceCollection.AddExceptionHandler<T>. La duración de una instancia IExceptionHandler es singleton. Se pueden agregar varias implementaciones y se llaman en el orden registrado.

Si un controlador de excepciones controla una solicitud, puede devolver true para detener el procesamiento. Si un controlador de excepciones no controla una excepción, el control vuelve al comportamiento predeterminado y a las opciones del middleware. Se emiten diferentes métricas y registros para excepciones controladas frente a excepciones no controladas.

En el siguiente ejemplo se muestra una implementación de IExceptionHandler:

using Microsoft.AspNetCore.Diagnostics;

namespace ErrorHandlingSample
{
    public class CustomExceptionHandler : IExceptionHandler
    {
        private readonly ILogger<CustomExceptionHandler> logger;
        public CustomExceptionHandler(ILogger<CustomExceptionHandler> logger)
        {
            this.logger = logger;
        }
        public ValueTask<bool> TryHandleAsync(
            HttpContext httpContext,
            Exception exception,
            CancellationToken cancellationToken)
        {
            var exceptionMessage = exception.Message;
            logger.LogError(
                "Error Message: {exceptionMessage}, Time of occurrence {time}",
                exceptionMessage, DateTime.UtcNow);
            // Return false to continue with the default behavior
            // - or - return true to signal that this exception is handled
            return ValueTask.FromResult(false);
        }
    }
}

En el ejemplo siguiente se muestra cómo registrar una implementación de IExceptionHandler para la inserción de dependencias:

using ErrorHandlingSample;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddRazorPages();
builder.Services.AddExceptionHandler<CustomExceptionHandler>();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

// Remaining Program.cs code omitted for brevity

Cuando el código anterior se ejecuta en el entorno de desarrollo:

  • Se llama primero al CustomExceptionHandler para controlar una excepción.
  • Tras registrar la excepción, el método TryHandleException devuelve false, por lo que se muestra la página de excepción del desarrollador.

En otros entornos:

  • Se llama primero al CustomExceptionHandler para controlar una excepción.
  • Tras registrar la excepción, el método TryHandleException devuelve false, por lo que se muestra la página de /Error.

UseStatusCodePages

Una aplicación ASP.NET Core no proporciona de forma predeterminada ninguna página de códigos de estado para los códigos de estado HTTP, como 404 - No encontrado. Cuando la aplicación establece un código de estado de error HTTP 400-599 que no tiene un cuerpo, devuelve el código de estado y un cuerpo de respuesta vacío. Para habilitar los controladores de solo texto predeterminados para los códigos de estado de error comunes, llame a UseStatusCodePages en Program.cs:

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePages();

Llame a UseStatusCodePages antes del middleware de control de solicitudes. Por ejemplo, llame a UseStatusCodePages antes del middleware de archivos estáticos y el middleware de puntos de conexión.

Cuando no se usa UseStatusCodePages, al navegar a una dirección URL sin punto de conexión se devuelve un mensaje de error dependiente del explorador que indica que no se encuentra el punto de conexión. Cuando se llama a UseStatusCodePages, el explorador devuelve la siguiente respuesta:

Status Code: 404; Not Found

UseStatusCodePages no se utiliza normalmente en producción, ya que devuelve un mensaje que no es útil para los usuarios.

Nota

El middleware de páginas de códigos de estado no detecta excepciones. Para proporcionar una página de control de errores personalizada, use la página del controlador de excepciones.

UseStatusCodePages con cadena de formato

Para personalizar el texto y el tipo de contenido de la respuesta, use la sobrecarga de UseStatusCodePages que adopta una cadena de tipo de contenido y formato:

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

// using static System.Net.Mime.MediaTypeNames;
app.UseStatusCodePages(Text.Plain, "Status Code Page: {0}");

En el código anterior, {0} es un marcador de posición para el código de error.

UseStatusCodePages con una cadena de formato no se utiliza normalmente en producción, ya que devuelve un mensaje que no es útil para los usuarios.

UseStatusCodePages con una expresión lambda

Para especificar el código de escritura de respuesta y control de errores personalizado, use la sobrecarga de UseStatusCodePages que adopta una expresión lambda:

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePages(async statusCodeContext =>
{
    // using static System.Net.Mime.MediaTypeNames;
    statusCodeContext.HttpContext.Response.ContentType = Text.Plain;

    await statusCodeContext.HttpContext.Response.WriteAsync(
        $"Status Code Page: {statusCodeContext.HttpContext.Response.StatusCode}");
});

UseStatusCodePages con una expresión lambda no se utiliza normalmente en producción, ya que devuelve un mensaje que no es útil para los usuarios.

UseStatusCodePagesWithRedirects

Método de extensión UseStatusCodePagesWithRedirects:

  • Envía un código de estado 302 - Encontrado al cliente.
  • Redirige el cliente al punto de conexión de control de errores proporcionado en la plantilla de dirección URL. El punto de conexión de control de errores suele mostrar información de error y devuelve HTTP 200.
var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePagesWithRedirects("/StatusCode/{0}");

La plantilla de dirección URL puede incluir un marcador de posición {0} para el código de estado, tal y como se muestra en el código anterior. Si la plantilla de dirección URL comienza con ~ (tilde), la ~ se sustituye por el elemento PathBase de la aplicación. Si especifica un punto de conexión en la aplicación, cree una vista de MVC o una página de Razor para ese punto de conexión.

Este método se usa normalmente cuando la aplicación:

  • Debe redirigir al cliente a un punto de conexión diferente, normalmente en casos en los que una aplicación diferente procesa el error. En el caso de aplicaciones web, la barra de direcciones del explorador del cliente refleja el punto de conexión redirigido.
  • No debe conservar ni devolver el código de estado original con la respuesta de redirección inicial.

UseStatusCodePagesWithReExecute

Método de extensión UseStatusCodePagesWithReExecute:

  • Genera el cuerpo de respuesta, para lo cual vuelve a ejecutar la canalización de solicitud mediante una ruta de acceso alternativa.
  • No modifica el código de estado antes o después de volver a ejecutar la canalización.

La nueva ejecución de canalización puede modificar el código de estado de la respuesta, ya que la nueva canalización tiene un control absoluto del código de estado. Si la nueva canalización no modifica el código de estado, se envía el código de estado original al cliente.

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePagesWithReExecute("/StatusCode/{0}");

Si se especifica un punto de conexión en la aplicación, cree una vista de MVC o una página de Razor para ese punto de conexión.

Este método se usa normalmente cuando la aplicación debe:

  • Procesar la solicitud sin redirigirla a un punto de conexión diferente. En el caso de aplicaciones web, la barra de direcciones del explorador del cliente refleja el punto de conexión solicitado originalmente.
  • Conservar y devolver el código de estado original con la respuesta.

La plantilla de dirección URL debe empezar con / y puede incluir un marcador de posición {0} para el código de estado. Para pasar el código de estado como un parámetro de cadena de consulta, pase un segundo argumento a UseStatusCodePagesWithReExecute. Por ejemplo:

var app = builder.Build();  
app.UseStatusCodePagesWithReExecute("/StatusCode", "?statusCode={0}");

El punto de conexión que procesa el error puede obtener la dirección URL original que generó el error, como se muestra en el ejemplo siguiente:

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public class StatusCodeModel : PageModel
{
    public int OriginalStatusCode { get; set; }

    public string? OriginalPathAndQuery { get; set; }

    public void OnGet(int statusCode)
    {
        OriginalStatusCode = statusCode;

        var statusCodeReExecuteFeature =
            HttpContext.Features.Get<IStatusCodeReExecuteFeature>();

        if (statusCodeReExecuteFeature is not null)
        {
            OriginalPathAndQuery = string.Join(
                statusCodeReExecuteFeature.OriginalPathBase,
                statusCodeReExecuteFeature.OriginalPath,
                statusCodeReExecuteFeature.OriginalQueryString);
        }
    }
}

Dado que este middleware puede volver a ejecutar la canalización de solicitudes:

  • Los middleware deben controlar la reentrada con la misma solicitud. Normalmente, esto significa limpiar su estado después de llamar a _next o almacenar en caché su procesamiento en HttpContext para evitar volver a hacerlo. Al tratar con el cuerpo de la solicitud, esto significa almacenar en búfer o en caché los resultados como el lector de formularios.
  • Los servicios con ámbito siguen siendo los mismos.

Deshabilitar las páginas de códigos de estado

Para deshabilitar las páginas de códigos de estado de un método de acción o controlador MVC, use el atributo [SkipStatusCodePages].

Para deshabilitar las páginas de códigos de estado de solicitudes específicas de un método de controlador de Razor Pages o un controlador MVC, use IStatusCodePagesFeature:

public void OnGet()
{
    var statusCodePagesFeature =
        HttpContext.Features.Get<IStatusCodePagesFeature>();

    if (statusCodePagesFeature is not null)
    {
        statusCodePagesFeature.Enabled = false;
    }
}

Código de control de excepciones

El código de las páginas de control de excepciones también puede iniciar excepciones. Las páginas de errores de producción se deben probar minuciosamente y se debe tener especial cuidado para evitar que inicien sus propias excepciones.

Encabezados de respuesta

Una vez enviados los encabezados de una respuesta:

  • La aplicación no puede cambiar el código de estado de la respuesta.
  • No se pueden ejecutar páginas o controladores de excepciones. Deberá completarse la respuesta o anularse la conexión.

Control de excepciones del servidor

Además de la lógica de control de excepciones de la aplicación, la implementación del servidor HTTP puede controlar algunas excepciones. Si el servidor almacena en caché una excepción antes de que se envíen los encabezados de respuesta, envía una respuesta 500 - Internal Server Error sin cuerpo. Si el servidor almacena en caché una excepción después de que se envían los encabezados de respuesta, cierra la conexión. El servidor controla las solicitudes que no controla la aplicación. El control de excepciones del servidor controla cualquier excepción que se produzca cuando el servidor controle la solicitud. Las páginas de error personalizadas, el middleware de control de excepciones y los filtros de la aplicación no afectan este comportamiento.

Control de excepciones de inicio

Solo el nivel de hospedaje puede controlar las excepciones que tienen lugar durante el inicio de la aplicación. El host puede configurarse para capturar errores de inicio y capturar errores detallados.

La capa de hospedaje puede mostrar una página de error para un error de inicio capturado solo si este se produce después del enlace de puerto/dirección del host. Si se produce un error de enlace:

  • La capa de hospedaje registra una excepción crítica.
  • El proceso de dotnet se bloquea.
  • No se muestra ninguna página de error si el servidor HTTP es Kestrel.

Si se ejecuta en IIS, en Azure App Service o en IIS Express, el módulo ASP.NET Core devuelve un error de proceso 502.5 si el proceso no se puede iniciar. Para más información, consulte Solución de problemas de ASP.NET Core en Azure App Service e IIS.

Página de error de la base de datos

El filtro de excepciones AddDatabaseDeveloperPageExceptionFilter de la página del desarrollador de bases de datos captura las excepciones relacionadas con la base de datos que se pueden resolver mediante migraciones de Entity Framework Core. Cuando se producen estas excepciones, se genera una respuesta HTML con los detalles de las acciones posibles para resolver el problema. Esta página debe habilitarse solo en el entorno de desarrollo. El código siguiente agrega el filtro de la página de excepciones del desarrollador:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddRazorPages();

Filtros de excepciones

En las aplicaciones de MVC, los filtros de excepciones se pueden configurar globalmente, o bien por controlador o por acción. En las aplicaciones de Razor Pages, se pueden configurar a nivel global o por modelo de página. Estos filtros controlan todas las excepciones no controladas que se hayan producido durante la ejecución de una acción de controlador o de otro filtro. Para más información, consulte Filtros en ASP.NET Core.

Los filtros de excepciones son útiles para interceptar las excepciones que se producen en las acciones de MVC, pero no son tan flexibles como el middleware de control de excepciones integrado, UseExceptionHandler. Se recomienda usar UseExceptionHandler, a no ser que tenga que realizar el control de errores de otra forma según la acción de MVC elegida.

Errores de estado del modelo

Para obtener información sobre cómo controlar los errores de estado de los modelos, vea Enlace de modelos y Validación de modelos.

Detalles del problema

Los detalles del problema no son el único formato de respuesta para describir un error de la API HTTP; sin embargo, se usan normalmente para notificar errores para las API HTTP.

El servicio de detalles del problema implementa la interfaz IProblemDetailsService, que admite la creación de detalles del problema en ASP.NET Core. El método de extensión AddProblemDetails de IServiceCollection registra la implementación IProblemDetailsService predeterminada.

En aplicaciones de ASP.NET Core, el middleware siguiente genera respuestas HTTP de detalles del problema cuando se llama a AddProblemDetails, excepto cuando el encabezado HTTP de solicitud Accept no incluye uno de los tipos de contenido admitidos por el IProblemDetailsWriter registrado (valor predeterminado: application/json):

El código siguiente configura la aplicación para generar una respuesta de detalles del problema para todas las respuestas de error de servidor y cliente HTTP que aún no tienen contenido en el cuerpo:

builder.Services.AddProblemDetails();

var app = builder.Build();        

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler();
    app.UseHsts();
}

app.UseStatusCodePages();

En la sección siguiente se muestra cómo personalizar el cuerpo de la respuesta de detalles del problema.

Personalización de los detalles del problema

La creación automática de un objeto ProblemDetails se puede personalizar mediante las siguientes opciones:

  1. Use ProblemDetailsOptions.CustomizeProblemDetails
  2. Uso de una IProblemDetailsWriter personalizada
  3. Llamada a IProblemDetailsService en un middleware

Operation de CustomizeProblemDetails:

Los detalles del problema generado se pueden personalizar mediante CustomizeProblemDetails, y las personalizaciones se aplican a todos los detalles del problema generados automáticamente.

El código siguiente usa ProblemDetailsOptions para establecer CustomizeProblemDetails:

builder.Services.AddProblemDetails(options =>
    options.CustomizeProblemDetails = ctx =>
            ctx.ProblemDetails.Extensions.Add("nodeId", Environment.MachineName));

var app = builder.Build();        

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler();
    app.UseHsts();
}

app.UseStatusCodePages();

Por ejemplo, un resultado del punto de conexión HTTP Status 400 Bad Request genera el siguiente cuerpo de respuesta de detalles del problema:

{
  "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
  "title": "Bad Request",
  "status": 400,
  "nodeId": "my-machine-name"
}

IProblemDetailsWriter personalizado

Se puede crear una implementación de IProblemDetailsWriter para personalizaciones avanzadas.

public class SampleProblemDetailsWriter : IProblemDetailsWriter
{
    // Indicates that only responses with StatusCode == 400
    // are handled by this writer. All others are
    // handled by different registered writers if available.
    public bool CanWrite(ProblemDetailsContext context)
        => context.HttpContext.Response.StatusCode == 400;

    public ValueTask WriteAsync(ProblemDetailsContext context)
    {
        // Additional customizations.

        // Write to the response.
        var response = context.HttpContext.Response;
        return new ValueTask(response.WriteAsJsonAsync(context.ProblemDetails));
    }
}

Nota: Cuando se usa un elemento personalizado IProblemDetailsWriter, el elemento personalizado IProblemDetailsWriter se debe registrar antes de llamar a AddRazorPages, AddControllers, AddControllersWithViews o AddMvc:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddTransient<IProblemDetailsWriter, SampleProblemDetailsWriter>();

var app = builder.Build();

// Middleware to handle writing problem details to the response.
app.Use(async (context, next) =>
{
    await next(context);
    var mathErrorFeature = context.Features.Get<MathErrorFeature>();
    if (mathErrorFeature is not null)
    {
        if (context.RequestServices.GetService<IProblemDetailsWriter>() is
            { } problemDetailsService)
        {

            if (problemDetailsService.CanWrite(new ProblemDetailsContext() { HttpContext = context }))
            {
                (string Detail, string Type) details = mathErrorFeature.MathError switch
                {
                    MathErrorType.DivisionByZeroError => ("Divison by zero is not defined.",
                        "https://en.wikipedia.org/wiki/Division_by_zero"),
                    _ => ("Negative or complex numbers are not valid input.",
                        "https://en.wikipedia.org/wiki/Square_root")
                };

                await problemDetailsService.WriteAsync(new ProblemDetailsContext
                {
                    HttpContext = context,
                    ProblemDetails =
                    {
                        Title = "Bad Input",
                        Detail = details.Detail,
                        Type = details.Type
                    }
                });
            }
        }
    }
});

// /divide?numerator=2&denominator=4
app.MapGet("/divide", (HttpContext context, double numerator, double denominator) =>
{
    if (denominator == 0)
    {
        var errorType = new MathErrorFeature
        {
            MathError = MathErrorType.DivisionByZeroError
        };
        context.Features.Set(errorType);
        return Results.BadRequest();
    }

    return Results.Ok(numerator / denominator);
});

// /squareroot?radicand=16
app.MapGet("/squareroot", (HttpContext context, double radicand) =>
{
    if (radicand < 0)
    {
        var errorType = new MathErrorFeature
        {
            MathError = MathErrorType.NegativeRadicandError
        };
        context.Features.Set(errorType);
        return Results.BadRequest();
    }

    return Results.Ok(Math.Sqrt(radicand));
});

app.Run();

Detalles del problema del middleware

Un enfoque alternativo para usar ProblemDetailsOptions con CustomizeProblemDetails es establecer ProblemDetails en middleware. Una respuesta de detalles del problema se puede escribir llamando a IProblemDetailsService.WriteAsync:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStatusCodePages();

// Middleware to handle writing problem details to the response.
app.Use(async (context, next) =>
{
    await next(context);
    var mathErrorFeature = context.Features.Get<MathErrorFeature>();
    if (mathErrorFeature is not null)
    {
        if (context.RequestServices.GetService<IProblemDetailsService>() is
                                                           { } problemDetailsService)
        {
            (string Detail, string Type) details = mathErrorFeature.MathError switch
            {
                MathErrorType.DivisionByZeroError => ("Divison by zero is not defined.",
                "https://en.wikipedia.org/wiki/Division_by_zero"),
                _ => ("Negative or complex numbers are not valid input.", 
                "https://en.wikipedia.org/wiki/Square_root")
            };

            await problemDetailsService.WriteAsync(new ProblemDetailsContext
            {
                HttpContext = context,
                ProblemDetails =
                {
                    Title = "Bad Input",
                    Detail = details.Detail,
                    Type = details.Type
                }
            });
        }
    }
});

// /divide?numerator=2&denominator=4
app.MapGet("/divide", (HttpContext context, double numerator, double denominator) =>
{
    if (denominator == 0)
    {
        var errorType = new MathErrorFeature { MathError =
                                               MathErrorType.DivisionByZeroError };
        context.Features.Set(errorType);
        return Results.BadRequest();
    }

    return Results.Ok(numerator / denominator);
});

// /squareroot?radicand=16
app.MapGet("/squareroot", (HttpContext context, double radicand) =>
{
    if (radicand < 0)
    {
        var errorType = new MathErrorFeature { MathError =
                                               MathErrorType.NegativeRadicandError };
        context.Features.Set(errorType);
        return Results.BadRequest();
    }

    return Results.Ok(Math.Sqrt(radicand));
});

app.MapControllers();

app.Run();

En el código anterior, los puntos de conexión de API mínimas /divide y /squareroot devuelven la respuesta del problema personalizada esperada en la entrada de error.

Los puntos de conexión del controlador de API devuelven la respuesta del problema predeterminada en la entrada de error, no la respuesta del problema personalizada. Se devuelve la respuesta del problema predeterminada porque el controlador de API ha escrito en el flujo de respuesta, Detalles del problema para los códigos de estado de error, antes de llamar a IProblemDetailsService.WriteAsync y la respuesta no se vuelve a escribir.

El siguiente ValuesController devuelve BadRequestResult, que escribe en el flujo de respuesta y, por lo tanto, impide que se devuelva la respuesta del problema personalizado.

[Route("api/[controller]/[action]")]
[ApiController]
public class ValuesController : ControllerBase
{
    // /api/values/divide/1/2
    [HttpGet("{Numerator}/{Denominator}")]
    public IActionResult Divide(double Numerator, double Denominator)
    {
        if (Denominator == 0)
        {
            var errorType = new MathErrorFeature
            {
                MathError = MathErrorType.DivisionByZeroError
            };
            HttpContext.Features.Set(errorType);
            return BadRequest();
        }

        return Ok(Numerator / Denominator);
    }

    // /api/values/squareroot/4
    [HttpGet("{radicand}")]
    public IActionResult Squareroot(double radicand)
    {
        if (radicand < 0)
        {
            var errorType = new MathErrorFeature
            {
                MathError = MathErrorType.NegativeRadicandError
            };
            HttpContext.Features.Set(errorType);
            return BadRequest();
        }

        return Ok(Math.Sqrt(radicand));
    }

}

El siguiente Values3Controller devuelve ControllerBase.Problem para que se devuelva el resultado esperado del problema personalizado:

[Route("api/[controller]/[action]")]
[ApiController]
public class Values3Controller : ControllerBase
{
    // /api/values3/divide/1/2
    [HttpGet("{Numerator}/{Denominator}")]
    public IActionResult Divide(double Numerator, double Denominator)
    {
        if (Denominator == 0)
        {
            var errorType = new MathErrorFeature
            {
                MathError = MathErrorType.DivisionByZeroError
            };
            HttpContext.Features.Set(errorType);
            return Problem(
                title: "Bad Input",
                detail: "Divison by zero is not defined.",
                type: "https://en.wikipedia.org/wiki/Division_by_zero",
                statusCode: StatusCodes.Status400BadRequest
                );
        }

        return Ok(Numerator / Denominator);
    }

    // /api/values3/squareroot/4
    [HttpGet("{radicand}")]
    public IActionResult Squareroot(double radicand)
    {
        if (radicand < 0)
        {
            var errorType = new MathErrorFeature
            {
                MathError = MathErrorType.NegativeRadicandError
            };
            HttpContext.Features.Set(errorType);
            return Problem(
                title: "Bad Input",
                detail: "Negative or complex numbers are not valid input.",
                type: "https://en.wikipedia.org/wiki/Square_root",
                statusCode: StatusCodes.Status400BadRequest
                );
        }

        return Ok(Math.Sqrt(radicand));
    }

}

Producción de una carga de ProblemDetails para excepciones

Considere la aplicación siguiente:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseExceptionHandler();
app.UseStatusCodePages();

if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}

app.MapControllers();
app.Run();

En entornos que no son de desarrollo, cuando se produce una excepción, lo siguiente es una respuesta ProblemDetails estándar que se devuelve al cliente:

{
"type":"https://tools.ietf.org/html/rfc7231#section-6.6.1",
"title":"An error occurred while processing your request.",
"status":500,"traceId":"00-b644<snip>-00"
}

Para la mayoría de las aplicaciones, el código anterior es todo lo que se necesita para las excepciones. Sin embargo, en la sección siguiente se muestra cómo obtener respuestas a problemas más detalladas.

Una alternativa a una página del controlador de excepciones personalizada es proporcionar una expresión lambda en UseExceptionHandler. El uso de una expresión lambda permite acceder al error y escribir una respuesta de detalles del problema con IProblemDetailsService.WriteAsync:

using Microsoft.AspNetCore.Diagnostics;
using static System.Net.Mime.MediaTypeNames;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseExceptionHandler();
app.UseStatusCodePages();

if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler(exceptionHandlerApp =>
    {
        exceptionHandlerApp.Run(async context =>
        {
            context.Response.StatusCode = StatusCodes.Status500InternalServerError;
            context.Response.ContentType = Text.Plain;

            var title = "Bad Input";
            var detail = "Invalid input";
            var type = "https://errors.example.com/badInput";

            if (context.RequestServices.GetService<IProblemDetailsService>() is
                { } problemDetailsService)
            {
                var exceptionHandlerFeature =
               context.Features.Get<IExceptionHandlerFeature>();

                var exceptionType = exceptionHandlerFeature?.Error;
                if (exceptionType != null &&
                   exceptionType.Message.Contains("infinity"))
                {
                    title = "Argument exception";
                    detail = "Invalid input";
                    type = "https://errors.example.com/argumentException";
                }

                await problemDetailsService.WriteAsync(new ProblemDetailsContext
                {
                    HttpContext = context,
                    ProblemDetails =
                {
                    Title = title,
                    Detail = detail,
                    Type = type
                }
                });
            }
        });
    });
}

app.MapControllers();
app.Run();

Advertencia

No proporcione información de errores confidencial a los clientes. Proporcionar información de los errores es un riesgo para la seguridad.

Un enfoque alternativo para generar detalles del problema es usar el paquete NuGet de terceros Hellang.Middleware.ProblemDetails, que se puede usar para asignar excepciones y errores de cliente a los detalles del problema.

Recursos adicionales

Por Tom Dykstra

Este artículo trata sobre los métodos comunes para controlar errores en aplicaciones web ASP.NET Core. Consulte también Control de errores en API web de ASP.NET Core y Control de errores en aplicaciones de API mínimas.

Página de excepciones para el desarrollador

En la Página de excepciones para el desarrollador se muestra información detallada sobre las excepciones de la solicitud no controladas. De forma predeterminada, las aplicaciones de ASP.NET Core habilitan la página de excepciones del desarrollador cuando se cumplen las dos condiciones siguientes:

La página de excepciones del desarrollador se ejecuta al principio de la canalización del middleware, de modo que puede capturar las excepciones no controladas que se producen en el middleware que sigue.

La información detallada de las excepciones no debería mostrarse públicamente cuando la aplicación se ejecuta en el entorno de producción. Para más información sobre la configuración de los entornos, consulte Uso de varios entornos en ASP.NET Core.

La página de excepciones para el desarrollador puede incluir la siguiente información sobre la excepción y la solicitud:

  • Seguimiento de la pila
  • Parámetros de cadena de consulta (si existen)
  • Cookie (si existen)
  • encabezados

No se garantiza que la página de excepciones del desarrollador proporcione alguna información. Use el registro obtener información completa sobre el error.

Página del controlador de excepciones

Para configurar una página de control de errores personalizada para el entorno de producción, llame a UseExceptionHandler. Este middleware de control de excepciones:

  • Captura y registra las excepciones no controladas.
  • Vuelve a ejecutar la solicitud en una canalización alternativa con la ruta de acceso indicada. La solicitud no se vuelve a ejecutar si se ha iniciado la respuesta. El código generado por la plantilla vuelve a ejecutar la solicitud mediante la ruta de acceso /Error.

Advertencia

Si la canalización alternativa produce una excepción propia, el middleware de control de excepciones vuelve a producir la excepción original.

Dado que este middleware puede volver a ejecutar la canalización de solicitudes:

  • Los middleware deben controlar la reentrada con la misma solicitud. Normalmente, esto significa limpiar su estado después de llamar a _next o almacenar en caché su procesamiento en HttpContext para evitar volver a hacerlo. Al tratar con el cuerpo de la solicitud, esto significa almacenar en búfer o almacenar en caché los resultados como el lector de formularios.
  • En el caso de la sobrecarga UseExceptionHandler(IApplicationBuilder, String) que se usa en las plantillas, solo se modifica la ruta de acceso de la solicitud y se borran los datos de ruta. Los datos de la solicitud, como los encabezados, los métodos y los elementos, se reutilizan tal cual.
  • Los servicios con ámbito siguen siendo los mismos.

En el ejemplo siguiente, UseExceptionHandler agrega middleware de control de excepciones en entornos que no son de desarrollo:

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

La plantilla de aplicación de Razor Pages proporciona una página de error (.cshtml) y una clase PageModel (ErrorModel) en la carpeta Pages. Para una aplicación de MVC, la plantilla de proyecto incluye un método de acción de Error y una vista del error para el controlador de Home.

El middleware de control de excepciones vuelve a ejecutar la solicitud mediante el método HTTP original. Si un punto de conexión de controlador de errores está restringido a un conjunto específico de métodos HTTP, solo se ejecuta para esos métodos HTTP. Por ejemplo, una acción de controlador de MVC que usa el atributo [HttpGet] solo se ejecuta para solicitudes GET. Para asegurarse de que todas las solicitudes lleguen a la página de control de errores personalizada, no las restrinja a un conjunto específico de métodos HTTP.

Para controlar las excepciones de manera diferente en función del método HTTP original:

  • En el caso de Razor Pages, cree varios métodos de control. Por ejemplo, use OnGet para controlar las excepciones GET y use OnPost para controlar las excepciones POST.
  • Para MVC, aplique los atributos de verbo HTTP a varias acciones. Por ejemplo, use [HttpGet] para controlar las excepciones GET y use [HttpPost] para controlar las excepciones POST.

Para permitir que los usuarios no autenticados vean la página de control de errores personalizada, asegúrese de que admite el acceso anónimo.

Acceso a la excepción

Use IExceptionHandlerPathFeature para acceder a la ruta de acceso de la solicitud original y a la excepción en un controlador de errores. En el ejemplo siguiente se usa IExceptionHandlerPathFeature para obtener más información sobre la excepción que se produjo:

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
[IgnoreAntiforgeryToken]
public class ErrorModel : PageModel
{
    public string? RequestId { get; set; }

    public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);

    public string? ExceptionMessage { get; set; }

    public void OnGet()
    {
        RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;

        var exceptionHandlerPathFeature =
            HttpContext.Features.Get<IExceptionHandlerPathFeature>();

        if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
        {
            ExceptionMessage = "The file was not found.";
        }

        if (exceptionHandlerPathFeature?.Path == "/")
        {
            ExceptionMessage ??= string.Empty;
            ExceptionMessage += " Page: Home.";
        }
    }
}

Advertencia

No proporcione información de errores confidencial a los clientes. Proporcionar información de los errores es un riesgo para la seguridad.

Lambda del controlador de excepciones

Una alternativa a una página del controlador de excepciones personalizada es proporcionar una expresión lambda en UseExceptionHandler. Usar una expresión lambda permite acceder al error antes de devolver la respuesta.

En el código siguiente se usa una expresión lambda para el control de excepciones:

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler(exceptionHandlerApp =>
    {
        exceptionHandlerApp.Run(async context =>
        {
            context.Response.StatusCode = StatusCodes.Status500InternalServerError;

            // using static System.Net.Mime.MediaTypeNames;
            context.Response.ContentType = Text.Plain;

            await context.Response.WriteAsync("An exception was thrown.");

            var exceptionHandlerPathFeature =
                context.Features.Get<IExceptionHandlerPathFeature>();

            if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
            {
                await context.Response.WriteAsync(" The file was not found.");
            }

            if (exceptionHandlerPathFeature?.Path == "/")
            {
                await context.Response.WriteAsync(" Page: Home.");
            }
        });
    });

    app.UseHsts();
}

Advertencia

No proporcione información de errores confidencial a los clientes. Proporcionar información de los errores es un riesgo para la seguridad.

UseStatusCodePages

Una aplicación ASP.NET Core no proporciona de forma predeterminada ninguna página de códigos de estado para los códigos de estado HTTP, como 404 - No encontrado. Cuando la aplicación establece un código de estado de error HTTP 400-599 que no tiene un cuerpo, devuelve el código de estado y un cuerpo de respuesta vacío. Para habilitar los controladores de solo texto predeterminados para los códigos de estado de error comunes, llame a UseStatusCodePages en Program.cs:

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePages();

Llame a UseStatusCodePages antes del middleware de control de solicitudes. Por ejemplo, llame a UseStatusCodePages antes del middleware de archivos estáticos y el middleware de puntos de conexión.

Cuando no se usa UseStatusCodePages, al navegar a una dirección URL sin punto de conexión se devuelve un mensaje de error dependiente del explorador que indica que no se encuentra el punto de conexión. Cuando se llama a UseStatusCodePages, el explorador devuelve la siguiente respuesta:

Status Code: 404; Not Found

UseStatusCodePages no se utiliza normalmente en producción, ya que devuelve un mensaje que no es útil para los usuarios.

Nota

El middleware de páginas de códigos de estado no detecta excepciones. Para proporcionar una página de control de errores personalizada, use la página del controlador de excepciones.

UseStatusCodePages con cadena de formato

Para personalizar el texto y el tipo de contenido de la respuesta, use la sobrecarga de UseStatusCodePages que adopta una cadena de tipo de contenido y formato:

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

// using static System.Net.Mime.MediaTypeNames;
app.UseStatusCodePages(Text.Plain, "Status Code Page: {0}");

En el código anterior, {0} es un marcador de posición para el código de error.

UseStatusCodePages con una cadena de formato no se utiliza normalmente en producción, ya que devuelve un mensaje que no es útil para los usuarios.

UseStatusCodePages con una expresión lambda

Para especificar el código de escritura de respuesta y control de errores personalizado, use la sobrecarga de UseStatusCodePages que adopta una expresión lambda:

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePages(async statusCodeContext =>
{
    // using static System.Net.Mime.MediaTypeNames;
    statusCodeContext.HttpContext.Response.ContentType = Text.Plain;

    await statusCodeContext.HttpContext.Response.WriteAsync(
        $"Status Code Page: {statusCodeContext.HttpContext.Response.StatusCode}");
});

UseStatusCodePages con una expresión lambda no se utiliza normalmente en producción, ya que devuelve un mensaje que no es útil para los usuarios.

UseStatusCodePagesWithRedirects

Método de extensión UseStatusCodePagesWithRedirects:

  • Envía un código de estado 302 - Encontrado al cliente.
  • Redirige el cliente al punto de conexión de control de errores proporcionado en la plantilla de dirección URL. El punto de conexión de control de errores suele mostrar información de error y devuelve HTTP 200.
var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePagesWithRedirects("/StatusCode/{0}");

La plantilla de dirección URL puede incluir un marcador de posición {0} para el código de estado, tal y como se muestra en el código anterior. Si la plantilla de dirección URL comienza con ~ (tilde), la ~ se sustituye por el elemento PathBase de la aplicación. Si especifica un punto de conexión en la aplicación, cree una vista de MVC o una página de Razor para ese punto de conexión.

Este método se usa normalmente cuando la aplicación:

  • Debe redirigir al cliente a un punto de conexión diferente, normalmente en casos en los que una aplicación diferente procesa el error. En el caso de aplicaciones web, la barra de direcciones del explorador del cliente refleja el punto de conexión redirigido.
  • No debe conservar ni devolver el código de estado original con la respuesta de redirección inicial.

UseStatusCodePagesWithReExecute

Método de extensión UseStatusCodePagesWithReExecute:

  • Genera el cuerpo de respuesta, para lo cual vuelve a ejecutar la canalización de solicitud mediante una ruta de acceso alternativa.
  • No modifica el código de estado antes o después de volver a ejecutar la canalización.

La nueva ejecución de canalización puede modificar el código de estado de la respuesta, ya que la nueva canalización tiene un control absoluto del código de estado. Si la nueva canalización no modifica el código de estado, se envía el código de estado original al cliente.

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePagesWithReExecute("/StatusCode/{0}");

Si se especifica un punto de conexión en la aplicación, cree una vista de MVC o una página de Razor para ese punto de conexión.

Este método se usa normalmente cuando la aplicación debe:

  • Procesar la solicitud sin redirigirla a un punto de conexión diferente. En el caso de aplicaciones web, la barra de direcciones del explorador del cliente refleja el punto de conexión solicitado originalmente.
  • Conservar y devolver el código de estado original con la respuesta.

La plantilla de dirección URL debe empezar con / y puede incluir un marcador de posición {0} para el código de estado. Para pasar el código de estado como un parámetro de cadena de consulta, pase un segundo argumento a UseStatusCodePagesWithReExecute. Por ejemplo:

var app = builder.Build();  
app.UseStatusCodePagesWithReExecute("/StatusCode", "?statusCode={0}");

El punto de conexión que procesa el error puede obtener la dirección URL original que generó el error, como se muestra en el ejemplo siguiente:

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public class StatusCodeModel : PageModel
{
    public int OriginalStatusCode { get; set; }

    public string? OriginalPathAndQuery { get; set; }

    public void OnGet(int statusCode)
    {
        OriginalStatusCode = statusCode;

        var statusCodeReExecuteFeature =
            HttpContext.Features.Get<IStatusCodeReExecuteFeature>();

        if (statusCodeReExecuteFeature is not null)
        {
            OriginalPathAndQuery = string.Join(
                statusCodeReExecuteFeature.OriginalPathBase,
                statusCodeReExecuteFeature.OriginalPath,
                statusCodeReExecuteFeature.OriginalQueryString);
        }
    }
}

Dado que este middleware puede volver a ejecutar la canalización de solicitudes:

  • Los middleware deben controlar la reentrada con la misma solicitud. Normalmente, esto significa limpiar su estado después de llamar a _next o almacenar en caché su procesamiento en HttpContext para evitar volver a hacerlo. Al tratar con el cuerpo de la solicitud, esto significa almacenar en búfer o en caché los resultados como el lector de formularios.
  • Los servicios con ámbito siguen siendo los mismos.

Deshabilitar las páginas de códigos de estado

Para deshabilitar las páginas de códigos de estado de un método de acción o controlador MVC, use el atributo [SkipStatusCodePages].

Para deshabilitar las páginas de códigos de estado de solicitudes específicas de un método de controlador de Razor Pages o un controlador MVC, use IStatusCodePagesFeature:

public void OnGet()
{
    var statusCodePagesFeature =
        HttpContext.Features.Get<IStatusCodePagesFeature>();

    if (statusCodePagesFeature is not null)
    {
        statusCodePagesFeature.Enabled = false;
    }
}

Código de control de excepciones

El código de las páginas de control de excepciones también puede iniciar excepciones. Las páginas de errores de producción se deben probar minuciosamente y se debe tener especial cuidado para evitar que inicien sus propias excepciones.

Encabezados de respuesta

Una vez enviados los encabezados de una respuesta:

  • La aplicación no puede cambiar el código de estado de la respuesta.
  • No se pueden ejecutar páginas o controladores de excepciones. Deberá completarse la respuesta o anularse la conexión.

Control de excepciones del servidor

Además de la lógica de control de excepciones de la aplicación, la implementación del servidor HTTP puede controlar algunas excepciones. Si el servidor almacena en caché una excepción antes de que se envíen los encabezados de respuesta, envía una respuesta 500 - Internal Server Error sin cuerpo. Si el servidor almacena en caché una excepción después de que se envían los encabezados de respuesta, cierra la conexión. El servidor controla las solicitudes que no controla la aplicación. El control de excepciones del servidor controla cualquier excepción que se produzca cuando el servidor controle la solicitud. Las páginas de error personalizadas, el middleware de control de excepciones y los filtros de la aplicación no afectan este comportamiento.

Control de excepciones de inicio

Solo el nivel de hospedaje puede controlar las excepciones que tienen lugar durante el inicio de la aplicación. El host puede configurarse para capturar errores de inicio y capturar errores detallados.

La capa de hospedaje puede mostrar una página de error para un error de inicio capturado solo si este se produce después del enlace de puerto/dirección del host. Si se produce un error de enlace:

  • La capa de hospedaje registra una excepción crítica.
  • El proceso de dotnet se bloquea.
  • No se muestra ninguna página de error si el servidor HTTP es Kestrel.

Si se ejecuta en IIS, en Azure App Service o en IIS Express, el módulo ASP.NET Core devuelve un error de proceso 502.5 si el proceso no se puede iniciar. Para más información, consulte Solución de problemas de ASP.NET Core en Azure App Service e IIS.

Página de error de la base de datos

El filtro de excepciones AddDatabaseDeveloperPageExceptionFilter de la página del desarrollador de bases de datos captura las excepciones relacionadas con la base de datos que se pueden resolver mediante migraciones de Entity Framework Core. Cuando se producen estas excepciones, se genera una respuesta HTML con los detalles de las acciones posibles para resolver el problema. Esta página debe habilitarse solo en el entorno de desarrollo. El código siguiente agrega el filtro de la página de excepciones del desarrollador:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddRazorPages();

Filtros de excepciones

En las aplicaciones de MVC, los filtros de excepciones se pueden configurar globalmente, o bien por controlador o por acción. En las aplicaciones de Razor Pages, se pueden configurar a nivel global o por modelo de página. Estos filtros controlan todas las excepciones no controladas que se hayan producido durante la ejecución de una acción de controlador o de otro filtro. Para más información, consulte Filtros en ASP.NET Core.

Los filtros de excepciones son útiles para interceptar las excepciones que se producen en las acciones de MVC, pero no son tan flexibles como el middleware de control de excepciones integrado, UseExceptionHandler. Se recomienda usar UseExceptionHandler, a no ser que tenga que realizar el control de errores de otra forma según la acción de MVC elegida.

Errores de estado del modelo

Para obtener información sobre cómo controlar los errores de estado de los modelos, vea Enlace de modelos y Validación de modelos.

Detalles del problema

Los detalles del problema no son el único formato de respuesta para describir un error de la API HTTP; sin embargo, se usan normalmente para notificar errores para las API HTTP.

El servicio de detalles del problema implementa la interfaz IProblemDetailsService, que admite la creación de detalles del problema en ASP.NET Core. El método de extensión AddProblemDetails de IServiceCollection registra la implementación IProblemDetailsService predeterminada.

En aplicaciones de ASP.NET Core, el middleware siguiente genera respuestas HTTP de detalles del problema cuando se llama a AddProblemDetails, excepto cuando el encabezado HTTP de solicitud Accept no incluye uno de los tipos de contenido admitidos por el IProblemDetailsWriter registrado (valor predeterminado: application/json):

El código siguiente configura la aplicación para generar una respuesta de detalles del problema para todas las respuestas de error de servidor y cliente HTTP que aún no tienen contenido en el cuerpo:

builder.Services.AddProblemDetails();

var app = builder.Build();        

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler();
    app.UseHsts();
}

app.UseStatusCodePages();

En la sección siguiente se muestra cómo personalizar el cuerpo de la respuesta de detalles del problema.

Personalización de los detalles del problema

La creación automática de un objeto ProblemDetails se puede personalizar mediante las siguientes opciones:

  1. Use ProblemDetailsOptions.CustomizeProblemDetails
  2. Uso de una IProblemDetailsWriter personalizada
  3. Llamada a IProblemDetailsService en un middleware

Operation de CustomizeProblemDetails:

Los detalles del problema generado se pueden personalizar mediante CustomizeProblemDetails, y las personalizaciones se aplican a todos los detalles del problema generados automáticamente.

El código siguiente usa ProblemDetailsOptions para establecer CustomizeProblemDetails:

builder.Services.AddProblemDetails(options =>
    options.CustomizeProblemDetails = ctx =>
            ctx.ProblemDetails.Extensions.Add("nodeId", Environment.MachineName));

var app = builder.Build();        

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler();
    app.UseHsts();
}

app.UseStatusCodePages();

Por ejemplo, un resultado del punto de conexión HTTP Status 400 Bad Request genera el siguiente cuerpo de respuesta de detalles del problema:

{
  "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
  "title": "Bad Request",
  "status": 400,
  "nodeId": "my-machine-name"
}

IProblemDetailsWriter personalizado

Se puede crear una implementación de IProblemDetailsWriter para personalizaciones avanzadas.

public class SampleProblemDetailsWriter : IProblemDetailsWriter
{
    // Indicates that only responses with StatusCode == 400
    // are handled by this writer. All others are
    // handled by different registered writers if available.
    public bool CanWrite(ProblemDetailsContext context)
        => context.HttpContext.Response.StatusCode == 400;

    public ValueTask WriteAsync(ProblemDetailsContext context)
    {
        // Additional customizations.

        // Write to the response.
        var response = context.HttpContext.Response;
        return new ValueTask(response.WriteAsJsonAsync(context.ProblemDetails));
    }
}

Nota: Cuando se usa un elemento personalizado IProblemDetailsWriter, el elemento personalizado IProblemDetailsWriter se debe registrar antes de llamar a AddRazorPages, AddControllers, AddControllersWithViews o AddMvc:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddTransient<IProblemDetailsWriter, SampleProblemDetailsWriter>();

var app = builder.Build();

// Middleware to handle writing problem details to the response.
app.Use(async (context, next) =>
{
    await next(context);
    var mathErrorFeature = context.Features.Get<MathErrorFeature>();
    if (mathErrorFeature is not null)
    {
        if (context.RequestServices.GetService<IProblemDetailsWriter>() is
            { } problemDetailsService)
        {

            if (problemDetailsService.CanWrite(new ProblemDetailsContext() { HttpContext = context }))
            {
                (string Detail, string Type) details = mathErrorFeature.MathError switch
                {
                    MathErrorType.DivisionByZeroError => ("Divison by zero is not defined.",
                        "https://en.wikipedia.org/wiki/Division_by_zero"),
                    _ => ("Negative or complex numbers are not valid input.",
                        "https://en.wikipedia.org/wiki/Square_root")
                };

                await problemDetailsService.WriteAsync(new ProblemDetailsContext
                {
                    HttpContext = context,
                    ProblemDetails =
                    {
                        Title = "Bad Input",
                        Detail = details.Detail,
                        Type = details.Type
                    }
                });
            }
        }
    }
});

// /divide?numerator=2&denominator=4
app.MapGet("/divide", (HttpContext context, double numerator, double denominator) =>
{
    if (denominator == 0)
    {
        var errorType = new MathErrorFeature
        {
            MathError = MathErrorType.DivisionByZeroError
        };
        context.Features.Set(errorType);
        return Results.BadRequest();
    }

    return Results.Ok(numerator / denominator);
});

// /squareroot?radicand=16
app.MapGet("/squareroot", (HttpContext context, double radicand) =>
{
    if (radicand < 0)
    {
        var errorType = new MathErrorFeature
        {
            MathError = MathErrorType.NegativeRadicandError
        };
        context.Features.Set(errorType);
        return Results.BadRequest();
    }

    return Results.Ok(Math.Sqrt(radicand));
});

app.Run();

Detalles del problema del middleware

Un enfoque alternativo para usar ProblemDetailsOptions con CustomizeProblemDetails es establecer ProblemDetails en middleware. Una respuesta de detalles del problema se puede escribir llamando a IProblemDetailsService.WriteAsync:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStatusCodePages();

// Middleware to handle writing problem details to the response.
app.Use(async (context, next) =>
{
    await next(context);
    var mathErrorFeature = context.Features.Get<MathErrorFeature>();
    if (mathErrorFeature is not null)
    {
        if (context.RequestServices.GetService<IProblemDetailsService>() is
                                                           { } problemDetailsService)
        {
            (string Detail, string Type) details = mathErrorFeature.MathError switch
            {
                MathErrorType.DivisionByZeroError => ("Divison by zero is not defined.",
                "https://en.wikipedia.org/wiki/Division_by_zero"),
                _ => ("Negative or complex numbers are not valid input.", 
                "https://en.wikipedia.org/wiki/Square_root")
            };

            await problemDetailsService.WriteAsync(new ProblemDetailsContext
            {
                HttpContext = context,
                ProblemDetails =
                {
                    Title = "Bad Input",
                    Detail = details.Detail,
                    Type = details.Type
                }
            });
        }
    }
});

// /divide?numerator=2&denominator=4
app.MapGet("/divide", (HttpContext context, double numerator, double denominator) =>
{
    if (denominator == 0)
    {
        var errorType = new MathErrorFeature { MathError =
                                               MathErrorType.DivisionByZeroError };
        context.Features.Set(errorType);
        return Results.BadRequest();
    }

    return Results.Ok(numerator / denominator);
});

// /squareroot?radicand=16
app.MapGet("/squareroot", (HttpContext context, double radicand) =>
{
    if (radicand < 0)
    {
        var errorType = new MathErrorFeature { MathError =
                                               MathErrorType.NegativeRadicandError };
        context.Features.Set(errorType);
        return Results.BadRequest();
    }

    return Results.Ok(Math.Sqrt(radicand));
});

app.MapControllers();

app.Run();

En el código anterior, los puntos de conexión de API mínimas /divide y /squareroot devuelven la respuesta del problema personalizada esperada en la entrada de error.

Los puntos de conexión del controlador de API devuelven la respuesta del problema predeterminada en la entrada de error, no la respuesta del problema personalizada. Se devuelve la respuesta del problema predeterminada porque el controlador de API ha escrito en el flujo de respuesta, Detalles del problema para los códigos de estado de error, antes de llamar a IProblemDetailsService.WriteAsync y la respuesta no se vuelve a escribir.

El siguiente ValuesController devuelve BadRequestResult, que escribe en el flujo de respuesta y, por lo tanto, impide que se devuelva la respuesta del problema personalizado.

[Route("api/[controller]/[action]")]
[ApiController]
public class ValuesController : ControllerBase
{
    // /api/values/divide/1/2
    [HttpGet("{Numerator}/{Denominator}")]
    public IActionResult Divide(double Numerator, double Denominator)
    {
        if (Denominator == 0)
        {
            var errorType = new MathErrorFeature
            {
                MathError = MathErrorType.DivisionByZeroError
            };
            HttpContext.Features.Set(errorType);
            return BadRequest();
        }

        return Ok(Numerator / Denominator);
    }

    // /api/values/squareroot/4
    [HttpGet("{radicand}")]
    public IActionResult Squareroot(double radicand)
    {
        if (radicand < 0)
        {
            var errorType = new MathErrorFeature
            {
                MathError = MathErrorType.NegativeRadicandError
            };
            HttpContext.Features.Set(errorType);
            return BadRequest();
        }

        return Ok(Math.Sqrt(radicand));
    }

}

El siguiente Values3Controller devuelve ControllerBase.Problem para que se devuelva el resultado esperado del problema personalizado:

[Route("api/[controller]/[action]")]
[ApiController]
public class Values3Controller : ControllerBase
{
    // /api/values3/divide/1/2
    [HttpGet("{Numerator}/{Denominator}")]
    public IActionResult Divide(double Numerator, double Denominator)
    {
        if (Denominator == 0)
        {
            var errorType = new MathErrorFeature
            {
                MathError = MathErrorType.DivisionByZeroError
            };
            HttpContext.Features.Set(errorType);
            return Problem(
                title: "Bad Input",
                detail: "Divison by zero is not defined.",
                type: "https://en.wikipedia.org/wiki/Division_by_zero",
                statusCode: StatusCodes.Status400BadRequest
                );
        }

        return Ok(Numerator / Denominator);
    }

    // /api/values3/squareroot/4
    [HttpGet("{radicand}")]
    public IActionResult Squareroot(double radicand)
    {
        if (radicand < 0)
        {
            var errorType = new MathErrorFeature
            {
                MathError = MathErrorType.NegativeRadicandError
            };
            HttpContext.Features.Set(errorType);
            return Problem(
                title: "Bad Input",
                detail: "Negative or complex numbers are not valid input.",
                type: "https://en.wikipedia.org/wiki/Square_root",
                statusCode: StatusCodes.Status400BadRequest
                );
        }

        return Ok(Math.Sqrt(radicand));
    }

}

Producción de una carga de ProblemDetails para excepciones

Considere la aplicación siguiente:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseExceptionHandler();
app.UseStatusCodePages();

if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}

app.MapControllers();
app.Run();

En entornos que no son de desarrollo, cuando se produce una excepción, lo siguiente es una respuesta ProblemDetails estándar que se devuelve al cliente:

{
"type":"https://tools.ietf.org/html/rfc7231#section-6.6.1",
"title":"An error occurred while processing your request.",
"status":500,"traceId":"00-b644<snip>-00"
}

Para la mayoría de las aplicaciones, el código anterior es todo lo que se necesita para las excepciones. Sin embargo, en la sección siguiente se muestra cómo obtener respuestas a problemas más detalladas.

Una alternativa a una página del controlador de excepciones personalizada es proporcionar una expresión lambda en UseExceptionHandler. El uso de una expresión lambda permite acceder al error y escribir una respuesta de detalles del problema con IProblemDetailsService.WriteAsync:

using Microsoft.AspNetCore.Diagnostics;
using static System.Net.Mime.MediaTypeNames;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddProblemDetails();

var app = builder.Build();

app.UseExceptionHandler();
app.UseStatusCodePages();

if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler(exceptionHandlerApp =>
    {
        exceptionHandlerApp.Run(async context =>
        {
            context.Response.StatusCode = StatusCodes.Status500InternalServerError;
            context.Response.ContentType = Text.Plain;

            var title = "Bad Input";
            var detail = "Invalid input";
            var type = "https://errors.example.com/badInput";

            if (context.RequestServices.GetService<IProblemDetailsService>() is
                { } problemDetailsService)
            {
                var exceptionHandlerFeature =
               context.Features.Get<IExceptionHandlerFeature>();

                var exceptionType = exceptionHandlerFeature?.Error;
                if (exceptionType != null &&
                   exceptionType.Message.Contains("infinity"))
                {
                    title = "Argument exception";
                    detail = "Invalid input";
                    type = "https://errors.example.com/argumentException";
                }

                await problemDetailsService.WriteAsync(new ProblemDetailsContext
                {
                    HttpContext = context,
                    ProblemDetails =
                {
                    Title = title,
                    Detail = detail,
                    Type = type
                }
                });
            }
        });
    });
}

app.MapControllers();
app.Run();

Advertencia

No proporcione información de errores confidencial a los clientes. Proporcionar información de los errores es un riesgo para la seguridad.

Un enfoque alternativo para generar detalles del problema es usar el paquete NuGet de terceros Hellang.Middleware.ProblemDetails, que se puede usar para asignar excepciones y errores de cliente a los detalles del problema.

Recursos adicionales

Por Tom Dykstra

Este artículo trata sobre los métodos comunes para controlar errores en aplicaciones web ASP.NET Core. Consulte Control de errores en API web de ASP.NET Core. para las API web.

Página de excepciones para el desarrollador

En la Página de excepciones para el desarrollador se muestra información detallada sobre las excepciones de la solicitud no controladas. De forma predeterminada, las aplicaciones de ASP.NET Core habilitan la página de excepciones del desarrollador cuando se cumplen las dos condiciones siguientes:

La página de excepciones del desarrollador se ejecuta al principio de la canalización del middleware, de modo que puede capturar las excepciones no controladas que se producen en el middleware que sigue.

La información detallada de las excepciones no debería mostrarse públicamente cuando la aplicación se ejecuta en el entorno de producción. Para más información sobre la configuración de los entornos, consulte Uso de varios entornos en ASP.NET Core.

La página de excepciones para el desarrollador puede incluir la siguiente información sobre la excepción y la solicitud:

  • Seguimiento de la pila
  • Parámetros de cadena de consulta (si existen)
  • Cookie (si existen)
  • encabezados

No se garantiza que la página de excepciones del desarrollador proporcione alguna información. Use el registro obtener información completa sobre el error.

Página del controlador de excepciones

Para configurar una página de control de errores personalizada para el entorno de producción, llame a UseExceptionHandler. Este middleware de control de excepciones:

  • Captura y registra las excepciones no controladas.
  • Vuelve a ejecutar la solicitud en una canalización alternativa con la ruta de acceso indicada. La solicitud no se vuelve a ejecutar si se ha iniciado la respuesta. El código generado por la plantilla vuelve a ejecutar la solicitud mediante la ruta de acceso /Error.

Advertencia

Si la canalización alternativa produce una excepción propia, el middleware de control de excepciones vuelve a producir la excepción original.

En el ejemplo siguiente, UseExceptionHandler agrega middleware de control de excepciones en entornos que no son de desarrollo:

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

La plantilla de aplicación de Razor Pages proporciona una página de error (.cshtml) y una clase PageModel (ErrorModel) en la carpeta Pages. Para una aplicación de MVC, la plantilla de proyecto incluye un método de acción de Error y una vista del error para el controlador de Home.

El middleware de control de excepciones vuelve a ejecutar la solicitud mediante el método HTTP original. Si un punto de conexión de controlador de errores está restringido a un conjunto específico de métodos HTTP, solo se ejecuta para esos métodos HTTP. Por ejemplo, una acción de controlador de MVC que usa el atributo [HttpGet] solo se ejecuta para solicitudes GET. Para asegurarse de que todas las solicitudes lleguen a la página de control de errores personalizada, no las restrinja a un conjunto específico de métodos HTTP.

Para controlar las excepciones de manera diferente en función del método HTTP original:

  • En el caso de Razor Pages, cree varios métodos de control. Por ejemplo, use OnGet para controlar las excepciones GET y use OnPost para controlar las excepciones POST.
  • Para MVC, aplique los atributos de verbo HTTP a varias acciones. Por ejemplo, use [HttpGet] para controlar las excepciones GET y use [HttpPost] para controlar las excepciones POST.

Para permitir que los usuarios no autenticados vean la página de control de errores personalizada, asegúrese de que admite el acceso anónimo.

Acceso a la excepción

Use IExceptionHandlerPathFeature para acceder a la ruta de acceso de la solicitud original y a la excepción en un controlador de errores. En el ejemplo siguiente se usa IExceptionHandlerPathFeature para obtener más información sobre la excepción que se produjo:

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
[IgnoreAntiforgeryToken]
public class ErrorModel : PageModel
{
    public string? RequestId { get; set; }

    public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);

    public string? ExceptionMessage { get; set; }

    public void OnGet()
    {
        RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;

        var exceptionHandlerPathFeature =
            HttpContext.Features.Get<IExceptionHandlerPathFeature>();

        if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
        {
            ExceptionMessage = "The file was not found.";
        }

        if (exceptionHandlerPathFeature?.Path == "/")
        {
            ExceptionMessage ??= string.Empty;
            ExceptionMessage += " Page: Home.";
        }
    }
}

Advertencia

No proporcione información de errores confidencial a los clientes. Proporcionar información de los errores es un riesgo para la seguridad.

Lambda del controlador de excepciones

Una alternativa a una página del controlador de excepciones personalizada es proporcionar una expresión lambda en UseExceptionHandler. Usar una expresión lambda permite acceder al error antes de devolver la respuesta.

En el código siguiente se usa una expresión lambda para el control de excepciones:

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler(exceptionHandlerApp =>
    {
        exceptionHandlerApp.Run(async context =>
        {
            context.Response.StatusCode = StatusCodes.Status500InternalServerError;

            // using static System.Net.Mime.MediaTypeNames;
            context.Response.ContentType = Text.Plain;

            await context.Response.WriteAsync("An exception was thrown.");

            var exceptionHandlerPathFeature =
                context.Features.Get<IExceptionHandlerPathFeature>();

            if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
            {
                await context.Response.WriteAsync(" The file was not found.");
            }

            if (exceptionHandlerPathFeature?.Path == "/")
            {
                await context.Response.WriteAsync(" Page: Home.");
            }
        });
    });

    app.UseHsts();
}

Advertencia

No proporcione información de errores confidencial a los clientes. Proporcionar información de los errores es un riesgo para la seguridad.

UseStatusCodePages

Una aplicación ASP.NET Core no proporciona de forma predeterminada ninguna página de códigos de estado para los códigos de estado HTTP, como 404 - No encontrado. Cuando la aplicación establece un código de estado de error HTTP 400-599 que no tiene un cuerpo, devuelve el código de estado y un cuerpo de respuesta vacío. Para habilitar los controladores de solo texto predeterminados para los códigos de estado de error comunes, llame a UseStatusCodePages en Program.cs:

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePages();

Llame a UseStatusCodePages antes del middleware de control de solicitudes. Por ejemplo, llame a UseStatusCodePages antes del middleware de archivos estáticos y el middleware de puntos de conexión.

Cuando no se usa UseStatusCodePages, al navegar a una dirección URL sin punto de conexión se devuelve un mensaje de error dependiente del explorador que indica que no se encuentra el punto de conexión. Cuando se llama a UseStatusCodePages, el explorador devuelve la siguiente respuesta:

Status Code: 404; Not Found

UseStatusCodePages no se utiliza normalmente en producción, ya que devuelve un mensaje que no es útil para los usuarios.

Nota

El middleware de páginas de códigos de estado no detecta excepciones. Para proporcionar una página de control de errores personalizada, use la página del controlador de excepciones.

UseStatusCodePages con cadena de formato

Para personalizar el texto y el tipo de contenido de la respuesta, use la sobrecarga de UseStatusCodePages que adopta una cadena de tipo de contenido y formato:

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

// using static System.Net.Mime.MediaTypeNames;
app.UseStatusCodePages(Text.Plain, "Status Code Page: {0}");

En el código anterior, {0} es un marcador de posición para el código de error.

UseStatusCodePages con una cadena de formato no se utiliza normalmente en producción, ya que devuelve un mensaje que no es útil para los usuarios.

UseStatusCodePages con una expresión lambda

Para especificar el código de escritura de respuesta y control de errores personalizado, use la sobrecarga de UseStatusCodePages que adopta una expresión lambda:

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePages(async statusCodeContext =>
{
    // using static System.Net.Mime.MediaTypeNames;
    statusCodeContext.HttpContext.Response.ContentType = Text.Plain;

    await statusCodeContext.HttpContext.Response.WriteAsync(
        $"Status Code Page: {statusCodeContext.HttpContext.Response.StatusCode}");
});

UseStatusCodePages con una expresión lambda no se utiliza normalmente en producción, ya que devuelve un mensaje que no es útil para los usuarios.

UseStatusCodePagesWithRedirects

Método de extensión UseStatusCodePagesWithRedirects:

  • Envía un código de estado 302 - Encontrado al cliente.
  • Redirige el cliente al punto de conexión de control de errores proporcionado en la plantilla de dirección URL. El punto de conexión de control de errores suele mostrar información de error y devuelve HTTP 200.
var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePagesWithRedirects("/StatusCode/{0}");

La plantilla de dirección URL puede incluir un marcador de posición {0} para el código de estado, tal y como se muestra en el código anterior. Si la plantilla de dirección URL comienza con ~ (tilde), la ~ se sustituye por el elemento PathBase de la aplicación. Si especifica un punto de conexión en la aplicación, cree una vista de MVC o una página de Razor para ese punto de conexión.

Este método se usa normalmente cuando la aplicación:

  • Debe redirigir al cliente a un punto de conexión diferente, normalmente en casos en los que una aplicación diferente procesa el error. En el caso de aplicaciones web, la barra de direcciones del explorador del cliente refleja el punto de conexión redirigido.
  • No debe conservar ni devolver el código de estado original con la respuesta de redirección inicial.

UseStatusCodePagesWithReExecute

Método de extensión UseStatusCodePagesWithReExecute:

  • Devuelve el código de estado original al cliente.
  • Genera el cuerpo de respuesta, para lo cual vuelve a ejecutar la canalización de solicitud mediante una ruta de acceso alternativa.
var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseStatusCodePagesWithReExecute("/StatusCode/{0}");

Si se especifica un punto de conexión en la aplicación, cree una vista de MVC o una página de Razor para ese punto de conexión.

Este método se usa normalmente cuando la aplicación debe:

  • Procesar la solicitud sin redirigirla a un punto de conexión diferente. En el caso de aplicaciones web, la barra de direcciones del explorador del cliente refleja el punto de conexión solicitado originalmente.
  • Conservar y devolver el código de estado original con la respuesta.

La plantilla de dirección URL debe empezar con / y puede incluir un marcador de posición {0} para el código de estado. Para pasar el código de estado como un parámetro de cadena de consulta, pase un segundo argumento a UseStatusCodePagesWithReExecute. Por ejemplo:

app.UseStatusCodePagesWithReExecute("/StatusCode", "?statusCode={0}");

El punto de conexión que procesa el error puede obtener la dirección URL original que generó el error, como se muestra en el ejemplo siguiente:

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public class StatusCodeModel : PageModel
{
    public int OriginalStatusCode { get; set; }

    public string? OriginalPathAndQuery { get; set; }

    public void OnGet(int statusCode)
    {
        OriginalStatusCode = statusCode;

        var statusCodeReExecuteFeature =
            HttpContext.Features.Get<IStatusCodeReExecuteFeature>();

        if (statusCodeReExecuteFeature is not null)
        {
            OriginalPathAndQuery = string.Join(
                statusCodeReExecuteFeature.OriginalPathBase,
                statusCodeReExecuteFeature.OriginalPath,
                statusCodeReExecuteFeature.OriginalQueryString);
        }
    }
}

Deshabilitar las páginas de códigos de estado

Para deshabilitar las páginas de códigos de estado de un método de acción o controlador MVC, use el atributo [SkipStatusCodePages].

Para deshabilitar las páginas de códigos de estado de solicitudes específicas de un método de controlador de Razor Pages o un controlador MVC, use IStatusCodePagesFeature:

public void OnGet()
{
    var statusCodePagesFeature =
        HttpContext.Features.Get<IStatusCodePagesFeature>();

    if (statusCodePagesFeature is not null)
    {
        statusCodePagesFeature.Enabled = false;
    }
}

Código de control de excepciones

El código de las páginas de control de excepciones también puede iniciar excepciones. Las páginas de errores de producción se deben probar minuciosamente y se debe tener especial cuidado para evitar que inicien sus propias excepciones.

Encabezados de respuesta

Una vez enviados los encabezados de una respuesta:

  • La aplicación no puede cambiar el código de estado de la respuesta.
  • No se pueden ejecutar páginas o controladores de excepciones. Deberá completarse la respuesta o anularse la conexión.

Control de excepciones del servidor

Además de la lógica de control de excepciones de la aplicación, la implementación del servidor HTTP puede controlar algunas excepciones. Si el servidor almacena en caché una excepción antes de que se envíen los encabezados de respuesta, envía una respuesta 500 - Internal Server Error sin cuerpo. Si el servidor almacena en caché una excepción después de que se envían los encabezados de respuesta, cierra la conexión. El servidor controla las solicitudes que no controla la aplicación. El control de excepciones del servidor controla cualquier excepción que se produzca cuando el servidor controle la solicitud. Las páginas de error personalizadas, el middleware de control de excepciones y los filtros de la aplicación no afectan este comportamiento.

Control de excepciones de inicio

Solo el nivel de hospedaje puede controlar las excepciones que tienen lugar durante el inicio de la aplicación. El host puede configurarse para capturar errores de inicio y capturar errores detallados.

La capa de hospedaje puede mostrar una página de error para un error de inicio capturado solo si este se produce después del enlace de puerto/dirección del host. Si se produce un error de enlace:

  • La capa de hospedaje registra una excepción crítica.
  • El proceso de dotnet se bloquea.
  • No se muestra ninguna página de error si el servidor HTTP es Kestrel.

Si se ejecuta en IIS, en Azure App Service o en IIS Express, el módulo ASP.NET Core devuelve un error de proceso 502.5 si el proceso no se puede iniciar. Para más información, consulte Solución de problemas de ASP.NET Core en Azure App Service e IIS.

Página de error de la base de datos

El filtro de excepciones AddDatabaseDeveloperPageExceptionFilter de la página del desarrollador de bases de datos captura las excepciones relacionadas con la base de datos que se pueden resolver mediante migraciones de Entity Framework Core. Cuando se producen estas excepciones, se genera una respuesta HTML con los detalles de las acciones posibles para resolver el problema. Esta página debe habilitarse solo en el entorno de desarrollo. El código siguiente agrega el filtro de la página de excepciones del desarrollador:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddRazorPages();

Filtros de excepciones

En las aplicaciones de MVC, los filtros de excepciones se pueden configurar globalmente, o bien por controlador o por acción. En las aplicaciones de Razor Pages, se pueden configurar a nivel global o por modelo de página. Estos filtros controlan todas las excepciones no controladas que se hayan producido durante la ejecución de una acción de controlador o de otro filtro. Para más información, consulte Filtros en ASP.NET Core.

Los filtros de excepciones son útiles para interceptar las excepciones que se producen en las acciones de MVC, pero no son tan flexibles como el middleware de control de excepciones integrado, UseExceptionHandler. Se recomienda usar UseExceptionHandler, a no ser que tenga que realizar el control de errores de otra forma según la acción de MVC elegida.

Errores de estado del modelo

Para obtener información sobre cómo controlar los errores de estado de los modelos, vea Enlace de modelos y Validación de modelos.

Recursos adicionales

De Kirk Larkin, Tom Dykstra y Steve Smith

Este artículo trata sobre los métodos comunes para controlar errores en aplicaciones web ASP.NET Core. Consulte Control de errores en API web de ASP.NET Core. para las API web.

Vea o descargue el código de ejemplo. (Cómo descargarlo). La pestaña Red de las herramientas de desarrollo del explorador F12 resulta útil al probar la aplicación de ejemplo.

Página de excepciones para el desarrollador

En la Página de excepciones para el desarrollador se muestra información detallada sobre las excepciones de la solicitud no controladas. Las plantillas de ASP.NET Core generan el siguiente código:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

El código resaltado anterior habilita la página de excepciones para el desarrollador cuando la aplicación se ejecuta en el entorno de desarrollo.

Las plantillas colocan UseDeveloperExceptionPage al principio de la canalización de middleware para que pueda detectar las excepciones no controladas que se producen en el middleware que sigue.

El código anterior solo habilita la página de excepciones del desarrollador cuando la aplicación se ejecuta en el entorno de desarrollo. La información detallada de las excepciones no debería mostrarse públicamente cuando la aplicación se ejecuta en el entorno de producción. Para más información sobre la configuración de los entornos, consulte Uso de varios entornos en ASP.NET Core.

La página de excepciones para el desarrollador puede incluir la siguiente información sobre la excepción y la solicitud:

  • Seguimiento de la pila
  • Parámetros de cadena de consulta (si existen)
  • Cookie (si existen)
  • encabezados

No se garantiza que la página de excepciones del desarrollador proporcione alguna información. Use el registro obtener información completa sobre el error.

Página del controlador de excepciones

Para configurar una página de control de errores personalizada para el entorno de producción, llame a UseExceptionHandler. Este middleware de control de excepciones:

  • Captura y registra las excepciones no controladas.
  • Vuelve a ejecutar la solicitud en una canalización alternativa con la ruta de acceso indicada. La solicitud no se vuelve a ejecutar si se ha iniciado la respuesta. El código generado por la plantilla vuelve a ejecutar la solicitud mediante la ruta de acceso /Error.

Advertencia

Si la canalización alternativa produce una excepción propia, el middleware de control de excepciones vuelve a producir la excepción original.

En el ejemplo siguiente, UseExceptionHandler agrega middleware de control de excepciones en entornos que no son de desarrollo:

if (env.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

La plantilla de aplicación de Razor Pages proporciona una página de error (.cshtml) y una clase PageModel (ErrorModel) en la carpeta Pages. Para una aplicación de MVC, la plantilla de proyecto incluye un método de acción de Error y una vista del error para el controlador de Home.

El middleware de control de excepciones vuelve a ejecutar la solicitud mediante el método HTTP original. Si un punto de conexión de controlador de errores está restringido a un conjunto específico de métodos HTTP, solo se ejecuta para esos métodos HTTP. Por ejemplo, una acción de controlador de MVC que usa el atributo [HttpGet] solo se ejecuta para solicitudes GET. Para asegurarse de que todas las solicitudes lleguen a la página de control de errores personalizada, no las restrinja a un conjunto específico de métodos HTTP.

Para controlar las excepciones de manera diferente en función del método HTTP original:

  • En el caso de Razor Pages, cree varios métodos de control. Por ejemplo, use OnGet para controlar las excepciones GET y use OnPost para controlar las excepciones POST.
  • Para MVC, aplique los atributos de verbo HTTP a varias acciones. Por ejemplo, use [HttpGet] para controlar las excepciones GET y use [HttpPost] para controlar las excepciones POST.

Para permitir que los usuarios no autenticados vean la página de control de errores personalizada, asegúrese de que admite el acceso anónimo.

Acceso a la excepción

Use IExceptionHandlerPathFeature para acceder a la ruta de acceso de la solicitud original y a la excepción en un controlador de errores. El código siguiente agrega ExceptionMessage al archivo Pages/Error.cshtml.cs predeterminado que se genera con las plantillas de ASP.NET Core:

[ResponseCache(Duration=0, Location=ResponseCacheLocation.None, NoStore=true)]
[IgnoreAntiforgeryToken]
public class ErrorModel : PageModel
{
    public string RequestId { get; set; }
    public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
    public string ExceptionMessage { get; set; }
    private readonly ILogger<ErrorModel> _logger;

    public ErrorModel(ILogger<ErrorModel> logger)
    {
        _logger = logger;
    }

    public void OnGet()
    {
        RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;

        var exceptionHandlerPathFeature =
        HttpContext.Features.Get<IExceptionHandlerPathFeature>();
        if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
        {
            ExceptionMessage = "File error thrown";
            _logger.LogError(ExceptionMessage);
        }
        if (exceptionHandlerPathFeature?.Path == "/index")
        {
            ExceptionMessage += " from home page";
        }
    }
}

Advertencia

No proporcione información de errores confidencial a los clientes. Proporcionar información de los errores es un riesgo para la seguridad.

Para probar la excepción en la aplicación de ejemplo:

  • Establezca el entorno en producción.
  • Quite los comentarios de webBuilder.UseStartup<Startup>(); en Program.cs.
  • Seleccione Trigger an exception (Desencadenar una excepción) en la página principal.

Lambda del controlador de excepciones

Una alternativa a una página del controlador de excepciones personalizada es proporcionar una expresión lambda en UseExceptionHandler. Usar una expresión lambda permite acceder al error antes de devolver la respuesta.

En el código siguiente se usa una expresión lambda para el control de excepciones:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler(errorApp =>
        {
            errorApp.Run(async context =>
            {
                context.Response.StatusCode = (int) HttpStatusCode.InternalServerError;;
                context.Response.ContentType = "text/html";

                await context.Response.WriteAsync("<html lang=\"en\"><body>\r\n");
                await context.Response.WriteAsync("ERROR!<br><br>\r\n");

                var exceptionHandlerPathFeature =
                    context.Features.Get<IExceptionHandlerPathFeature>();

                if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
                {
                    await context.Response.WriteAsync(
                                              "File error thrown!<br><br>\r\n");
                }

                await context.Response.WriteAsync(
                                              "<a href=\"/\">Home</a><br>\r\n");
                await context.Response.WriteAsync("</body></html>\r\n");
                await context.Response.WriteAsync(new string(' ', 512)); 
            });
        });
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

Advertencia

No proporcione información de errores confidencial de IExceptionHandlerFeature o IExceptionHandlerPathFeature a los clientes. Proporcionar información de los errores es un riesgo para la seguridad.

Para probar la expresión lambda de control de excepciones en la aplicación de ejemplo:

  • Establezca el entorno en producción.
  • Quite los comentarios de webBuilder.UseStartup<StartupLambda>(); en Program.cs.
  • Seleccione Trigger an exception (Desencadenar una excepción) en la página principal.

UseStatusCodePages

Una aplicación ASP.NET Core no proporciona de forma predeterminada ninguna página de códigos de estado para los códigos de estado HTTP, como 404 - No encontrado. Cuando la aplicación establece un código de estado de error HTTP 400-599 que no tiene un cuerpo, devuelve el código de estado y un cuerpo de respuesta vacío. Para proporcionar páginas de códigos de estado, use el middleware de páginas de códigos de estado. Para habilitar los controladores de solo texto predeterminados para los códigos de estado de error comunes, llame a UseStatusCodePages en el método Startup.Configure:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseStatusCodePages();

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

Llame a UseStatusCodePages antes del middleware de control de solicitudes. Por ejemplo, llame a UseStatusCodePages antes del middleware de archivos estáticos y el middleware de puntos de conexión.

Cuando no se usa UseStatusCodePages, al navegar a una dirección URL sin punto de conexión se devuelve un mensaje de error dependiente del explorador que indica que no se encuentra el punto de conexión. Por ejemplo, al navegar a Home/Privacy2. Cuando se llama a UseStatusCodePages, el explorador devuelve:

Status Code: 404; Not Found

UseStatusCodePages no se utiliza normalmente en producción, ya que devuelve un mensaje que no es útil para los usuarios.

Para probar UseStatusCodePages en la aplicación de ejemplo:

  • Establezca el entorno en producción.
  • Quite los comentarios de webBuilder.UseStartup<StartupUseStatusCodePages>(); en Program.cs.
  • Seleccione los vínculos en la página principal.

Nota:

El middleware de páginas de códigos de estado no detecta excepciones. Para proporcionar una página de control de errores personalizada, use la página del controlador de excepciones.

UseStatusCodePages con cadena de formato

Para personalizar el texto y el tipo de contenido de la respuesta, use la sobrecarga de UseStatusCodePages que adopta una cadena de tipo de contenido y formato:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseStatusCodePages(
        "text/plain", "Status code page, status code: {0}");

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

En el código anterior, {0} es un marcador de posición para el código de error.

UseStatusCodePages con una cadena de formato no se utiliza normalmente en producción, ya que devuelve un mensaje que no es útil para los usuarios.

Para probar UseStatusCodePages en la aplicación de ejemplo, quite los comentarios de webBuilder.UseStartup<StartupFormat>(); en Program.cs.

UseStatusCodePages con una expresión lambda

Para especificar el código de escritura de respuesta y control de errores personalizado, use la sobrecarga de UseStatusCodePages que adopta una expresión lambda:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseStatusCodePages(async context =>
    {
        context.HttpContext.Response.ContentType = "text/plain";

        await context.HttpContext.Response.WriteAsync(
            "Status code page, status code: " +
            context.HttpContext.Response.StatusCode);
    });

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

UseStatusCodePages con una expresión lambda no se utiliza normalmente en producción, ya que devuelve un mensaje que no es útil para los usuarios.

Para probar UseStatusCodePages en la aplicación de ejemplo, quite los comentarios de webBuilder.UseStartup<StartupStatusLambda>(); en Program.cs.

UseStatusCodePagesWithRedirects

Método de extensión UseStatusCodePagesWithRedirects:

  • Envía un código de estado 302 - Encontrado al cliente.
  • Redirige el cliente al punto de conexión de control de errores proporcionado en la plantilla de dirección URL. El punto de conexión de control de errores suele mostrar información de error y devuelve HTTP 200.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseStatusCodePagesWithRedirects("/MyStatusCode?code={0}");

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

La plantilla de dirección URL puede incluir un marcador de posición {0} para el código de estado, tal y como se muestra en el código anterior. Si la plantilla de dirección URL comienza con ~ (tilde), la ~ se sustituye por el elemento PathBase de la aplicación. Si especifica un punto de conexión en la aplicación, cree una vista de MVC o una página de Razor para ese punto de conexión. Para obtener un ejemplo de Razor Pages, consulte Pages/MyStatusCode.cshtml en la aplicación de ejemplo.

Este método se usa normalmente cuando la aplicación:

  • Debe redirigir al cliente a un punto de conexión diferente, normalmente en casos en los que una aplicación diferente procesa el error. En el caso de aplicaciones web, la barra de direcciones del explorador del cliente refleja el punto de conexión redirigido.
  • No debe conservar ni devolver el código de estado original con la respuesta de redirección inicial.

Para probar UseStatusCodePages en la aplicación de ejemplo, quite los comentarios de webBuilder.UseStartup<StartupSCredirect>(); en Program.cs.

UseStatusCodePagesWithReExecute

Método de extensión UseStatusCodePagesWithReExecute:

  • Devuelve el código de estado original al cliente.
  • Genera el cuerpo de respuesta, para lo cual vuelve a ejecutar la canalización de solicitud mediante una ruta de acceso alternativa.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseStatusCodePagesWithReExecute("/MyStatusCode2", "?code={0}");

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

Si se especifica un punto de conexión en la aplicación, cree una vista de MVC o una página de Razor para ese punto de conexión. Asegúrese de que UseStatusCodePagesWithReExecute se coloca antes de UseRouting para que la solicitud se pueda volver a enrutar a la página de estado. Para ver un ejemplo de Razor Pages, consulte Pages/MyStatusCode2.cshtml en la aplicación de ejemplo.

Este método se usa normalmente cuando la aplicación debe:

  • Procesar la solicitud sin redirigirla a un punto de conexión diferente. En el caso de aplicaciones web, la barra de direcciones del explorador del cliente refleja el punto de conexión solicitado originalmente.
  • Conservar y devolver el código de estado original con la respuesta.

Las plantillas de dirección URL y cadena de consulta pueden incluir un marcador de posición {0} relativo al código de estado. La plantilla de dirección URL debe empezar con /.

El punto de conexión que procesa el error puede obtener la dirección URL original que generó el error, como se muestra en el ejemplo siguiente:

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public class MyStatusCode2Model : PageModel
{
    public string RequestId { get; set; }
    public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);

    public string ErrorStatusCode { get; set; }

    public string OriginalURL { get; set; }
    public bool ShowOriginalURL => !string.IsNullOrEmpty(OriginalURL);

    public void OnGet(string code)
    {
        RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
        ErrorStatusCode = code;

        var statusCodeReExecuteFeature = HttpContext.Features.Get<
                                               IStatusCodeReExecuteFeature>();
        if (statusCodeReExecuteFeature != null)
        {
            OriginalURL =
                statusCodeReExecuteFeature.OriginalPathBase
                + statusCodeReExecuteFeature.OriginalPath
                + statusCodeReExecuteFeature.OriginalQueryString;
        }
    }
}

Para ver un ejemplo de Razor Pages, consulte Pages/MyStatusCode2.cshtml en la aplicación de ejemplo.

Para probar UseStatusCodePages en la aplicación de ejemplo, quite los comentarios de webBuilder.UseStartup<StartupSCreX>(); en Program.cs.

Deshabilitar las páginas de códigos de estado

Para deshabilitar las páginas de códigos de estado de un método de acción o controlador MVC, use el atributo [SkipStatusCodePages].

Para deshabilitar las páginas de códigos de estado de solicitudes específicas de un método de controlador de Razor Pages o un controlador MVC, use IStatusCodePagesFeature:

public void OnGet()
{
    // using Microsoft.AspNetCore.Diagnostics;
    var statusCodePagesFeature = HttpContext.Features.Get<IStatusCodePagesFeature>();

    if (statusCodePagesFeature != null)
    {
        statusCodePagesFeature.Enabled = false;
    }
}

Código de control de excepciones

El código de las páginas de control de excepciones también puede iniciar excepciones. Las páginas de errores de producción se deben probar minuciosamente y se debe tener especial cuidado para evitar que inicien sus propias excepciones.

Encabezados de respuesta

Una vez enviados los encabezados de una respuesta:

  • La aplicación no puede cambiar el código de estado de la respuesta.
  • No se pueden ejecutar páginas o controladores de excepciones. Deberá completarse la respuesta o anularse la conexión.

Control de excepciones del servidor

Además de la lógica de control de excepciones de la aplicación, la implementación del servidor HTTP puede controlar algunas excepciones. Si el servidor almacena en caché una excepción antes de que se envíen los encabezados de respuesta, envía una respuesta 500 - Internal Server Error sin cuerpo. Si el servidor almacena en caché una excepción después de que se envían los encabezados de respuesta, cierra la conexión. El servidor controla las solicitudes que no controla la aplicación. El control de excepciones del servidor controla cualquier excepción que se produzca cuando el servidor controle la solicitud. Las páginas de error personalizadas, el middleware de control de excepciones y los filtros de la aplicación no afectan este comportamiento.

Control de excepciones de inicio

Solo el nivel de hospedaje puede controlar las excepciones que tienen lugar durante el inicio de la aplicación. El host puede configurarse para capturar errores de inicio y capturar errores detallados.

La capa de hospedaje puede mostrar una página de error para un error de inicio capturado solo si este se produce después del enlace de puerto/dirección del host. Si se produce un error de enlace:

  • La capa de hospedaje registra una excepción crítica.
  • El proceso de dotnet se bloquea.
  • No se muestra ninguna página de error si el servidor HTTP es Kestrel.

Si se ejecuta en IIS, en Azure App Service o en IIS Express, el módulo ASP.NET Core devuelve un error de proceso 502.5 si el proceso no se puede iniciar. Para más información, consulte Solución de problemas de ASP.NET Core en Azure App Service e IIS.

Página de error de la base de datos

El filtro de excepciones AddDatabaseDeveloperPageExceptionFilter de la página del desarrollador de bases de datos captura las excepciones relacionadas con la base de datos que se pueden resolver mediante migraciones de Entity Framework Core. Cuando se producen estas excepciones, se genera una respuesta HTML con los detalles de las acciones posibles para resolver el problema. Esta página debe habilitarse solo en el entorno de desarrollo. El siguiente código fue generado por las plantillas de Razor Pages de ASP.NET Core cuando se especificaron cuentas de usuario individuales:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(
            Configuration.GetConnectionString("DefaultConnection")));
    services.AddDatabaseDeveloperPageExceptionFilter();
    services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
        .AddEntityFrameworkStores<ApplicationDbContext>();
    services.AddRazorPages();
}

Filtros de excepciones

En las aplicaciones de MVC, los filtros de excepciones se pueden configurar globalmente, o bien por controlador o por acción. En las aplicaciones de Razor Pages, se pueden configurar a nivel global o por modelo de página. Estos filtros controlan todas las excepciones no controladas que se hayan producido durante la ejecución de una acción de controlador o de otro filtro. Para más información, consulte Filtros en ASP.NET Core.

Los filtros de excepciones son útiles para interceptar las excepciones que se producen en las acciones de MVC, pero no son tan flexibles como el middleware de control de excepciones integrado, UseExceptionHandler. Se recomienda usar UseExceptionHandler, a no ser que tenga que realizar el control de errores de otra forma según la acción de MVC elegida.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
    });
}

Errores de estado del modelo

Para obtener información sobre cómo controlar los errores de estado de los modelos, vea Enlace de modelos y Validación de modelos.

Recursos adicionales

De Tom Dykstra y Steve Smith

Este artículo trata sobre los métodos comunes para controlar errores en aplicaciones web ASP.NET Core. Consulte Control de errores en API web de ASP.NET Core. para las API web.

Vea o descargue el código de ejemplo. (Cómo descargarlo).

Página de excepciones para el desarrollador

En la Página de excepciones para el desarrollador se muestra información detallada sobre las excepciones de la solicitud. Las plantillas de ASP.NET Core generan el siguiente código:

if (env.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

El código anterior habilita la página de excepciones para el desarrollador cuando la aplicación se ejecuta en el entorno de desarrollo.

Las plantillas colocan UseDeveloperExceptionPage antes que cualquier middleware para que las excepciones se detecten en el middleware que sigue.

El código anterior solo habilita la página de excepciones para el desarrollador cuando la aplicación se ejecuta en el entorno de desarrollo. La información detallada de la excepción no debe mostrarse públicamente cuando la aplicación se ejecuta en producción. Para más información sobre la configuración de los entornos, consulte Uso de varios entornos en ASP.NET Core.

La página de excepciones para el desarrollador incluye la siguiente información sobre la excepción y la solicitud:

  • Seguimiento de la pila
  • Parámetros de cadena de consulta (si existen)
  • Cookie (si existen)
  • encabezados

Página del controlador de excepciones

Para configurar una página de control de errores personalizada para el entorno de producción, use el middleware de control de excepciones. El middleware:

  • Captura y registra las excepciones.
  • Vuelve a ejecutar la solicitud en una canalización alternativa correspondiente a la página o el controlador indicados. La solicitud no se vuelve a ejecutar si se ha iniciado la respuesta. El código generado por la plantilla vuelve a ejecutar la solicitud para /Error.

En el ejemplo siguiente, UseExceptionHandler agrega middleware de control de excepciones en entornos que no son de desarrollo:

if (env.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

La plantilla de aplicación de Razor Pages proporciona una página de error ( .cshtml) y una clase PageModel (ErrorModel) en la carpeta Pages. Para una aplicación de MVC, la plantilla de proyecto incluye un método de acción para el error y una vista del error en el controlador de Home.

No marque el método de acción del controlador de errores con atributos de método HTTP, como HttpGet. Los verbos explícitos impiden que algunas solicitudes lleguen al método. Permita el acceso anónimo al método si los usuarios no autenticados deben recibir la vista del error.

Acceso a la excepción

Use IExceptionHandlerPathFeature para acceder a la ruta de acceso a la solicitud original y a la excepción en una página o un controlador de errores:

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public class ErrorModel : PageModel
{
    public string RequestId { get; set; }
    public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
    public string ExceptionMessage { get; set; }

    public void OnGet()
    {
        RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;

        var exceptionHandlerPathFeature =
            HttpContext.Features.Get<IExceptionHandlerPathFeature>();
        if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
        {
            ExceptionMessage = "File error thrown";
        }
        if (exceptionHandlerPathFeature?.Path == "/index")
        {
            ExceptionMessage += " from home page";
        }
    }
}

Advertencia

No proporcione información de errores confidencial a los clientes. Proporcionar información de los errores es un riesgo para la seguridad.

Para desencadenar la página de control de excepciones anterior, establezca el entorno en producciones y fuerce una excepción.

Lambda del controlador de excepciones

Una alternativa a una página del controlador de excepciones personalizada es proporcionar una expresión lambda en UseExceptionHandler. Usar una expresión lambda permite acceder al error antes de devolver la respuesta.

Este es un ejemplo del uso de una expresión lambda para el control de excepciones:

if (env.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
   app.UseExceptionHandler(errorApp =>
   {
        errorApp.Run(async context =>
        {
            context.Response.StatusCode = (int) HttpStatusCode.InternalServerError;
            context.Response.ContentType = "text/html";

            await context.Response.WriteAsync("<html lang=\"en\"><body>\r\n");
            await context.Response.WriteAsync("ERROR!<br><br>\r\n");

            var exceptionHandlerPathFeature = 
                context.Features.Get<IExceptionHandlerPathFeature>();

            if (exceptionHandlerPathFeature?.Error is FileNotFoundException)
            {
                await context.Response.WriteAsync("File error thrown!<br><br>\r\n");
            }

            await context.Response.WriteAsync("<a href=\"/\">Home</a><br>\r\n");
            await context.Response.WriteAsync("</body></html>\r\n");
            await context.Response.WriteAsync(new string(' ', 512)); // IE padding
        });
    });
    app.UseHsts();
}

En el código anterior, se agrega await context.Response.WriteAsync(new string(' ', 512)); para que el explorador Internet Explorer muestre el mensaje de error en lugar de un mensaje de error de IE. Para más información, consulte este problema de GitHub.

Advertencia

No proporcione información de errores confidencial de IExceptionHandlerFeature o IExceptionHandlerPathFeature a los clientes. Proporcionar información de los errores es un riesgo para la seguridad.

Para ver el resultado de la expresión lambda de control de excepciones en la aplicación de ejemplo, use las directivas de preprocesador ProdEnvironment y ErrorHandlerLambda y, después, seleccione Trigger an exception (Desencadenar una excepción) en la página principal.

UseStatusCodePages

Una aplicación ASP.NET Core no proporciona de forma predeterminada una página de códigos de estado para los códigos de estado HTTP, como 404 - No encontrado. La aplicación devuelve un código de estado y un cuerpo de respuesta vacío. Para proporcionar páginas de códigos de estado, use el middleware de páginas de códigos de estado.

El middleware está disponible en el paquete Microsoft.AspNetCore.Diagnostics.

Para habilitar los controladores de solo texto predeterminados para los códigos de estado de error comunes, llame a UseStatusCodePages en el método Startup.Configure:

app.UseStatusCodePages();

Llame a UseStatusCodePages antes del middleware de control de solicitudes (por ejemplo, middleware de archivos estáticos y middleware de MVC).

Cuando no se usa UseStatusCodePages, al navegar a una dirección URL sin punto de conexión se devuelve un mensaje de error dependiente del explorador que indica que no se encuentra el punto de conexión. Por ejemplo, al navegar a Home/Privacy2. Cuando se llama a UseStatusCodePages, el explorador devuelve:

Status Code: 404; Not Found

UseStatusCodePages con cadena de formato

Para personalizar el texto y el tipo de contenido de la respuesta, use la sobrecarga de UseStatusCodePages que adopta una cadena de tipo de contenido y formato:

app.UseStatusCodePages(
    "text/plain", "Status code page, status code: {0}");

UseStatusCodePages con una expresión lambda

Para especificar el código de escritura de respuesta y control de errores personalizado, use la sobrecarga de UseStatusCodePages que adopta una expresión lambda:

app.UseStatusCodePages(async context =>
{
    context.HttpContext.Response.ContentType = "text/plain";

    await context.HttpContext.Response.WriteAsync(
        "Status code page, status code: " + 
        context.HttpContext.Response.StatusCode);
});

UseStatusCodePagesWithRedirects

Método de extensión UseStatusCodePagesWithRedirects:

  • Envía un código de estado 302 - Encontrado al cliente.
  • Redirige al cliente a la ubicación proporcionada en la plantilla de dirección URL.
app.UseStatusCodePagesWithRedirects("/StatusCode?code={0}");

La plantilla de dirección URL puede incluir un marcador de posición {0} para el código de estado, como se muestra en el ejemplo. Si la plantilla de dirección URL comienza con ~ (tilde), la ~ se sustituye por el elemento PathBase de la aplicación. Si apunta a un punto de conexión de dentro de la aplicación, cree una vista de MVC o una página de Razor para ese punto de conexión. Para obtener un ejemplo de Razor Pages, consulte Pages/StatusCode.cshtml en la aplicación de ejemplo.

Este método se usa normalmente cuando la aplicación:

  • Debe redirigir al cliente a un punto de conexión diferente, normalmente en casos en los que una aplicación diferente procesa el error. En el caso de aplicaciones web, la barra de direcciones del explorador del cliente refleja el punto de conexión redirigido.
  • No debe conservar ni devolver el código de estado original con la respuesta de redirección inicial.

UseStatusCodePagesWithReExecute

Método de extensión UseStatusCodePagesWithReExecute:

  • Devuelve el código de estado original al cliente.
  • Genera el cuerpo de respuesta, para lo cual vuelve a ejecutar la canalización de solicitud mediante una ruta de acceso alternativa.
app.UseStatusCodePagesWithReExecute("/StatusCode","?code={0}");

Si apunta a un punto de conexión de dentro de la aplicación, cree una vista de MVC o una página de Razor para ese punto de conexión. Asegúrese de que UseStatusCodePagesWithReExecute se coloca antes de UseRouting para que la solicitud se pueda volver a enrutar a la página de estado. Para ver un ejemplo de Razor Pages, consulte Pages/StatusCode.cshtml en la aplicación de ejemplo.

Este método se usa normalmente cuando la aplicación debe:

  • Procesar la solicitud sin redirigirla a un punto de conexión diferente. En el caso de aplicaciones web, la barra de direcciones del explorador del cliente refleja el punto de conexión solicitado originalmente.
  • Conservar y devolver el código de estado original con la respuesta.

Las plantillas de dirección URL y cadena de consulta pueden incluir un marcador de posición ({0}) relativo al código de estado. La plantilla de dirección URL debe empezar con una barra diagonal (/). Cuando se use un marcador de posición en la ruta de acceso, confirme que el punto de conexión (página o controlador) puede procesar el segmento de línea. Por ejemplo, una página de Razor de errores debe aceptar el valor de segmento de línea opcional con la directiva @page:

@page "{code?}"

El punto de conexión que procesa el error puede obtener la dirección URL original que generó el error, como se muestra en el ejemplo siguiente:

var statusCodeReExecuteFeature = HttpContext.Features.Get<IStatusCodeReExecuteFeature>();
if (statusCodeReExecuteFeature != null)
{
    OriginalURL =
        statusCodeReExecuteFeature.OriginalPathBase
        + statusCodeReExecuteFeature.OriginalPath
        + statusCodeReExecuteFeature.OriginalQueryString;
}

No marque el método de acción del controlador de errores con atributos de método HTTP, como HttpGet. Los verbos explícitos impiden que algunas solicitudes lleguen al método. Permita el acceso anónimo al método si los usuarios no autenticados deben recibir la vista del error.

Deshabilitar las páginas de códigos de estado

Para deshabilitar las páginas de códigos de estado de un método de acción o controlador MVC, use el atributo [SkipStatusCodePages].

Para deshabilitar las páginas de códigos de estado de solicitudes específicas de un método de controlador de Razor Pages o un controlador MVC, use IStatusCodePagesFeature:

var statusCodePagesFeature = HttpContext.Features.Get<IStatusCodePagesFeature>();

if (statusCodePagesFeature != null)
{
    statusCodePagesFeature.Enabled = false;
}

Código de control de excepciones

El código de las páginas de control de excepciones puede producir excepciones. Es recomendable que las páginas de errores de producción incluyan únicamente contenido estático.

Encabezados de respuesta

Una vez enviados los encabezados de una respuesta:

  • La aplicación no puede cambiar el código de estado de la respuesta.
  • No se pueden ejecutar páginas o controladores de excepciones. Deberá completarse la respuesta o anularse la conexión.

Control de excepciones del servidor

Además de la lógica de control de excepciones de la aplicación, la implementación del servidor HTTP puede controlar algunas excepciones. Si el servidor almacena en caché una excepción antes de que se envíen los encabezados de respuesta, envía una respuesta 500 - Error interno del servidor sin cuerpo. Si el servidor almacena en caché una excepción después de que se envían los encabezados de respuesta, cierra la conexión. El servidor controla las solicitudes que no controla la aplicación. El control de excepciones del servidor controla cualquier excepción que se produzca cuando el servidor controle la solicitud. Las páginas de error personalizadas, el middleware de control de excepciones y los filtros de la aplicación no afectan este comportamiento.

Control de excepciones de inicio

Solo el nivel de hospedaje puede controlar las excepciones que tienen lugar durante el inicio de la aplicación. El host puede configurarse para capturar errores de inicio y capturar errores detallados.

La capa de hospedaje puede mostrar una página de error para un error de inicio capturado solo si este se produce después del enlace de puerto/dirección del host. Si se produce un error de enlace:

  • La capa de hospedaje registra una excepción crítica.
  • El proceso de dotnet se bloquea.
  • No se muestra ninguna página de error si el servidor HTTP es Kestrel.

Si se ejecuta en IIS, en Azure App Service o en IIS Express, el módulo ASP.NET Core devuelve un error de proceso 502.5 si el proceso no se puede iniciar. Para más información, consulte Solución de problemas de ASP.NET Core en Azure App Service e IIS.

Página de error de la base de datos

El middleware de la página de error de la base de datos captura excepciones relacionadas con la base de datos que se pueden resolver mediante migraciones de Entity Framework. Cuando se producen estas excepciones, se genera una respuesta HTML con los detalles de las acciones posibles para resolver el problema. Esta página debe habilitarse solo en el entorno de desarrollo. Habilitar la página mediante la adición de código a Startup.Configure:

if (env.IsDevelopment())
{
    app.UseDatabaseErrorPage();
}

UseDatabaseErrorPage requiere el paquete NuGet Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.

Filtros de excepciones

En las aplicaciones de MVC, los filtros de excepciones se pueden configurar globalmente, o bien por controlador o por acción. En las aplicaciones de Razor Pages, se pueden configurar a nivel global o por modelo de página. Estos filtros controlan todas las excepciones no controladas que se hayan producido durante la ejecución de una acción de controlador o de otro filtro. Para más información, consulte Filtros en ASP.NET Core.

Sugerencia

Los filtros de excepciones son útiles para interceptar las excepciones que se producen en las acciones de MVC, pero no son tan flexibles como el middleware de control de excepciones. Se recomienda usar el middleware. Use filtros únicamente cuando deba realizar el control de errores de manera diferente según la acción de MVC elegida.

Errores de estado del modelo

Para obtener información sobre cómo controlar los errores de estado de los modelos, vea Enlace de modelos y Validación de modelos.

Recursos adicionales