Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Beginning ASP.NET 2

.0.pdf
Скачиваний:
19
Добавлен:
17.08.2013
Размер:
24.67 Mб
Скачать

Dealing with Errors

What Are Exceptions?

Exceptions are a range of classes, all deriving from the same base class called Exception. Many different exceptions exist such as FileNotFoundException, which would occur when trying to access a file that doesn’t exist; and SqlException, which would occur when there is a database problem. For example, at some stage while working through the examples in the book you may have seen something like Figure 15-5.

Figure 15-5

This occurred because of an incorrect SQL statement — one of the column names was wrong. This sort of exception is unlikely to get out into a live application, but other exceptions are, so you need to learn how to trap them.

The Exception Object

The Exception object is the base class for all exceptions and defines the base properties that all exceptions have. These properties are explained in the following table.

Property

Description

 

 

Data

A collection of key/value pairs that provide

 

additional details about the exception.

HelpLink

The link to the help file that contains a description

 

of the exception.

InnerException

The underlying exception that caused the problem.

Message

Text that describes the exception.

Source

The name of the application or object that caused

 

the problem.

StackTrace

The trace of method calls that lead up to the problem.

TargetSite

The method that threw the current exception.

 

 

569

Chapter 15

One thing that that you might notice is that there is an InnerException property. This allows exceptions to be stacked, which is useful when you have layers of code. For example, an underlying part of the .NET Framework may raise an exception, but that might be wrapped within another exception to provide more information.

Other exceptions might define additional properties to describe the specifics on the exception. For example, SqlException also defines the following properties:

Property

Description

 

 

Class

The severity of the error, as defined by SQL Server.

Errors

A collection of SqlError objects detailing the problem.

LineNumber

The line number within the SQL or stored procedure

 

where the problem occurred.

Number

The error number.

Procedure

The name of the stored procedure where the problem

 

occurred.

Server

The name of the SQL Server machine.

Source

The name of the data provider.

State

The error code from SQL Server.

You can see that when exceptions occur you have a certain amount of information regarding the problem, but that if you use the correct type of exception you get more information. This is discussed later in the chapter.

How to Trap Exceptions

Trapping exceptions is crucial to maintaining control of your application, and is done by way of Try Catch statements. This is easiest to understand through an example, using some code you saw earlier in the book (Chapter 14) — the code that generates thumbnails for uploaded images:

Public Shared Sub GenerateThumbnail(ByVal SourceImagePath As String, _ ByVal TargetImagePath As String)

Dim newHeight As Short

Dim newWidth As Short

Using sourceImage As Image = Image.FromFile(SourceImagePath)

newHeight = CShort(sourceImage.Height * 0.25) newWidth = CShort(sourceImage.Width * 0.25)

Dim cb As New Image.GetThumbnailImageAbort(AddressOf ThumbnailCallback) Using targetImage As Image = _

sourceImage.GetThumbnailImage(newWidth, newHeight, cb, IntPtr.Zero) targetImage.Save(TargetImagePath, Imaging.ImageFormat.Gif)

570

Dealing with Errors

End Using

End Using

End Sub

At the moment this code doesn’t protect itself in any way, but you can add both defensive coding and exception handling. The source image, for example, must exist before it can be converted, so that can be checked first, but the target image doesn’t exist, so you need to protect against some problem when saving it. Here’s how the code could look:

Public Shared Sub GenerateThumbnail(ByVal SourceImagePath As String, _ ByVal TargetImagePath As String)

If Not File.Exists(sourceImagePath) Then

Return

End If

Dim newHeight As Short

Dim newWidth As Short

Using sourceImage As Image = Image.FromFile(SourceImagePath)

newHeight = CShort(sourceImage.Height * 0.25) newWidth = CShort(sourceImage.Width * 0.25)

Dim cb As New Image.GetThumbnailImageAbort(AddressOf ThumbnailCallback) Using targetImage As Image = _

sourceImage.GetThumbnailImage(newWidth, newHeight, cb, IntPtr.Zero)

Try

targetImage.Save(TargetImagePath, Imaging.ImageFormat.Gif) Catch ex As Exception

Tools.Log(“ImageHandling.GenerateThumbnail”, ex) End Try

End Using End Using

End Sub

The first thing to note is the defensive coding for the source image; this uses the Exists method of the File class to see if the file exists, and if it doesn’t you simply exit silently. The exception handling is wrapped around the saving of the target file and follows this general principle:

Try

Place the code here that you need to trap exceptions for

Catch ex As Exception

Place the code here to handle the exception

This could be displaying a tidy message or logging the exception

End Try

So in the code targetImage.Save is the line that could potentially cause an exception — it might not be possible to save the file for a variety of reasons: an incorrect file name, permissions, and so on. If an

571

Chapter 15

exception does occur, execution immediately transfers to the Catch block, so any lines within the Try that were after the line in fault would not be executed.

The Catch block is interesting because there can be more than one of them. For example:

Try

code to read from a database Catch sqlEx As SqlException

A SQL exception, so something is wrong with the database Catch ex As Exception

Any other error End Try

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 — 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 End Try 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 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 ex As Exception

Any other error

Catch sqlEx As SqlException

A SQL exception, so something is wrong with the database End Try

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 ex As Exception

Code to handle the exception Finally

This code will always run End Try

What happens here depends upon 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 performing some database action, and you want to close the database connection whether or not an exception was raised:

572

Dealing with Errors

Dim conn As SqlConnection

Try

conn = New SqlConnection(“ . . . “) conn.Open()

do some database action Catch sqlEx As Exception

log the exception

Tools.Log(“A database exception occurred”, sqlEx)

Finally

If conn IsNot Nothing Then

conn.Close() End If

End Try

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:

Dim trans As SqlTransaction = Nothing

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

. . .

573

Chapter 15

4.Now you need to do the same at the end of the method, so modify the code to look like this:

cmd.ExecuteNonQuery() Next

trans.Commit()

Catch SqlEx As SqlException

If trans IsNot Nothing Then

trans.Rollback()

End If

CreateOrderErrorLabel.Visible = True

Return

Finally

If conn IsNot Nothing Then

conn.Close()

End If

End Try

Profile.Cart.Items.Clear()

End Sub

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 and 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.

How It Works

The code to trap any database exceptions is fairly straightforward, but you’ve added in 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.

574

Dealing with Errors

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:

Dim trans As SqlTransaction = Nothing

It is assigned an initial value of Nothing to avoid potential runtime errors. In your code this isn’t a problem, but if you remove = Nothing from the declaration you can see that VWD displays a warning stating that Variable ‘trans’ is used before it has been assigned a value. A null reference exception could result at runtime. 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()

Once 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 to if an exception occurs. The

BeginTransaction method of the SqlConnection returns a SqTransaction object, so later in the code this can be used to commit or rollback the database changes:

trans = conn.BeginTransaction

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. Once 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 SqlEx As SqlException

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 Nothing. 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

575

Chapter 15

trans variable would still have its default value of Nothing. That’s why the compiler gives a warning 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 Nothing:

If trans IsNot Nothing Then trans.Rollback()

End If

Once the transaction has been rolled back an error message is displayed, by simply making a Label control visible. Once 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 Nothing, and this is used to ensure that there is a connection before you try to close it:

Finally

If conn IsNot Nothing Then

conn.Close()

End If

End Try

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 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 all very well, 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 will be 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. Give this a try in the following Try It Out, modifying the exception code you added to the checkout.

576

Dealing with Errors

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 SqlEx As SqlException

If trans IsNot Nothing Then

trans.Rollback()

End If

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.vb class file, add the following Imports statements after the initial one that imports the Microsoft.VisualBasic namespace:

Imports System.IO

Imports System.Web

6.Within the Tools class, add the following methods:

Public Shared Sub Log(ByVal Message As String)

Log(Message, Nothing)

End Sub

Public Shared Sub Log(ByVal Message As String, ByVal Ex As Exception)

Dim fileName As String = Path.Combine( _ HttpContext.Current.Request.PhysicalApplicationPath, “WroxUnited.log”)

Using logFile As New StreamWriter(fileName, True) logFile.WriteLine(“{0}: {1}”, DateTime.Now, Message) If Ex IsNot Nothing Then

logFile.WriteLine(Ex.ToString()) End If

logFile.Close() End Using

End Sub

7.Save the file and run the application. Log in and add some items to the shopping cart, 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 and you’ll see the exception details logged.

577

Chapter 15

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 Shared method so you haven’t had 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:

Imports System.IO

Imports 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)

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 Shared, so a class instance isn’t required – Shared 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 Nothing as a second parameter:

Public Shared Sub Log(ByVal Message As String)

Log(Message, Nothing)

End Sub

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 Shared Sub Log(ByVal Message As String, ByVal Ex As Exception)

578