Introduction
Note that this article was first published on 30/03/2003. The original article is available on DotNetJohn.
I came to the realisation a little time ago that I really wasn’t making the most of the error handling facilities of ASP.NET. The sum of my ASP.Net error handling knowledge up until that point was the new (to VB) Try … Catch … Finally construct at the page level. While I shall examine this new addition there are more facilities at our disposal and this article shall share the results of my recent investigations.
In anything but the simplest of cases your application WILL contain errors. You should identify where errors might be likely to occur and code to anticipate and handle them gracefully.
The .NET Framework’s Common Language Runtime (CLR) implements exception handling as one of its fundamental features. As you might expect due to the language independence of the framework you can also write error handling in VB.NET that can handle errors in C# code, for example. The exception object thrown is of the same primitive type (System.Exception).
Options … Options (preventing runtime errors)
If we can we should capture errors as early as possible as fewer will then make it through to the runtime environment. VB.Net offers the Option Strict and Option Explicit statements to prevent errors at design time.
Classic ASP programmers should be familiar with Option Explicit – it forces explicit declaration of all variables at the module level. In addition when programming ASP.NET pages in VB.NET when Option Explicit is enabled, you must declare all variables using Public, Private, Dim or Redim.
The obvious mistake that Option Explicit captures is using the same variable name multiple times within the same scope … a situation which is very likely to lead to runtime errors, if not exceptions.
Thankfully, in ASP.NET Option Explicit is set to on by default. If for any reason you did need to reset, the syntax is:
Option Explicit Off
at the top of a code module or
<%@ Page Explicit=”False”%>
in a web form.
Enabling Option Strict causes errors to be raised if you attempt a data type conversion that leads to a loss in data. Thus, if this lost data is of potential importance to your application Option Strict should be enabled. Option Strict is said to only allow ‘widening’ conversions, where the target data type is able to accommodate a greater amount of data than that being converted from.
The syntax is as per Option Explicit.
Exceptions
What precisely is an exception? The exception class is a member of the System namespace and is the base class for all exceptions. Its two sub-classes are the SystemException class and the ApplicationException class.
The SystemException class defines the base class for all .NET predefined exceptions. One I commonly encounter is the SQLException class, typically when I haven’t quite specified my stored procedure parameters correctly in line with the stored procedure itself.
When an exception object is thrown that is derived from the System.Exception class you can obtain information from it regarding the exception that occurred. For example, the following properties are exposed:
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 frames on the call stack at the time the current exception was thrown. |
TargetSite |
Gets the method that throws the current exception. |
See the SDK documentation for more information.
The ApplicationException class allows you to define your own exceptions. It contains all the same properties and methods as the SystemException class. We shall return to creating such custom exceptions after examining the main error handling construct at our disposal: 'Try … Catch … Finally'.
Structured and Unstructured Error Handling
Previous versions of VB had only unstructured error handling. This is the method of using a single error handler within a method that catches all exceptions. It’s messy and limited. Briefly, VB’s unstructured error handlers (still supported) are:
On Error GoTo line[or]label
On Error Resume Next
On Error GoTo 0
On Error GoTo -1
But forget about these as we now have new and improved C++ like structured exception handling with the Try ... Catch … Finally construct, as follows:
Try
[ tryStatements ]
[ Catch [ exception [ As type ] ] [ When expression ]
[ catchStatements ] ]
[ Exit Try ]
...
[ Finally
[ finallyStatements ] ]
End Try
Thus we try to execute some code; if this code raises an exception the runtime will check to see if the exception is handled by any of the Catch blocks in order. Finally we may execute some cleanup code, as appropriate. Exit Try optionally allows us to break out of the construct and continue executing code after End Try. When optionally allows specification of an additional condition which must evaluate to true for the Catch block to be executed.
Here’s my code snippet for SQL server operations by way of a simple, and not particularly good (see later comments), example:
Try
myConnection.open()
myCommand = new SQLCommand("USP_GENERIC_select_event_dates", myConnection)
myCommand.CommandType = CommandType.StoredProcedure
myCommand.Parameters.Add(New SQLParameter("@EventId",SQLDBType.int))
myCommand.Parameters("@EventId").value=EventId
objDataReader=myCommand.ExecuteReader()
Catch objError As Exception
'display error details
outError.InnerHtml = "<b>* Error while executing data command (ADMIN: Select Event Dates)</b>.<br />" _
& objError.Message & "<br />" & objError.Source & _
". Please <a href='mailto:mascymru@cymru-web.net'>e-mail us</a> providing as much detail as possible including the error message, what page you were viewing and what you were trying to achieve.<p /><p />"
Exit Function ' and stop execution
End Try
There are several problems with this code as far as best practice is concerned, the more general of which I’ll leave to the reader to pick up from the following text, but in particular there should be a Finally section which tidies up the database objects.
Note it is good form to have multiple Catch blocks to catch different types of possible exceptions. The order of the Catch blocks affects the possible outcome … they are checked in order.
You can also throw your own exceptions for the construct to deal with; or re-throw existing exceptions so they are dealt with elsewhere. See the next section for a little more detail.
You could just have a Catch block that trapped general exceptions – exception type ‘exception’ (see above!). This is not recommended as it suggests a laziness to consider likely errors. The initial catch blocks should be for possible specific errors with a general exception catch block as a last resort if not covered by earlier blocks.
For example, if accessing SQLServer you know that a SQLException is possible. If you know an object may return a null value and will cause an exception, you can handle it gracefully by writing a specific catch statement for a NullReferenceException.
For you information here’s a list of the predefined exception types provided by the .NET Runtime:
Exception type | Base type | Description | Example |
Exception |
Object |
Base class for all exceptions. |
None (use a derived class of this exception). |
SystemException |
Exception |
Base class for all runtime-generated errors. |
None (use a derived class of this exception). |
IndexOutOfRangeException |
SystemException |
Thrown by the runtime only when an array is indexed improperly. |
Indexing an array outside its valid range: arr[arr.Length+1] |
NullReferenceException |
SystemException |
Thrown by the runtime only when a null object is referenced. |
object o = null; o.ToString(); |
InvalidOperationException |
SystemException |
Thrown by methods when in an invalid state. |
Calling Enumerator.GetNext() after removing an Item from the underlying collection. |
ArgumentException |
SystemException |
Base class for all argument exceptions. |
None (use a derived class of this exception). |
ArgumentNullException |
ArgumentException |
Thrown by methods that do not allow an argument to be null. |
String s = null; "Calculate".IndexOf (s); |
ArgumentOutOfRangeException |
ArgumentException |
Thrown by methods that verify that arguments are in a given range. |
String s = "string"; s.Chars[9]; |
ExternalException |
SystemException |
Base class for exceptions that occur or are targeted at environments outside the runtime. |
None (use a derived class of this exception). |
ComException |
ExternalException |
Exception encapsulating COM HRESULT information. |
Used in COM interop. |
SEHException |
ExternalException |
Exception encapsulating Win32 structured exception handling information. |
Used in unmanaged code interop. |
Throwing Exceptions
As indicated earlier, not only can you react to raised exceptions, you can throw exceptions too when needed. For example, you may wish to re-throw an exception after catching it and not being able to recover from the exception. Your application-level error handling could then redirect to an appropriate error page.
You may further wish to throw your own custom exceptions in reaction to error conditions in your code.
The syntax is
Creating Custom Exceptions
As mentioned earlier, via the ApplicationException class you have the ability to create your own exception types.
Here’s an example VB class to do just that:
Imports System
Imports System.Text
Namespace CustomExceptions
Public Class customException1: Inherits ApplicationException
Public Sub New()
MyBase.New("<H4>Custom Exception</H4><BR>")
Dim strBuild As New StringBuilder()
strBuild.Append("<p COLOR='RED'>")
strBuild.Append("For more information ")
strBuild.Append("please visit: ")
strBuild.Append("<a href='http://www.cymru-web.net/exceptions'>")
strBuild.Append("Cymru-Web.net</a></p>")
MyBase.HelpLink = strBuild.ToString()
End Sub
End Class
End Namespace
Looking at this code. On line 6 you see that to create a custom exception we must inherit from the ApplicationException class. In the initialization code for the class we construct a new ApplicationException object using a string (one of the overloaded constructors of ApplicationException – see the .NET documentation for details of the others). We also set the HelpLink property string for the exception – this is a user friendly message for presentation to any client application.
The MyBase keyword behaves like an object variable referring to the base class of the current instance of a class (ApplicationException). MyBase is commonly used to access base class members that are overridden or shadowed in a derived class. In particular, MyBase.New is used to explicitly call a base class constructor from a derived class constructor.
Next a small test client, written in VB.NET using Visual Studio.Net so we have both web form and code behind files:
WebForm1.aspx:
<%@ Page Language="vb" AutoEventWireup="false" Codebehind="WebForm1.aspx.vb" Inherits="article_error_handling.WebForm1"%>
<html>
<body>
</body>
</html>
WebForm1.aspx.vb:
Imports article_error_handling.CustomExceptions
Public Class WebForm1
Inherits System.Web.UI.Page
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Try
Throw New customException1()
Catch ex As customException1
Response.Write(ex.Message & " " & ex.HelpLink)
End Try
End Sub
End Class
This (admittedly simple) example you can extend to your own requirements.
Page Level Error Handling
Two facets:
- Page redirection
- Using the Page objects error event to capture exceptions
Taking each in turn:
Page redirection
Unforeseen errors can be trapped with the ErrorPage property of the Page object. This allows definition of a redirection URL in case of unhandled exceptions. A second step is required to enable such error trapping however – setting of the customErrors attribute of your web.config file, as follows:
<configuration>
<system.web>
<customErrors mode="On">
</customErrors>
</system.web>
</configuration>
Then you’ll get your page level redirection rather than the page itself returning an error, so the following would work:
<%@ Page ErrorPage="http://www.cymru-web.net/GenericError.htm" %>
A useful option in addition to ‘on’ and ‘off’ for the mode attribute of customErrors is ‘RemoteOnly’ as when specified redirection will only occur if the browser application is running on a remote computer. This allows those with access to the local computer to continue to see the actual errors raised.
Page Objects Error Event
The Page object has an error event that is fired when an unhandled exception occurs in the page. It is not fired if the exception is handled in the code for the page. The relevant sub is Page_Error which you can use in your page as illustrated by the following code snippet:
Sub Page_Error(sender as Object, e as EventArgs)
dim PageException as string = Server.GetLastError().ToString()
dim strBuild as new StringBuilder()
strBuild.Append("Exception!")
strBuild.Append(PageException)
Response.Write(strBuild.ToString())
Context.ClearError()
End Sub
As with page redirection this allows you to handle unexpected errors in a more user-friendly fashion. We’ll return to explain some of the classes used in the snippet above when we look at a similar scenario at the application level in the next section.
Application Level Error Handling
In reality you are more likely to use application level error handling rather than the page level just introduced. The Application_Error event of the global.asax exists for this purpose. Unsurprisingly, this is fired when an exception occurs in the corresponding web application.
In the global.asx you code against the event as follows:
Sub Application_Error(sender as Object, e as EventArgs)
'Do Something
End Sub
Unfortunately the EventArgs in this instance are unlikely to be sufficiently informative but there are alternative avenues including that introduced in the code of the last section – the HttpServerUtility.GetLastError method which returns a reference to the last error thrown in the application. This can be used as follows:
Sub Application_Error(sender as Object, e as EventArgs)
dim LastException as string = Server.GetLastError().ToString()
Context.ClearError()
Response.Write(LastException)
End Sub
Note that the ClearError method of the Context class clears all exceptions from the current request – if you don’t clear it the normal exception processing and consequent presentation will still occur.
Alternatively there is the HttpContext’s class Error property which returns a reference to the first exception thrown for the current HTTP request/ response. An example:
Sub Application_Error(sender as Object, e as EventArgs)
dim LastException as string = Context.Error.ToString()
Context.ClearError()
Response.Redirect("CustomErrors.aspx?Err=" & Server.UrlEncode(LastException))
End Sub
This illustrates one method for handling application errors – redirection to a custom error page which can then customise the output to the user dependent on the actual error received.
Finally, we also have the ability to implement an application wide error page redirect, via the customErrors section of web.config already introduced, using the defaultRedirect property:
<configuration>
<system.web>
<customErrors mode="on" defaultRedirect="customerrors.aspx?err=Unspecified">
<error statusCode="404" redirect="customerrors.aspx?err=File+Not+Found"/>
</customErrors>
</system.web>
</configuration>
Note this also demonstrates customised redirection via the error attribute. The HttpStatusCode enumeration holds the possible values of statusCode. This is too long a list to present here - see the SDK documentation.
Conclusion
I hope this has provided a useful introduction to the error handling facilities of ASP.NET. I further hope you’ll now go away and produce better code that makes use of these facilities! In summary:
- Trap possible errors at design time via Option Explicit and Option Strict.
- Consider in detail the possible errors that could occur in your application.
- Use the Try … Catch … Finally construct to trap these errors at the page level and elsewhere.
- It is generally considered poor programming practice to use exceptions for anything except unusual but anticipated problems that are beyond your programmatic control (such as losing a network connection). Exceptions should not be used to handle programming bugs!
- Use application level exception handling (and perhaps also Page level) to trap foreseen and unforeseen errors in a user friendly way.
References
ASP.NET: Tips, Tutorial and Code
Scott Mitchell et al.
Sams
Programming Visual Basic .NET
Francesco Balena
Microsoft Press
.NET SDK Documentation
http://15seconds.com/issue/030102.htm
15 Seconds : Web Application Error Handling in ASP.NET
http://msdn.microsoft.com/msdnmag/issues/02/11/NETExceptions/default.aspx
.NET Exceptions: Make the Transition from Traditional Visual Basic Error Handling to the Object-Oriented Model in .NET