Tim

Footprints in the snow of a warped mind

ASP.Net WebException and Error Reporting useful code

Where to find me

Flickr Icon  Twitter Icon  Linked In Icon  FaceBook Icon  Windows Live Alerts Butterfly  RSS 2.0 

Tag Cloud

AJAX (4) ASP (6) ASP.Net (46) Error Reporting (3) Web Service (1) WSDL (1) Atlas (2) Business (73) Business Start-up Advice (24) Client (12) Expanding Your Business (16) C# (14) Canoeing (4) Canoe Racing (5) Cheshire Ring Race (5) Racing (2) Training (4) CIMA (1) Cisco (1) 7970G (1) CSS (3) dasBlog (4) DDD (1) Design (9) Icons (1) Development (10) General (39) Christmas (6) Fun and Games (11) Internet (22) Random (46) RX-8 (8) Home Cinema (2) Hosting (1) IIS (8) iPhone (1) JavaScript (2) Marketing (4) Email (1) Multipack (1) Networking (2) Nintendo (1) OS Commerce (1) Photography (1) PHP (1) PowerShell (1) Press Release (1) Productivity (1) Security (2) SEO (5) Server Maintenance (3) Server Management (8) Social Networking (1) Experiment (1) Software (9) Office (5) Visual Studio (10) Windows (4) Vista (1) SQL Server (12) Testing (1) The Site Doctor (101) Turnover Challenge (1) Twitter (1) Umbraco (13) Web Development (49) WebDD (33) Wii (1)

Blog Archive

Search

<November 2006>
SunMonTueWedThuFriSat
2930311234
567891011
12131415161718
19202122232425
262728293012
3456789

Recent Comments

Blog Archive

Various Links

Blogs I Read

[Feed] Google Blog
Official Google Webmaster Central Blog
[Feed] Matt Cutts
Gadgets, Google, and SEO
[Feed] Ol' Deano's Blog
My mate Dean's blog on my space, equally as random as mine but not off on as much of a tangent!
[Feed] Sam's Blog
Sam is one of my younger brothers studying Product Design and Manufacture at Loughborough, this is his blog :) Enjoy!

Recent Tracks

last.fm - The Social Music Revolution

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

newtelligence dasBlog 2.2.8279.16125

Send mail to the author(s) Email Me (Tim Gaunt)

© 2009 Tim Gaunt.

Sign In

# Monday, November 06, 2006

ASP.Net WebException and Error Reporting useful code

Monday, November 06, 2006 6:59:55 PM (GMT Standard Time, UTC+00:00)

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 awtxhf@infobrokers.co.uk Content-Type: multipart/mixed; boundary="===============0240301364==" MIME-Version: 1.0 Subject: 342f9a6 To: awtxhf@infobrokers.co.uk bcc: homeigoldstein@aol.com From: awtxhf@infobrokers.co.uk 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 = "you@yourdomain.co.uk";
        WE.MailTo = "you@yourdomain.co.uk";
        WE.MailAdmin = "you@yourdomain.co.uk";
        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.