Recipe 8.4. Handling Errors at the Application Level


Problem

You want to report and log all errors in a common location, regardless of where they arise within the application.

Solution

Incorporate the error handling in methods (described in Recipe 8.1), add code to the Page_Error event handler to rethrow the page errors, and add the code to the Application_Error event handler to perform the logging and redirection.

In the code-behind class for your ASP.NET pages that need to perform error handling, use the .NET language of your choice to:

  1. Create a Page_Error event handler.

  2. Rethrow the page errors from within the method. (This is needed to avoid all errors being wrapped with an HttpUnhandledException exception.)

In global.asax, use the .NET language of your choice to:

  1. Create an Application_Error event handler.

  2. Create a detailed message and write it to the event log.

  3. Redirect the user to the error page using Server.Transfer.

The code we've written to demonstrate this solution is shown in Examples 8-6, 8-7, 8-8 through 8-9. The Page_Error code required in all pages is shown in Examples 8-6 (VB) and 8-7 (C#). The Application_Error code required in global.asax is shown in Examples 8-8 (VB) and 8-9 (C#). (Because the .aspx file for this example contains nothing related to the error handling, it is not included here.)

Discussion

The exception model in ASP.NET provides the ability for exceptions to be handled at any level, from the method level to the application level. An unhandled exception is sequentially rethrown to each method in the call stack. If no methods in the call stack handle the exception, the Page_Error event will be raised. If the exception is not handled in the Page_Error event, the event will be rethrown and the Application_Error event is raised. The rethrowing of exceptions to the application level allows for processing in a single location for the application.

To process errors at the application level, each page must include the Page_Error event handler with a single line of code to rethrow the last exception that occurred:

 

Private Sub Page_Error(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles MyBase.Error 'rethrow the last error that occurred Throw Server.GetLastError( ) End Sub 'Page_Error

private void Page_Error(Object sender, System.EventArgs e) { // rethrow the last error that occurred throw Server.GetLastError( ); } // Page_Error

We do this step to avoid having the exception information wrapped with an HttpUnhandledException exception. ASP.NET automatically creates a new HttpUnhandledException at the page level unless you rethrow the last exception, which from ASP.NET's perspective constitutes handling the exception.

One school of thought says this step is unnecessary and that it's fine to have ASP.NET wrap your exceptions at will; all you have to do is ignore all the "outer" exceptions and get the first inner exception. We don't subscribe to this view, however, because there are cases, such as page parse errors, that do not get wrapped with the HttpUnhandledException. This can make it difficult to extract the "real" exception information when no guarantee exists that the "real" exception information is the first inner exception in the chain of exceptions.

Visual Studio users can make the insertion of the Page_Error code on each page much easier by using the built-in macro facilities or adding the code block for the Page_Error event to the toolbox. Alternatively, you can create a base page that contains the Page_Error method and have all of your pages inherit from this base page. With this approach, you do not have to deal with implementing Page_Error in all of your pages.


The error processing for the application is placed in the Application_Error event handler (in global.asax). Much of this code follows a standard pattern, which is illustrated in Examples 8-8 (VB) and 8-9 (C#). The first step is to get a reference to the last exception that occurred:

 

lastException = Server.GetLastError()

lastException = Server.GetLastError();

The next step is to create a detailed message to insert into the event log. This message should contain the message from the most recent exception and a complete dump of all error information in the link list of exceptions. The complete dump is obtained by calling the ToString method of the last exception, as in the following example code:

 

message = lastException.Message & _ vbCrLf & vbCrLf & _ lastException.ToString()

message = lastException.Message + "\r\r" + lastException.ToString();

Next, you can write the message to the event log. As we show in Examples 8-8 and 8-9, this is done by creating a new EventLog object, setting the Source property to a constant containing the name of the event source to write the information to (the Application log in our example), and writing the message to the event log. When writing the entry to the event log, the event type can be set to Error, FailureAudit, Information, SuccessAudit, and Warning, all of which are members of the EventLogEntryType enumeration. Here is the code responsible for writing to the event log in our example:

 

Log = New EventLog Log.Source = EVENT_LOG_NAME Log.WriteEntry(message, _ EventLogEntryType.Error)

log = new EventLog( ); log.Source = EVENT_LOG_NAME; log.WriteEntry(message, EventLogEntryType.Error);

The event log entry created by our example is shown in Example 8-5. The entry shows that a NullReferenceException occurred at line 41 in the code-behind for the example page. If the exception had been wrapped by throwing new exceptions at each error-handling point in the code, they would all have been listed here. This is useful for troubleshooting runtime errors because the source of the error is shown, along with the complete call path to the point the error occurred.

At this point any other notifications, such as sending an email to the system administrator, should be performed. Refer to Recipe 21.7 for information regarding sending emails.

The final step to processing errors at the application level is to clear the error and redirect the user to the page where an error message is displayed:

 

Server.ClearError( ) Server.Transfer("CH08DisplayErrorVB.aspx" &_ "?PageHeader=Error Occurred" &_ "&Message1=" &lastException.Message &_ "&Message2=" &_ "This error was processed at the application level")

Server.ClearError( ); Server.Transfer("CH08DisplayErrorCS.aspx" + "?PageHeader=Error Occurred" + "&Message1=" + lastException.Message + "&Message2=" + "This error was processed at the application level");

If you do not clear the error, ASP.NET will assume the error has not been processed and will handle it for you with its infamous "yellow" screen.


You can use one of two methods to redirect the user to the error page. The first method is to call Response.Redirect, which works by returning information to the browser instructing the browser to do a redirect to the page indicated. This results in an additional round trip to the server. As we show in Examples 8-8 (VB) and 8-9 (C#), the second method is Server.Transfer, which is the method we favor because it transfers the request to the indicated page without the extra browser/server round trip.

Calling Response.Redirect throws a ThreadAbortException as a result of aborting the execution of the page. This exception is handled differently than other exceptions and will not bubble up to the page or application level.


By default, Windows 2000 and 2003 Server provide three event sources: Application, Security, and System. Of the three sources, only the default ASP.NET user (ASPNET) has permission to write to the Application log. Attempts to write to the Security or System logs will result in an exception being thrown in the Application_Error event.


You can make the errors for your application easier to find in the event viewer by creating an event source specific to your application. Without escalating the privileges for the ASP.NET user to the System level (a bad option), you cannot create a new event source within your ASP.NET application. Two options are available. You can use the registry editor and add a new key to the HKEY_LOCAL_MACHINE\System\ CurrentControlSet\Services\EventLog key or create a console application to do the work for you. We suggest the console application because it is easy and repeatable.

Create a console application in the usual fashion, add the following code to it, and run the application while being logged in as a user with administrative privileges. This will create an event source specific to your application. The only change required to the code described here is to change the EVENT_LOG_NAME constant value to the name of your new event source.

 

Const EVENT_LOG_NAME As String = "Your Application" If (Not EventLog.SourceExists(EVENT_LOG_NAME)) Then EventLog.CreateEventSource(EVENT_LOG_NAME, EVENT_LOG_NAME) End If

const String EVENT_LOG_NAME = "Your Application"; if (EventLog.SourceExists(EVENT_LOG_NAME) != null) { EventLog.CreateEventSource(EVENT_LOG_NAME, EVENT_LOG_NAME); }

See Also

Recipes 8.1 and 21.7

Example 8-5. Event log entry for this example

 System.NullReferenceException: {"Object reference not set to an instance of an object."} Data: {System.Collections.ListDictionaryInternal} HelpLink: Nothing HResult: -2147467261 InnerException: Nothing IsTransient: False Message: "Object reference not set to an instance of an object." Source: "App_Web_3q7je6zg" StackTrace: " at ASPNetCookbook.VBExamples. CH08ApplicationLevelErrorHandlingVB.Page_Error(Object sender, EventArgs e) in E:\ASPNetCookbook2\projects\ASPNetCookbookVB2\ CH08ApplicationLevelErrorHandlingVB.aspx.vb:line 41 at System.Web.UI.TemplateControl.OnError(EventArgs e) at System.Web.UI.Page.HandleError(Exception e) at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) at System.Web.UI.Page.ProcessRequest(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) at System.Web.UI.Page.ProcessRequest( ) at System.Web.UI.Page.ProcessRequest(HttpContext context) at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web. HttpApplication.IExecutionStep.Execute( ) at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)" TargetSite: {System.Reflection.RuntimeMethodInfo} 

Example 8-6. Page_Error code for handling errors at the application level (.vb)

 '''*********************************************************************** ''' <summary> ''' This routine handles the error event for the page. It is used to ''' trap all errors for the page and rethrow the exception. The rethrow ''' is needed to avoid all errors being wrapped with an ''' "HttpUnhandledException" exception. ''' </summary> ''' ''' <param name="sender">Set to the sender of the event</param> ''' <param name="e">Set to the event arguments</param> Protected Sub Page_Error(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles Me.Error 'rethrow the last error that occurred Throw Server.GetLastError() End Sub 'Page_Error 

Example 8-7. Page_Error code for handling errors at the application level (.cs)

 ///*********************************************************************** /// <summary> /// This routine handles the error event for the page. It is used to /// trap all errors for the page and rethrow the exception. The rethrow /// is needed to avoid all errors being wrapped with an /// "HttpUnhandledException" exception. /// </summary> /// /// <param name="sender">Set to the sender of the event</param> /// <param name="e">Set to the event arguments</param> private void Page_Error(Object sender, System.EventArgs e) { // rethrow the last error that occurred throw Server.GetLastError( ); } // Page_Error 

Example 8-8. Application_Error code for handling errors at the application level (.vb)

 '''*********************************************************************** ''' <summary> ''' This routine provides the event handler for the application error ''' event. It is responsible for processing errors at the application ''' level. ''' </summary> ''' ''' <param name="sender">Set to the sender of the event</param> ''' <param name="e">Set to the event arguments</param> Sub Application_Error(ByVal sender As Object, ByVal e As EventArgs) Const EVENT_LOG_NAME As String = "Application" Dim lastException As Exception Dim Log As EventLog Dim message As String 'get the last error that occurred lastException = Server.GetLastError() 'create the error message from the message in the last exception along 'with a complete dump of all of the inner exceptions (all exception 'data in the linked list of exceptions) message = lastException.Message & _   vbCrLf & vbCrLf & _   lastException.ToString() 'Insert error information into the event log Log = New EventLog Log.Source = EVENT_LOG_NAME Log.WriteEntry(message, _ EventLogEntryType.Error) 'perform other notifications, etc. here 'clear the error and redirect to the page used to display the 'error information Server.ClearError() Server.Transfer("CH08DisplayErrorVB.aspx" & _ "?PageHeader=Error Occurred" & _  "&Message1=" & lastException.Message & _ "&Message2=" &_ "This error was processed at the application level") End Sub 'Application_Error 

Example 8-9. Application_Error code for handling errors at the application level (.cs)

 ///*********************************************************************** /// <summary> /// This routine provides the event handler for the application error /// event. It is responsible for processing errors at the application /// level. /// </summary> /// /// <param name="sender">Set to the sender of the event</param> /// <param name="e">Set to the event arguments</param> protected void Application_Error(Object sender, EventArgs e) { const String EVENT_LOG_NAME = "Application"; Exception lastException = null; System.Diagnostics.EventLog log = null; String message = null; // get the last error that occurred lastException = Server.GetLastError( ); // create the error message from the message in the last exception along // with a complete dump of all of the inner exceptions (all exception // data in the linked list of exceptions) message = lastException.Message +   "\r\r" +   lastException.ToString( ); // Insert error information into the event log log = new System.Diagnostics.EventLog( ); log.Source = EVENT_LOG_NAME; log.WriteEntry(message, EventLogEntryType.Error); // perform other notifications, etc. here // clear the error and redirect to the page used to display the // error information Server.ClearError( ); Server.Transfer("CH08DisplayErrorCS.aspx" + "?PageHeader=Error Occurred" + "&Message1=" + lastException.Message + "&Message2=" + "This error was processed at the application level"); } // Application_Error 



ASP. NET Cookbook
ASP.Net 2.0 Cookbook (Cookbooks (OReilly))
ISBN: 0596100647
EAN: 2147483647
Year: 2003
Pages: 202

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net