Páginas de error personalizadas en asp.net MVC3

144

Estoy desarrollando un sitio web base MVC3 y estoy buscando una solución para manejar errores y renderizar vistas personalizadas para cada tipo de error. Imagine que tengo un controlador "Error" donde su acción principal es "Índice" (página de error genérico) y este controlador tendrá un par de acciones más para los errores que pueden aparecer al usuario como "Handle500" o "HandleActionNotFound".

Por lo tanto, cada error que pueda ocurrir en el sitio web puede ser manejado por este controlador "Error" (ejemplos: "Controlador" o "Acción" no encontrado, 500, 404, dbException, etc.).

Estoy usando el archivo Sitemap para definir las rutas del sitio web (y no la ruta).

Esta pregunta ya fue respondida, esta es una respuesta a Gweebz

Mi método final applicationiton_error es el siguiente:

protected void Application_Error() {
//while my project is running in debug mode
if (HttpContext.Current.IsDebuggingEnabled && WebConfigurationManager.AppSettings["EnableCustomErrorPage"].Equals("false"))
{
    Log.Logger.Error("unhandled exception: ", Server.GetLastError());
}
else
{
    try
    {
        var exception = Server.GetLastError();

        Log.Logger.Error("unhandled exception: ", exception);

        Response.Clear();
        Server.ClearError();
        var routeData = new RouteData();
        routeData.Values["controller"] = "Errors";
        routeData.Values["action"] = "General";
        routeData.Values["exception"] = exception;

        IController errorsController = new ErrorsController();
        var rc = new RequestContext(new HttpContextWrapper(Context), routeData);
        errorsController.Execute(rc);
    }
    catch (Exception e)
    {
        //if Error controller failed for same reason, we will display static HTML error page
        Log.Logger.Fatal("failed to display error page, fallback to HTML error: ", e);
        Response.TransmitFile("~/error.html");
    }
}
}
John Louros
fuente
¿Cuál debería ser la configuración en web.config para admitir esto? ¿Probablemente no incluirías ninguna configuración de httperrors?
philbird
forums.asp.net/p/1782402/4894514.aspx/… tiene algunos buenos consejos como IE no mostrará su página de error si tiene menos de 512 bytes
RickAndMSFT

Respuestas:

201

Aquí hay un ejemplo de cómo manejo los errores personalizados. Defino un ErrorsControllercon acciones que manejan diferentes errores HTTP:

public class ErrorsController : Controller
{
    public ActionResult General(Exception exception)
    {
        return Content("General failure", "text/plain");
    }

    public ActionResult Http404()
    {
        return Content("Not found", "text/plain");
    }

    public ActionResult Http403()
    {
        return Content("Forbidden", "text/plain");
    }
}

y luego me suscribo al Application_Errorin Global.asaxe invoco este controlador:

protected void Application_Error()
{
    var exception = Server.GetLastError();
    var httpException = exception as HttpException;
    Response.Clear();
    Server.ClearError();
    var routeData = new RouteData();
    routeData.Values["controller"] = "Errors";
    routeData.Values["action"] = "General";
    routeData.Values["exception"] = exception;
    Response.StatusCode = 500;
    if (httpException != null)
    {
        Response.StatusCode = httpException.GetHttpCode();
        switch (Response.StatusCode)
        {
            case 403:
                routeData.Values["action"] = "Http403";
                break;
            case 404:
                routeData.Values["action"] = "Http404";
                break;
        }
    }

    IController errorsController = new ErrorsController();
    var rc = new RequestContext(new HttpContextWrapper(Context), routeData);
    errorsController.Execute(rc);
}
Darin Dimitrov
fuente
44
Solo una pequeña nota. Como quería representar una vista en cada caso (404, 500, etc.) en cada ActionResult, devolví una vista. Sin embargo, intenté detectar el contenido de Application_Error y, en caso de error, se devuelve una página HTML estática. (Puedo publicar el código si alguien lo desea)
John Louros
44
No puedo obtener vistas razonables para renderizar usando esta solución en MVC3. return View (modelo), por ejemplo, solo obtiene una pantalla en blanco.
Extrakun
2
Se agregó TrySkipIisCustomErrors para arreglarlo para IIS7 integrado. Ver stackoverflow.com/questions/1706934/…
Pavel Savara el
1
@ajbeaven, Executees un método definido en la IControllerinterfaz. Esto no puede ser protegido. Mire mi código con más cuidado: IController errorsController = new ErrorsController();y observe el tipo de errorsControllervariable en la que estoy invocando el Executemétodo. Es de tipo, IControllerpor lo que no hay absolutamente nada que le impida llamar a este método. Y, por cierto, también Executeestaba protegido en la clase Controller en MVC 3, por lo que no hay cambios a este respecto.
Darin Dimitrov
2
Se solucionó especificando explícitamente el tipo de contenido de la respuesta:Response.ContentType = "text/html";
ajbeaven
6

También puede hacerlo en el archivo Web.Config. Aquí hay un ejemplo que funciona en IIS 7.5.

     <system.webServer>
          <httpErrors errorMode="DetailedLocalOnly" defaultResponseMode="File">
                <remove statusCode="502" subStatusCode="-1" />
                <remove statusCode="501" subStatusCode="-1" />
                <remove statusCode="412" subStatusCode="-1" />
                <remove statusCode="406" subStatusCode="-1" />
                <remove statusCode="405" subStatusCode="-1" />
                <remove statusCode="404" subStatusCode="-1" />
                <remove statusCode="403" subStatusCode="-1" />
                <remove statusCode="401" subStatusCode="-1" />
                <remove statusCode="500" subStatusCode="-1" />
                <error statusCode="500" path="/notfound.html" responseMode="ExecuteURL" />
                <error statusCode="401" prefixLanguageFilePath="" path="/500.html" responseMode="ExecuteURL" />
                <error statusCode="403" prefixLanguageFilePath="" path="/403.html" responseMode="ExecuteURL" />
                <error statusCode="404" prefixLanguageFilePath="" path="/404.html" responseMode="ExecuteURL" />
                <error statusCode="405" prefixLanguageFilePath="" path="/405.html" responseMode="ExecuteURL" />
                <error statusCode="406" prefixLanguageFilePath="" path="/406.html" responseMode="ExecuteURL" />
                <error statusCode="412" prefixLanguageFilePath="" path="/412.html" responseMode="ExecuteURL" />
                <error statusCode="501" prefixLanguageFilePath="" path="/501.html" responseMode="ExecuteURL" />
                <error statusCode="502" prefixLanguageFilePath="" path="/genericerror.html" responseMode="ExecuteURL" />
           </httpErrors>
</system.webServer>
Brett Allred
fuente
3

Veo que agregaste un valor de configuración EnableCustomErrorPagey también estás verificando IsDebuggingEnabledpara determinar si debes ejecutar tu manejo de errores.

Como ya hay una <customErrors/>configuración en ASP.NET (que está diseñada exactamente para este propósito), es más fácil decir:

    protected void Application_Error()
    {
        if (HttpContext.Current == null) 
        {
                // errors in Application_Start will end up here                
        }
        else if (HttpContext.Current.IsCustomErrorEnabled)
        {
                // custom exception handling
        }
    }

Luego, en la configuración que pondría, <customErrors mode="RemoteOnly" />que es segura de implementar de esa manera, y cuando necesite probar su página de error personalizada, la configurará para <customErrors mode="On" />que pueda verificar que funciona.

Tenga en cuenta que también debe verificar si HttpContext.Currentes nulo porque una excepción Application_Startseguirá siendo su método, aunque no habrá un contexto activo.

Simon_Weaver
fuente
2

Puede mostrar una página de error fácil de usar con el código de estado http correcto implementando el módulo de Manejo de excepciones amigable de Jeff Atwood con una ligera modificación del código de estado http. Funciona sin redireccionamientos. Aunque el código es de 2004 (!), Funciona bien con MVC. Se puede configurar completamente en su web.config, sin ningún cambio en el código fuente del proyecto MVC.

La modificación requerida para devolver el estado HTTP original en lugar de un 200estado se describe en esta publicación relacionada del foro .

Básicamente, en Handler.vb, puede agregar algo como:

' In the header...
Private _exHttpEx As HttpException = Nothing

' At the top of Public Sub HandleException(ByVal ex As Exception)...
HttpContext.Current.Response.StatusCode = 500
If TypeOf ex Is HttpException Then
    _exHttpEx = CType(ex, HttpException)
    HttpContext.Current.Response.StatusCode = _exHttpEx.GetHttpCode()
End If
Martin_W
fuente
0

Estoy usando MVC 4.5 y estaba teniendo problemas con la solución de Darin. Nota: la solución de Darin es excelente y la utilicé para encontrar mi solución. Aquí está mi solución modificada:

protected void Application_Error(object sender, EventArgs e)
{           
var exception = Server.GetLastError();
var httpException = exception as HttpException;
Response.StatusCode = httpException.GetHttpCode();

Response.Clear();
Server.ClearError();


if (httpException != null)
{
    var httpContext = HttpContext.Current;

    httpContext.RewritePath("/Errors/InternalError", false);

    // MVC 3 running on IIS 7+
    if (HttpRuntime.UsingIntegratedPipeline)
    {
        switch (Response.StatusCode)
        {
            case 403:
                httpContext.Server.TransferRequest("/Errors/Http403", true);
                break;
            case 404:
                httpContext.Server.TransferRequest("/Errors/Http404", true);
                break;
            default:
                httpContext.Server.TransferRequest("/Errors/InternalError", true);
                break;
        }
    }
    else
    {
        switch (Response.StatusCode)
        {
            case 403:
                httpContext.RewritePath(string.Format("/Errors/Http403", true));
                break;
            case 404:
                httpContext.RewritePath(string.Format("/Errors/Http404", true));
                break;
            default:
                httpContext.RewritePath(string.Format("/Errors/InternalError", true));
                break;
        }

        IHttpHandler httpHandler = new MvcHttpHandler();
        httpHandler.ProcessRequest(httpContext);
    }
}
}
MVCdragon
fuente
2
¿Qué problemas tenía con la solución de Darin?
Kenny Evitt
No describió el problema que experimentó que provocó una respuesta competitiva.
ivanjonas