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

Beginning ASP.NET 2.0 With CSharp (2006) [eng]

.pdf
Скачиваний:
84
Добавлен:
16.08.2013
Размер:
20.33 Mб
Скачать

Chapter 15

With this code, a check is made for the number of paragraphs before the loop is done, and if there is only a single paragraph, no looping is done.

This is a surprisingly common occurrence — errors made because of simple assumptions. This particular code has been in use for several years without failure, but today, although it was user error that caused the problem, the failure is in the code for not protecting itself. The dumb user (me) had what looked like multiple paragraphs, but because of soft carriage returns in fact had only one. So the user was wrong, but the code didn’t protect against it.

Query Parameters

If you build your applications using the wizards and data source controls, then you won’t run into this problem, because the generated SQL statements contain parameters. But if you manually construct SQL, or inherit an application, you may see code like this:

string SQL = “SELECT * FROM Products “ +

“WHERE ProductID=” + Request.QueryString[“ProductID”[

Or like this:

string SQL = “SELECT * FROM Employee “ +

“WHERE LastName=’” + LastName.Text + “‘“

Both of these dynamically construct a SQL statement, concatenating strings. The first uses a value from the QueryString, the second from a TextBox. The problem here is that there’s no validation on the user input, so it’s easy to perform what’s known as SQL injection, where rogue SQL statements are injected into existing ones. With the second example, if the user enters a last name, the SQL would be as follows:

SELECT * FROM Employee WHERE LastName=’Paton’

However, the user could enter the following:

Paton’ OR 1=1 --

This would turn the SQL into the following:

SELECT * FROM Employee WHERE LastName=’Paton’ OR 1=1 --’

The first thing to know is that -- is a comment in SQL, so the trailing quote is ignored. The SQL statement matches any LastName that is Paton, which is the intended match, but also the 1=1 statement, which will always be true. The result is that all employees would be shown, which is definitely not the intended purpose. This is mild, however, compared to the user who enters the following:

Paton’ ; DROP TABLE Employee –

The SQL statement is now as follows:

SELECT * FROM Employee WHERE LastName= ‘Paton’ ; DROP TABLE Employee --’

558

Dealing with Errors

The SELECT statement works as expected, but there’s now a second SQL statement — the semicolon separates statements in SQL. So the query runs, and then the table is dropped — no more employees. Serious trouble. This could easily have been any other SELECT statement to find out more information about the database, or even dropping the entire database.

The solution to SQL injection is to use parameters, because these automatically prevent this type of attack. If you’re using stored procedures, which you should be, then parameters are required for passing information into the procedure, but when you’re building SQL dynamically, you can still use parameters. So if you were running a SqlCommand, you could do this:

string SQL = “SELECT * FROM Employee WHERE LastName=@LastName” SqlCommand cmd = new SqlCommand(SQL, conn);

cmd.Parameters.Append(“@LastName”, SqlDbType.VarChar, 50);

cmd.Paramaters[“@LastName”].Value = LastName.Text;

Here @LastName is the parameter name, and because the value is assigned via the parameter, no SQL injection can take place. This is because ADO.NET protects against SQL injection attacks when using parameters — the values passed into parameters are checked for specific content that would signify an attack.

Validation

We’ve already mentioned that any user input is automatically suspicious, and one of the things that can be done to mitigate the risk is to validate that input, stopping the user from entering bad input in the first place. This is good for two reasons: first, it means the code is safer, and second, users won’t have to deal with potentially confusing error messages. They might still get messages telling them that their input is incorrect, but at least they won’t get an obscure error message because your code failed.

To help with this problem, there is a suite of five validation controls with ASP.NET 2.0 that can check user input before it even gets to your code:

RequiredFieldValidator: Ensures that a field is not left empty.

CompareValidator: Compares the entered text with a value, or with another control.

RangeValidator: Ensures the entered text is within a specified range.

RegularExpressionValidator: Matches the entered text against a regular expression.

CustomValidator: Runs custom code to validate the entered text.

These are standard ASP.NET controls that are placed on the page, generally alongside the control they are validating. There is also a ValidationSummary control that allows display of error messages in one place. You add these in the following Try It Out.

Try It Out

Validation Controls

1.In VWD, in the Wrox United project for this chapter (C:\BegASPNET2\Chapters\Begin\ Chapter15\WroxUnited), open FanClub\FanClubMatchReport.aspx.

559

Chapter 15

2.Find FormView1 and find TextBox1 in the <EditItemTemplate>. Between TextBox1 and the

<br />, add the following:

<br />

<asp:RequiredFieldValidator id=”rfv1” runat=”server”

ControlToValidate=”TextBox1”

Text=”You haven’t entered the report!” />

3.Do the same for the <InsertItemTemplate> — enter exactly the same code between TextBox1 and the <br />.

4.Save the file and run the application, logging in as a fan club member (use Dave or Lou as the user — the passwords are dave@123 and lou@123).

5.Select the Add Match Report from the Fan Club menu, pick a match, and select the Add New Report link.

6.Don’t enter any text in the text area, but press the Save New Report link. You’ll see an error message, shown in Figure 15-1.

Figure 15-1

7.Enter some text and save the report.

8.Click the Add New Report link again to add another report.

9.Don’t enter any text, but click the Cancel New Report link instead. Notice that the error message is displayed again. You can’t cancel unless some text has been entered, so let’s correct that.

10.Stop the application and, back in VWD, edit the page. Find btnCancelReport (a LinkButton that is in both the <EditItemTemplate> and <InsertItemTemplate>), and for both occurrences, add the following attribute:

CausesValidation=”False”

11.Save the file and repeat steps 5 and 6. Notice that you can now cancel even if no text has been entered.

560

Dealing with Errors

How It Works

This works because the validation control is linked to the text box and won’t allow the page to be posted back to the server until some text has been entered. Here’s a look at the validator in more detail:

<asp:RequiredFieldValidator id=”rfv1” runat=”server” ControlToValidate=”TextBox1”

Text=”You haven’t entered the report!” />

The two properties you’re interested in are the ControlToValidate and the Text. The first is what links the controls — in this case, it’s TextBox1 that the validator will check. If no text is entered, the contents of the Text property are displayed. If text has been entered, the validator does nothing and no output is shown.

Validation occurs whenever a control would cause the page to be posted back to the server — this could be a Button, a LinkButton, and so forth, but what’s important to note is that by default no postback occurs because the validation is done client-side in the browser. This means that the user sees a responsive display without that refresh that can be so annoying. However, you don’t want the validation to take place when you’re trying to cancel an edit — if you’re canceling, it doesn’t matter whether there’s anything in the text box. By default, all means of posting back cause validation to occur, but by setting the CausesValidation property to False for the cancel link, no validation takes place.

Validation should always be done server-side, but can optionally be done client-side as well. You should never rely solely upon client-side validation, because it’s easy for hackers to post data directly to a page, bypassing the client-side validation. The default settings in ASP.NET 2.0 are for both clientand server-side validation.

Validating Multiple Controls

The previous example showed how to validate a single control, with the error message shown directly by the control. However, there are times when you want to validate several controls, and in these cases, it’s not always sensible to have the error message presented directly by the control. For example, you might have a grid of data, or the data you are editing might be neatly aligned. Under these circumstances, you might not want to have the message displayed, because it would change the layout of the screen. However, you probably still want some form of indication as to which editable field failed the validation, as well as having the full error message displayed somewhere. For that, you use the ValidationSummary control. The following Try It Out shows how this can be done.

Try It Out

The ValidationSummary Control

1.Open FanClub.aspx and add RequiredFieldValidator controls between the end of the text boxes and the </td>. You’ll need the following validators:

<asp:RequiredFieldValidator id=”rfv1” runat=”server” ControlToValidate=”txtName”

Text=”*” ErrorMessage=”You must enter a value for your name” />

<asp:RequiredFieldValidator id=”rfv2” runat=”server” ControlToValidate=”txtAddress”

Text=”*” ErrorMessage=”You must enter a value for the address” />

561

Chapter 15

<asp:RequiredFieldValidator id=”rfv3” runat=”server” ControlToValidate=”txtCity”

Text=”*” ErrorMessage=”You must enter a value for the city” /> <asp:RequiredFieldValidator id=”rfv4” runat=”server”

ControlToValidate=”txtCounty”

Text=”*” ErrorMessage=”You must enter a value for the county” /> <asp:RequiredFieldValidator id=”rfv5” runat=”server”

ControlToValidate=”txtPostCode”

Text=”*” ErrorMessage=”You must enter a value for the post code” />

<asp:RequiredFieldValidator id=”rfv6” runat=”server” ControlToValidate=”txtCountry”

Text=”*” ErrorMessage=”You must enter a value for the country” /> <asp:RequiredFieldValidator id=”rfv7” runat=”server”

ControlToValidate=”txtEmail”

Text=”*” ErrorMessage=”You must enter a value for the email address” /> <asp:RequiredFieldValidator id=”rfv8” runat=”server”

ControlToValidate=”txtAlias”

Text=”*” ErrorMessage=”You must enter a value for the membership alias” />

For example, the Name would be:

<tr>

<td>Name:</td>

<td><asp:TextBox id=”txtName” runat=”server” /> <asp:RequiredFieldValidator id=”rfv1” runat=”server”

ControlToValidate=”txtName”

Text=”*” ErrorMessage=”You must enter a value for the name” />

</td>

</tr>

2.After the validator for the e-mail address, add the following:

<asp:RegularExpressionValidator id=”rev1” runat=”server” ControlToValidate=”txtEmail”

Text=”*” ErrorMessage=”Please enter a valid email address” />

3.Switch the page into Design View and select the LoginView control, FCLoginView. From the Common Tasks menu, select RoleGroup[0]–FanClubMember from the Views list (see Figure 15-2). This will show the controls in the RoleGroup for users in the FanClubMemberRole.

Figure 15-2

4.Scroll down the page to find the RegularExpressionValidator — this is the right-hand validator next to the Email text box. Select this validator, and in the Properties area, click the ValidationExpression property. Then click the button that appears, as shown in Figure 15-3.

562

Dealing with Errors

Figure 15-3

5.The window that appears is the Regular Expression Editor, which comes pre-filled with a selection of common expressions. Select Internet E-mail Address and click OK, and the selected expression will be filled into the ValidationExpression property.

6.Switch the page back to Source View, and before the Button (btnSaveChanges), add the following:

<asp:ValidationSummary id=”vs” runat=”server” DisplayMode=”BulletList” /><br />

7.Save the file and run the application. Log in as either Dave (using the password dave@123) or Lou (using the password lou@123), and navigate to the Fan Club home page.

8.Add text to the fields, but leave the City and Email fields blank. Click the Save Changes link and you’ll see Figure 15-4.

Figure 15-4

9.Enter some text into the Email field, but make sure it isn’t a valid e-mail address. Click the Save Changes link again and notice how the error message has changed.

563

Chapter 15

10.Delete the text from the Name field and press the Tab key to move to the next field. Notice that as soon as you tab out of the field, a red star appears next to the field, indicating an error.

How It Works

Several differences exist between this example and the first one where you used only one Required FieldValidator. In this example, there are several, each for different fields, as well as other validation controls. One thing you may have noticed about the required field validators is that no error message was displayed alongside the fields they are validating. That’s because the Text property was set to * — just a simple marker to indicate an error in the field. The Text property is displayed inline — that is, wherever the validator is placed. The ErrorMessage property is used wherever the error is displayed, and in this case, that is shown by the ValidationSummary.

The ValidationSummary is fairly obvious in its purpose, because it simply displays in one location all error messages from validators. This allows the validators to be discrete, showing just a simple asterisk to mark the field in error without causing displacement on the screen, but still to have a full error message. The DisplayMode for the summary can be a list, bulleted list, or a paragraph.

The other validator used was the RegularExpressionValidator, which checks the entered text against a regular expression — this is a format for defining character sequences. If you want to use regular expressions for other fields, such as postal codes, then the Regular Expression Editor has some expressions. Otherwise, you can find many others at www.regexlib.com/, which has a searchable database of expressions freely available. You may have noticed that the Email field had two validators: one to ensure some text was entered, and one to ensure that the entered text was valid. Both validate the same control because they both have their ControlToValidate property set to txtEmail. You can mix and match the other validators in the same way, which gives great flexibility in ensuring that entered data is correct.

The other thing you may have noticed was that tabbing from field to field also showed the asterisk, but not the error message in the summary. Because validation is being done client-side, the client script knows that text is required for a field, so if the field is left empty, it can signal an error. But because the summary is intended to show all errors, this isn’t filled in unless a postback is tried. So you get the best of both worlds — dynamic error handling with full error messages.

Exception Handling

Exception handling is the term given to anything out of the ordinary that happens in your application — exceptional circumstances. Although handling exceptions is part of defensive coding, there’s a difference between programming for things that could quite easily go wrong, or are even expected to go wrong, such as incorrect passwords, and those things over which you have no control. For example, consider what happens if you can’t connect to the database. How would you handle that situation? Now it’s likely that there’s nothing you can really do in your application, because it may well be dependent upon the database, but you still don’t want your users to see an ugly error message. Instead, you want to display your own message, maybe letting them know that there is a problem and they can try again later.

The trick is to have a strategy, to work out in advance what you’re going to do when something goes wrong.

564

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.

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.

 

 

565

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 properties described in the following table.

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 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 with 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 static void GenerateThumbnail(string SourceImagePath, string TargetImagePath)

{

using (Image sourceImage = Image.FromFile(SourceImagePath))

{

short newHeight = (short) Math.Round((double)(sourceImage.Height * 0.25)); short newWidth = (short) Math.Round((double)(sourceImage.Width * 0.25)); Image.GetThumbnailImageAbort abort1 = new

Image.GetThumbnailImageAbort(ImageHandling.ThumbnailCallback);

using (Image targetImage = sourceiMage.GetThumbnailImage (newWidth, newHeight, abort1, IntPtr.Zero))

{

targetImage.Save(TargetImagePath, ImageFormat.Gif);

}

}

}

566

Dealing with Errors

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 static void GenerateThumbnail(string SourceImagePath, string TargetImagePath)

{

if (!File.Exists(sourceImage))

return;

using (Image sourceImage = Image.FromFile(SourceImagePath))

{

short newHeight = (short) Math.Round((double)(sourceImage.Height * 0.25)); short newWidth = (short) Math.Round((double)(sourceImage.Width * 0.25)); Image.GetThumbnailImageAbort abort1 = new

Image.GetThumbnailImageAbort(ImageHandling.ThumbnailCallback);

using (Image targetImage = sourceiMage.GetThumbnailImage(newWidth, newHeight, abort1, IntPtr.Zero))

{

try

{

targetImage.Save(TargetImagePath, ImageFormat.Gif);

}

catch(Exception ex)

{

Tools.Log(“ImageHandling.GenerateThumbnail”, ex);

}

}

}

}

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 (Exception ex)

{

Place the code here to handle the exception

This could be displaying a tidy message or logging the exception

}

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

567