Recipe 7.3 Handling Errors at the Application Level

     

7.3.1 Problem

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

7.3.2 Solution

Incorporate the error handling in methods (described in Recipe 7.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 the code-behind for 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 Example 7-6 through Example 7-9. The Page_Error code required in all pages is shown in Example 7-6 (VB) and Example 7-7 (C#). The Application_Error code required in the global.asax code-behind is shown in Example 7-8 (VB) and Example 7-9 (C#). (Because the .aspx file for this example contains nothing related to the error handling, it is not included here.)

7.3.3 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 is raised. If the exception is not handled in the Page_Error event, the event is rethrown and the Application_Error event is raised. The rethrowing of exceptions at 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, as follows :

 
figs/vbicon.gif
 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 
figs/csharpicon.gif
 private void Page_Error(Object sender, System.EventArgs e) { // rethrow the last error that occurred  throw Server.GetLastError( );  } // Page_Error 

Why is this step required? We do this to avoid having the exception information wrapped with an HttpUnhandledException exception. It turns out that ASP.NET automatically creates a new HttpUnhandledException at the page level unless you simply rethrow the last exception, which from ASP.NET's prospective constitutes handling the exception.

There is a school of thought that says this step isn't necessary and that it's fine to have ASP.NET wrap your exceptions at will; all you have to do is just 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 there is no guarantee that the "real" exception information is the first inner exception in the chain of exceptions.

Visual Studio .NET users can make the insertion of the Page_Error code on each page much easier by either using the built-in macro facilities or adding the code block for the Page_Error event to the toolbox. Alternately, 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 the global.asax code-behind). Much of this code follows a fairly standard pattern, which is illustrated in Example 7-8 (VB) and Example 7-9 (C#). The first step is to get a reference to the last exception that occurred:

 
figs/vbicon.gif
 lastException = Server.GetLastError( ) 
figs/csharpicon.gif
 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:

 
figs/vbicon.gif
 message =  lastException.Message  & _ vbCrLf & vbCrLf & _  lastException.ToString( )  
figs/csharpicon.gif
 message =  lastException.Message  + "\r\r" +  lastException.ToString( );  

Next, you can write the message to the event log. As we show in Example 7-8 and Example 7-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), and then 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:

 
figs/vbicon.gif
 Log = New EventLog( ) Log.Source = EVENT_LOG_NAME Log.WriteEntry(message, _ EventLogEntryType.Error) 
figs/csharpicon.gif
 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 7-5. The entry shows that a NullReferenceException occurred at line 64 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 be listed here. This is very useful for troubleshooting runtime errors because the actual source of the error is shown, along with the complete call path to the point the error occurred.

Example 7-5. Event log entry for this example
  {System.NullReferenceException}   [System.NullReferenceException]: {System.NullReferenceException}   HelpLink: Nothing   InnerException: Nothing   Message: "Object reference not set to an instance of an object."   Source: "VBExamples"   StackTrace: " at ASPNetCookbook.VBExamples.CH07ApplicationLevelErrorHandlingVB.Page_ Error(Object sender, EventArgs e) in D:\ASPNetBook\Projects\ASPNetCookbookSolution\ UIProjects\VBExamples\CH07ApplicationLevelErrorHandlingVB.aspx.vb:line 64  at System.Web.UI.TemplateControl.OnError(EventArgs e) at System.Web.UI.Page.HandleError(Exception e) at System.Web.UI.Page.ProcessRequestMain( ) at System.Web.UI.Page.ProcessRequest( ) at System.Web.UI.Page.ProcessRequest(HttpContext context) at System.Web.CallHandlerExecutionStep.System.Web.HttpApplication+IExecutionStep. Execute( ) at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)" TargetSite: {System.Reflection.RuntimeMethodInfo} 

At this point any other notifications, such as sending an email to the system administrator, should be performed. Refer to Recipe 18.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:

 
figs/vbicon.gif
 Server.ClearError( ) Server.Transfer("CH07DisplayErrorVB.aspx" & _ "?PageHeader=Error Occurred" & _ "&Message1=" & lastException.Message & _ "&Message2=" & _ "This error was processed at the application level") 
figs/csharpicon.gif
 Server.ClearError( ); Server.Transfer("CH07DisplayErrorCS.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 either 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 Example 7-8 (VB) and Example 7-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.

By default, Windows 2000 and 2003 Server provides three event sources: Application, Security, and System. Of the three sources, the default ASP.NET user (ASPNET) only 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 ASPNET user to the System level (not a good 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 simple 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 then run the application while logged in as a user with administrative privileges. This will create an event source specific to your application. The only change that is required to the code described here is to change the EVENT_LOG_NAME constant value to the name of your new event source.

 
figs/vbicon.gif
 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 
figs/csharpicon.gif
 const String EVENT_LOG_NAME = "Your Application"; if (EventLog.SourceExists(EVENT_LOG_NAME) != null) { EventLog.CreateEventSource(EVENT_LOG_NAME, EVENT_LOG_NAME); } 

7.3.4 See Also

Recipe 7.1; Recipe 18.7

Example 7-6. Page_Error code for handling errors at the application level (.vb)
 '************************************************************************* ' ' ROUTINE: Page_Error ' ' DESCRIPTION: 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. '------------------------------------------------------------------------- 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 

Example 7-7. Page_Error code for handling errors at the application level (.cs)
 //************************************************************************ // // ROUTINE: Page_Error // // DESCRIPTION: This routine provides the event handler for the page // error event. It builds a URL with the error information // then sets the ErrorPage property to the URL. //------------------------------------------------------------------------ private void Page_Error(Object sender, System.EventArgs e) {  // rethrow the last error that occurred   throw Server.GetLastError( );  } // Page_Error 

Example 7-8. Application_Error code for handling errors at the application level (.vb)
 '************************************************************************* ' ' ROUTINE: Application_Error ' ' DESCRIPTION: This routine provides the event handler for the ' application error event. It is responsible for ' processing errors at the application level. '------------------------------------------------------------------------- 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("CH07DisplayErrorVB.aspx" & _ "?PageHeader=Error Occurred" & _ "&Message1=" & lastException.Message & _ "&Message2=" & _ "This error was processed at the application level") End Sub 'Application_Error 

Example 7-9. Application_Error code for handling errors at the application level (.cs)
 //************************************************************************ // // ROUTINE: Application_Error // // DESCRIPTION: This routine provides the event handler for the // application error event. It is responsible for // processing errors at the application level. //------------------------------------------------------------------------ protected void Application_Error(Object sender, EventArgs e) { const String EVENT_LOG_NAME = "Application"; Exception lastException = null; 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 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("CH07DisplayErrorCS.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: 2006
Pages: 179

Similar book on Amazon

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