Página de error personalizada de ASP.NET: Server.GetLastError () es nulo

112

Tengo una página de error personalizada configurada para mi aplicación:

<customErrors mode="On" defaultRedirect="~/errors/GeneralError.aspx"
/>

En Global.asax, Application_Error (), el siguiente código funciona para obtener los detalles de la excepción:

  Exception ex = Server.GetLastError();
  if (ex != null)
    {
        if (ex.GetBaseException() != null)
            ex = ex.GetBaseException();
    }

Para cuando llego a mi página de error (~ / errors / GeneralError.aspx.cs), Server.GetLastError () es nulo

¿Hay alguna forma de que pueda obtener los detalles de la excepción en la página de error, en lugar de en Global.asax.cs?

ASP.NET 3.5 en Vista / IIS7

nailitdown
fuente
Se aplica también a ASP.NET 4.0 en Win7 con Cassini
Marcel
agregue "<customErrors mode =" RemoteOnly "defaultRedirect =" ~ / errors / GeneralError.aspx "redirectMode =" ResponseRewrite "/>" como respuesta confirmada
elle0087

Respuestas:

137

Mirando más de cerca la configuración de mi web.config, uno de los comentarios en esta publicación es muy útil

en asp.net 3.5 sp1 hay un nuevo parámetro redirectMode

Entonces podemos modificar customErrorspara agregar este parámetro:

<customErrors mode="RemoteOnly" defaultRedirect="~/errors/GeneralError.aspx" redirectMode="ResponseRewrite" />

el ResponseRewritemodo nos permite cargar la «Página de error» sin redirigir el navegador, por lo que la URL permanece igual y, lo que es más importante para mí, la información de excepción no se pierde.

nailitdown
fuente
4
Esto no funcionó para mí. Se pierde la información de la excepción. Lo almacenaría en la sesión en Application_Error () y lo sacaría en el controlador Page_Load () de mi página de error.
BrianK
2
Esa debería ser la norma en toda la documentación. Esto es tan bueno que ya no veo ninguna razón para apoyar el antiguo comportamiento. Siempre que el código de estado sea correcto, no debería haber ningún problema con dejar intacta la URL de la solicitud original (sin realizar una redirección del navegador). De hecho, es más correcto según HTTP porque el código de respuesta se relaciona con la URL solicitada, no con una solicitud de página de error compartida. ¡Gracias por el puntero, me perdí esa nueva función!
Tony Wall
Esto no funciona con excepciones activadas por controles dentro de UpdatePanels; la página de error ya no se mostrará.
Sam
2
ya que es una respuesta anterior, agregando mi comentario para demostrar que este valor de ResponseRewrite de Nice one en redirectmode funciona en Asp.Net 4.5
Sundara Prabu
38

OK, encontré esta publicación: http://msdn.microsoft.com/en-us/library/aa479319.aspx

con este diagrama muy ilustrativo:

diagrama
(fuente: microsoft.com )

En esencia, para obtener esos detalles de excepción, necesito almacenarlos yo mismo en Global.asax, para su posterior recuperación en mi página de error personalizada.

parece que la mejor manera es hacer la mayor parte del trabajo en Global.asax, con las páginas de error personalizadas manejando contenido útil en lugar de lógica.

nailitdown
fuente
18

Una combinación de lo que dijeron NailItDown y Victor. La forma preferida / más fácil es usar su Global.Asax para almacenar el error y luego redirigir a su página de error personalizada.

Global.asax :

    void Application_Error(object sender, EventArgs e) 
{
    // Code that runs when an unhandled error occurs
    Exception ex = Server.GetLastError();
    Application["TheException"] = ex; //store the error for later
    Server.ClearError(); //clear the error so we can continue onwards
    Response.Redirect("~/myErrorPage.aspx"); //direct user to error page
}

Además, debe configurar su web.config :

  <system.web>
    <customErrors mode="RemoteOnly" defaultRedirect="~/myErrorPage.aspx">
    </customErrors>
  </system.web>

Y finalmente, haga lo que necesite con la excepción que ha almacenado en su página de error :

protected void Page_Load(object sender, EventArgs e)
{

    // ... do stuff ...
    //we caught an exception in our Global.asax, do stuff with it.
    Exception caughtException = (Exception)Application["TheException"];
    //... do stuff ...
}
rlb.usa
fuente
35
Si lo almacena en la aplicación, ¿qué pasa con todos los demás usuarios del sistema? ¿No debería estar en la sesión?
BrianK
11
de hecho, es un método muy malo almacenar esto en la aplicación ["TheException"]
Junior Mayhé
4
Además, si desea admitir varias "pestañas" por usuario, es posible que desee asignar a la excepción una clave única en el almacén de sesiones y luego incluir esa clave como un parámetro de cadena de consulta al redirigir a la página de error.
Anders Fjeldstad
5
+1 Pero tenga en cuenta que Application[]es un objeto global. Teóricamente, podría tener una condición de carrera en la que una segunda página sobrescribe el error. Sin embargo, dado Session[]que no siempre está disponible en condiciones de error, creo que esta es la mejor opción.
Andomar
3
Simplemente agregue un nuevo prefijo GUID a la clave utilizada para almacenar la excepción y pase el GUID como parámetro a la página de error personalizada.
SteveGSD
6

Intente usar algo como Server.Transfer("~/ErrorPage.aspx");desde dentro del Application_Error()método de global.asax.cs

Luego, desde dentro Page_Load()de ErrorPage.aspx.cs, debería estar bien para hacer algo como:Exception exception = Server.GetLastError().GetBaseException();

Server.Transfer() parece mantener la excepción dando vueltas.


fuente
Así es como lo hizo mi aplicación y funcionó bastante bien para el 99% de los errores. Pero hoy encontré una excepción que ocurre durante el paso de renderizado. Si Server.Transferdespués de que una página está medio renderizada, entonces el HTML de la página a la que transfiere simplemente se concatena con lo que ya se haya renderizado. Por lo tanto, puede terminar con la mitad de una página rota seguida de la página de error debajo.
Kevin
Por alguna razón, llamar a Server.Transfer () causa problemas y el error no se muestra en absoluto. Y por lo tanto, no recomiendo usar este método. Simplemente use la línea web.config como se sugirió anteriormente (<customErrors mode = "RemoteOnly" defaultRedirect = "~ / errors / GeneralError.aspx" redirectMode = "ResponseRewrite" />) y funciona bien
Naresh Mittal
5

Si bien aquí hay varias buenas respuestas, debo señalar que no es una buena práctica mostrar mensajes de excepción del sistema en las páginas de error (que es lo que supongo que desea hacer). Sin darse cuenta, puede revelar cosas que no desea hacer a usuarios malintencionados. Por ejemplo, los mensajes de excepción de Sql Server son muy detallados y pueden proporcionar el nombre de usuario, la contraseña y la información del esquema de la base de datos cuando se produce un error. Esa información no debe mostrarse a un usuario final.

Phil
fuente
1
En mi caso, solo quería la información de excepción para el uso de back-end, pero ese es un buen consejo.
nailitdown
2
No responde la pregunta.
Arne Evertsson
5

Aquí está mi solución ...

En Global.aspx:

void Application_Error(object sender, EventArgs e)
    {
        // Code that runs when an unhandled error occurs

        //direct user to error page 
        Server.Transfer("~/ErrorPages/Oops.aspx"); 
    }

En Oops.aspx:

protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
            LoadError(Server.GetLastError()); 
    }

    protected void LoadError(Exception objError)
    {
        if (objError != null)
        {
            StringBuilder lasterror = new StringBuilder();

            if (objError.Message != null)
            {
                lasterror.AppendLine("Message:");
                lasterror.AppendLine(objError.Message);
                lasterror.AppendLine();
            }

            if (objError.InnerException != null)
            {
                lasterror.AppendLine("InnerException:");
                lasterror.AppendLine(objError.InnerException.ToString());
                lasterror.AppendLine();
            }

            if (objError.Source != null)
            {
                lasterror.AppendLine("Source:");
                lasterror.AppendLine(objError.Source);
                lasterror.AppendLine();
            }

            if (objError.StackTrace != null)
            {
                lasterror.AppendLine("StackTrace:");
                lasterror.AppendLine(objError.StackTrace);
                lasterror.AppendLine();
            }

            ViewState.Add("LastError", lasterror.ToString());
        }
    }

   protected void btnReportError_Click(object sender, EventArgs e)
    {
        SendEmail();
    }

    public void SendEmail()
    {
        try
        {
            MailMessage msg = new MailMessage("webteam", "webteam");
            StringBuilder body = new StringBuilder();

            body.AppendLine("An unexcepted error has occurred.");
            body.AppendLine();

            body.AppendLine(ViewState["LastError"].ToString());

            msg.Subject = "Error";
            msg.Body = body.ToString();
            msg.IsBodyHtml = false;

            SmtpClient smtp = new SmtpClient("exchangeserver");
            smtp.Send(msg);
        }

        catch (Exception ex)
        {
            lblException.Text = ex.Message;
        }
    }
user825345
fuente
4

Una consideración importante que creo que todos faltan aquí es un escenario de equilibrio de carga (granja web). Dado que el servidor que está ejecutando global.asax puede ser diferente del servidor que está a punto de ejecutar la página de error personalizada, ocultar el objeto de excepción en la Aplicación no es confiable.

Todavía estoy buscando una solución confiable a este problema en la configuración de una granja web y / o una buena explicación de MS sobre por qué no puede detectar la excepción con Server.GetLastError en la página de error personalizada como puede hacerlo en global.asax Application_Error.

PD: No es seguro almacenar datos en la colección de aplicaciones sin primero bloquearlos y luego desbloquearlos.

Leonard Lobel
fuente
Este solo sería el caso si realiza una redirección del lado del cliente. Cuando se realiza una transferencia de servidor, todo es parte de la única solicitud, por lo que application_error -> page_load ocurrirá en el único servidor de la granja, en secuencia.
Davewasthere
2

Esto relacionado con estos 2 temas a continuación, quiero obtener GetHtmlErrorMessage y Sesión en la página de error.

La sesión es nula después de ResponseRewrite

¿Por qué HttpContext.Session es nulo cuando redirectMode = ResponseRewrite?

Intenté y veo una solución que no es necesaria Server.Transfer() or Response.Redirect()

Primero: elimine ResponseRewrite en web.config

Web.config

<customErrors defaultRedirect="errorHandler.aspx" mode="On" />

Entonces Global.asax

    void Application_Error(object sender, EventArgs e)
    {
         if(Context.IsCustomErrorEnabled)
         {     
            Exception ex = Server.GetLastError();
            Application["TheException"] = ex; //store the error for later
         }
    }

Entonces errorHandler.aspx.cs

        protected void Page_Load(object sender, EventArgs e)
            {       
                string htmlErrorMessage = string.Empty ;
                Exception ex = (Exception)Application["TheException"];
                string yourSessionValue = HttpContext.Current.Session["YourSessionId"].ToString();

                //continue with ex to get htmlErrorMessage 
                if(ex.GetHtmlErrorMessage() != null){              
                    htmlErrorMessage = ex.GetHtmlErrorMessage();
                }   
                // continue your code
            }

Para referencias

http://www.developer.com/net/asp/article.php/3299641/ServerTransfer-Vs-ResponseRedirect.htm

Bao
fuente
2

Funcionó para mí. en MVC 5


en ~\Global.asax

void Application_Error(object sender, EventArgs e)
{
    FTools.LogException();
    Response.Redirect("/Error");
}


en ~\ControllersCrearErrorController.cs

using System.Web.Mvc;

namespace MVC_WebApp.Controllers
{
    public class ErrorController : Controller
    {
        // GET: Error
        public ActionResult Index()
        {
            return View("Error");
        }
    }
}


en ~\ModelsCrearFunctionTools.cs

using System;
using System.Web;

namespace MVC_WebApp.Models
{
    public static class FTools
    {
        private static string _error;
        private static bool _isError;

        public static string GetLastError
        {
            get
            {
                string cashe = _error;
                HttpContext.Current.Server.ClearError();
                _error = null;
                _isError = false;
                return cashe;
            }
        }
        public static bool ThereIsError => _isError;

        public static void LogException()
        {
            Exception exc = HttpContext.Current.Server.GetLastError();
            if (exc == null) return;
            string errLog = "";
            errLog += "**********" + DateTime.Now + "**********\n";
            if (exc.InnerException != null)
            {
                errLog += "Inner Exception Type: ";
                errLog += exc.InnerException.GetType() + "\n";
                errLog += "Inner Exception: ";
                errLog += exc.InnerException.Message + "\n";
                errLog += "Inner Source: ";
                errLog += exc.InnerException.Source + "\n";
                if (exc.InnerException.StackTrace != null)
                {
                    errLog += "\nInner Stack Trace: " + "\n";
                    errLog += exc.InnerException.StackTrace + "\n";
                }
            }
            errLog += "Exception Type: ";
            errLog += exc.GetType().ToString() + "\n";
            errLog += "Exception: " + exc.Message + "\n";
            errLog += "\nStack Trace: " + "\n";
            if (exc.StackTrace != null)
            {
                errLog += exc.StackTrace + "\n";
            }
            _error = errLog;
            _isError = true;
        }
    }
}


en ~\ViewsCrear carpeta Error y en ~\Views\ErrorCrearError.cshtml

@using MVC_WebApp.Models
@{
    ViewBag.Title = "Error";
    if (FTools.ThereIsError == false)
    {
        if (Server.GetLastError() != null)
        {
            FTools.LogException();
        }
    }
    if (FTools.ThereIsError == false)
    {
        <br />
        <h1>No Problem!</h1>
    }
    else
    {
        string log = FTools.GetLastError;
        <div>@Html.Raw(log.Replace("\n", "<br />"))</div>
    }
}


Si ingresa esta dirección localhost/Error página abierta sin error



Y si ocurre un error se produce un error

Como puede ser en lugar de mostrar errores, la variable 'log' se almacenará en la base de datos


Fuente: Microsoft ASP.Net

MRT2017
fuente
1

Creo que tienes un par de opciones aquí.

puede almacenar la última excepción en la sesión y recuperarla de su página de error personalizada; o simplemente puede redirigir a su página de error personalizada dentro del evento Application_error. Si elige este último, querrá asegurarse de utilizar el método Server.Transfer.

Víctor
fuente