Error Handling


Error handling has always been a bit of a juggling act “ that fine line between what you need to protect your code against and what you don't. While it would be nice to assume that all data handled by your code is correct (it was validated wasn't it?) and that all resources will be available, that's not something you can take for granted. As applications become more complex, they rely on other programs, components , external resources, and so on, and you have to assume that at some point something might go wrong. Taking this defensive attitude will lead to more robust applications, as well as making development easier in the future.

In previous versions of ASP you probably relied upon the VBScript On Error statement (or Try...Catch if you are one of the few who used JScript server-side). While acceptable, it was never a good solution, as it was difficult to build centralized error routines and provide a neat way to manage the errors. The CLR has solved this by providing support for structured exception handling .

Structured Exception Handling

Structured exception handling is a fundamental part of the CLR, and provides .NET programmers with a great way of managing errors, and has a good set of features:

  • It is cross-language; therefore exceptions can be raised in one language and caught in another.

  • It is cross-process and cross-machine, so even when remote .NET components raise exceptions, they can be caught locally.

  • It is a hierarchical system, allowing exceptions to be layered, with each exception able to encompass another. This means that components can trap exceptions from underlying objects (such as data access layers ), and raise their own exception, including the original as part of it. This allows programs to trap exceptions at a high-level, but drill-down through the exception list to find more fine-grained exception information.

  • It obviates the need to check for return values from every function or method call “ errors that are raised as exceptions will never be missed.

  • There is no performance downside unless an exception is raised.

All exceptions in the CLR are derived from a single base class, and many of the classes in the framework extend from this to provide finer-grained errors.

The Exception Class

The Exception class provides the following properties to detail the problem:

Property

Description

HelpLink

A URN or URL indicating the help file associated with this error.

HResult

For COM interoperability, the Windows 32-bit HRESULT .

InnerException

For nested exceptions, an exception object representing the inner exception.

Message

The textual error message.

Source

The name of the application, or object, that raised the error. This will be the assembly name if left blank by the application throwing the exception.

StackTrace

A string containing the stack trace.

TargetSite

A MethodBase object detailing the method that raised the exception.

There is also one method, GetBaseException (for use with nested exceptions), that returns the original exception that was raised.

Try...Catch

You can manage exceptions by use of the Try...Catch statement. In Visual Basic .NET, the general syntax is:

  Try   ' code block to run   [Catch [  exception  [As  type  ]] [When  expression  ]   ' code to run if the exception generated matches   ' the exception and expression defined above   [Exit Try]   ]   Catch [  exception  [As  type  ]] [When  expression  ]   ' code to run if the exception generated matches   ' the exception and expression defined above   [Exit Try]   [Finally   ' code that always runs, whether or not an exception   ' was caught, unless Exit Try is called   ]   End Try  

There can be multiple Catch blocks to allow for fine-grained control over exceptions. For example, imagine a function that returns the contents of a file:

  Public Function GetFileContents(fileName As String) As String   Dim fileContents As String = ""   Try   Dim sr As New StreamReader(fileName)   fileContents = sr.ReadToEnd()   sr.Close()   Catch exArg As ArgumentException   ' argument not supplied   Catch exFNF As FileNotFoundException   ' file wasn't found  handle error   Finally   Return fileContents   End Try   End Function  

When using multiple Catch blocks, you should put the finest-grain exception first, and the widest (the base class Exception , for example) last. This is because these blocks are tried in the order of declaration. So, putting the widest exception first could hide a narrower one.

The Finally block will always be run, except when you use Exit Try to exit from the block, whether or not an exception is thrown. Even if used within a procedure, and the procedure is exited from within a Catch block, the Finally block still runs.

Raising Exceptions

To raise exceptions use the Throw statement. For example, if you forget the file name to the constructor of the StreamReader you normally get the message shown in Figure 22-6:

click to expand
Figure 22-6:
Note

In this example, the error is being thrown again “ back to the calling procedure. This is why the Try...Catch block is visible in the code.

If you wanted to provide your own error message, you could throw a new error containing the new details:

  Public Function GetFileContents(fileName As String) As String   Dim fileContents As String = ""   Try   Dim sr As New StreamReader(fileName)   fileContents = sr.ReadToEnd()   sr.Close()   Catch exArg As ArgumentException   Throw New ArgumentException("Doh  you forgot the filename!")   Finally   Return fileContents   End Try   End Function  

This gives you the results shown in Figure 22-7:

click to expand
Figure 22-7:

This gives you the new error message, but the source error is shown on the line where you raised the error, rather than where the error was actually generated. If this isn't acceptable, you can pass in the original exception as an argument when you throw the new exception. For example:

  Catch exArg As ArgumentException   Throw New ArgumentException("Doh  you forgot the filename.", exArg)  

The result is shown in Figure 22-8:

click to expand
Figure 22-8:

Hmm, but wait a minute “ where has the new error message gone? You have re-thrown the error using the original exception details, which is why you are now on the correct line, but your error message isn't shown. Well, if you look at the stack trace you will see it as in Figure 22-9:

click to expand
Figure 22-9:

The original exception is shown first, and your error is shown next . It is probably not a scenario you would use that often, but the reason for generating exceptions is so that you can catch them and take some appropriate action, such as displaying a message to the user (who doesn't need to see the stack trace or the errors).

Custom Exceptions

Taking the custom error message one step further allows you to create your own exception class, derived not from the Exception class, but from ApplicationException . For example, consider a parser for a text file of a specific format, and you want to have parsing errors integrated with the exception handling system. You could create a new exception like this:

  Imports System   Namespace Wrox   Public Class InvalidContentException   Inherits ApplicationException   Public Sub New()   MyBase.New()   End Sub   Public Sub New(message As String)   MyBase.New(message)   End Sub   Public Sub New(message As String, inner As Exception)   MyBase.New(message, inner)   End Sub   End Class   End Namespace  

This simply defines a new class derived from ApplicationException . The class can be any name, but by convention it should end in Exception . Within this class the three standard constructors are implemented to call the base class constructors. Now, when you throw this exception you get the results shown in Figure 22-10:

click to expand
Figure 22-10:

Notice this is the same layout as standard exceptions “ the only difference is that your exception name is used. The advantage of this system is that you can just trap your custom exception. For example:

  Try   ' run custom parser   Catch ex As InvalidContentException   ' notify user of error   End Try  

Within the new exception class you can do more than just call the base class methods . For example, you could add extra tracing information, or even overload the properties to provide further information.

Exception Types

Many of the internal classes define their own exceptions that you can catch. For example, when connecting to SQL Server, a data access problem might raise a SQLException . Although the documentation details what exceptions will be raised, the simplest way to find a list of the exceptions available is to use a tool like WinCV, and search for exception. This gives you a list as shown in Figure 22-11:

click to expand
Figure 22-11:

Here you can see exactly what exceptions are available, and to which namespace they belong. WinCV is in the bin directory of the SDK install.

Exceptions and COM

The exception handling system in .NET integrates well with the error handling of COM components, whatever type of interoperability you are performing. If you are using .NET components from COM, then exceptions raised in the .NET component will lead to a Windows HRESULT being passed to the COM component. If you are using COM components from .NET, and the COM component returns an HRESULT , this will be mapped to a .NET exception. The type of exception depends on the HRESULT “ for example, the HRESULT E_ACCESSDENIED error will be mapped to UnauthorizedAccessException , while unknown instances of HRESULTs will be mapped to COMException . Additionally, COM components that support IErrorInfo will have these details mapped to the properties of the exception.

This topic is covered in more detail in Chapter 23 where you look at migration and interoperability.

ASP.NET Error Handling

In addition to the CLR exception system, ASP.NET also provides ways of handling errors. There are three ways in which this can be done:

  • At the page-level error event for errors on an individual page

  • At the application-level error event for errors in an application

  • In the application configuration file to perform declarative error handling for an application

Which method you use depends on what you want to do when handling errors and what sort of structure you wish your application to have.

Page_Error Event

At the page level you can use the Page_Error event to trap errors on a page. For example:

  <%@ Import Namespace="System.Data.SqlClient" %>   <script runat="server">   Sub Page_Load(Sender As Object, E As EventArgs)   Dim conn As SqlConnection   Response.Write(conn.ToString())   End Sub   Sub Page_Error(Sender As Object, E As EventArgs)   Dim err As String = "Error in: " & Request.Url.ToString() & "<p/>" & _   "Stack Trace Below:<br/>" & _   Server.GetLastError().ToString()   Response.Write(err)   Server.ClearError()   End Sub   </script>  

This uses the Page_Load event to generate a runtime error (by trying to display the connection string details when the connection hasn't been opened), which can be caught by the Page_Error event. The error details are available by calling the GetLastError method to return the exception object generated by the error. In this case, just output the details, but you could log the error or notify the web site administrator (see the Notifying Administrators of Errors section a little later in this chapter for details).

Application_Error Event

Like the Page_Error event, there is also an Application_Error event, which is declared in the global.asax file. For example, the following block uses similar code as seen in the previous section:

  Sub Application_Error(Sender As Object, E As EventArgs)     Dim err As String = "<h1>Application Error</h1>" & _   "Error in: " & Request.Url.ToString() & "<p/>" & _   "Stack Trace Below:<br/>" & _   Server.GetLastError().ToString()   Response.Write(err)   Server.ClearError()   End Sub  

This event will be run if no Page_Error event traps the error. This event is one place where you could put application-wide error logging information.

Application Error Configuration

As well as programmatic handling of errors, there is also the declarative method utilizing the web.config file. This method is for handling HTTP errors or errors not handled elsewhere in the application, and providing a simple way to return custom error pages. It is configured with the customErrors element of the system.web section in the configuration file:

  <system.web>   <customErrors defaultRedirect="  url  " mode="OnOffRemoteOnly">   <error statusCode="  code  " redirect="  url  "/>   </customErrors>   </system.web>  

The defaultRedirect attribute indicates the page that should be shown if no other errors are trapped. It should be the last resort page for completely unexpected errors. The mode can be:

  • On :To specify that custom errors be enabled

  • Off :To specify that custom errors be disabled

  • RemoteOnly :To specify that custom errors are only enabled for remote clients

The RemoteOnly option allows you to define custom errors for all users, except those accessing the page locally. This allows users to see a nice error page while you can log onto the server and see the exception details and stack trace.

The error sub-element allows individual pages to be shown for individual errors. For example:

  <system.web>   <customErrors defaultRedirect="DefaultErrorPage.aspx" mode="RemoteOnly">   <error statusCode="404" redirect="FileNotFound.aspx"/>   </customErrors>   </system.web>  

Here, the 404 (not found) error will redirect to FileNotFound.aspx , while all other unhandled errors will go to DefaultErrorPage.aspx . You can have multiple error elements to cater to multiple HTTP errors.

Notifying Administrators of Errors

The rich set of error handling that ASP.NET provides is a great way to trap errors, but the users aren't the only people who need to know that something has gone wrong. A user will see the error page, but administrators and developers also need to know when things go wrong. There are many ways in which you can do this, but by far the simplest is to automatically write an event to the event log or to mail the administrator. Both of these are techniques you could add to the error pages so that whenever an error occurs, the details are logged somehow.

One important point to note about the event log is that you require special permissions to create new logs. In Chapter 14, we discussed the ASPNET account that, by default, all ASP.NET pages run under, and this account doesn't have permissions to create new event logs. This is because creation of new logs requires write permission in the registry, and by default, the ASPNET account doesn't require this. By and large you shouldn't need to create new logs within your ASP.NET pages, since this should really be an installation task. Once the log is created, then you don't need any special permission to write to the log.

If you do need to create a new log from within ASP.NET then you have two options, both of which involve the account under which ASP.NET runs. This first option is to modify the configuration file so that ASP.NET runs under a different account “ perhaps the system account. This account allows the creation of event logs, but means that your entire application is now running in a less secure environment, which isn't recommended. Alternatively, you can move just the event log pages to a new directory and create a web.config file to allow impersonation for just this task, adding the following lines:

  <configuration>   <system.web>   <identity impersonate="true" userName="RegWriter" password="iRule" />   </system.web>   <configuration>  

The user name should be an account with permissions to write to the registry.

Writing to the Event Log

Writing to the event log is extremely simple, as there is an EventLog class in the System.Diagnostics namespace. Consider the following function:

  <%@ Import Namespace="System.Diagnostics" %>   ... ' rest of page here   Sub WriteToEventLog(LogName As String, Message As String)   ' Create event log if it doesn't exist   If (Not EventLog.SourceExists(LogName)) Then   EventLog.CreateEventSource(LogName, LogName)   End if   ' Fire off to event log   Dim Log as New EventLog(LogName)   Log.Source = LogName   Log.WriteEntry(Message, EventLogEntryType.Error)   End Sub  

This first creates a custom event log if it doesn't already exist, and then writes a new entry into the log. You could call this function like this:

  Try   ' do something here   Catch ex As Exception   WriteToEventLog("Wrox", ex.ToString())   End Try  

The result appears in the event log like any other event (Figure 22-12). Notice that this is in a custom log.

click to expand
Figure 22-12:

Sending Email Error Notifications

An alternative approach would be to send an automatic mail message, using the MailMessage class of the System.Web.Mail namespace. For example:

  <%@ Import Namespace="System.Web.Mail" %>   ... ' rest of page here   Public Sub SendMail(message As String)   Dim MyMessage as New MailMessage   MyMessage.To = "webmaster@yourcompany.com"   MyMessage.From = "ASPApplication@yourcompany.com"   MyMessage.Subject = "Unhandled ASP.NET Error"   MyMessage.BodyFormat = MailFormat.Text   MyMessage.Body = message   SmtpMail.SmtpServer = "YourSMTPServer"   SmtpMail.Send(MyMessage)   End Sub  

Notice that the Send method is a static method, and therefore you don't need to create an instance of the SmtpMail component.

You could call this like so:

  Try   ' do something here   Catch ex As Exception   SendMail(ex.ToString())   End Try  



Professional ASP. NET 1.1
Professional ASP.NET MVC 1.0 (Wrox Programmer to Programmer)
ISBN: 0470384611
EAN: 2147483647
Year: 2006
Pages: 243

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