Cómo hacer que las páginas de error personalizadas funcionen en ASP.NET MVC 4


247

Quiero que se muestre una página de error personalizada para 500, 404 y 403. Esto es lo que he hecho:

  1. Se habilitaron errores personalizados en web.config de la siguiente manera:

    <customErrors mode="On" 
                  defaultRedirect="~/Views/Shared/Error.cshtml">
    
        <error statusCode="403" 
               redirect="~/Views/Shared/UnauthorizedAccess.cshtml" />
    
        <error statusCode="404" 
               redirect="~/Views/Shared/FileNotFound.cshtml" />
    
    </customErrors>
    
  2. Registrado HandleErrorAttributecomo un filtro de acción global en la FilterConfigclase de la siguiente manera:

    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new CustomHandleErrorAttribute());
        filters.Add(new AuthorizeAttribute());
    }
    
  3. Creó una página de error personalizada para cada uno de los mensajes anteriores. El predeterminado para 500 ya estaba disponible de fábrica.

  4. En cada vista de página de error personalizada se declara que el modelo de la página es System.Web.Mvc.HandleErrorInfo

Para 500, muestra la página de error personalizada. Para otros, no lo hace.

¿Hay algo que este olvidando?

Parece que esto no es todo lo que hay para mostrar errores personalizados mientras leo el código en el OnExceptionmétodo de la HandleErrorAttributeclase y solo maneja 500.

¿Qué debo hacer para manejar otros errores?


21
Lo extraño de esta configuración es que redirige a las vistas, no a las acciones del controlador. ¿Quién se supone que representa esas vistas y pasa un modelo, por ejemplo? Sólo de pensar.
Oliver

2
La mayoría de las respuestas aquí no manejan todos los casos o hacen que el servidor web responda de manera "incorrecta", es decir, redirige a una página de error en lugar de devolver una respuesta de error. Si le importa que el servidor responda de la manera esperada de los servidores web, aquí encontrará un artículo bastante detallado al respecto: benfoster.io/blog/aspnet-mvc-custom-error-pages . Tenga en cuenta que no es tan sencillo como las respuestas aquí, así que si desea una respuesta fácil, use una de las siguientes.
Rdans

1
Aquí hay otro gran artículo sobre varias técnicas para el manejo de errores asp.net dusted.codes/…
Godsayah

Respuestas:


352

Mi configuración actual (en MVC3, pero creo que todavía se aplica) se basa en tener una ErrorController, así que uso:

<system.web>
    <customErrors mode="On" defaultRedirect="~/Error">
      <error redirect="~/Error/NotFound" statusCode="404" />
    </customErrors>
</system.web>

Y el controlador contiene lo siguiente:

public class ErrorController : Controller
{
    public ViewResult Index()
    {
        return View("Error");
    }
    public ViewResult NotFound()
    {
        Response.StatusCode = 404;  //you may want to set this to 200
        return View("NotFound");
    }
}

Y las vistas tal como las implementas. Sin embargo, tiendo a agregar un poco de lógica, para mostrar el seguimiento de la pila y la información de error si la aplicación está en modo de depuración. Entonces Error.cshtml se parece a esto:

@model System.Web.Mvc.HandleErrorInfo
@{
    Layout = "_Layout.cshtml";
    ViewBag.Title = "Error";
}
<div class="list-header clearfix">
    <span>Error</span>
</div>
<div class="list-sfs-holder">
    <div class="alert alert-error">
        An unexpected error has occurred. Please contact the system administrator.
    </div>
    @if (Model != null && HttpContext.Current.IsDebuggingEnabled)
    {
        <div>
            <p>
                <b>Exception:</b> @Model.Exception.Message<br />
                <b>Controller:</b> @Model.ControllerName<br />
                <b>Action:</b> @Model.ActionName
            </p>
            <div style="overflow:scroll">
                <pre>
                    @Model.Exception.StackTrace
                </pre>
            </div>
        </div>
    }
</div>

77
¿Tuviste que poner algo en tu Application_Error en tu Global.asax para este Pablo?
Alicia

12
El código en el controlador no parece ejecutarse desde mi experiencia. MVC4: lanzar una excepción System.Exception en un controlador diferente hará que el archivo Error.cshtml se procese, pero no a través del ErrorController. ¿Alguien más está experimentando esto?
Nilzor

53
Para cualquier otra persona que lo haya encontrado útil, pero necesitara más contexto; La etiqueta <customErrors> va dentro de <system.web> en web.config.
gooberverse

77
Actualización para otros : aparentemente mi problema estaba ocurriendo porque tenía redirectMode="ResponseRewrite"en el CustomerErrorselemento
KyleMit

42
Por favor, por el amor de Dios, ignore el comentario que dice //you may want to set this to 200en el código. ¡NO HAGAS ESO!
Demencia

40

He hecho la solución de pablo y siempre tuve el error (MVC4)

La vista 'Error' o su maestro no se encontró o ningún motor de vista admite la ubicación buscada.

Para deshacerse de esto, quite la línea

 filters.Add(new HandleErrorAttribute());

en FilterConfig.cs


Miré a todas partes para resolver esto. Esto finalmente tuvo la respuesta. Sabía por qué lo estaba haciendo, pero por mi culpa no podía, sin pensar drásticamente en lo que otras personas han dicho. Me imagino que comparto el dolor de 360Airwalk cuando digo gracias por señalar esto. ¡Leyenda!
Adam

Esta es una opción y el controlador de error funciona bien. Pero parece que cuando registra filtros en FilterConfig.cs, busca Error.cshtml en las carpetas de vista de los controladores compartidos y originales. Cuando cambia el Error.cshtml a algo distinto a eso, nuestro ErrorController personalizado funciona. Pero hay un lugar donde puede agregar este registro y es global.asax.cs. Si agrega la línea mencionada en la función RegisterGlobalFilters (filtros GlobalFilterCollection) en global.asax.cs y la elimina de FilterConfig.cs, funciona.
isaolmez

Creo que está relacionado con el orden de los registros de filtro. Mantenga el controlador de errores y mueva el registro del filtro a global.asax.cs. public static void RegisterGlobalFilters (filtros GlobalFilterCollection) {filters.Add (new HandleErrorAttribute ()); }
isaolmez

24

Hago algo que requiere menos codificación que las otras soluciones publicadas.

Primero, en mi web.config, tengo lo siguiente:

<customErrors mode="On" defaultRedirect="~/ErrorPage/Oops">
   <error redirect="~/ErrorPage/Oops/404" statusCode="404" />
   <error redirect="~/ErrorPage/Oops/500" statusCode="500" />
</customErrors>

Y el controlador (/Controllers/ErrorPageController.cs) contiene lo siguiente:

public class ErrorPageController : Controller
{
    public ActionResult Oops(int id)
    {
        Response.StatusCode = id;

        return View();
    }
}

Y finalmente, la vista contiene lo siguiente (despojado por simplicidad, pero puede contactarse:

@{ ViewBag.Title = "Oops! Error Encountered"; }

<section id="Page">
  <div class="col-xs-12 well">
    <table cellspacing="5" cellpadding="3" style="background-color:#fff;width:100%;" class="table-responsive">
      <tbody>
        <tr>
          <td valign="top" align="left" id="tableProps">
            <img width="25" height="33" src="~/Images/PageError.gif" id="pagerrorImg">
          </td>
          <td width="360" valign="middle" align="left" id="tableProps2">
            <h1 style="COLOR: black; FONT: 13pt/15pt verdana" id="errortype"><span id="errorText">@Response.Status</span></h1>
          </td>
        </tr>
        <tr>
          <td width="400" colspan="2" id="tablePropsWidth"><font style="COLOR: black; FONT: 8pt/11pt verdana">Possible causes:</font>
          </td>
        </tr>
        <tr>
          <td width="400" colspan="2" id="tablePropsWidth2">
            <font style="COLOR: black; FONT: 8pt/11pt verdana" id="LID1">
                            <hr>
                            <ul>
                                <li id="list1">
                                    <span class="infotext">
                                        <strong>Baptist explanation: </strong>There
                                        must be sin in your life. Everyone else opened it fine.<br>
                                    </span>
                                </li>
                                <li>
                                    <span class="infotext">
                                        <strong>Presbyterian explanation: </strong>It's
                                        not God's will for you to open this link.<br>
                                    </span>
                                </li>
                                <li>
                                    <span class="infotext">
                                        <strong> Word of Faith explanation:</strong>
                                        You lack the faith to open this link. Your negative words have prevented
                                        you from realizing this link's fulfillment.<br>
                                    </span>
                                </li>
                                <li>
                                    <span class="infotext">
                                        <strong>Charismatic explanation: </strong>Thou
                                        art loosed! Be commanded to OPEN!<br>
                                    </span>
                                </li>
                                <li>
                                    <span class="infotext">
                                        <strong>Unitarian explanation:</strong> All
                                        links are equal, so if this link doesn't work for you, feel free to
                                        experiment with other links that might bring you joy and fulfillment.<br>
                                    </span>
                                </li>
                                <li>
                                    <span class="infotext">
                                        <strong>Buddhist explanation:</strong> .........................<br>
                                    </span>
                                </li>
                                <li>
                                    <span class="infotext">
                                        <strong>Episcopalian explanation:</strong>
                                        Are you saying you have something against homosexuals?<br>
                                    </span>
                                </li>
                                <li>
                                    <span class="infotext">
                                        <strong>Christian Science explanation: </strong>There
                                        really is no link.<br>
                                    </span>
                                </li>
                                <li>
                                    <span class="infotext">
                                        <strong>Atheist explanation: </strong>The only
                                        reason you think this link exists is because you needed to invent it.<br>
                                    </span>
                                </li>
                                <li>
                                    <span class="infotext">
                                        <strong>Church counselor's explanation:</strong>
                                        And what did you feel when the link would not open?
                                    </span>
                                </li>
                            </ul>
                            <p>
                                <br>
                            </p>
                            <h2 style="font:8pt/11pt verdana; color:black" id="ietext">
                                <img width="16" height="16" align="top" src="~/Images/Search.gif">
                                HTTP @Response.StatusCode - @Response.StatusDescription <br>
                            </h2>
                        </font>
          </td>
        </tr>
      </tbody>
    </table>
  </div>
</section>

Es tan simple como eso. Podría extenderse fácilmente para ofrecer información de error más detallada, pero ELMAH maneja eso para mí y el statusCode & statusDescription es todo lo que generalmente necesito.


Creo que la redirección en el archivo .config de "~ / ErrorPage / Oops / 404" probablemente debería ser "~ / ErrorPage / Oops? 404" ¿verdad? Al menos eso fue lo que funcionó para mí. Quizás eso solo depende de la ruta.
Josh Sutterfield

Cómo simular un error arrojado por IIS. Ya sea 500 o 504. Qué hacer en ASP.Net MVC: código 5 para simular la excepción de IIS para poder probar mi página de error personalizada
irrompible el

12

Parece que hay una serie de pasos aquí mezclados. Voy a presentar lo que hice desde cero.

  1. Crea el ErrorPagecontrolador

    public class ErrorPageController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }
    
        public ActionResult Oops(int id)
        {
            Response.StatusCode = id;
            return View();
        }
    }
  2. Agregue vistas para estas dos acciones (clic derecho -> Agregar vista). Deben aparecer en una carpeta llamada ErrorPage.

  3. Dentro App_Startabre FilterConfig.csy comenta el filtro de manejo de errores.

    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        // Remove this filter because we want to handle errors ourselves via the ErrorPage controller
        //filters.Add(new HandleErrorAttribute());
    }
  4. Dentro de web.config agregue las siguientes <customerErrors>entradas, enSystem.Web

    <customErrors mode="On" defaultRedirect="~/ErrorPage/Oops">
        <error redirect="~/ErrorPage/Oops/404" statusCode="404" />
        <error redirect="~/ErrorPage/Oops/500" statusCode="500" />
    </customErrors>
  5. Prueba (por supuesto). Lance una excepción no controlada en su código y vea que vaya a la página con id 500, y luego use una URL para una página que no existe para ver 404.


Recibí este error. An exception occurred while processing your request. Additionally, another exception occurred while executing the custom error page for the first exception. The request has been terminated.Todo lo que obtuve de su código está en el archivo web.config, agregué <error redirect = "~/ControllerName/ActionName" statusCode="404"/>y funcionó bien :) El resto del código fue de la respuesta de @Pablo. Estoy usando MVC 5 y 6. marco de la entidad que no me quito filters.Add(new HandleErrorAttribute())deFilterConfig.cs
sumedha

Cómo simular un error arrojado por IIS. Ya sea 500 o 504. Qué hacer en ASP.Net MVC: código 5 para simular la excepción de IIS para poder probar mi página de error personalizada
irrompible el

Además, cómo lanzar una excepción no controlada (paso 5). Soy nuevo en la codificación por favor guía.
Irrompible el

¿Todavía no funciona para mí? ¿Qué pasa con el enrutamiento? ¿Debo agregar también la página Enrutamiento por error? Si llego a la página: localhost: 84 / Enforcer / blah me redirigen a: localhost: 84 / Enforcer / Enforcer / Error / NotFound? Aspxerrorpath = / ... La página de error parece una página de error estándar proporcionada por Asp.NET. ¿Algunas ideas?
Radek Strugalski

El elemento customerrors en la configuración web debería estar manejando esto. Su código de ruta predeterminado (creado por el proyecto) debería funcionar bien.
VictorySaber

11

Recomendaría usar el archivo Global.asax.cs.

 protected void Application_Error(Object sender, EventArgs e)
{
    var exception = Server.GetLastError();
    if (exception is HttpUnhandledException)
    {
        Server.Transfer("~/Error.aspx");
    }
    if (exception != null)
    {
        Server.Transfer("~/Error.aspx");
    }
    try
    {
        // This is to stop a problem where we were seeing "gibberish" in the
        // chrome and firefox browsers
        HttpApplication app = sender as HttpApplication;
        app.Response.Filter = null;
    }
    catch
    {
    }
}

1
No pensé que pudieras hacer un Server.Transfer () en MVC. ¿Estás pensando que el OP tiene un sitio mixto?
Rap

1
¿Por qué deberíamos usar Application_Error en mvc? Tenemos opciones como [handleerror] attribut con opciones de URL de redireccionamiento. ¿Hay alguna ventaja específica con application_error en eso?
Kurkula

Deberíamos usar HandleErrorAttribute en MVC y anulando el método OnException, podemos manejarlos de una manera mucho mejor
Kumar Lachhani

7

Sobre la base de la respuesta publicada por maxspan, he reunido un proyecto de muestra mínimo en GitHub que muestra todas las partes de trabajo.

Básicamente, simplemente agregamos un Application_Errormétodo a global.asax.cs para interceptar la excepción y darnos la oportunidad de redirigir (o más correctamente, la solicitud de transferencia ) a una página de error personalizada.

    protected void Application_Error(Object sender, EventArgs e)
    {
        // See http://stackoverflow.com/questions/13905164/how-to-make-custom-error-pages-work-in-asp-net-mvc-4
        // for additional context on use of this technique

        var exception = Server.GetLastError();
        if (exception != null)
        {
            // This would be a good place to log any relevant details about the exception.
            // Since we are going to pass exception information to our error page via querystring,
            // it will only be practical to issue a short message. Further detail would have to be logged somewhere.

            // This will invoke our error page, passing the exception message via querystring parameter
            // Note that we chose to use Server.TransferRequest, which is only supported in IIS 7 and above.
            // As an alternative, Response.Redirect could be used instead.
            // Server.Transfer does not work (see https://support.microsoft.com/en-us/kb/320439 )
            Server.TransferRequest("~/Error?Message=" + exception.Message);
        }

    }

Controlador de error:

/// <summary>
/// This controller exists to provide the error page
/// </summary>
public class ErrorController : Controller
{
    /// <summary>
    /// This action represents the error page
    /// </summary>
    /// <param name="Message">Error message to be displayed (provided via querystring parameter - a design choice)</param>
    /// <returns></returns>
    public ActionResult Index(string Message)
    {
        // We choose to use the ViewBag to communicate the error message to the view
        ViewBag.Message = Message;
        return View();
    }

}

Vista de página de error:

<!DOCTYPE html>

<html>
<head>
    <title>Error</title>
</head>
<body>

    <h2>My Error</h2>
    <p>@ViewBag.Message</p>
</body>
</html>

Nada más está involucrado, aparte de deshabilitar / eliminar filters.Add(new HandleErrorAttribute())en FilterConfig.cs

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        //filters.Add(new HandleErrorAttribute()); // <== disable/remove
    }
}

Si bien es muy simple de implementar, el único inconveniente que veo en este enfoque es usar la cadena de consulta para entregar información de excepción a la página de error de destino.


3

Tenía todo configurado, pero aún no podía ver las páginas de error adecuadas para el código de estado 500 en nuestro servidor provisional, a pesar de que todo funcionaba bien en los servidores de desarrollo local.

Encontré esta publicación de blog de Rick Strahl que me ayudó.

Necesitaba agregar Response.TrySkipIisCustomErrors = true;a mi código de manejo de errores personalizado.


@ Shaun314 ¿Quieres decir dónde pones ese código? En la acción que maneja la solicitud. Puedes ver ejemplos en esa publicación de blog.
DCShannon

2

Aquí está mi solución. Usar [ExportModelStateToTempData] / [ImportModelStateFromTempData] es incómodo en mi opinión.

~ / Views / Home / Error.cshtml:

@{
    ViewBag.Title = "Error";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h2>Error</h2>
<hr/>

<div style="min-height: 400px;">

    @Html.ValidationMessage("Error")

    <br />
    <br />

    <button onclick="Error_goBack()" class="k-button">Go Back</button>
    <script>
        function Error_goBack() {
            window.history.back()
        }
    </script>

</div>

~ / Controllers / HomeController.sc:

public class HomeController : BaseController
{
    public ActionResult Index()
    {
        return View();
    }

    public ActionResult Error()
    {
        return this.View();
    }

    ...
}

~ / Controllers / BaseController.sc:

public class BaseController : Controller
{
    public BaseController() { }

    protected override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        if (filterContext.Result is ViewResult)
        {
            if (filterContext.Controller.TempData.ContainsKey("Error"))
            {
                var modelState = filterContext.Controller.TempData["Error"] as ModelState;
                filterContext.Controller.ViewData.ModelState.Merge(new ModelStateDictionary() { new KeyValuePair<string, ModelState>("Error", modelState) });
                filterContext.Controller.TempData.Remove("Error");
            }
        }
        if ((filterContext.Result is RedirectResult) || (filterContext.Result is RedirectToRouteResult))
        {
            if (filterContext.Controller.ViewData.ModelState.ContainsKey("Error"))
            {
                filterContext.Controller.TempData["Error"] = filterContext.Controller.ViewData.ModelState["Error"];
            }
        }

        base.OnActionExecuted(filterContext);
    }
}

~ / Controllers / MyController.sc:

public class MyController : BaseController
{
    public ActionResult Index()
    {
        return View();
    }

    public ActionResult Details(int id)
    {
        if (id != 5)
        {
            ModelState.AddModelError("Error", "Specified row does not exist.");
            return RedirectToAction("Error", "Home");
        }
        else
        {
            return View("Specified row exists.");
        }
    }
}

Te deseo proyectos exitosos ;-)


2

Puede obtener errores que funcionen correctamente sin hackear global.cs, jugar con HandleErrorAttribute, hacer Response. TrySkipIisCustomErrors, conectar Application_Error o lo que sea:

En system.web (solo lo habitual, activar / desactivar)

<customErrors mode="On">
  <error redirect="/error/401" statusCode="401" />
  <error redirect="/error/500" statusCode="500" />
</customErrors>

y en system.webServer

<httpErrors existingResponse="PassThrough" />

Ahora las cosas deberían comportarse como se esperaba, y puede usar su ErrorController para mostrar lo que necesite.


Cómo simular un error arrojado por IIS. Ya sea 500 o 504. Qué hacer en ASP.Net MVC: código 5 para simular la excepción de IIS para poder probar mi página de error personalizada
irrompible el

@Unbreakable cambia temporalmente tu código para lanzar una excepción.
conmemoran el

No hizo la diferencia para mí. No tengo acceso a mi página de error personalizada con una excepción o error 404 no encontrado.
pnizzle

0

Parece que llegué tarde a la fiesta, pero deberías comprobar esto también.

Entonces, system.webpara almacenar en caché excepciones dentro de la aplicación, como return HttpNotFound ()

  <system.web>
    <customErrors mode="RemoteOnly">
      <error statusCode="404" redirect="/page-not-found" />
      <error statusCode="500" redirect="/internal-server-error" />
    </customErrors>
  </system.web>

y system.webServerpara detectar errores que fueron capturados por IIS y no llegaron al marco asp.net

 <system.webServer>
    <httpErrors errorMode="DetailedLocalOnly">
      <remove statusCode="404"/>
      <error statusCode="404" path="/page-not-found" responseMode="Redirect"/>
      <remove statusCode="500"/>
      <error statusCode="500" path="/internal-server-error" responseMode="Redirect"/>
  </system.webServer>

En el último caso se preocupan por la respuesta del cliente a continuación, cambiar la responseMode="Redirect"a responseMode="File"y servir a un archivo HTML estático, ya que éste se mostrará una página amigable con un código de respuesta 200.


0

En web.config agregue esto bajo la etiqueta system.webserver como se muestra a continuación,

<system.webServer>
<httpErrors errorMode="Custom" existingResponse="Replace">
  <remove statusCode="404"/>
  <remove statusCode="500"/>
  <error statusCode="404" responseMode="ExecuteURL" path="/Error/NotFound"/>
  <error statusCode="500" responseMode="ExecuteURL"path="/Error/ErrorPage"/>
</httpErrors>

y agregue un controlador como,

public class ErrorController : Controller
{
    //
    // GET: /Error/
    [GET("/Error/NotFound")]
    public ActionResult NotFound()
    {
        Response.StatusCode = 404;

        return View();
    }

    [GET("/Error/ErrorPage")]
    public ActionResult ErrorPage()
    {
        Response.StatusCode = 500;

        return View();
    }
}

y agreguen sus puntos de vista respetados, esto funcionará definitivamente, supongo para todos.

Esta solución la encontré en: Neptune Century

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.