Errors at Run Time


Run-time errors can occur after the program has been compiled, and while the code is running. For these situations you have to build in defenses, to try and prevent things from going wrong. Sometimes though, you have to find out what's going on yourself. So, let's revisit a web page we created early in the book (the MailingList example from Chapter 3), and look at an error that we left in.

Try It Out—Finding an Exception

  1. Open MailingList.aspx. (MailingList_part2.aspx in the code download.)

  2. Press F5 to run the program.

  3. Without filling in any fields or changing any defaults, press the Register button:

    click to expand

    Aha, an exception, but not a helpful message. This is because debugging is turned off.

  4. In Web Matrix, switch to the All view, and add the following to the top line:

     Debug="true" 

    It should now look like this:

     <%@ Page Language="VB" Debug="true" %> 
  5. Save the page, run it, and press the button again:

    click to expand

    Much better – now we can see the line that the error occurred on.

How It Works

This is fairly simple – by default debugging is turned off. This means that you can't see the line numbers or the code where exceptions happen. The reason for this is security – once you have deployed a page you wouldn't want any errors to show source code – this could give hackers information about your system that you wouldn't want them to have. So, here's an important note:

Important

Always turn off debugging before you deploy your web pages.

Turning debugging on allows ASP.NET to keep track of line numbers and code details, and from that it can show you where the error was. Of course, showing you where the error was is great, but the error message can often be confusing. Take our error above as an example. What is an 'Object reference', and why should it be set to an 'instance of an object' anyway? Although this sounds like programmer gobbledygook, it does make sense. What you have to remember is that everything in .NET is an object, but objects don't always exist.

To explain what we mean by this let's look at a simple analogy. Consider a catalogue of products – let's pick ice cream. Each item in the catalogue is an object, but it doesn't exist in your space (that is, your house, room, or other area) – it's just a description of what could exist. Until you purchase that item and have it sitting in front of you, it's just an object. Once you have that large tub of double choc–chip it becomes a real-life thing – an instance. The same goes for our code – not everything exists when we think it does. Let's look at the error line in detail, to see what this means:

If radMail.SelectedItem.Value = "HTML" Then

radMail is the radio button list, and we know that exists since it's on the page. But what about SelectedItem? Surely this exists – it defines which radio button from the list has been selected. Aha – we haven't selected anything, so there's no instance – it doesn't exist.

Preventing Invalid User Input

So now we can see where the error occurred, what can we do to prevent it? One option would be to trap the exception, and handle it gracefully, perhaps by displaying a nice message to the user. However, this isn't the best solution on this occasion. The problem occurs because the radio button list for the mail format (HTML or Text) doesn't have a default – neither one is set. The solution here is to just set one of these to be selected, so that if the user does just press the button, then it doesn't matter. You can set the default by selecting the RadioButtonList on the design surface, and viewing the ListItem Collection Editor for the Items property. This shows the list items, and on each item there is a Selected property – this is the default state of the radio button. Select either one of the items in the left-hand pane and change the Selected property in the right-hand pane to True.

So we've prevented an error by ensuring that the interface doesn't allow an invalid selection. But what about the text entry fields, for name and e-mail address? Well we can do the same thing here, by protecting the interface, to ensure that the name and e-mail address are filled in, and that the e-mail address looks like a valid one. You've already seen some validation in earlier chapters, where we used the RequiredFieldValidator to ensure that fields were filled in. We won't cover that again here – instead we'll look at making sure that the e-mail address is valid.

Try It Out—Validating E-mail Addresses

  1. Drag a RegularExpressionValidator onto the page, and drop it next to the e-mail textbox. You may need to make your layout table a bit wider to accommodate this.

  2. Set the ControlToValidate property to txtEmail.

  3. Set the ErrorMessage property to Invalid Email Address.

  4. Click into the ValidationExpression property and click the builder button. From the Regular Expression Editor window select Internet E-mail Address:

  5. Press OK, save the file and run it.

  6. Enter some text in the e-mail address field – make sure it isn't a valid e-mail address:

    click to expand

How It Works

The RegularExpressionValidator works in the same way as the RequiredFieldValidator, in that when a button is pressed, it validates the content of a control. However, instead of just checking that the field has been filled in, it compares the text to a regular expression. A regular expression is a special string that defines how patterns in strings can be matched. In our case it defines how an e-mail address should look, and is checking for something@something.something. So, it's not a great check, but it is better than nothing. The only way to do proper validation of e-mail addresses is to use some code, and actually contact the mail server that the user uses, checking for the e-mail alias. That's all a bit beyond the scope of this book.

As you may have seen in the Regular Expression Editor dialog, you can have expressions to check for phone numbers, zip codes, and so on.

Other Validation Controls

There are several other validation controls that are worth mentioning:

  • CompareValidator, which compares the contents of two fields. This is useful, for example, in registration pages, where you ask for a password, and a confirmation of that password. The CompareValidator ensures that both fields are the same.

  • RangeValidator, which compares a control with a set of values. This could be used if you had pages asking for an age, or a quantity.

  • CustomValidator, which allows you to call a custom function to perform validation. This is useful if you have specific validation rules, such as for an ISBN number.

Defensive Coding

Of course, ensuring that the user interface doesn't allow users to make errors is only one part of the process. There are plenty of times when this just isn't possible, so we have to add preventative measures in our code. This all revolves around the understanding that exceptions can happen, but that you can contain them in a safe way. To do this in code we use Try ... Catch statements, to allow us to Try some piece of code, and Catch any exceptions that occur. The general syntax is like this:

 Try  'code to try goes here Catch ex As Exception  'code to run when an Exception occurs End Try 

What you do when an Exception occurs really depends upon what the exception is. For example, an error connecting to a database might be fatal since you can't continue with the program. On the other hand, trying to access a file that you don't have permission for might not be fatal – you could display an error message to the user and continue. Let's have a look at the first of these, and see how the Try ... Catch works.

Try It Out—Adding Try … Catch

  1. Create a new ASP.NET File called TryCatch.aspx.

  2. Switch to the All view, and add the following between the Register and Script parts:

    <%@ Page Language="VB" %> <%@ Import Namespace="System.Data" %> <%@ Import Namespace="System.Data.Sqlclient" %> <script runat="server">
  3. Switch back to Design view, and add a Button, a DataGrid, and a Label to the page.

  4. Double-click the button to get to the code view. Add the following code:

    Sub Button1_Click(sender As Object, e As EventArgs)  Dim conn As New _  SqlConnection("Server=foo;Database=pubs;Trusted_Connection=true")   Dim cmd As New SqlCommand("select * from authors", conn)  Try  Conn.Open()  DataGrid1.DataSource = cmd.ExecuteReader(CommandBehavior.CloseConnection)  DataGrid1.DataBind()  Catch ex As Exception  Label1.Text = "Could not connect to the database – " & _  "please try again later."  End Try End Sub 

  5. Save the page and run it. Click the button:

    click to expand

How It Works

The workings of this are fairly simple. First we define a connection to a database, and a command to run on that database. However, the details of the database server are incorrect – it's pointing to a server called foo, which doesn't exist. This will cause the connection to fail.

Dim conn As New _     SqlConnection("Server=foo;Database=pubs;Trusted_Connection=true") Dim cmd As New SqlCommand("select * from authors", conn)

Now we come to the Try block – this is where the code is placed in which something may go wrong.

Try

The code it contains opens a connection to the database, fetches some data, and displays it in a grid.

  Conn.Open()   DataGrid1.DataSource = cmd.ExecuteReader(CommandBehavior.CloseConnection)   DataGrid1.DataBind() 

Now we have the Catch block. With this line we are saying that if an exception occurs, we don't want automatic messages shown. Instead, we want to handle the exception ourselves. So we define a variable (ex) of type Exception, and when an exception is raised, ex will contain the details of any problems that have occurred:

Catch ex As Exception

Now we add the code that will run if something goes wrong. Here we set the text of a label to some nice message, rather than displaying a nasty error message.

  Label1.Text = "Could not connect to the database – " & _                 "please try again later." End Try

Exceptions

It's worth looking at this topic a little more, since it's very important to understand how exceptions work and can be handled. The example above shows the simplest form – just trapping an exception and displaying some text. However, it's not a very clever piece of code, since we are only displaying some generic text. What happens if the connection to the database succeeds, but some other error occurs? We'd still get the same error message, and this could be very confusing to the user.

What we need is a way of breaking down exceptions into narrow bands, and luckily this has already been done for us. There are many types of exception, including:

  • Exception, which identifies a generic, default exception.

  • SqlException, which is for SQL Server exceptions.

  • NullReferenceException, which is when an object is not set. You saw this happen in the second example in this chapter.

  • DivideByZeroException, for arithmetic errors where a number is divided by 0.

There are many others, and you'll need to look in the documentation for a complete list.

The use of these becomes much clearer when you look at another form of Try ... Catch:

 Try  'code to try goes here Catch SQLex As SqlException  'code to run when a SQL Server exception occurs Catch ex As Exception  'code to run when an Exception occurs End Try 

Here you can see we've got two Catch blocks – one to run if a SQL Server exception is raised, and one for any other type of exception. The SqlException has a narrower focus (only SQL Server or MSDE will raise this), so we handle this type of exception first. If we put the general exception first, then the general exception handler would always catch the exception, whatever type it was. So now we have code that caters for two types of exceptions. However, it's still not perfect, because there could be a number of types of SQL Server exception – for example, insufficient permissions for accessing a particular table in a database. In this case, you wouldn't want to display the message we've displayed – you'd probably want to tell the user to contact the web site administrator.

To find out the details of an Exception we can examine its properties and methods:

  • Message – the descriptive text of the exception

  • Number – the unique exception number

  • Errors – a collection of SqlError objects giving detailed information about
    the exception

  • ToString()– displays all details of the exception

There are a few others, but those listed above are the important ones. We could use the Number property to make our exception handling code more intelligent:

 Try  conn.Open()  DataGrid1.DataSource = cmd.ExecuteReader(CommandBehavior.CloseConnection)  DataGrid1.DataBind() Catch SQLex As SQLException  If SQLex.Number = 17 Then  Label1.text = "Could not connect to the database - " & _  "please try again later."  Else  Label1.Text = "SQL Error: " & SQLex.Message  End If Catch ex As Exception  Label1.Text = "Fatal error: " & ex.Message End Try 

Here we check for the number of the error – 17 indicates a connection problem, so we show a connection error message. This technique isolates us from one message being used for potentially more than one fault. Of course the big question is how did I know that the error number 17 means a connection problem? Well, I cheated. I just displayed the error number and the message, and then took the number that had already been displayed and used it in the code. The number isn't documented, so it makes it hard to find out, and hard to understand for someone not familiar with it. I use it here to illustrate an important point – you should avoid hard-coded numbers like this in your code. Not only does it make code harder to maintain, but also it ties you down to a specific error number. What happens if Microsoft changes the error number in future releases?

A much better way of tackling this problem would be to wrap distinct parts of the code in Try ... Catch statements:

 Try  conn.Open() Catch ex As Exception  Label1.text = "Could not connect to the database – " & _  "please try again later." End Try Try  DataGrid1.DataSource = cmd.ExecuteReader(CommandBehavior.CloseConnection)  DataGrid1.DataBind() Catch SQLex As SQLException  Label1.text = "Error fetching data."  Catch ex As Exception  Label1.Text = "Fatal error: " & ex.Message End Try 

This code allows us to isolate the process of connecting to the data source from other data-access code. We can now have an explicit message for each exception.




Beginning Dynamic Websites with ASP. NET Web Matrix
Beginning Dynamic Websites: with ASP.NET Web Matrix (Programmer to Programmer)
ISBN: 0764543741
EAN: 2147483647
Year: 2003
Pages: 141

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net