ASP.NET Basics (part 10): Making Exceptions

Learn all about ASP.NET exceptions and how to write code that traps and resolves them.

Burning Up

No programmer, no matter how good (s)he is, writes bug-free code all the time. And so, most programming languages come with built-in capabilities to catch errors and take remedial action. This action could be something as simple as displaying an error message, or as complex as heating your computer's innards until they burst into flame (just kidding!).

ASP.NET is no different. The language comes with fairly sophisticated exception-handling hooks, equivalent to what you would find in Java or Python. And over the next few pages, I'm going to demonstrate them by deliberately introducing errors into my ASP.NET scripts and then using the built-in exception handlers to catch and resolve them in an efficient and effective manner. So come on in - this is the last episode of this particular tutorial, and you don't want to miss what's coming up!

Word Games

Normally, when an ASP.NET program encounters an error, be it syntactical or logical, it exits the program at that stage itself with a message indicating the cause of the error. Now, while this behaviour is acceptable during the development phase, it cannot continue once an ASP.NET application has been released to actual users. In these "live" situations, it is unprofessional to display cryptic error messages (which are usually incomprehensible to non-technical users); rather, it is more professional to intercept these errors and either resolve them (if resolution is possible), or notify the user with a clear error message (if not).

The term "exceptions" refers to those errors which can be tracked and controlled. For example, if a function attempts an unsupported operation on a built-in ASP.NET object (say, trying to run a query on a database server when the database connection itself has failed), ASP.NET will "throw" an exception, together with a stack trace or detailed explanation of the problem. Exceptions like these can be caught by the application, and appropriately diverted to an exception-handling routine.

In order to see what an exception looks like, consider the following simple ASP.NET script, which contains a deliberate error - trying to access an element of an array which does not exist.

<script language="c#" runat="server">
void Page_Load()
{

    // create five-element array
    string [] desserts = new string[5];

    // try to access the eighth element of the array
    desserts[7] = "tiramisu";

}
</script>
<html>
<head></head>
<body>
</body>
</html>

and its output, which contains an exception:

There are two basic ways of handling ASP.NET exceptions like the one above: the simple way, and the complicated way. The simple way involves redirecting the HTTP client to a generic error page when an exception occurs, and protecting the user from the technical details of the exception by replacing them with a user-friendly error message. This approach has the advantage of being simple to understand and easy to set up; however, it is somewhat inflexible, especially for applications that need more sophisticated exception handling.

That's where the second option comes in. In languages such as C# (my language of choice for the .NET platform), an exception is represented as an instance of the Exception class. This is similar to the Java exception handling model, in which every exception is an instance of an object; in ASP.NET, an Exception object is available to identify and manage exceptions. This Exception object can be used in combination with the "try-catch" exception handling mechanism to trap exceptions in your code and handle them as per your own custom requirements.

Exceptionally Clever

Let's look at the simple approach first. As explained earlier, this technique merely involves sending the user to a generic page when an exception occurs. There are two basic steps in this process: creating a page containing a generic error message, and modifying the server configuration so that the page is invoked when an exception occurs.

Let's illustrate this process with a simple example. Here's an ASP.NET script that divides a number by zero - a process guaranteed to make any programming language scream in anguish.

<script language="c#" runat="server">
void Page_Load() {

int a = 19;
int b = 0;
int c = a/b;

}
</script>
<html>
<head></head>
<body>
<asp:label id="output" runat="server" />
</body>
</html>

Here's the output:

Ugly, huh?

Now, create the page that is shortly going to replace the gobbledygook above (call it "error.aspx"):

<% @ Page Language="C#" %>
<html>
<head><title>Error </title></head>
<body>
<asp:label id="errorMessage" runat="server" value="An error occurred. Please exit in single file, turning the light off after you leave." />
</body>
</html>

Next, you need to tell the Web server that it should load the page above whenever an exception occurs. This can be done by setting a value for the <customErrors> element in the "web.config" file. This file, usually located in the same directory as your ASP.NET scripts, allows you to configure some aspects of your application.

Here's what your "web.config" file should look like:

<configuration>
    <system.web>
        <customErrors defaultRedirect="error.aspx" mode="On" />
    </system.web>
</configuration>

The "defaultRedirect" attribute of the <customErrors> element allows you to specify the error page to be displayed in the event of an exception. The "mode" attribute allows you to control the behaviour of the server in the event of an exception, and can take any of the following three values: "On", which will display the custom error page defined in the "defaultRedirect" attribute under all conditions; "Off", which will display the standard exception dump under all conditions; and "RemoteOnly", which will display the exception dump when the script containing the erroneous code is accessed from the local system itself, and the custom error page when it is accessed over the network or Internet. This allows a developer to debug errors without having to worry about their visual impact on end users, as they will only get to see the error page defined in the "defaultRedirect" attribute.

Now, if you retry the script above, you should see your custom error page instead of the standard exception page:

A Custom Job

You can also specify the custom error page on a per-script basis, redirecting the client to different pages depending on which script caused the exception. In order to do this, simply add the ErrorPage property in the Page directive, as in the example below:

<% @ Page ErrorPage="divbyzero.aspx" %>
<script language="c#" runat="server">
void Page_Load() {

int a = 19;
int b = 0;
int c = a/b;

}
</script>
<html>
<head></head>
<body>
<asp:label id="output" runat="server" />
</body>
</html>

In this case, when the exception occurs, the browser will jump to "divbyzero.aspx" instead of the default page defined in "web.config". This mechanism thus lets you specify a custom error page to catch all exceptions, yet override it on a per-script basis if you need to.

You Throw(), I'll Catch()

Java programmers have always had the upper hand when it came to handling exceptions in their code, via the well-known (and very clever) "try-catch" exception handling mechanism. Not to be left behind, C# also offer a similar mechanism to ASP.NET programmers. As in Java, you can wrap your code in a "try" block and have exceptions generated by that code resolved through exception-handling routines in one or more corresponding "catch" blocks.

Additionally, you can now use the "throw" construct to artificially induce an exception in your ASP.NET script. This comes in handy, for example, when validating form field data - if the values entered are not in the expected format, you can throw an exception (with an informative error message) and re-direct the user to an error page.

The structure of a "try-catch" block looks like this:

try {
    code block
} catch (Exception1 err1)  {
    execute this block if exception "err1" is generated
} catch (Exception2 err1)  {
    execute this block if exception "err2" is generated

    ... and so on ...

}

In order to demonstrate this, let's re-write the previous example to use the "try-catch" exception-handling mechanism:

<script language="c#" runat="server">
void Page_Load() {

    try {
        int a = 19;
        int b = 0;
        int c = a/b;
    } catch(Exception e) {
        message.Text = e.Message + e.StackTrace;
    }
}
</script>
<html>
<head></head>
<body>
<asp:label id="message" runat="server" />
</body>
</html>

Here's the output:

The "try-catch" mechanism provides misbehaving code with a soft cushion to land on. There are two components to this mechanism: the "try" block, which wraps around your code and traps exceptions generated by it, and the "catch" block which is triggered when an exception takes place, and contains the code to handle it gracefully.

When an exception is raised by the code within the "try" block, the exception is propagated to the "catch" block. Within the "catch" block, the exception is now represented by an instance of the Exception object. This object instance comes with two useful properties, "Message" and "StackTrace", which contain the error message and detailed stack trace respectively.

The More, The Merrier

Why stop there? ASP.NET also allows you to have multiple "catch" blocks linked to a single "try" block, so that you can handle different exceptions differently. Consider the following example, which demonstrates:

<script language="c#" runat="server">
void Page_Load()
{
    int a, b;
    double c;

    if(IsPostBack) {

        try {

            checked {
                a = Convert.ToInt32(Request.Form["num1"]);
                b = Convert.ToInt32(Request.Form["num2"]);
                c = a/b;
            }

            output.Text = a.ToString() + " divided by " + b.ToString() + " gives " + c.ToString();

        } catch (FormatException  fe) {

            output.Text = "Can't you read? I asked for numbers!";

        } catch (DivideByZeroException  dbze) {

            output.Text = "Trying to divide by zero, are we?";

        } catch (OverflowException oe) {

            output.Text = "My brain can't handle such large numbers, give me something smaller";

        } catch (Exception e) {

            output.Text = e.Message;
        }
    }
}
</script>

<html>
<head>
<basefont face="Arial">
</head>
<body>
<center>
<asp:label id="output" runat="server" />
<form method="POST" runat="server">
<table cellspacing="5" cellpadding="5" border="0">
<tr>
<td>
<font size="1">Gimme a number...</font>
</td>
<td align="left">
<asp:textbox id="num1" runat="server" />
</td>
</tr>
<tr>
<td>
<font size="1">Gimme another...</font>
</td>
<td align="left">
<asp:textbox id="num2" runat="server" />
</td>
</tr>
<tr>
<td colspan="2" align="center">
<input type="submit" name="submit" value="Enter">
</td>
</tr>
</table>
</form>
</center>
</body>
</html>

In this case, depending on the values entered into the form, ASP.NET will either perform the division, generate a FormatException if the values entered by the user cannot be converted to integers, generate a DivideByZeroException if the denominator is zero, or generate an OverflowException if the numbers involved are too large. The manner in which these exceptions are handled is different - FormatException exceptions will be handled by the first "catch" block, DivideByZeroException exceptions will be handled by the second block, OverflowException exceptions will be handled by the third, and all other exceptions will be handled by the last generic "catch" block.

Incidentally, you can also add a "finally" block to the "try-catch" structure, which contains code that must be executed after the code in the "try" and "catch" blocks. More on this in an upcoming example.

Sending It To The Bitbucket

Now, the "try" statement can only deal with exceptions that it knows about. What about the ones the developer can't predict? Well, it's possible to use the generic keyword "Exception" to handle any type of exception generated by the application. The following code snippet illustrates this technique:

<script language="c#" runat="server">
void Page_Load()
{

    string [] desserts = new string[5];

    try {
        // try to access an element which doesn't exist
        desserts[7] = "tiramisu";
    } catch (Exception e) {

        // do nothing
    }

    // carry on with our work
    Response.Write("Really, the dessert was the best part of this meal!");
}
</script>
<html>
<head></head>
<body>
</body>
</html>

In this case, it doesn't matter what type of exception is generated - the generic handler will catch it, ignore it and continue to process the rest of the script. Here's what the output might look like:

It should be noted, however, that this approach, although extremely simple, is not recommended for general use. It is poor programming practice to trap all errors, regardless of type, and ignore them; it is far better - and more professional - to anticipate the likely errors ahead of time, and use the "try-catch" construct to isolate and resolve them.

Rolling Your Own

Thus far, you've been working with ASP.NET's built-in exceptions, which can handle most logical or syntactical expressions. However, ASP.NET also allows you to get creative with exceptions, by generating your own custom exceptions if the need arises.

This is accomplished via the "throw" statement, which is used to raise errors which can be detected and resolved by the "try" family of exception handlers. The "throw" statement needs to be passed an exception name, and an optional descriptive string. When the exception is raised, this exception name and description will be made available to the defined exception handler.

<script language="c#" runat="server">
void Page_Load()
{
    if(IsPostBack) {
        check_user(Request.QueryString["name"]);
    }
}

void check_user(string name) {
    if(name == "Neo") {
            Response.Write("Welcome to The Matrix, " + name + "!");
        } else {
            throw new Exception("Access denied to The Matrix. Try again.");
        }
}
</script>
<html>
<head>
<basefont face="Arial">
</head>
<body>
<center>
<form method="GET" runat="server">
<table cellspacing="5" cellpadding="5" border="0">
<tr>
<td>
<font size="1">Name, rank and serial, number, soldier!</font>
</td>
<td align="left">
<asp:textbox id="name" runat="server" />
</td>
</tr>
<tr>
<td colspan="2" align="center">
<input type="submit" name="submit" value="Enter">
</td>
</tr>
</table>
</form>
</center>
</body>
</html>

In this case, if the name entered in the text box is not "Neo", ASP.NET will raise a user-defined exception with a string of text describing the nature of the error. Take a look:

Trapping user-defined exceptions is exactly the same as trapping pre-defined ASP.NET exceptions. The following refinement of the code above illustrates this:

<script language="c#" runat="server">
void Page_Load()
{
    if(IsPostBack) {
        try {
            check_user(Request.QueryString["name"]);
        } catch (Exception e) {
            output.Text = e.Message;
        }
    }
}

void check_user(string name)    {
    if(name == "Neo") {
            output.Text = "Welcome to The Matrix, " + name + "!";
        } else {
            throw new Exception("Access denied to The Matrix. Try again.");
        }
}
</script>
<html>
<head>
<basefont face="Arial">
</head>
<body>
<center>
<asp:label id="output" runat="server" />
<form method="GET" runat="server">
<table cellspacing="5" cellpadding="5" border="0">
<tr>
<td>
<font size="1">Name, rank and serial, number, soldier!</font>
</td>
<td align="left">
<asp:textbox id="name" runat="server" />
</td>
</tr>
<tr>
<td colspan="2" align="center">
<input type="submit" name="submit" value="Enter">
</td>
</tr>
</table>
</form>
</center>
</body>
</html>

Here's the output of the script above, when the wrong username is entered.

Meeting The Family

A number of standard exceptions are built into ASP.NET - here's a list of the most common ones.

System.DivideByZeroException - generated when an attempt is made to divide by zero

System.IndexOutOfRangeException - generated when an attempt is made to access a non-existent element index;

System.NullReferenceException - a null reference is used in a way that causes the referenced object to be required.

System.OutOfMemoryException - generated when an out-of-memory error occurs;

System.DllNotFoundException - generated when a DLL that an application may require is not found

For a complete list, take a look at http://msdn.microsoft.com/library/default.asp?url=/library/en-us/csspec/html/vclrfcsharpspec_16_4.asp

All Wrapped Up

Take a close look at this final example on exception handling - it highlights several important concepts that will prove useful when you get down to coding complex business logic in your ASP.NET scripts.

<%@ Import Namespace="System.Data"%>
<%@ Import Namespace="System.Data.SqlClient"%>
<script Language="C#" runat="server">
void Page_Load()
{

    try {

        // build the connection string
        string strConn = "user id=sa;password=;";
        strConn += "initial catalog=pubs;data source=tatooine;";

        // create an instance of the SqlConnection object
        SqlConnection objConn = new SqlConnection(strConn);

        try {
            // create an instance of the Command object
            SqlCommand objCommand = new SqlCommand("SELECT * FROM starwars;", objConn);

            // open the connection
                objConn.Open();

                // populate a SqlDataReader object
                SqlDataReader objReader = objCommand.ExecuteReader();

            // set the source of our "starwars" datagrid
                starwars.DataSource = objReader;

                // bind the data to the grid
                starwars.DataBind();

            // close the Reader object
            objReader.Close();

                // display output message
                output.Text = "Star Wars";

        } catch (SqlException e) {

            throw new Exception("The following error occurred: " + e.Message);

        } catch (Exception e) {

            throw new Exception("The following error occurred: " + e.Message);

        } finally {

            // close the connection object
            objConn.Close();
        }

    } catch (SqlException e) {

        output.Text = e.Message;

    } catch (Exception e) {

        output.Text = e.Message;

    }
}
</script>
<html>
<title>Star Wars</title>
<body>
<asp:label id="output" runat="server" />
<asp:datagrid id="starwars" runat="server"/>

</body>
</html>

If all goes well, you should see something like this:

However, it's quite likely that something might go wrong - for example, the database connection might not open successfully, or there might be an error in the SQL query. Therefore, the cautious ASP.NET developer always wraps his or her code in multiple "try-catch" blocks, to ensure that exceptions are trapped and resolved.

The example above demonstrates this concept, by nesting multiple "try-catch" blocks within each other. If you were to take out all the business logic from the code listing above, the skeleton would look something like this:

<%
    try {
        try {

        } catch (SqlException e) {

        } catch (Exception e) {

        } finally {

    }

    } catch (SqlException e) {

    } catch (Exception e) {

}
%>

In this example, the outermost "try-catch" block is used to handle database connection exceptions, while the inner one is used to resolve exceptions in the query and result set.

If a connection cannot be opened to the database server, or if an error occurs in the SqlConnection object, an exception will be raised, which can be caught and handled by the outer "try" block. Here's what the output would look like in this scenario:

But what about the inner "try-catch" block? Well, this handles the SqlCommand and SqlDataReader objects. If, for some reason, you are unable to create these objects - say, the table "starwars" has been deleted by some DBA - you need to inform the user accordingly. This is where the inner block is used, and it generates output like the following:

A couple of interesting things to note in the example above:

  1. The inner "try-catch" block throws user-defined exception if an error occurs in query or result set processing using the "throw" statement discussed previously. Since the blocks are nested, exceptions generated in an inner block will be propagated upwards to the parent block, and can be managed by the parent block's "catch" handler.

  2. When an exception occurs in the inner block, it implies that an error occurred after the database connection was opened, either during the query phase or the result processing phase. Therefore, when an exception occurs, it becomes necessary to close the database connection before propagating the exception upwards. That's where the inner "finally" block comes in - it contains code that will destroy the SqlConnection object and free up memory associated with the database connection. Since this code is enclosed in a "finally" block, it will be executed without fail.

In case you're wondering - there's no need to do this in the outer "try-catch" block because there's only one possible exception that could occur within the outer block - a failure in opening the database connection. And if the connection can't be opened in the first place, it's pointless having a "finally" block to close it...

Digging Deeper

In addition to what you've already seen, you can also add a couple of extra Page directives to your code to assist in debugging rogue scripts. The first of these is the Debug directive - it allows the programmer to view those sections of the code where the error might have occurred. To understand how this works, consider the following example:

<% @ Page Debug="true" %>
<script language="c#" runat="server">
void Page_Load()
{

    // create five-element array
    string [] desserts = new string[5];

    // try to access the eighth element of the array
    desserts[7] = "tiramisu";

}
</script>
<html>
<head></head>
<body>
</body>
</html>

Here's what the output might look like:

You can also toss in the Trace directive, to view a list of environment variables with their current values. Here's an example,

<% @ Page Trace="true" %>
<script language="c#" runat="server">
void Page_Load()
{

    string [] desserts = new string[5];

    desserts[0] =  "tiramisu";

}
</script>
<html>
<head></head>
<body>
</body>
</html>

and here's the output:

Endgame

And that's about it for this series. Over the last ten tutorials, I've attempted to introduce you to the basics of ASP.NET development, using simple examples and illustrations to explain the basics of the language to you. Among the things we've covered:

  • a quick introduction to Microsoft's .NET vision, followed by the installation and configuration of the .NET SDK on a PC;

  • the anatomy of an ASP.NET script using ASP.NET HTML server controls;

  • variables and simple data types;

  • conditional expressions;

  • different types of loops;

  • multi-dimensional and jagged arrays;

  • form submission with the GET and POST methods;

  • functions;

  • database interaction with ADO.NET;

Finally, I wrapped things up with this final article, a gentle introduction to the ASP.NET exception handling mechanism. First, I showed you how to redirect the client to a simple error page when an exception is generated using the "web.config" file. Then, I demonstrated the "try-catch" exception handling construct, which can be used to easily trap and resolve exceptions within your script itself. Finally, I showed you how to throw your own custom exceptions, and tied it all up with a real-world example using nested "try-catch-finally" blocks.

Of course, there's a lot more to learn in ASP.NET - and the following links should give you more than enough food for thought:

Microsoft .NET Development Environment, at http://www.extremetech.com/print_article/0,3998,a=1610,00.asp

ASP.NET from MSDN, at http://msdn.microsoft.net/net/aspnet/default.asp

ASP.NET Reference, at http://www.asp.net/

4GuysFromRolla, a popular site for ASP.NET tutorials and tips, at http://www.4guysfromrolla.com

ASPToday, at http://www.asptoday.com

ASP Index, at http://www.aspin.com

ASP 101, at http://www.asp101.com

I'll be back with more interesting ASP.NET goodies shortly, so stay tuned!

Note: Examples are illustrative only, and are not meant for a production environment. Melonfire provides no warranties or support for the source code described in this article. YMMV!

This article was first published on28 Nov 2003.