Error handling?everyone's favorite topic right?

Even the best designed applications need to handle and properly manage errors the errors you can plan for and those you cannot.

In this article, you'll learn error handling techniques in ASP.NET. Topics will range from handling common errors with the Try...Catch syntax to logging unhandled errors into the Windows Event Log.

Error handling, paying taxes, and root canals. Each one conjures up a number of negative mental images?some real, some imagined. While I cannot do much to ease the pain of paying taxes or reduce the suffering you might experience during a root canal, this article will enlighten you regarding the ins and outs of error handling in ASP.NET.

Errors you need to concern yourself with fall into two distinct error types?handled errors and unhandled errors. A handled error occurs within your application?you are ready for it and have code in place to take action upon it. For example, maybe you have coded your application to divide one number by another. What if the second number is 0? A divide by zero error will occur and hopefully you will have code in place to handle such a problem. Unhandled errors are those errors that you cannot foresee happening. An example is an incorrectly typed URL reference for a page on your Web site that results in a “404 Page Not Found” error. Hopefully you will have the pieces in place that will direct the user to a much cleaner and more refined page than the default “404 Page Not Found” page.

Let's start by discussing the language constructs available to handle errors within ASP.NET applications. We will take a look at system exception objects, the Try...Catch...Finally commands, and creating you own Exception objects.

Errors and Exceptions

While the terms error and exception are often used interchangeably, there is a distinct difference between the two. An error happens during the execution of a block of program code and alters the normal program flow, thus creating an Exception object. When the flow of a program is interrupted by an error, the program will search for exception handling code to tell the program how to react. Simply put, an error is an event. That event creates an object called an exception. This Exception object contains information about the error including when and where it occurred.

The <customErrors> section of the web.config file is your last chance to handle an unexpected error.

Unlike throwing a punch, you will often hear the phrase “throwing an exception.” This means that an error occurred in a code block and an Exception object was created.

The generic Exception class from which all Exception objects are derived is the System.Exception class. It contains a number of properties designed to give you an easy way to capture and manage information about the Exception object. Table 1 lists System.Exception properties.

Structured vs. Unstructured Error Handling

Visual Basic .NET and ASP.NET support structured error handling. Structured error handling uses a control structure that acts as the exception handling mechanism. This structure allows your program to determine which type of exception was thrown and act accordingly.

Unstructured error handling is the type of error handling supported in prior versions of Visual Basic and VBScript. It is comprised of an Err object and commands such as On Error, Resume, and Error. This article will not discuss the details of unstructured error handling since structured error handling is the preferred method to trap and manage errors.

Try...Catch...Finally Block

The cornerstone upon which structured error handling is built is the Try...Catch...Finally code block. This control structure tests a block of code and if an error occurs, throws an exception.

The Try command signifies the beginning of a block of code. If an error occurs during the execution of the Try block of code, an exception object is created and the appropriate Catch line handles the exception. If the code in the Try command executes without error, the optional Finally code runs. The Finally code executes regardless of whether or not an exception was thrown. It will also execute after an Exit Try and Exit Sub.

Try
    'Code to try and run
Catch ExceptionObject As Exception [Filters]
    'Handle the problem here
Finally
    'Tie up any loose ends here like closing
    'a database connection
End Try

Catch identifies one or more exceptions that may have been thrown. You can use any number of Catch statements, each taking one of three possible forms: Catch, Catch...As, and Catch...When. If an exception does occurs, the Catch statements are evaluated in the order they appear within the Try...Catch...Finally code block, so you should always list your Catch clauses in order, from the more specific down to the more general. A good programming practice is to always use the following as the last Catch statement in a group of Catch statements.

Catch ex As Exception
    'Code to handle the exception here

Optionally, the Catch statement can include a When clause. The When clause is followed by a Boolean expression, and the exception is only handled if the When expression evaluates to True. The opposite is NOT true though. Just because the expression evaluates to true does NOT mean the Catch code will run. In the following example, if the user enters 20 in the txtCustomerRequest textbox then intBananas ends up with 0 after the calculation in the Try block.

Dim intBananas As Integer = 20
Try
  intBananas = intBananas - Me.txtRequest.Text
Catch ex As Exception When intBananas = 0
  Response.Write("DOH!! Yes, we have no bananas!")
  Exit Sub
Catch ex As Exception When intBananas &lt; 10
  Response.Write("Time to order more bananas!")
  Exit Sub
Catch ex As Exception
  Response.Write("Uh oh... unexpected problem")
  Exit Sub
Finally
  Response.Write("mmmmmm bananas!!!")
End Try

If no error occurs during the calculation in the Try block, the Catch clause with “When intBananas = 0” does NOT fire.

The optional Finally code block is always the last code to be executed as control leaves a Try...Catch...Finally code block. This is true whether or not an unhandled exception occurs or if an Exit Try statement is encountered. In the revised code, a Finally clause has been added and will fire whether or not a calculation error occurred.

Dim intBananas As Integer = 20
Try
  intBananas = intBananas - Me.txtRequest.Text
Catch ex As Exception When intBananas = 0
  Response.Write("DOH!! Yes, we have no bananas!")
  Exit Sub
Catch ex As Exception When intBananas &lt; 10
  Response.Write("Time to order more bananas!")
  Exit Sub
Catch ex As Exception
  Response.Write("Uh oh... unexpected problem")
  Exit Sub
Finally
  Response.Write("mmmmmm bananas!!!")
End Try

Common Exception Types

There are a number of different exception types in the .NET Framework. Table 2 lists a few of the more common exception classes and their derived classes, if any.

The Try...Catch...Finally code block in Listing 1 attempts to calculate the date and time 25 years from the value provided in the StartDate variable.

'I hope StartDate contains date/time information.
Dim StartDate As Object 
Dim EndDate As Date
Dim intYearsToAdd As Integer = 25
Try
  EndDate = _
DateAdd("yyyy", intYearsToAdd, StartDate)
Catch ex As System.ArgumentException
  'At least one argument has an invalid value
Catch ex As ArgumentOutOfRangeException
  ' EndDate is later than December 31, 9999
Catch ex As InvalidCastException
  ' StartDate is not a date/time value
Catch ex As Exception
  ' Something unexpected happened
Finally
  ' Last code to execute before leaving
End Try

You can handle the exceptions that you might expect from the DateAdd function in the first three Catch blocks. Any unexpected exception can be handled in the last Catch block. Keep in mind that no matter what happens, the Finally block will always be the last code to execute before leaving this Try...Catch...Finally code block.

SQLException Exception

Since a large number of the applications being developed today work with databases, the example code in Listing 2 shows the SQLException exception being used. The code in Listing 2 demonstrates how to use the SQLException object.

In the above code, the two lines that are most likely to fail and are thus most in need of error handling are the mySQLConnection.Open() and mySQLCommand.ExecuteReader() methods.

Constructing Your Own Exceptions

In addition to using the built-in exception types, you can also build your own. All user-defined application exception classes are derived from ApplicationException, unlike the built-in common language runtime exception classes derived from SystemException.

Let's take a look at a user-defined Exception class.

Public Class NotEnoughPizzaException
    Inherits System.ApplicationException

    Public Sub New()

    End Sub

    Public Sub New(ByVal strMessage As String)
        MyBase.New(strMessage)

    End Sub
End Class

As you can see, the new Exception class is based on System.ApplicationException. I added a second overloading New method in order to pass in to the exception what to do about the problem.

Try
  Throw New NotEnoughPizzaException(_
       "Better order more")
Catch ex As NotEnoughPizzaException
  Response.Write(ex.Message)
End Try

The above code uses the Throw command, which manually throws an exception for the appropriate Catch statement to process.

Working with Unhandled Errors

So far you've used VB.NET to handle potential problems that may arise in the application. What about errors that pop up due to unforeseen circumstances and could not be planned for in your code, such as an invalid URL?

There are three places in ASP.NET that determine how unhandled errors are managed and responded to. Listed in the order in which they occur, the three places are:

Page-Level Exception Handling

Your first option for dealing with an unhandled error happens at the page level. There are two functions you can use to manage the error:

Private Sub Page_Error(ByVal sender As Object, 
ByVal e As System.EventArgs) Handles MyBase.Error

End Sub

Or

Protected Overrides Sub OnError(ByVal e As 
   System.EventArgs)

End Sub

Handling errors in these procedures is simple. A call to Server.GetLastError will return the most recent error. You could also redirect the user to a specific page with Response.Redirect (“MyErrorPage.aspx”) or whatever your default error page may be. While it will not be the most common way you will handle errors, the page-level technique for handling errors has its merits.

  • You may need to override the Application_Error or the customErrors setup in the web.config file (more on this later).
  • You can process the error here and if you want to cancel the error processing, so it doesn't go to the Application_Error or customErrors levels, a call to Server.ClearError will do the trick.

Application-Level Exception Handling

Along with page level error handlers, ASP.NET gives developers the ability to provide application-level error handling. This next section will explain how to add application-level exception handling to your ASP.NET applications.

global.asax

After the page level error handling, the global.asax file provides the next opportunity to defend against errors.

The global.asax file, also known as the ASP.NET application file, resides in the root directory of an ASP.NET application and is an optional file that contains code for responding to application-level events.

Since the global.asax file is optional, if you do not use one, ASP.NET assumes that you have not defined any application or session event handlers. Visual Studio .NET automatically creates a global.asax when you create a new ASP.NET project.

When an error occurs in your application, ASP.NET executes the Application_Error procedure. This is a great place to track and log errors because it is the most functional place to do so. Odds are that during your ASP.NET development you will find that you will not handle too many errors at the page level. Instead you will opt to handle them at the application level.

As stated before, since the page-level error handling comes first, after ASP.NET calls the Page_Error procedure, then ASP.NET calls the Application_Error procedure. There are a number of things you can do here including e-mailing the system administrator, logging the error to the Windows Event Log, and/or redirect the user to another page with Response.Redirect**().**

E-mailing the System Administrator

The code in Listing 3 is from the global.asax file. I've removed a number of methods for the sake of space and clarity. I've coded the Application_Error method to send an e-mail containing information about the most recent error.

One thing to note about the code above is that I've added System.Web.Mail to provide SMTP e-mail capability to the Application_Error method.

Logging Errors to the Windows Event Log

The code in Listing 4 is also from the global.asax file. I've added the ability to write information about the current error to the Windows Event Log to the Application_Error method.

Listing 4 expands on the Listing 3 version of the Application_Error method to include writing a record to the event log. If an event log entry does not exist for the entry, one is created before the new entry is added.

web.config

ASP.NET provides support for an application configuration file called web.config. ASP.NET lets you use the customErrors element of the web.config file as your last chance to gracefully manage an unhandled error since the other two error handlers mentioned so far, Application_Error and Page_Error (or OnError), will get called before customErrors is utilized.

Open up the web.config file for your project and scan down until you find the <customErrors> section. It looks like this:

&lt;customErrors mode="On|Off|RemoteOnly" 
   defaultRedirect="url" 
   &lt;error statusCode="statuscode" redirect="url"/&gt;
&lt;/customErrors&gt;

The mode attribute plays a key role in how ASP.NET handles custom errors. Table 3 lists the possible settings and their effect.

The following will display a generic error page to the user.

&lt;customErrors mode="On" /&gt;

In the event an unhandled error such as a Response.Redirect(“FakePage.aspx”) occurs, the user would see a page similar to Figure 1.

Figure 1: The results of an unhandled error with mode = On without a redirected page to display
Figure 1: The results of an unhandled error with mode = On without a redirected page to display

To redirect the user to another page you would change it to this:

&lt;customErrors mode="On" 
defaultRedirect="ErrorPage.htm" /&gt;

The same Response.Redirect(“FakePage.aspx”) line would now result in the user seeing an error page (see Figure 2).

Figure 2: The results of an unhandled error with mode = On with a redirected page to display.
Figure 2: The results of an unhandled error with mode = On with a redirected page to display.

Using the error clause you can also handle specific errors and redirect the user to the error page for each error.

&lt;customErrors mode="On" 
    defaultRedirect="myerror.htm"&gt;
    &lt;error statusCode="403" 
       redirect="authfailed.aspx"/&gt;
    &lt;error statusCode="404" 
       redirect="filenotfound.aspx"/&gt;
&lt;/customErrors&gt; 

Summary

See, that wasn't all that tough, was it? By using a combination of language features like Try...Catch...Finally and exception objects, as well as understanding the built in error handling mechanisms in ASP.NET, there is no reason why your next ASP.NET application shouldn't be ready to take on the obstacles and errors thrown at it on a daily basis.

Listing 1: Code demonstrating the use of stacked Exception objects

'I hope StartDate contains date/time information.
Dim StartDate As Object 
Dim EndDate As Date
Dim intYearsToAdd As Integer = 25
Try
  EndDate = _
DateAdd("yyyy", intYearsToAdd, StartDate)
Catch ex As System.ArgumentException
  'At least one argument has an invalid value
Catch ex As ArgumentOutOfRangeException
  ' EndDate is later than December 31, 9999
Catch ex As InvalidCastException
  ' StartDate is not a date/time value
Catch ex As Exception
  ' Something unexpected happened
Finally
  ' Last code to execute before leaving
End Try

Listing 2: Code demonstrating the use of the SQLException object


Dim mySqlConnection As New SqlConnection(MyConnectionString)
Dim mySqlCommand As SqlCommand
Dim strSql As String

strSql = "INSERT INTO Orders (OrderDate, CustomerID)"
strSQL += " values ('4/30/2003', '812')"
mySqlCommand = New SqlCommand(strSql, mySqlConnection)

Try
   mySqlConnection.Open()              
   mySqlCommand.ExecuteReader(_
      CommandBehavior.CloseConnection)
   Message.text = "New Order added"
Catch SQLexc As sqlexception
   Message.text = Message.text + SQLexc.tostring()
Catch ex As Exception
   If InStr(1, ex.ToString, "duplicate key") &gt; 0 Then
      Message.text = Message.text + "No duplicate values."
   Else
      Message.text = Message.text + ex.ToString()
   End If
Finally
   mySqlConnection.Close()
End Try

Listing 3: Code from the global.asax to send an e-mail in the event of an application error

Imports System.Web
Imports System.Web.SessionState
Imports System.Diagnostics
Imports System.Web.Mail

Public Class Global
    Inherits System.Web.HttpApplication

    Sub Application_Error(ByVal sender As Object, _
        ByVal e As EventArgs)
        
        'Send an email to admin when site wide error occurs
        Dim mail As New MailMessage()
        Dim ErrorMessage = _
           "The error description is as follows : " _
           &amp; Server.GetLastError.ToString
        mail.From = "<a href="mailto://jduffy@takenote.com">jduffy@takenote.com</a>"
        mail.To = "<a href="mailto://info@takenote.com">info@takenote.com</a>"
        mail.Subject = "Error in the Site"
        mail.Priority = MailPriority.High
        mail.BodyFormat = MailFormat.Text
        mail.Body = ErrorMessage
        SmtpMail.SmtpServer = "put.your.SMTP.server.here"
        SmtpMail.Send(mail)
    End Sub

End Class

Listing 4: Code from the global.asax to write an entry to the Windows Event Log

Imports System.Web
Imports System.Web.SessionState
Imports System.Diagnostics
Imports System.Web.Mail

Public Class Global
    Inherits System.Web.HttpApplication

    Sub Application_Error(ByVal sender As Object, _
        ByVal e As EventArgs)

        'Add a record to the log with the latest 
        ' error information
        Dim ErrorDescription As String = _
            Server.GetLastError.ToString

        'Create the event log record if it does not exist 
        Dim EventLogName As String = "MySample"
        If (Not EventLog.SourceExists(EventLogName)) Then
            EventLog.CreateEventSource(EventLogName, EventLogName)
        End If

        'Add a record to the new event log entry
        Dim Log As New EventLog()
        Log.Source = EventLogName
        Log.WriteEntry(ErrorDescription, EventLogEntryType.Error)

        'Send an email to admin when site wide error occurs
        Dim mail As New MailMessage()
        Dim ErrorMessage = _
           "The error description is as follows : " _
           &amp; Server.GetLastError.ToString
        mail.From = "<a href="mailto://jduffy@takenote.com">jduffy@takenote.com</a>"
        mail.To = "<a href="mailto://info@takenote.com">info@takenote.com</a>"
        mail.Subject = "Error in the Site"
        mail.Priority = MailPriority.High
        mail.BodyFormat = MailFormat.Text
        mail.Body = ErrorMessage
        SmtpMail.SmtpServer = "put.your.SMTP.server.here"
        SmtpMail.Send(mail)

    End Sub

End Class

Table 1: System.Exception properties

PropertyDescription
HelpLinkGets or sets a link to the Help file associated with this exception
InnerExceptionGets the Exception instance that caused the current exception
MessageGets a message that describes the current exception
SourceGets or sets the name of the application or the object that causes the error
StackTraceGets a string representation of the items on the call stack at the time the current exception was thrown
TargetSiteGets the method that throws the current exception

Table 2: Common exception types

Exception ClassDescriptionDerived Classes
ArgumentExceptionGenerated when at least one of the arguments passed to a method is not validArgumentNullException ArgumentOutOfRangeException ComponentModel.InvalidEnumArgumentException DuplicateWaitObjectException
ArithmeticExceptionGenerated by error occurring in an arithmetic, casting, or conversion processDivideByZeroException NotFiniteNumberException OverflowException
Data.DataExceptionGenerated by an error using ADO.NET componentsData.ConstraintException Data.DeletedRowInaccessibleException Data.DuplicateNameException Data.InRowChangingEventException Data.InvalidConstraintException Data.InvalidExpressionException Data.MissingPrimaryKeyException Data.NoNullAlllowedException Data.ReadOnlyException Data.RowNotInTableException Data.StringTypingException Data.TypedDataSetGeneratorException Data.VersionNotFoundException
Data.SqlClient.SqlExceptionGenerated by a warning or error returned by SQL ServerNone
IndexOutofRangeExceptionGenerated by an attempt to access an element of an array using an index that is outside the bounds of the arrayNone
InvalidCastExceptionGenerated by an invalid casting or explicit conversionNone
IO.IOExceptionGenerated when an I/O error occursIO.DirectoryNotFoundException IO.EndOfStreamException IO.FileLoadException IO.FileNotFoundException IO.PathTooLongException

Table 3: customErrors mode settings

SettingDescription
OnSpecifies that custom errors are enabled. If there is no defaultRedirect specified the user sees a generic error.
OffSpecifies that custom errors are disabled.
RemoteOnlySpecifies that custom errors are shown only to remote users. ASP.NET errors will display in the user is located on the local host. RemoteOnly is the default setting.