Click here to Skip to main content
15,881,882 members
Articles / Web Development / HTML

ASP.NET Application Error Handling

Rate me:
Please Sign up or sign in to vote.
4.96/5 (38 votes)
26 Jan 2015CPOL6 min read 196.9K   3.2K   94   37
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:

Image 1

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:

Image 2

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:

Image 3

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.

Image 4

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.

ASP.NET
<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:

Image 5

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

Image 6

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

Image 7

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.

C#
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.

C#
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;
}

Image 8

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):

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

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

C#
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.

C#
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)


Written By
Chief Technology Officer Shift iQ
Canada Canada
I have been building software systems for more than 20 years, working for organizations that range from small non-profit associations in my local community to global Fortune 500 enterprises.

I specialize in the design and implementation of online database solutions. My work-related research interests include software design patterns and information architecture.

Comments and Discussions

 
QuestionIts not working Pin
Senthilaan4-May-15 22:52
professionalSenthilaan4-May-15 22:52 
AnswerRe: Its not working Pin
Daniel Miller10-May-15 4:38
professionalDaniel Miller10-May-15 4:38 
GeneralMy vote of 5 Pin
Humayun Kabir Mamun26-Jan-15 21:50
Humayun Kabir Mamun26-Jan-15 21:50 
Questiongood Pin
Soumitra Mithu26-Jan-15 5:43
professionalSoumitra Mithu26-Jan-15 5:43 
Question[My vote of 1] Demo.Global Pin
snashter25-Jan-15 11:05
snashter25-Jan-15 11:05 
AnswerRe: [My vote of 1] Demo.Global Pin
Daniel Miller26-Jan-15 3:47
professionalDaniel Miller26-Jan-15 3:47 
GeneralMy vote of 5 Pin
jgakenhe23-Dec-14 18:02
professionaljgakenhe23-Dec-14 18:02 
Questionthanks.and question Pin
Uthman Rahimi1-Dec-14 4:57
professionalUthman Rahimi1-Dec-14 4:57 
GeneralGood Pin
Santosh K. Tripathi27-Nov-14 19:58
professionalSantosh K. Tripathi27-Nov-14 19:58 
GeneralMy vote of 5 Pin
anil.singh58110-Sep-14 1:05
anil.singh58110-Sep-14 1:05 
Questiondemo.global Pin
Member 1029795125-Sep-13 16:18
Member 1029795125-Sep-13 16:18 
AnswerRe: demo.global Pin
Daniel Miller1-Oct-13 4:23
professionalDaniel Miller1-Oct-13 4:23 
QuestionRe: demo.global Pin
aarif moh shaikh12-Oct-14 22:00
professionalaarif moh shaikh12-Oct-14 22:00 
AnswerRe: demo.global Pin
Praveen Kumar Gundu13-Jan-15 15:19
Praveen Kumar Gundu13-Jan-15 15:19 
QuestionhttpErrors on IIS 6 Pin
waroets19-Jun-13 0:39
waroets19-Jun-13 0:39 
AnswerRe: httpErrors on IIS 6 Pin
Daniel Miller21-Jun-13 5:22
professionalDaniel Miller21-Jun-13 5:22 
QuestionIntegrated Pipeline Mode Pin
murphymj520915-Jun-13 12:03
murphymj520915-Jun-13 12:03 
AnswerRe: Integrated Pipeline Mode Pin
Daniel Miller16-Jun-13 3:40
professionalDaniel Miller16-Jun-13 3:40 
Open the Internet Information Services (IIS) Manager and select Application Pools from the navigation hierarchy in the left panel. Then select the application pool to which your web application is assigned, and click Edit Application Pool Basic Settings from the Actions menu. You should see a dialog with a field labeled "Managed pipeline mode", having two options: Integrated and Classic.
GeneralRe: Integrated Pipeline Mode Pin
murphymj520916-Jun-13 4:26
murphymj520916-Jun-13 4:26 
GeneralRe: Integrated Pipeline Mode Pin
Daniel Miller16-Jun-13 14:41
professionalDaniel Miller16-Jun-13 14:41 
GeneralMy vote of 5 Pin
Ștefan-Mihai MOGA13-Jun-13 20:56
professionalȘtefan-Mihai MOGA13-Jun-13 20:56 
GeneralMy vote of 5 Pin
Oleksii Prosiankin10-Jun-13 11:54
Oleksii Prosiankin10-Jun-13 11:54 
SuggestionChange the HttpContext.Current.Cache Pin
TheMessiah3-Jun-13 19:39
TheMessiah3-Jun-13 19:39 
GeneralRe: Change the HttpContext.Current.Cache Pin
Daniel Miller5-Jun-13 5:45
professionalDaniel Miller5-Jun-13 5:45 
GeneralRe: Change the HttpContext.Current.Cache Pin
Richard Deeming7-Jun-13 6:42
mveRichard Deeming7-Jun-13 6:42 

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

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