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.

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.

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.

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:

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 "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:

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:

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.

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.

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:

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:

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.

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:

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: