ProblemYou want to report and log all errors in a common location, regardless of where they arise within the application. SolutionIncorporate 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:
In global.asax, use the .NET language of your choice to:
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.) DiscussionThe 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.
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");
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.
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 AlsoRecipes 8.1 and 21.7 Example 8-5. Event log entry for this example
Example 8-6. Page_Error code for handling errors at the application level (.vb)
Example 8-7. Page_Error code for handling errors at the application level (.cs)
Example 8-8. Application_Error code for handling errors at the application level (.vb)
Example 8-9. Application_Error code for handling errors at the application level (.cs)
|