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 < 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 < 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:
<customErrors mode="On|Off|RemoteOnly"
defaultRedirect="url"
<error statusCode="statuscode" redirect="url"/>
</customErrors>
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.
<customErrors mode="On" />
In the event an unhandled error such as a Response.Redirect(“FakePage.aspx”) occurs, the user would see a page similar to Figure 1.
To redirect the user to another page you would change it to this:
<customErrors mode="On"
defaultRedirect="ErrorPage.htm" />
The same Response.Redirect(“FakePage.aspx”) line would now result in the user seeing an error page (see Figure 2).
Using the error clause you can also handle specific errors and redirect the user to the error page for each error.
<customErrors mode="On"
defaultRedirect="myerror.htm">
<error statusCode="403"
redirect="authfailed.aspx"/>
<error statusCode="404"
redirect="filenotfound.aspx"/>
</customErrors>
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") > 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 : " _
& 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 : " _
& 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
Property | Description |
---|---|
HelpLink | Gets or sets a link to the Help file associated with this exception |
InnerException | Gets the Exception instance that caused the current exception |
Message | Gets a message that describes the current exception |
Source | Gets or sets the name of the application or the object that causes the error |
StackTrace | Gets a string representation of the items on the call stack at the time the current exception was thrown |
TargetSite | Gets the method that throws the current exception |
Table 2: Common exception types
Exception Class | Description | Derived Classes |
---|---|---|
ArgumentException | Generated when at least one of the arguments passed to a method is not valid | ArgumentNullException ArgumentOutOfRangeException ComponentModel.InvalidEnumArgumentException DuplicateWaitObjectException |
ArithmeticException | Generated by error occurring in an arithmetic, casting, or conversion process | DivideByZeroException NotFiniteNumberException OverflowException |
Data.DataException | Generated by an error using ADO.NET components | Data.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.SqlException | Generated by a warning or error returned by SQL Server | None |
IndexOutofRangeException | Generated by an attempt to access an element of an array using an index that is outside the bounds of the array | None |
InvalidCastException | Generated by an invalid casting or explicit conversion | None |
IO.IOException | Generated when an I/O error occurs | IO.DirectoryNotFoundException IO.EndOfStreamException IO.FileLoadException IO.FileNotFoundException IO.PathTooLongException |
Table 3: customErrors mode settings
Setting | Description |
---|---|
On | Specifies that custom errors are enabled. If there is no defaultRedirect specified the user sees a generic error. |
Off | Specifies that custom errors are disabled. |
RemoteOnly | Specifies 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. |