
Beginning ASP.NET 2.0 With CSharp (2006) [eng]
.pdf
Chapter 15
The catch block is interesting because there can be more than one of them. For example:
try
{
code to read from a database
}
catch (SqlException sqlEx)
{
A SQL exception, so something is wrong with the database
}
catch (Exception ex)
{
Any other error
}
When there is more than one catch block and an exception occurs, the exceptions are compared in the order in which they are declared. So if an exception was raised, it would be tested to see if it was a SqlException, and if it was, the SqlException catch block would be run. If the exception wasn’t a SqlException, the next one down would be tried until a match was found or until the closing brace (}) of the try code block was reached. Because all exceptions ultimately inherit from Exception, this is the last chance saloon — the default exception if you will.
It’s important that when you’re using multiple catch blocks, you place the most granular first. For example, in the previous code example, consider if this had been done:
try
{
code to read from a database
}
catch (Exception ex)
{
Any other error
}
catch (SqlException sqlEx)
{
A SQL exception, so something is wrong with the database
}
Even if a SqlException was raised, it would never be trapped because the catch for Exception would trap it. This is because Exception matches all exceptions, which is why you should always place it last when using multiple catch blocks.
There is also another part to try catch, which is the finally block. This is optional, and is a block of code that always runs, whether or not an exception is raised. Again, this is easier to see as code:
try
{
code to read from a database
}
catch (Exception ex)
{
Code to handle the exception
568

Dealing with Errors
}
finally
{
This code will always run
}
What happens here depends on whether an exception is raised. If not, then all of the lines of code in the try block are run, followed by the code in the finally block. If an exception is raised, the appropriate catch block is run, followed by the finally block. This gives you a chance to perform any clean-up operations that might be required. A typical example of this is when you’re performing some database action, and you want to close the database connection whether or not an exception was raised:
SqlConnection conn;
try
{
conn = new SqlConnection(“ . . . “); conn.Open();
// do some database action
}
catch (Exception sqlEx)
{
// log the exception
Tools.Log(“A database exception occurred”, sqlEx);
}
finally
{
if (conn != null) conn.Close();
}
In this code, the connection is declared outside of the try block, allowing it to be referenced from within all code blocks (such as the catch and finally blocks). In the finally block, you don’t just automatically close the connection because the exception might have been raised when trying to open the connection, so the connection object (conn) might not have a value. So conn is first checked to see if it has a value, and only then is it closed.
The following Try It Out gives you a better feel for how it can be used. You’re going to update the Checkout page for the Wrox United shop to ensure that any database errors are handled gracefully and that they don’t leave the database in an inconsistent state.
Try It Out |
Trapping Exceptions |
1.In the Wrox United application for the chapter, open the code file for Checkout.aspx and move to the Wizard1_FinishButtonClick event. This is the event that runs when the checkout wizard has finished collecting information from the user and will move the order from the shopping cart into the database.
2.Add the following variable declaration at the top of the method:
SqlTransaction trans = null;
569

Chapter 15
3.Modify the first few lines after the variable declarations so it looks like the following code — the new lines to add are shaded:
try
{
conn = New SqlConnection( . . . ); conn.Open();
trans = conn.BeginTransaction();
cmd = New SqlCommand(); cmd.Connection = conn; cmd.Transaction = trans;
.. .
4.You need to do the same at the end of the method, so modify the code to look like this:
cmd.ExecuteNonQuery();
}
trans.Commit();
}
catch (SqlException SqlEx)
{
if (trans != null) trans.Rollback();
CreateOrderErrorLabel.Visible = true; return;
}
finally
{
if (conn != null) conn.Close();
}
Profile.Cart.Items.Clear();
}
5.To ensure that the exception is seen in practice, there actually needs to be some error, so you’ll force one by making the SQL statement incorrect. Change the SQL statement that inserts into the Orders table to insert into no_Orders:
cmd.CommandText = “INSERT INTO no_ORDERS(MemberName ...”;
6.Save the file and run the application.
7.Go to the shop and add some items to your cart and then go to the Checkout page. Step through the checkout wizard. After clicking the Finish button, you’ll see the message stating that an error has occurred. Although it says that the administrator and shop know about this, you’ll be coding those bits later in the chapter.
570

Dealing with Errors
How It Works
The code to trap any database exceptions is fairly straightforward, but you’ve added something else to ensure the integrity of the database, and this is a transaction. A transaction, in database terms, ensures that either all commands succeed or none of them do. The problem is that an order is being created in the Orders table and the items being ordered are created in the Order Items table. So at least two commands are being run, one for each table, but there will be a command for each order line. With any database operation, there is a chance of failure, and you need to ensure that both operations succeed, inserting the data into the Orders and Order Items table. You wouldn’t want the order to be created but no order items, nor would you want the reverse. Both are problems. With only an order line, the system thinks that an order is there, but there are no items. The reverse means there would be order lines, but no corresponding order, and thus no way to access those order lines.
Wrapping all of these commands within a transaction means that the order and order lines are not only directly written to their tables, but they are also placed in a transaction log, a special table handled by the database. If there are no problems during the transaction, it is committed and all is as expected. If there are problems, however, the transaction is rolled back, which means that the rows that were added as part of the transaction are removed. So there are only two outcomes — all rows are added, or none are added. This means that an exception will leave the database in a consistent state, as it was when the transaction started. (It’s a bit like time travel, but without all those weird states of seeing your parents when they were your age.)
Look at the code to see how this fits in with the exception handling. You saw the creation of this code in Chapter 13, when looking at e-commerce and the creation of the checkout process, but the explanation of the exception handling was postponed until this chapter.
The first thing is the declaration of a new variable, of type SqlTransaction:
SqlTransaction trans = null;
It is assigned an initial value of null to avoid potential runtime errors. In your code, this isn’t a problem, but if you remove = null from the declaration and recompile the application, you can see that VWD displays an error stating “Use of unassigned local variable ‘conn’” The reason is that the compiler cannot be sure that the variable will have an assigned value, so it gives a warning. You’ll see where this assigned value of Nothing comes in later.
After the declaration, the code is wrapped within a Try Catch block:
try
{
conn = New SqlConnection( . . . ); conn.Open();
After the connection is open, a transaction is started. This gives you the marker point for the database commands, and it’s to this state of the database that you will roll back if an exception occurs. The
BeginTransaction method of the SqlConnection returns a SqlTransaction object, so later in the code, this can be used to commit or rollback the database changes:
trans = conn.BeginTransaction();
571

Chapter 15
With the transaction created, it is then assigned to the command using the Transaction property:
cmd = New SqlCommand(); cmd.Connection = conn; cmd.Transaction = trans;
Now that the transaction has been assigned, the code can insert the required data into the tables. After that has happened, the transaction can be committed:
trans.Commit();
That’s all the code for a successful operation, but if there is a problem, the exception needs to be caught:
catch (SqlException SqlEx)
Within the catch block, the transaction needs to be rolled back because some problem arose. But, before the transaction can be rolled back, the trans object needs to be checked to see if it contains a value. Remember that when the SqlTransaction object was declared, it was given a value of null. Within the try statement, the first lines of code create and open a connection, and these two lines of code could raise an exception. If that happened, the SqlTransaction object wouldn’t have been created, so the trans variable would still have its default value of null. That’s why the compiler gives an error and why the default value is set. So you only want to roll back the transaction if it was actually started, in which case the trans variable would not be null:
if (trans != null) trans.Rollback();
After the transaction has been rolled back, an error message is displayed, by simply making a Label control visible. After this is done, you can return from the method:
CreateOrderErrorLabel.Visible = true; return;
Before you return, though, the connection needs to be closed, and this needs to be done whether or not an exception occurred, so a finally block is used. Remember that the finally block is always run. Like the transaction, the connection also has an initial value of null, and this is used to ensure that there is a connection before you try to close it:
finally
{
if (conn != null) conn.Close();
}
The final thing to do is to clear the items from the cart, because they’ve now been added to the Orders and Order Items tables:
Profile.Cart.Items.Clear();
You can see that in use, the try catch block is quite simple, and that in conjunction with transactions, it allows the integrity of the database to remain. Displaying an error on the screen to let users know a
572

Dealing with Errors
problem occurred is a good thing, but the site administrators also need to know, so the next section looks at how these details could be logged to a file.
Logging Exceptions
Trapping exceptions is good, but you generally need to log them somehow. After all, if things go wrong you really want to know about it. Logging can be performed in many ways, with the log file being written to a variety of places, including a database table, the Event Log (either under Application events or a custom log), and a log file. There are advantages and disadvantages to all forms of logging. For example, you might not be able to write to the Event Log, because you might not have permission. Or if you can write to the Event Log, how would you then view the entries? Most web servers are tightly guarded and viewing the Event Log might not be allowed. The advantage of the Event Log is that exceptions are contained along with other application errors.
This section looks at the log file option, because it provides an easy way to log exceptions. A simple text file is used to store details of exceptions, showing the time and the details of the exception. Each exception is appended to the existing file, or if no file exists it will be created. Although you are using a log file, the technique works for the other ways of logging; the difference is simply where you log the exception. In the following Try It Out, you modify the exception code you added to the checkout.
Try It Out |
Logging Exceptions |
1.In the Wrox United application for the chapter, open the code file for Checkout.aspx and move to the Wizard1_FinishButtonClick event.
2.In the section where the SqlException is caught, modify the code so that it looks like this:
catch (SqlException SqlEx)
{
if (trans != null)
trans.Rollback();
Tools.Log(“An error occurred while creating the order”, SqlEx);
CreateOrderErrorLabel.Visible = true; return;
3.Save the file.
4.In the App_Code directory, create a new class called Tools (right-click App_Code and select Add New Item).
5.In the new Tools.cs class file, add the following Imports statements at the top of the file:
using System.IO;
using System.Web;
6.Within the Tools class, add the following methods:
public static void Log(string Message)
{
Log(Message, null);
573

Chapter 15
}
public static void Log(string Message, Exception Ex)
{
string fileName = Path.Combine( HttpContext.Current.Request.PhysicalApplicationPath, “WroxUnited.log”);
using (StreamWriter logFile = new StreamWriter(fileName, true))
{
logFile.WriteLine(“{0}: {1}”, DateTime.Now, Message); if (Ex != null)
logFile.WriteLine(Ex.ToString());
logFile.Close();
}
}
7.Save the file and run the application. Log in, add some items to the shopping cart, and then follow the checkout procedure. When you finish the checkout, the screen will say that an error occurred.
8.Navigate to the directory in which the web site is running and open WroxUnited.log. You’ll see the exception details logged.
9.Don’t forget to change the SQL statement back to a correct one after you’ve run this example. Just remove the no_ from the table name:
cmd.CommandText = “INSERT INTO ORDERS(MemberName ...”;
How It Works
The first code modification was to log the exception from within the Catch block in the Checkout page:
Tools.Log(“An error occurred while creating the order”, SqlEx);
This calls the Log method of the Tools class, passing into it a string describing the problem, and the exception SqlException object. The Log method is a static method, so you didn’t have to create an instance of the Tools class.
The exception will now be logged, so the logging class is created. This is a class file and is placed in the App_Code directory, which means that it will automatically be compiled. Within the Tools class, the first thing that is done is to add some namespaces:
using System.IO; using System.Web;
System.IO is required because that is where the file handling routines are stored, and System.Web will allow you access to details of the current web site.
Next is the routine that trapped the exception, where the following was added:
Tools.Log(“An error occurred while creating the order”, SqlEx);
574

Dealing with Errors
This calls the Log method of the Tools class, passing in an error message and the exception. Notice that it wasn’t necessary to create an instance of the Tools class. Because the class is just a container for simple methods, those methods have been made static, so a class instance isn’t required (static methods were explained in Chapter 9). The code for these is fairly simple, too, and shows a good case of overloaded methods.
The first method accepts a single argument, the error message to log. It doesn’t actually do any logging itself, but calls another Log method, passing the message into that. It also passes null as a second parameter:
public static void Log(string Message)
{
Log(Message, null);
}
The second method, also called Log, is where the real work is done. This method accepts two parameters. The first is the error message, and the second is an Exception object:
public static void Log(string Message, Exception Ex)
Next, the file name needs to be worked out. This can be placed in any suitable location, but keeping it in the home directory of the web site is a good idea. The path for that directory can be retrieved by the PhysicalApplicationPath property of the current Request. However, because this code isn’t within an ASP.NET page, but is a class, the Request property isn’t directly available. To get access to it, HttpContext.Current is used — this is the context of the current HTTP request that the ASP.NET page is running under.
The full file name needs to contain both the path and the file, so these are combined using the Combine method of the Path class. This will append a directory separator (the \ symbol) if one is required:
string fileName = Path.Combine( HttpContext.Current.Request.PhysicalApplicationPath, “WroxUnited.log”);
When the full file name is available, the file can be opened, and for this a StreamWriter is used:
using (StreamWriter logFile = new StreamWriter(fileName, true))
Streams can be of different types, such as memory, but passing in the fileName parameter when the StreamWriter is created enables you to write directly to a file. The second parameter, true, indicates that data should be appended to the file, rather than a new file being created (which is the default). The using statement ensures that the reference to this file is disposed of when the code block is closed — this frees up the file resource as soon as it is no longer used, a technique that should always be followed.
After the file is opened, the WriteLine method is used to write data to the file:
logFile.WriteLine(“{0}: {1}”, DateTime.Now, Message);
WriteLine is an interesting method, because it can take many parameters. It always takes at least one parameter, but there can be others. The first parameter is the string to be written, but you want the current date and time, plus the message to be written as well. Rather than concatenating a string with these
575

Chapter 15
details, they can be added as parameters and placeholders used in the string to identify where they should go. The placeholders are identified by a number within curly braces, so {0} will be replaced by the current date and time, and {1} will be replaced by the message.
After the main message has been written to the file, the exception, passed into the Log method as a parameter, is tested. If it is not null, then an exception has been passed in, so this is also written to the file. The ToString method of the exception is used to get the descriptive text of the exception:
if (Ex != null) logFile.WriteLine(Ex.ToString());
The file is closed when all of the details have been written to the file:
logFile.Close();
After the using code block is ended, the file resources will be freed:
}
}
At the end of this procedure, there is a Log method that can be called in two ways. The first way is as follows:
Tools.Log(“error message”);
The second way is like this:
Tools.Log(“error message”, exception);
This sort of overloaded method brings extra flexibility, because it allows you to use the same routine for different types of logging. If there’s no exception, you use a different form of the Log method.
One thing you might have noticed is that the second parameter of the Log method is of type Exception, but when this method was called, a variable of type SqlException was passed in. This is acceptable because SqlException is derived from Exception Temember in Chapter 9 where inheritance was covered? SqlException inherits from DbException, which inherits from ExternalException, which inherits from Exception. It seems a long chain of inheritance, but that doesn’t matter for parameter methods; as long as the type passed into a parameter ultimately derives from the type declared in the parameter, there will be no compilation errors. The types are compatible.
Mailing Exceptions
Log files are a great way to store exceptions, but they are generally passive — you have to go and look at them. A more active option for dealing with exceptions is to have them mailed to someone, perhaps the web site administrator. This is easy to achieve in ASP.NET 2.0, because classes are built in for sending mail messages, and these live in the System.Net.Mail namespace. Here’s some code that could be used in addition to, or instead of, the file logging:
576

Dealing with Errors
public static void SendMail(string Message, Exception Ex)
{
using (MailMessage msg =
new MailMessage(“website@wroxunited.net”, “admin@wroxunited.net”))
{
msg.Subject = “WroxUnited.net Web Site Error”; if (Ex == null)
msg.Body = “There was an error on the website”;
else
msg.Body = Ex.ToString();
SmtpClient client = new SmtpClient(“MyMailServer”); client.UseDefaultCredentials = true; client.Send(msg);
}
}
Two classes are in use here. The MailMessage class defines the message to be sent — the constructor sets the “from” and “to” addresses for the message, and the Subject and Body properties set the subject line and the contents. The second class is the SmtpClient, the constructor of which defines the name of the mail server. Setting UseDefaultCredentials to true allows the ASP.NET code to connect to the server using Windows network credentials. Finally, the Send method actually sends the mail.
You can configure some of these properties in the web configuration file, Web.config, which is where you can also set authentication settings if they are required for connection to the mail server:
<configuration xmlns=”http://schemas.microsoft.com/.NetConfiguration/v2.0”> <system.net>
<mailSettings>
<smtp deliveryMethod=”Network”> <network
defaultCredentials=”False”
host=”MyMailServer”
password=”MyPassword”
port=”25”
userName=”MyUserName”
from=”website@wroxunited.net”/>
</smtp>
</mailSettings>
</system.net>
</configuration>
The properties for configuring mail are fairly simple. Setting defaultCredentials to false ensures that the user name (userName) and password (password) specified are used to connect to the e-mail server (host), and from sets the e-mail address of whom the e-mail is from. The port number has to do with TCP networking — e-mail uses port number 25, and you don’t need to know any more about ports apart from that number.
Sending mail isn’t just for notifying administrators of exceptions, and you can use it for all sorts of things. The security framework will use these settings when it sends forgotten passwords if users request them.
577