Click here to Skip to main content
Click here to Skip to main content

Tagged as

ASP.NET Application Error Handling

, 5 Jun 2013 CPOL
Rate this:
Please Sign up or sign in to vote.
Application error handling in ASP.NET

Introduction

When an unhandled exception occurs in my application, I want my application to give the user a "graceful" response. Regardless of the error, I do not want the user to see an unfriendly technical error messages generated by IIS or ASP.NET. At the same time, I want to receive email notification for every unhandled exception.

This article describes a simple and comprehensive solution to this problem.

The Problem

When I have no error handling configured for my application, my users might see any one of three different error pages, depending on the type of error.

If a user requests a static resource that does not exist (for example, an HTML or JPG file), then the user sees the default HTTP error message generated by IIS:

If a user requests a dynamic resource that does not exist (for example, an ASPX file), then the user sees the default server error message generated by ASP.NET for HTTP 404 errors:

If an unhandled exception occurs in the application, then the user sees the default server error message generated by ASP.NET for HTTP 500 errors:

ASP.NET web application developers sometimes call these the "Blue Screen of Death" (BSOD) and the "Yellow Screen of Death" (YSOD).

The Solution

When a user sees an error message in my application, I want the error message to match the layout and style of my application, and I want every error message to follow the same layout and style.

Step 1: Integrated Pipeline Mode

As a first step, I set my application to use an application pool that is configured for Integrated managed pipeline mode.

Microsoft Internet Information System (IIS) version 6.0 (and previous versions) integrates ASP.NET as an ISAPI extension, alongside its own processing model for HTTP requests. In effect, this gives two separate server pipelines: one for native components and one for managed components. Managed components execute entirely within the ASP.NET ISAPI extension -- and only for requests specifically mapped to ASP.NET. IIS version 7.0 and above integrates these two pipelines so that services provided by both native and managed modules apply to all requests, regardless of the HTTP handler.

For more information on Integrated Pipeline mode, refer to the following Microsoft article:

Step 2: Application Configuration Settings

Next, I add error pages to my application for 404 and 500 error codes, and I update the application configuration file (web.config) with settings that instruct IIS to use my custom pages for these error codes.

<system.webServer>
  <httpErrors errorMode="Custom" existingResponse="Replace">
    <remove statusCode="404"/>
    <remove statusCode="500"/>
    <error statusCode="404" responseMode="ExecuteURL"
    path="/Pages/Public/Error404.aspx"/>
    <error statusCode="500" responseMode="ExecuteURL"
    path="/Pages/Public/Error500.aspx"/>
  </httpErrors>
</system.webServer>

Now, when a user requests a static resource that does not exist, the user sees the error message generated by my custom page:

Similarly, if a user requests a dynamic resource that does not exist, then the user sees the error message generated by my custom page:

And finally, if an unhandled exception occurs in the application, then the user sees the error message generated by my custom page:

Step 3: Exception Details

The first part of my problem is now solved: the error messages seen by my users are rendered inside a page that is consistent with the layout and style of my application, and the error messages themselves are consistent regardless of the underlying cause of the unhandled exception.

However, in this configuration, when an unhandled exception occurs and IIS executes Error500.aspx, the code behind this page has no details for the exception itself. When an unhandled exception occurs, I need a crash report saved to the file system on the server and sent to me by email. The crash report needs to include the exception details and a stack trace so that I can find and fix the cause of the error.

Some articles suggest that I can identify the unhandled exception using Server.GetLastError, but I get a null value whenever I attempt this.

Exception ex = HttpContext.Current.Server.GetLastError(); // <-- Returns null in Error500.aspx

Note: I have been unable to find a clear explanation for this in Microsoft's documentation. (Please drop me a note if you can direct me to the documentation that explains this behaviour.)

I can solve this by adding an HTTP module with an event handler for application errors. For example, after I add this code to Global.asax, my custom error page can access the current cache for the exception details.

protected void Application_Error(object sender, EventArgs e)
{
    Exception ex = HttpContext.Current.Server.GetLastError();
    CrashReport report = CrashReporter.CreateReport(ex, null);
    HttpContext.Current.Cache[Settings.Names.CrashReport] = report;
}

It is important to note that if I add code at the end of my event handler to invoke Server.ClearError(), my custom error message is NOT displayed to the user (and neither is the default server error message). In fact, if I invoke ClearError() here, then the error message becomes a blank page, with no HTML in the output rendered to the browser. Remember, the purpose of the event handler in this configuration is to store exception details in the current cache (or in the session state) so that it is accessible to the Error500.aspx page. The purpose is NOT to handle the exception itself, and this is the reason the error is not cleared here.

Note: Referring to my earlier point, if I have not cleared the error here, because it is required in order to ensure that my custom error page is executed, then it is not obvious why the call to Server.GetLastError() in the custom error page returns a null value. If you have an explanation for this, then please post a comment.

Improving the Solution

My solution needs to write a crash report to the file system (so we have a permanent record of the event) and it needs to send an email notification (so we are immediately alerted to the event). At any given time, my company is actively developing dozens of applications for various customers, so a reusable solution is important.

Code added to Global.asax is not easily reused across multiple applications, so I created an HTTP module (i.e., a class that inherits from System.Web.IHttpModule), which I can subsequently add to a library and then reference from many different applications.

In order for this solution to work, I add the following settings to the system.webServer element in my web application configuration file (Web.config):

<modules>
    <add name="ApplicationErrorModule" type="Demo.Classes.ApplicationErrorModule" />
</modules>

The code to wireup handling for an application error is simple:

public void Init(HttpApplication application)
{
    application.Error += Application_Error;
}

private void Application_Error(Object sender, EventArgs e)
{
    if (!Settings.Enabled)
        return;

    Exception ex = HttpContext.Current.Server.GetLastError();

    if (UnhandledExceptionOccurred != null)
        UnhandledExceptionOccurred(ex);

    ExceptionOccurred(ex);
}

The code to process the exception itself is basically the same as the code I originally added to the global application event handler, but here I also add code to save the crash report to the file system, and to send a copy to me by email.

private static void ExceptionOccurred(Exception ex)
{
    // If the current request is itself an error page 
    // then we need to allow the exception to pass through.

    HttpRequest request = HttpContext.Current.Request;
    if (Regex.IsMatch(request.Url.AbsolutePath, ErrorPagePattern))
        return;

    // Otherwise, we should handle the exception here

    HttpResponse response = HttpContext.Current.Response;
    CrashReport report = new CrashReport(ex, null);

    // Save the crash report in the current cache 
    // so it is accessible to my custom error pages

    if (HttpContext.Current.Cache != null)
        HttpContext.Current.Cache[Settings.Names.CrashReport] = report;

    // Save the crash report on the file system

    String path = SaveCrashReport(report, request, null);

    // Send the crash report to the programmers

    SendEmail(report, path);

    // Write the crash report to the browser 
    // if there is no replacement defined for the HTTP response

    if (!ReplaceResponse)
    {
        HttpContext.Current.Server.ClearError();

        try
        {
            response.Clear();
            response.StatusCode = 500;
            response.StatusDescription = "Server Error";
            response.TrySkipIisCustomErrors = true;
            response.Write(report.Body);
            response.End();
        }
        catch { }
    }
}

The last part of this function is especially important. If, for some reason, I forget to include the httpErrors section in my webserver configuration element, then I want the body of my crash report rendered to the browser and not the default Yellow Screen of Death (YSOD) that ASP.NET shows when a server error occurs.

Points of Interest

There are many good articles on the topic of ASP.NET application error handling, and there are many good products that are helpful in the development of solutions. Here are just a few references for more information:

History

June 1, 2013: When the BaseErrorPage is loaded on a PostBack request, the response is assigned an HTTP status code of 200 (OK). This enables the "Submit Quick Error Report" feature on the error page.

June 5, 2013: Modified the code to save the crash report for an unhandled exception using a session-safe key. This corrects for the scenario in which multiple concurrent users encounter different exceptions at the same time.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Daniel Miller
Software Developer InSite Information Systems
Canada Canada
I have been consulting as a professional software systems expert for nearly twenty years, building solutions for organizations all over the world. My clients range from small non-profit associations in my own local community to global enterprises like Nestle, Disney, ABC News, and others.
 
I specialize in the design and implementation of Internet-based business solutions at InSite Systems. My work-related research interests include component-based software development, software development process and methodology, and information architecture and data mining.

I have hands-on experience with every aspect of the software project lifecycle, including management and technical roles. Customers value my ability to understand and appreciate their business needs, applying creativity and ingenuity to arrive at technology solutions that are flexible, efficient, and cost-effective.

Comments and Discussions

 
GeneralMy vote of 5 PinmemberJoe Gakenheimer23-Dec-14 19:02 
Questionthanks.and question PinprofessionalUthman Rahimi1-Dec-14 5:57 
GeneralGood PinprofessionalS. K. Tripathi27-Nov-14 20:58 
GeneralMy vote of 5 Pinmemberanil.singh58110-Sep-14 2:05 
Questiondemo.global PinmemberMember 1029795125-Sep-13 17:18 
AnswerRe: demo.global PinprofessionalDaniel Miller1-Oct-13 5:23 
QuestionRe: demo.global Pinprofessionalaarif moh shaikh12-Oct-14 23:00 
QuestionhttpErrors on IIS 6 Pinmemberwaroets19-Jun-13 1:39 
AnswerRe: httpErrors on IIS 6 PinprofessionalDaniel Miller21-Jun-13 6:22 
QuestionIntegrated Pipeline Mode Pinmembermurphymj520915-Jun-13 13:03 
AnswerRe: Integrated Pipeline Mode PinprofessionalDaniel Miller16-Jun-13 4:40 
GeneralRe: Integrated Pipeline Mode Pinmembermurphymj520916-Jun-13 5:26 
GeneralRe: Integrated Pipeline Mode PinprofessionalDaniel Miller16-Jun-13 15:41 
GeneralMy vote of 5 PinmemberMihai MOGA13-Jun-13 21:56 
GeneralMy vote of 5 PinmemberAlexey Prosyankin10-Jun-13 12:54 
SuggestionChange the HttpContext.Current.Cache PinmemberTheMessiah3-Jun-13 20:39 
GeneralRe: Change the HttpContext.Current.Cache PinprofessionalDaniel Miller5-Jun-13 6:45 
GeneralRe: Change the HttpContext.Current.Cache PinprofessionalRichard Deeming7-Jun-13 7:42 
GeneralRe: Change the HttpContext.Current.Cache PinprofessionalDaniel Miller11-Jun-13 6:33 
GeneralRe: Change the HttpContext.Current.Cache PinprofessionalRichard Deeming11-Jun-13 8:30 
GeneralRe: Change the HttpContext.Current.Cache PinprofessionalDaniel Miller14-Jun-13 17:56 
GeneralRe: Change the HttpContext.Current.Cache PinprofessionalRichard Deeming17-Jun-13 3:16 
GeneralRe: Change the HttpContext.Current.Cache PinprofessionalDaniel Miller28-Jun-13 6:38 
GeneralMy vote of 5 PinprofessionalRelicV2-Jun-13 22:53 
GeneralRe: My vote of 5 PinprofessionalDaniel Miller11-Jun-13 6:34 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.141223.1 | Last Updated 5 Jun 2013
Article Copyright 2013 by Daniel Miller
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid