0121 31 45 374
Qoute Icon

ASP.Net WebException and Error Reporting useful code

Tim

I’ve been including Phil Winstanley’s error reporting code (http://weblogs.asp.net/plip/archive/2004/04/17/114984.aspx) in our projects for quite some time now and it’s become an invaluable resource when debugging site exceptions both while developing and on live sites.

Since the initial release Phil’s class, we’ve been expanding it in various ways to suit so I thought in addition to the trace output work around that I posted about a while ago I thought I would share some other techniques we use.

Quickly turning it off while developing

I have seen people writing checks to ensure the site isn’t local before reporting the error but rather than hard coding that we’ve added a value to the web.config “SendErrors” it it’s true then the error report is sent.

//A flag in the web.config file to allow you to disable error reporting while testing (if you want to!)
bool sendErrors = Convert.ToBoolean(ConfigurationManager.AppSettings["SendSiteErrors"]);

if (sendErrors)
{
    //Handle the Error
}

Check what sort of error it is

There’s no point in throwing an exception error if the error message is a 404 so check the error type and redirect the user to a specific error page for the given error page, i.e. for a 404 you could display the URL they attempted to visit explain what may have happened. How about doing a little selling to them while they’re there? Or as some companies have started doing, offer them a discount for the inconvienience!

//Redirect the user to a friendly page
if (CheckForErrorType(currentError, "FileNotFound"))
    RedirectToFriendlyUrl("Your404ErrorPage.htm");

/// <summary>
/// Helper method to check what sort of error we're dealing with
/// </summary>
/// <param name="ex">Exception to check</param>
/// <param name="errorText">String value to find</param>
/// <returns>true if found, false if not</returns>
private static bool CheckForErrorType(Exception ex, string errorText)
{
    if (ex != null)
    {
        //Check the exception if it's not found in the outer ex, check it's children
        if (ex.GetType().ToString().IndexOf(errorText) > 0)
            return true;
        else
            return CheckForErrorType(ex.InnerException, errorText);
    }
    else
    {
        return false;
    }
}

/// <summary>
/// Redirects the user to a given url in the event of an error
/// </summary>
/// <param name="Url">URL of the page to redirect to</param>
private static void RedirectToFriendlyUrl(string Url)
{
    //Only redirect the user if the URL is not empty and we're not on a dev machine
    //TODO: Check the referrer to ensure we don't redirect the user to the page causing the error!
    if (!String.IsNullOrEmpty(Url) && (context.Url.Host.IndexOf("localhost") < 0))
        HttpContext.Current.Response.Redirect(Url);
}

Make it more generalised

In Phil’s class he allows you to set the site name for the error report, we generally just wrote the domain name in for simplicity, so instead, just use: Request.Url.Host

WE.Site = context.Url.Host;

Rid yourself of Spambot attacks

Sean Ronan from Activepixels was showing me his method of getting around the spambot attacks that are becoming a daily annoyance once the method was implemented it cut our dud error reports from up to 400 per site per night to around 0 :)

Sean noticed that although the spambots were getting clever and identifying ASP.Net forms to post back to, they were also altering the __VIEWSTATE field which caused a System.FormatException error to be thrown.

If you’re also using Phil Winstanley's ASP.Net WebException handler you’ll no doubt have been inundated with error reports, the main issue imho is the flood count, if you were getting 50 form posts, you’re likely to miss the important one.

The solution is remarkably simple: if the error message is a System.FormatException then simply check if the one of the form fields is “__VIEWSTATE” and has “Content-Type” as part of it’s content, if it does then don’t report it –easy!

//Check the error type
if (CheckForErrorType(currentError, "System.FormatException"))
{
    //Ensure there's a form collection to parse
    if (context.Form.Count > 0)
    {
        //Loop through each item in the form collection to check for _VIEWSTATE
        foreach (string key in context.Form)
        {
            //If there's a viewstate match and within it "Content-Type" (used by the spambots) then return
            if (key.IndexOf("_VIEWSTATE") > 0 && context.Form[key].ToString().IndexOf("Content-Type") > 0)
                return;
        }
    }
}

An example of the “spambotted” ViewState courtesy of Infobrokers:

__VIEWSTATE [email protected] Content-Type: multipart/mixed; boundary="===============0240301364==" MIME-Version: 1.0 Subject: 342f9a6 To: [email protected] bcc: [email protected] From: [email protected] This is a multi-part message in MIME format. --===============0240301364== Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit oos --===============0240301364==--

Persisting the code across projects

There are perhaps better ways of persisting the code across projecst but we’ve copied the various code snippets for the Global.asax and Web.Config file onto the Visual Studio Toolbox offering us drag and drop setup.

One better alternative would be to add it to the Global.asax template, you can find it at:
%ProgramDir%\Microsoft Visual Studio 8\Common7\IDE\ItemTemplatesCache\Web\CSharp\1033\GlobalAsax.zip

Just paste your code in there and bobs your uncle. I’ve got a feeling you can write your own project templates too if you generate that many projects.

Eventually I expect I’ll wrap Phil’s WebException with our own generic handler that does all this for us but for now I’m happy with this method.

Enjoy.

The relevant code in its entirety:

//Declare for the scope of the class
private static HttpRequest context = HttpContext.Current.Request;

void Application_Error(object sender, EventArgs e)
{
    //A flag in the web.config file to allow you to disable error reporting while testing (if you want to!)
    bool sendErrors = Convert.ToBoolean(ConfigurationManager.AppSettings["SendSiteErrors"]);

    if (sendErrors)
    {
        #region Deal with 404's

        //Redirect the user to a friendly page
        if (CheckForErrorType(currentError, "FileNotFound"))
            RedirectToFriendlyUrl("Your404ErrorPage.htm");

        #endregion
        #region Deal with Spambots

        //Check the error type
        if (CheckForErrorType(currentError, "System.FormatException"))
        {
            //Ensure there's a form collection to parse
            if (context.Form.Count > 0)
            {
                //Loop through each item in the form collection to check for _VIEWSTATE
                foreach (string key in context.Form)
                {
                    //If there's a viewstate match and within it "Content-Type" (used by the spambots) then return
                    if (key.IndexOf("_VIEWSTATE") > 0 && context.Form[key].ToString().IndexOf("Content-Type") > 0)
                        return;
                }
            }
        }

        #endregion

        //Enable the trace for the duration of the error handling
        //Note: See previous blog post about this not working in the latest 
        //version of the reporting code (at time of writing v4)
        TraceContext t = HttpContext.Current.Trace;
        bool bCurrentState = t.IsEnabled;
        t.IsEnabled = true;

        #region Handle the Exception

        ErrorHandling.WebException WE = new ErrorHandling.WebException();
        WE.CurrentException = currentError;
        WE.MailFrom = "[email protected]";
        WE.MailTo = "[email protected]";
        WE.MailAdmin = "[email protected]";
        WE.Site = context.Url.Host;
        WE.SmtpServer = "localhost";
        WE.FloodCount = 10;
        WE.FloodMins = 5;

        #endregion
        #region Choose what you're interested in

        WE.ReturnCache = true;
        WE.DrillDownInCache = true;
        WE.IncludeApplication = true;
        WE.IncludeBrowser = true;
        WE.IncludeEnvironmentVariables = true;
        WE.IncludeForm = true;
        WE.IncludeProcess = true;
        WE.IncludeQueryString = true;
        WE.IncludeRequestCookies = true;
        WE.IncludeRequestHeader = true;
        WE.IncludeResponseCookies = true;
        WE.IncludeServerVariables = true;
        WE.IncludeSession = true;
        WE.IncludeTrace = true;
        WE.IncludeVersions = true;
        WE.IncludeAuthentication = true;

        #endregion

        //Send off the error report
        WE.Handle();

        //Return the trace to its original state
        t.IsEnabled = bCurrentState;

        //Redirect the user to a friendly page
        RedirectToFriendlyUrl("Your500ErrorPageHere.htm");
    }
}

protected void Application_PreRequestHandlerExecute(Object sender, EventArgs e)
{
    //WebResource.axd doesn't implement IRequireSessionState so will throw an error without this check
    if (Context.Handler is IRequiresSessionState || Context.Handler is IReadOnlySessionState)
        ErrorReporting.SessionTracker.AddRequest("Pre Request Handler Execute"truetruefalse);
}

/// <summary>
/// Helper method to check what sort of error we're dealing with
/// </summary>
/// <param name="ex">Exception to check</param>
/// <param name="errorText">String value to find</param>
/// <returns>true if found, false if not</returns>
private static bool CheckForErrorType(Exception ex, string errorText)
{
    if (ex != null)
    {
        //Check the exception if it's not found in the outer ex, check it's children
        if (ex.GetType().ToString().IndexOf(errorText) > 0)
            return true;
        else
            return  CheckForErrorType(ex.InnerException, errorText);
    }
    else
    {
        return false;
    }
}

/// <summary>
/// Redirects the user to a given url in the event of an error
/// </summary>
/// <param name="Url">URL of the page to redirect to</param>
private static void RedirectToFriendlyUrl(string Url)
{
    //Only redirect the user if the URL is not empty and we're not on a dev machine
    //TODO: Check the referrer to ensure we don't redirect the user to the page causing the error!
    if (!String.IsNullOrEmpty(Url) && (context.Url.Host.IndexOf("localhost") < 0))
        HttpContext.Current.Response.Redirect(Url);
}

Finally: Since playing with Atlas/AJAX I've found that the errors are caught by the Atlas engine, I've worked out a way around it and will post about it later.

Liked this post? Got a suggestion? Leave a comment