ASP.NET custom error page - Server.GetLastError() is null
Asked Answered
C

10

115

I have a custom error page set up for my application:

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

In Global.asax, Application_Error(), the following code works to get the exception details:

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

By the time I get to my error page (~/errors/GeneralError.aspx.cs), Server.GetLastError() is null

Is there any way I can get the exception details on the Error Page, rather than in Global.asax.cs ?

ASP.NET 3.5 on Vista/IIS7

Cabrilla answered 5/12, 2008 at 5:57 Comment(2)
Applies also on ASP.NET 4.0 on Win7 with CassiniFib
add "<customErrors mode="RemoteOnly" defaultRedirect="~/errors/GeneralError.aspx" redirectMode="ResponseRewrite" />" as confirmed answerWeeks
C
138

Looking more closely at my web.config set up, one of the comments in this post is very helpful

in asp.net 3.5 sp1 there is a new parameter redirectMode

So we can amend customErrors to add this parameter:

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

the ResponseRewrite mode allows us to load the «Error Page» without redirecting the browser, so the URL stays the same, and importantly for me, exception information is not lost.

Cabrilla answered 5/12, 2008 at 6:33 Comment(4)
This didn't work for me. The exception info is lost. I would up storing it in the session in Application_Error() and pulling it back out in the Page_Load() handler of my error page.Testaceous
That should be the norm in all the documentation. This is so good I see no reason to support the old behaviour anymore. As long as the status code is correct there should be no issue with leaving the original request URL intact (not doing a browser redirect). In fact that is more correct according to HTTP because the response code relates to the requested URL, not a shared error page request. Thanks for the pointer I missed that new feature!Capsicum
This doesn't work with exceptions triggered by controls inside UpdatePanels; the error page will no longer be displayed.Stammer
since its an old answer adding my comment to prove this Nice one ResponseRewrite value in redirectmode works in Asp.Net 4.5Crary
C
38

OK, I found this post: http://msdn.microsoft.com/en-us/library/aa479319.aspx

with this very illustrative diagram:

diagram
(source: microsoft.com)

in essence, to get at those exception details i need to store them myself in Global.asax, for later retrieval on my custom error page.

it seems the best way is to do the bulk of the work in Global.asax, with the custom error pages handling helpful content rather than logic.

Cabrilla answered 5/12, 2008 at 6:23 Comment(0)
E
18

A combination of what NailItDown and Victor said. The preferred/easiest way is to use your Global.Asax to store the error and then redirect to your custom error page.

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
}

In addition, you need to set up your web.config:

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

And finally, do whatever you need to with the exception you've stored in your error page:

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 ...
}
Epigrammatist answered 5/12, 2008 at 19:29 Comment(7)
If you store it in the application, what about all the other users of the system. Shouldn't it be in the session?Testaceous
indeed, that's a really bad approach storing this on Application["TheException"]Ayr
Plus, if you want to support multiple "tabs" per user, you might want to give the exception a unique key in the session store and then include that key as a querystring parameter when redirecting to the error page.Capsular
+1 But be aware that Application[] is a global object. Theoretically you could have a race condition where a second page overwrites the error. However, since Session[] is not always available under error conditions, I think this is the better choice.Seumas
Just add a new GUID prefix to the key used to store the exception and pass the GUID as a parameter to the custom error page.Racket
If there are 100 users online in web, and thrown 100 errors in the same time, not good using Application["TheException"] ?Nikkinikkie
@Seumas Session[] is not always available under error conditions ? which ?Nikkinikkie
S
6

Try using something like Server.Transfer("~/ErrorPage.aspx"); from within the Application_Error() method of global.asax.cs

Then from within Page_Load() of ErrorPage.aspx.cs you should be okay to do something like: Exception exception = Server.GetLastError().GetBaseException();

Server.Transfer() seems to keep the exception hanging around.

Sawhorse answered 23/4, 2009 at 4:27 Comment(2)
This is how my application did it, and it worked quite well for 99% of errors. But today I came across an exception that occurs during the rendering step. If you Server.Transfer after a page is half-rendered, then the HTML of the page you transfer to is simply concatenated to whatever has already been rendered. So you may end up with half a broken page followed by the error page below that.Stapler
For some reason, call to Server.Transfer() causes issues and error does not get displayed at all. And hence, I don't recommend using this method. Simply use the web.config line as suggested above (<customErrors mode="RemoteOnly" defaultRedirect="~/errors/GeneralError.aspx" redirectMode="ResponseRewrite" />) and it works fineApophysis
H
5

Whilst there are several good answers here, I must point out that it is not good practice to display system exception messages on error pages (which is what I am assuming you want to do). You may inadvertently reveal things you do not wish to do so to malicious users. For example Sql Server exception messages are very verbose and can give the user name, password and schema information of the database when an error occurs. That information should not be displayed to an end user.

Hopping answered 7/6, 2011 at 20:39 Comment(2)
In my case I only wanted the exception info for back end use, but that's good advice.Cabrilla
Doesn't answer the question.Democracy
C
5

Here is my solution..

In 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"); 
    }

In 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;
        }
    }
Cosmetic answered 28/9, 2011 at 16:3 Comment(2)
This looks nice, and I'm trying it out. So, I've typed in a wrong url for my webpage to get the 404 to appear and my error page loads, but no error is detected. I'm thinking that for some reason ServerGetLastError() isn't fetching anything in IIS 10.Apotropaic
Redirect to ~/ErrorPages/Oops.aspx Valid for several errors in web application for several user in the same time ?Nikkinikkie
V
4

One important consideration that I think everybody is missing here is a load-balancing (web farm) scenario. Since the server that's executing global.asax may be different than the server that's about the execute the custom error page, stashing the exception object in Application is not reliable.

I'm still looking for a reliable solution to this problem in a web farm configuration, and/or a good explanation from MS as to why you just can't pick up the exception with Server.GetLastError on the custom error page like you can in global.asax Application_Error.

P.S. It's unsafe to store data in the Application collection without first locking it and then unlocking it.

Visconti answered 5/6, 2010 at 20:23 Comment(1)
This would only be the case if you're doing a client-side redirect. When doing a server-transfer, it's all part of the one request, so application_error -> page_load will all happen on the one server in the farm, in sequence.Boehmenism
M
3

It worked for me. in MVC 5


in ~\Global.asax

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


in ~\Controllers Create ErrorController.cs

using System.Web.Mvc;

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


in ~\Models Create FunctionTools.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;
        }
    }
}


in ~\Views Create Folder Error and in ~\Views\Error Create Error.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>
    }
}


If you enter this address localhost/Error open page Whithout Error



And if an error occurs error occurs

As can be instead of displaying errors, variable 'log' to be stored in the database


Source: Microsoft ASP.Net

Matthiew answered 22/1, 2017 at 2:37 Comment(0)
D
2

This related to these 2 topics below, I want get both GetHtmlErrorMessage and Session on Error page.

Session is null after ResponseRewrite

Why is HttpContext.Session null when redirectMode = ResponseRewrite

I tried and see solution which no need Server.Transfer() or Response.Redirect()

First: remove ResponseRewrite in web.config

Web.config

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

Then Global.asax

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

Then 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
            }

For references

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

Detention answered 15/12, 2016 at 13:6 Comment(1)
If there are 100 users online in web, and thrown 100 errors in the same time, not good using Application["TheException"]Nikkinikkie
S
1

I think you have a couple of options here.

you could store the last Exception in the Session and retrieve it from your custom error page; or you could just redirect to your custom error page within the Application_error event. If you choose the latter, you want to make sure you use the Server.Transfer method.

Slip answered 5/12, 2008 at 19:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.