This content was uploaded by our users and we assume good faith they have the permission to share this book. If you own the copyright to this book and it is wrongfully on our website, we offer a simple DMCA procedure to remove your content from our site. Start by pressing the button below!
Comments:
The page in Listing 1.1 contains a simple HTML form that includes a text field for a username and a text area for comments. This form might be used, for example, in a simple guest book application at a Web site. You might notice that the HTML form posts back to itself (the ACTION attribute of the tag submits the form data to SimpleHTML.aspx). So, when the Add Comment button is clicked, the same form appears once again. The HTML form in Listing 1.1 doesn’t contain any ASP.NET controls. Now open the page in a Web browser (Microsoft Internet Explorer or Netscape Navigator); then try entering a username and some comments and submitting the form. Notice that the data you enter into the form fields disappears every time you submit the form. Now modify the page in Listing 1.1 so that it uses ASP.NET controls rather than the standard HTML form elements. You’re going to convert the form fields into “smart”
Building ASP.NET Pages CHAPTER 1
LISTING 1.2
SimpleASPX.aspx
Simple ASPX Page Username:
Comments:
Notice the four modifications to this page. First, you rename the page from SimpleHTML. to SimpleASPX.aspx. The particular name of the file is not important, but the .aspx extension is very important. Your Web server detects that the page is an ASP.NET page and not a normal HTML page or other type of page because of the special .aspx extension. You create all your ASP.NET pages with this special extension. aspx
ASP Classic Note What about files that end with the extension .asp? Classic ASP developers (users of versions of ASP before ASP.NET) named all their ASP files with the extension .asp instead of .aspx. You can continue to name your ASP files with the extension .asp, but they do not gain any of the new functionality of ASP.NET. Files with the .asp extension continue to be executed as classic ASP files. By default, files with the extension .asp are mapped by Internet Information Server (IIS) to asp.dll, and files with the extension .aspx are mapped to aspnet_eisapi.dll. So, both classic ASP files and ASP.NET files can happily live and work together on the same Web server.
1 BUILDING ASP.NET PAGES
server-side form fields. Enter the page contained in Listing 1.2 and save the file with the name SimpleASPX.aspx in a directory that your Web server can access.
19
20
Working with ASP.NET Web Forms PART I
Note You might want to use an extension other than .aspx for your ASP.NET pages in several situations. For example, you might have a Web site with hundreds of existing pages with names that end with the extension .htm or .html and that have already been indexed by a search engine such as AltaVista or Google. You might not want to change the extensions of these filenames because you fear that you’ll lose ranking in the search engines. You can, in fact, use any extension that you please for ASP.NET pages. See the first appendix at the back of this book, Migrating from ASP to ASP.NET, for details on how to do this.
Second, notice that the attribute Runat=”Server” is added to all the form tags. For example, instead of using
to create the username form field, you use the following tag instead:
The Runat=”Server” attribute converts these standard HTML tags into “smart” serverside HTML tags. When the SimpleASPX.aspx page is opened in a Web browser, the form tags with this attribute are executed on the Web server before any content is rendered to the Web browser. The Runat=”Server” attribute converts these standard HTML tags into ASP.NET controls. Third, notice that instead of using the NAME attribute to name the form fields, you give the form fields a unique identifier with the ID attribute. You use the ID attribute because you are no longer treating the form fields as simple HTML tags; you are converting the form fields into server-side objects. Finally, notice that you modify Listing 1.1 so that you can open the HTML form using
rather than the standard
If you want to convert any of the form fields within an HTML form into ASP.NET controls, you must use the Runat=”Server” attribute with the opening form tag.
Building ASP.NET Pages CHAPTER 1
Note By default, the standard HTML form tag’s Method attribute has the value GET. When a form is submitted with the Method=”Get” attribute, the form data is submitted as query string variables. Because a lot of form data cannot be passed in a query string (it’s browser-dependent, but typically not more than 1,000 to 2,000 characters), most people add a Method=”POST” to the form tag whenever they create an HTML form. The ASP.NET version of the form tag has the opposite behavior. By default, form data is posted with Method=”POST”. This version is nice because it saves some typing (I’ve spent the past six years typing Method=”POST” whenever I create a form). If you really want to, however, you can override this behavior and specify Method=”Get”.
Furthermore, notice that an Action attribute is not specified. The ASP.NET HTML Form control automatically posts back to the same page (SimpleASPX.aspx). Therefore, the server-side version of the form tag performs the same way as the original form tag, but with less typing on your part. In short, to convert a standard HTML file into an ASP.NET page, you make four modifications to the page: 1. You rename the file so that the filename ends with the extension .aspx. 2. You add the attribute Runat=”Server” to each of the form tags. 3. Instead of using the NAME attribute for the form tags, you use ID instead. 4. You convert the opening tag from to the simpler . Now that you’ve converted a standard HTML form into a “smart” ASP.NET form, what do you get out of it? Open the page in Listing 1.2 (SimpleASPX.aspx) in a Web browser and try submitting the form. Notice that all the form data (any text you type into the username and comments fields) is preserved between form posts. Now try to post the form over and over again. Notice that the form data does not disappear. Whenever I demonstrate a smart ASP.NET form to people, I always receive what I call the Yawn Reaction. “Okay,” a typical response goes, “the form saves the data, but what’s
1 BUILDING ASP.NET PAGES
Notice that the Method attribute of the form tag is not used with the control version of the form tag. By default, the ASP.NET HTML Form control automatically includes this attribute.
21
22
Working with ASP.NET Web Forms PART I
the big deal?” Users of earlier versions of ASP inevitably respond, “I could do the same thing with a couple lines of script in any case.” In the next section, I try to convince you that something interesting is, in fact, happening here. The ASP.NET page contained in Listing 1.2 is doing a lot of complicated work in the background that opens up several exciting programming possibilities.
Benefits of ASP.NET Controls In the preceding section, you took a standard HTML form and converted it into a smart ASP.NET form. You did so by converting all the form tags into server-side HTML tags. Why bother? Why use ASP.NET controls at all? Using these controls provides four important benefits: • ASP.NET controls expose the HTML elements of a page in an intuitive object model. • ASP.NET controls automatically retain the value of their properties by participating in view state. • ASP.NET controls enable you to cleanly separate the design content of a page from the application logic. • ASP.NET controls enable you to maintain browser compatibility while still supporting advanced browser features such as JavaScript. You look in greater detail at each of these benefits of ASP.NET controls in the following sections.
An Intuitive Object Model Normally, a Web server ignores the HTML tags in a page. From a Web server’s perspective, these tags are just meaningless strings of text sent to a Web browser. It’s the browser and not the server that performs all the hard work of parsing the tags in a Web page and displaying bold as bold. However, after an HTML tag is converted into an ASP.NET control, the tag is no longer a meaningless string of text to the Web server. The tag has become a server-side object with properties, methods, collections, and events that you can program against. Converting standard HTML tags into server-side objects opens up many programming possibilities. After the HTML elements of a page have been exposed as server-side objects, you can manipulate the properties of the objects through server-side code. You can write application logic that reads or modifies the properties of the objects. You can even programmatically add new objects to a page or hide existing ones.
Building ASP.NET Pages CHAPTER 1
Listing 1.3 contains a form that has two fields (labeled field1 and field2). If you enter text into the first form field and click the submit button, the text is copied to the second field. Now try the page (it’s included on the CD with the name CopyField.aspx). LISTING 1.3
CopyField.aspx
Sub Button_Click( s As Object, e As EventArgs ) field2.Value = field1.Value End Sub CopyField.aspx
Field 1:
Field 2:
1 BUILDING ASP.NET PAGES
For example, after the username form field has been converted into an ASP.NET control, you can set and read the username control’s Value property. The Value property determines the text that is displayed by the control. You can write application logic that assigns any value to the username control that you wish. Or you can write code that reads the Value property and saves it to a file or database.
23
24
Working with ASP.NET Web Forms PART I
The text is copied from the first field to the second with the following line of code: field2.Value = field1.Value
This statement assigns the Value property of field1 to the Value property of field2. Notice how intuitive the code is. By exposing the elements of an HTML page as serverside objects, you gain control over all the properties of a page in an easy-to-understand manner. Also notice how controls enable you to use an event-driven programming model. Clicking the submit button raises the Click event and the Button_Click subroutine executes. In classic Active Server Pages, you could only write pages that execute from top to bottom. ASP.NET, on the other hand, enables you to write code that executes in response to events raised by particular user actions. Once again, this is a much more intuitive programming model.
View State As you saw in the preceding section, when you submit a form built from ASP.NET controls, the data entered into all the form fields is preserved when the form is displayed again. Microsoft calls this automatic persistence of data view state. ASP.NET controls automatically preserve view state. When you started with the simple HTML form, you did not get any of the benefits of view state. With a standard HTML form, you don’t notice any difference between the first time the form is submitted and the seventh time; you must start over with empty form fields every time. Note that view state does not apply only to the ASP.NET controls that correspond to form elements; all the standard ASP.NET controls preserve their state. For example, you can take a standard HTML tag and make it into an ASP.NET control like this:
In this line, you magically convert the tag into an ASP.NET control by adding an ID attribute and a Runat=”Server” attribute. Now that the tag is a server-side control, any text assigned to it is preserved every time a Web Forms Page that contains the tag is posted back to itself. This is illustrated in the page contained in Listing 1.4. LISTING 1.4
Guestbook.aspx
Sub Button_Click( s As Object, e As EventArgs ) entries.innerHtml = “” & username.Value & “
” & comments.Value &
Building ASP.NET Pages CHAPTER 1 LISTING 1.4
continued
Guestbook.aspx Username:
Comments:
The page in Listing 1.4 contains the same guest book form as in Listing 1.2. Like the form in Listing 1.2, the text entered into the username and comments form fields is preserved whenever the form is submitted. The view state of the username and comments controls is automatically preserved. Furthermore, you should notice that all the previous entries into the guest book are also preserved at the bottom of the form (see Figure 1.1). These entries are stored in the tag’s view state. (The tag with the entries ID.) Every time the form is posted, the contents of the Username and Comments fields are added to the tag’s view state. The contents of the Username and Comments fields are added to the text displayed by the tag by modifying the tag’s innerHTML property. Every time a new guest book entry is added, the following code is executed: entries.innerHTML = “” & username.Value & “
” & comments.Value & ➥entries.innerHTML
1 BUILDING ASP.NET PAGES
entries.innerHTML End Sub
25
26
Working with ASP.NET Web Forms PART I
FIGURE 1.1 Preserving view state.
How is the view state of the tag preserved? When you look at the source of the HTML page (in Microsoft Internet Explorer, select View, Source, or in Netscape Navigator, select View, Page Source), you notice a hidden form field that looks something like this:
This long, ugly hidden form field contains all the previous entries in the guest book. The view state of all the controls contained in a Web Forms Page is preserved in the hidden form field named __VIEWSTATE. View state is preserved only if a form is posted back to itself. If you leave the Guestbook.aspx page and visit the Yahoo home page and return again, the view state of Guestbook.aspx is lost (and all the guest book entries disappear into the ether). Or, if a form posts to another page, all the view state information also is lost. Why is view state useful? One situation in which view state is important is form validation. Suppose that you create a user registration form and the form has several required form fields. For example, you don’t want anyone to be able to submit the form without entering a first and last name.
Building ASP.NET Pages CHAPTER 1
Separating Code from Content Good programmers are not necessarily good designers. Most companies divide the task of developing a Web site between a design team and an engineering team. The design team is responsible for making a Web site pretty; the engineering team is responsible for making the Web site work. This division of labor was difficult to carry out with previous versions of Active Server Pages. The problem was that there was no clean way to separate the application logic of a Web page from its design elements. Designers had a tendency to mess up programming code. Engineers had no easy way to integrate design elements. The code and content were jumbled together in the same page. ASP.NET controls provide you with a clean method of dividing the code (application logic) from the content (design elements of the page). Designers can place ASP.NET controls into a page without worrying about the application logic. They can concentrate on layout and graphics. After the page is designed, it can be handed off to the engineering team. The application logic can be added to a separate section of the page or, better yet, to a separate file. Designers can even piece together an ASP.NET page using a visual development environment such as Visual Studio. You can drag and drop ASP.NET controls from a toolbar and quickly create a complicated page. Using a tool such as Visual Studio, you can design ASP.NET pages in the same way that Visual Basic or Visual C++ programmers have traditionally created complicated Windows forms.
Browser Compatibility ASP.NET pages are executed on the server, not on the browser. When a browser requests an ASP.NET page, a standard HTML page is retrieved. This means that an ASP.NET page can be made to be compatible with any browser. The fact that ASP pages generate plain old HTML has always been a big advantage of developing Web pages with Active Server Pages. You don’t have to assume that a browser supports VBScript, Java, or JavaScript to display an Active Server Page. However, with versions of ASP before ASP.NET, this was both an advantage and a burden.
1 BUILDING ASP.NET PAGES
Now imagine that someone submits the user registration form without completing the required form fields. In this situation, you want to display an appropriate error message. Furthermore, you want to redisplay all the form information that the user has already entered. Making the user start over from scratch every time a problem occurs with the data the user submitted is not a very nice thing to do. View state, as you have seen, automatically preserves the form information that the user previously entered.
27
28
Working with ASP.NET Web Forms PART I
It was a burden because people really like client-side JavaScript. When JavaScript is used correctly in a page, it can create a better user experience. For example, when completing an HTML form, you can use JavaScript to display error messages while the user is in the process of completing the form. If you don’t use JavaScript, the form must be submitted back to the server before the error message can be displayed. ASP.NET controls can automatically detect whether a browser supports JavaScript and render JavaScript content only if the browser is able to support it. You don’t have to add any JavaScript code yourself to take advantage of this feature of ASP.NET controls. The controls make the decision of whether to use JavaScript and render the JavaScript code for you. You also have the option of preventing a control from ever using JavaScript. You can set a property so that an ASP.NET control always renders plain HTML without JavaScript. You examine some examples of controls that use JavaScript in Chapter 3, “Performing Form Validation with Validation Controls,” where the ASP.NET Validation controls are discussed.
Overview of ASP.NET Controls You can add more than 50 standard ASP.NET controls to your Web Forms Pages. These controls can be divided into two types: HTML controls and Web controls. The next two sections provide an overview of each type of control. To view a complete reference on the properties and methods of each control, see Appendix B, “HTML Control Reference,” and Appendix C, “Web Control Reference.”
ASP.NET HTML Controls You’ve already seen some examples of the HTML controls in earlier sections of this chapter. The HTML controls are server-side replicas of the standard HTML tags. Corresponding to the standard HTML text tag, for example, is the HTMLInputText control:
And corresponding to the standard HTML tag is the HTMLImage control:
You get the idea. For each of the most common HTML tags, a server-side HTML control corresponds to it. Just stick a Runat=”Server” attribute on the end of the tag, and you have created the HTML control version of it. Furthermore, any HTML tags that are not represented by a distinct server control can be represented with the
Building ASP.NET Pages CHAPTER 1 HtmlGenericControl. You
Note The HTML controls can represent any HTML tag through the HtmlGenericControl control. This means that if new HTML tags are introduced with a future version of HTML, you still can represent the new tags with HtmlGenericControl. You can even use HTMLGenericControl to represent a tag that you make up yourself—for example, the tag. There is nothing wrong with creating a server-side tag like this:
You might be curious about how the HTML controls handle new or obscure HTML attributes. For example, suppose that you add an attribute called myAttribute to the standard HtmlInputText control like this:
This attribute does not cause an error. If you add a strange attribute to an HTML control, the control simply passes the attribute with its value unaltered to the Web browser when the control is rendered. This guarantees that the HTML controls remain compatible with future versions of HTML.
HtmlAnchor HtmlAnchor
creates a server-side HTML anchor tag, as shown in this example:
Click Here
HtmlButton creates a server-side HTML 4.0 button tag. Don’t confuse the HtmlButton control with the HTMLInputButton control used for creating form buttons. The HtmlButton control renders an HTML 4.0–compliant tag, as shown here: HtmlButton
Click Here
HtmlForm HtmlForm creates a server-side HTML tag. You must use this control, as shown here, if you want to include other ASP.NET controls in the Web Forms Page:
1 BUILDING ASP.NET PAGES
can use the HTML controls to quickly convert any existing HTML document into a “smart” ASP.NET Web Forms Page. The following sections provide a complete list of the ASP.NET HTML controls.
29
30
Working with ASP.NET Web Forms PART I
HtmlGenericControl creates a server-side control for HTML tags that do not have an HTML control that directly corresponds to them. For example, if you use a , , or tag with the Runat=”Server” attribute, as shown here, an instance of the HTMLGenericControl is created: HtmlGenericControl
Hello World!
HtmlImage HtmlImage
creates a server-side tag, as in this example:
HtmlInputButton creates a server-side , ,
HtmlInputCheckBox HtmlInputCheckBox
creates a server-side
tag, as in this
example: Do you like this Web Site?
HtmlInputFile HtmlInputFile creates a server-side
tag used for accepting file
HtmlInputHidden HtmlInputHidden
creates a server-side
tag, as in this example:
HtmlInputImage HtmlInputImage
creates a server-side
tag, as in this example:
Building ASP.NET Pages CHAPTER 1
HtmlInputRadioButton creates a server-side form radio button, as in this example:
Gender? Male Female
HtmlInputText creates a server-side or type=”password”> tag, as in this example:
HtmlInputText
Tip Instead of working with include directives, consider working with user controls instead. User controls provide you with more flexibility. For example, you can create custom properties and methods with a user control. To learn more about user controls see Chapter 5, “Creating Custom Controls with User Controls.”
Literal Text and HTML Tags The final type of element that you can include in an ASP.NET page is HTML content. The static portion of your page is built with plain old HTML tags and text.
1 BUILDING ASP.NET PAGES
--%>
53
54
Working with ASP.NET Web Forms PART I
You might be surprised to discover that the HTML content in your ASP.NET page is compiled along with the rest of the elements. HTML content in a page is represented with the LiteralControl class. You can use the Text property of the LiteralControl class to manipulate the pure HTML portion of an ASP.NET page. The HTML content contained in the page in Listing 1.17, for example, is reversed when it is displayed (see Figure 1.6). The Page_Load subroutine walks through all the controls in a page (including the LiteralControl) and reverses the value of the Text property of each control. LISTING 1.17
Literal.aspx
Sub Page_Load Dim litControl As LiteralControl For each litControl in Page.Controls litControl.Text = strReverse( litControl.Text ) Next End Sub Literal.aspx This text is reversed
Building ASP.NET Pages CHAPTER 1
FIGURE 1.6
Summary This first chapter covered a great deal of material. At the beginning of this chapter, you learned how ASP.NET fits into the overall .NET framework, and you learned about the languages that you can use to build ASP.NET applications. Next, you learned about the two building blocks of an ASP.NET page. You were provided with an overview of ASP.NET controls. You also learned how to add application logic to respond to control and page events to your pages. Finally, you were introduced to the structure of an ASP.NET page. You learned about the standard elements that you can include in a page.
1 BUILDING ASP.NET PAGES
Reversing the contents of a page.
55
CHAPTER 2
Building Forms with Web Server Controls IN THIS CHAPTER • Building Smart Forms
58
• Controlling Page Navigation
131
• Applying Formatting to Controls
136
58
Working with ASP.NET Web Forms PART I
This chapter focuses on the ASP.NET controls that you use most often in your ASP.NET pages: the basic Web controls. In the first section, you learn how to use Web controls to build interactive HTML forms. This section provides a detailed overview of Web controls such as TextBox, RadioButton, DropDownList, and Button. Next, you learn how to handle page navigation between your ASP.NET pages. You learn how to automatically redirect a user to a new page by using the Response.Redirect() method. You also learn how to use the HyperLink control to link multiple ASP.NET pages. Note You can view “live” versions of many of the code samples in this chapter by visiting the Superexpert Web site: http://www.Superexpert.com/AspNetUnleashed/
Finally, you learn how to apply formatting to your controls. The final major section provides an overview of the formatting properties common to all Web controls. You also learn how to apply styles to controls.
Building Smart Forms You use several of the basic Web controls to represent standard HTML form elements such as radio buttons, text boxes, and list boxes. You can use these controls in your ASP.NET pages to create the user interface for your Web application. The following sections provide detailed overviews and programming samples for each of these Web controls. Note See Appendix C, “Web Control Reference,” for a detailed reference of all the properties, methods, and events of the Web controls.
Using Label Controls The easiest way to add static text to your Web Forms Page is simply to add the text to the body of the page. However, if you want to be able to modify the text displayed on a
Building Forms with Web Server Controls CHAPTER 2
59
page within your code, you need to display the text within a Label control. See Table 2.1 for a list of all the properties and methods of this control. TABLE 2.1
Label Properties, Methods, and Events
Properties
Description
Text
Gets or sets the text displayed by the Label control
Methods
Description
2
None
Description
None
The Label control has one important property: Text. You can assign any text or HTML content that you wish to this property. When the Label control is rendered on a page, any text assigned to the Text property is displayed in place of the control. You can assign a value to the Text property in two ways: declaratively or programmatically. First, you can assign a value to the Label control when you declare it on the page like this:
Alternatively, you can assign the text within the code of the page. For example, the page in Listing 2.1 displays different content in a Label control depending on the time of day. LISTING 2.1
Label.text
Sub Page_Load If TimeOfDay < #12:00pm# Then lblMessage.Text = “Good Morning!” Else lblMessage.Text = “Good Day!” End If End Sub
BUILDING FORMS WITH WEB SERVER CONTROLS
Events
60
Working with ASP.NET Web Forms PART I LISTING 2.1
continued
Label.aspx
In Listing 2.1, the Text property is assigned the value “Good “Good Day!” depending on the time of day.
Morning!”
or the value
The contents of a Label control are rendered within a tag (you can see this if you click View Source on your browser). This means that you must be careful about where you use the Label control in a page. For example, using the Label control in the tag of your page would not be a good idea because the literal text also would appear in the title.
Using the TextBox Control The TextBox control can render any one of three different HTML tags. The control can be used to display a standard HTML text input field, an HTML password input field, or an HTML text area. All the properties, methods, and events of this control are listed in Table 2.2. TABLE 2.2
TextBox Properties, Methods, and Events
Properties
Description
AutoPostBack
When True, automatically posts the form containing the text box whenever a change is made to the contents of the text box.
Columns
Indicates the horizontal size of the text box in characters.
MaxLength
Specifies the maximum number of characters that you can add to the text box (does not work with multiline text boxes).
Building Forms with Web Server Controls CHAPTER 2 TABLE 2.2
continued
Description
ReadOnly
Indicates whether the text box is readonly or can be modified.
Rows
Indicates the vertical size of a multiline text box in characters.
Text
Gets or sets the text displayed by the TextBox control.
TextMode
Specifies one of these possible values: MultiLine, Password, and SingleLine.
Wrap
Determines how text in a multiline text box word-wraps. If True, word wrapping is enabled.
Methods
Description
OnTextChanged
Raises the TextChanged event.
Events
Description
TextChanged
This event is raised when the contents of a text box have been changed.
The ASP.NET page in Listing 2.2 illustrates how you can use the TextBox control to display a single-line text box, password text box, and multi-line text box. textbox.aspx
TextBox.aspx Username:
Password:
2 BUILDING FORMS WITH WEB SERVER CONTROLS
Properties
LISTING 2.2
61
62
Working with ASP.NET Web Forms PART I LISTING 2.2
continued
Comments:
Even though the same Web control—TextBox—is used to create the Username, Password, and Comments fields on the page in Listing 2.2, very different user interface elements are displayed (see Figure 2.1). The different user interface elements are determined by the TextMode property of the TextBox control. FIGURE 2.1 A TextBox control with different TextMode
properties.
Building Forms with Web Server Controls CHAPTER 2
63
The TextMode property can have any one of the following three values: •
Single—This
is the default value. When the TextMode property is assigned the value Single, a normal single-line input text field is displayed.
•
Password—When the TextMode property is assigned the value Password, a password input field is displayed and the contents of the text box field are obscured (typically with * characters).
•
MultiLine—When the TextMode property is assigned the value MultiLine, a text area is displayed.
The values of the TextMode property are mutually exclusive. This means that you cannot have a multiline password text box. This fact shouldn’t be surprising because an ASP.NET control can display only HTML, and there is no such thing as a multiline password HTML element.
Notice that, unlike an HTML tag, the TextBox control does not use a Size property. Instead, the horizontal size of a text box is determined by the Columns property. In the case of a multiline text box, the vertical size is specified by the Rows property. Both the Columns and Rows properties specify the size in characters. Note You also can specify the horizontal and vertical size of a text box by using the Width and Height properties of the base WebControl class. The advantage of using these properties is that you can use units other than characters such as pixels or percentages. For more information, see the section “Base Web Control Properties” later in this chapter.
A TextBox control also has a MaxLength property, which you can use to limit the number of characters that can be entered into a text box. This property works only with singleline and password text boxes. You cannot use MaxLength with multiline text boxes. If you need to limit the number of characters entered into a multiline text box, use either the RegularExpressionValidator or CustomValidator validation control. (See “Validating Expressions: The RegularExpressionValidator Control” in the next chapter for a code sample.)
BUILDING FORMS WITH WEB SERVER CONTROLS
Note
2
64
Working with ASP.NET Web Forms PART I
Assigning Text to a Text Box You use the Text property of a TextBox control to assign a value to a text box or read a value that a user has entered into it. You can use this property in two ways. First, you can use the Text property when declaring the control to provide the text box with a default value like this:
Alternatively, you can assign or read a value from a text box in code. For example, in Listing 2.3, the Country text box is assigned the default value “United States” when the page is first loaded. LISTING 2.3
TextBoxText.aspx
Sub Page_Load( s As Object, e As EventArgs ) If Not IsPostBack Then txtCountry.Text = “United States” End If End Sub TextBoxText.aspx Please enter the name of your country:
Building Forms with Web Server Controls CHAPTER 2
65
Note How do you assign a string that contains a quotation mark to a text box? For example, suppose that you want to assign the value He said “Good Morning!” to a text box named Comments. If you try to use the following statement, you receive an error: Comments.Text = “He said “Good Morning!” “
To assign a string that contains a quotation mark to the Text property, you need to double your quotation marks like this: When the quotation marks are actually rendered on the page, the quotation marks appear as " characters.
Handling the TextBox Control’s TextChanged Event The TextBox control has a TextChanged event, which is raised whenever the contents of the text box are changed and the form containing the text box has been posted back to the server. For example, the page in Listing 2.4 uses the TextChanged event to report whether the contents of the text box contained in the form have been changed. LISTING 2.4
TextBoxChanged.aspx
Sub Page_Load lblMessage.Text = “” End Sub Sub txtSomeText_TextChanged( s As Object, e As EventArgs ) lblMessage.Text = “You changed the value!” End Sub TextBoxChanged.aspx
When the contents of the txtSomeText control are changed in Listing 2.4, the txtSomeText_TextChanged subroutine is executed. This subroutine displays the text You changed the value! in the Label control named lblMessage. Notice that the Page_Load subroutine clears the value of the Label control every time the page is loaded. The Page_Load subroutine always executes before the TextChanged event. You need to use the Page_Load event because the Label control participates in the form’s view state. If this control were not cleared on every page load, it would automatically retain the same value between posts. It would continue to display You changed the value! even when the value of the text box wasn’t changed.
The TextBox Control’s AutoPostBack Property The TextBox control also has an AutoPostBack property. When AutoPostBack has the value True, the form containing the text box is automatically posted whenever the contents of the text box are changed. When AutoPostBack is enabled, JavaScript code is sent to the browser when the page is rendered to capture TextBox onChange events. Because the AutoPostBack property relies on JavaScript, you cannot use this property with older browsers. (It works fine on the latest versions of Netscape Navigator and Internet Explorer, however.) The page in Listing 2.5, for example, uses the AutoPostBack property to create a simple game. This page contains three text boxes. To win the game, you must enter the word Apple into all three text boxes.
Building Forms with Web Server Controls CHAPTER 2 LISTING 2.5
67
TextBoxAutoPostBack.aspx
Sub ChangeBox1( s As Object, e As EventArgs ) txtBox1.Text = strReverse( txtBox1.Text ) CheckWin End Sub Sub changeBox3( s As Object, e As EventArgs ) txtBox3.Text = strReverse( txtBox3.Text ) CheckWin End Sub
TextboxAutoPostBack.aspx Enter the word Apple into all three TextBoxes:
The AutoPostBack property for each of the three text boxes has the value True. If you change the text in a text box, the form containing the text boxes is automatically submitted, and the subroutine associated with that text box is executed. If you change the text contained in the second text box, for example, the changeBox1 subroutine is executed. This subroutine reverses the text contained in the first text box. If you successfully enter the word Apple into all three text boxes, the message “You is displayed in the Label control named lblMessage (see Figure 2.2).
Win!”
FIGURE 2.2 The TextBox control’s AutoPostBack
property.
Building Forms with Web Server Controls CHAPTER 2
69
Using Button Controls You can add three types of buttons to a form by using Web controls: •
Button—Displays
a standard HTML form button
•
ImageButton—Displays
•
LinkButton—Displays
an image form button
a hypertext link
All three buttons do the same thing when they are clicked: They submit the form that contains the button to the Web server.
By default, the Button control renders the same form submit button as rendered by the normal HTML tag: the gray pushbutton box. All the properties, methods, and events of this control are listed in Table 2.3. TABLE 2.3
Button Properties, Methods, and Events
Properties
Description
Text
Gets or sets the text displayed on the button.
CommandName
Passes this value to the Command event when the button is clicked.
CommandArgument
Passes this value to the Command event when the button is clicked.
CausesValidation
When False, the form submitted by the button is not validated. Default value is True.
Methods
Description
OnClick
Raises the Click event.
OnCommand
Raises the Command event.
Events
Description
Click
This event is raised when the button is clicked and the form containing the button is submitted to the server.
Command
This event also is raised when the button is clicked. However, the values of the CommandName and CommandArgument properties are passed with this event.
BUILDING FORMS WITH WEB SERVER CONTROLS
The Button Control
2
70
Working with ASP.NET Web Forms PART I
When a button is clicked, the form containing the button is submitted, and both Click and Command events are raised. To handle a Click event, you add a subroutine to your page as follows: Sub Button_Click( s As Object, e As EventArgs ) End Sub
To handle a Command event, add a subroutine to your page like this: Sub Buttom_Command( s As Object, e As CommandEventArgs ) End Sub
The only difference between a Click event and a Command event is that additional information—the values of the CommandName and CommandArgs properties—is passed to the Command event. In Listing 2.6, for example, the btnCounter_Click subroutine is associated with the control’s Click event. This subroutine adds one to the value of the Button control’s Text property. Button
LISTING 2.6
Button.aspx
Sub btnCounter_Click( s As Object, e As EventArgs ) btnCounter.Text += 1 End Sub Button.aspx
Building Forms with Web Server Controls CHAPTER 2
71
If you need to add multiple buttons to a form, and you want to pass different information depending on which buttons are clicked, you can use the Command event instead of the Click event. For example, the page in Listing 2.7 displays different values depending on which buttons are clicked. LISTING 2.7
ButtonCommand.aspx
Sub Button_Command( s As Object, e As CommandEventArgs ) Dim intAmount As Integer
ButtonCommand.aspx
BUILDING FORMS WITH WEB SERVER CONTROLS
intAmount = e.CommandArgument If e.CommandName = “Add” Then lblCounter.Text += intAmount Else lblCounter.Text -= intAmount End If End Sub
2
72
Working with ASP.NET Web Forms PART I LISTING 2.7
continued
When you click the first button in Listing 2.7, the value of the Label control named lblCounter is incremented by five. The CommandName property of the first button has the value Add, and the CommandArgument property has the value 5. When you click the second button, the value of the Label control named lblCounter is decremented by 10. The CommandName property of the second button has the value Subtract, and the CommandArgument property has the value 10. The values of both the CommandName and CommandArgument properties are retrieved from the CommandEventArgs parameter. It is the second parameter passed to the subroutine that handles the Button control’s Command event (the Button_Command subroutine). You can assign anything you please to either the CommandName or CommandArgument properties. These properties accept any string value.
The ImageButton Control An ImageButton control is similar to a Button control except for the fact that you can use an ImageButton control to display an image. All the properties, methods, and events of this control are listed in Table 2.4. TABLE 2.4
ImageButton Properties, Methods, and Events
Properties
Description
AlternativeText
Gets or sets the text displayed when a browser does not support images.
CommandName
Passes this value to the Command event when the button is clicked.
CommandArgument
Passes this value to the Command event when the button is clicked.
CausesValidation
When False, the form submitted by the button is not validated. Default value is True.
ImageAlign
Specifies one of these possible values: AbsBottom, AbsMiddle, Baseline, Bottom, Left, Middle, NotSet, Right, TextTop, and Top.
ImageURL
Specifies the URL of the image to use for the button.
Building Forms with Web Server Controls CHAPTER 2 TABLE 2.4
continued
Description
OnClick
Raises the Click event.
OnCommand
Raises the Command event.
Events
Description
Click
This event is raised when the button is clicked and the form containing the button is submitted to the server.
Command
This event also is raised when the button is clicked. However, the values of the CommandName and CommandArgument properties are passed to this event.
For example, Listing 2.8 displays a simple Click
Here!
image button (see Figure 2.3).
FIGURE 2.3 The ImageButton control.
ImageButton.aspx
Sub ImageButton_Click( s As Object, e As ImageClickEventArgs ) Dim RanNum As New Random Select Case RanNum.Next( 3 ) Case 0
2 BUILDING FORMS WITH WEB SERVER CONTROLS
Methods
LISTING 2.8
73
74
Working with ASP.NET Web Forms PART I LISTING 2.8
continued
lblMessage.Text = “Boom!” Case 1 lblMessage.Text = “Zap!” Case 2 lblMessage.Text = “Yikes!” End Select End Sub ImageButton.aspx
Notice that the event handling routine for an ImageButton control looks like this: Sub ImageButton_Click( s As Object, e As ImageClickEventArgs ) End Sub
The ImageButton control uses an ImageClickEventArgs parameter because additional information is passed to the event-handling subroutine when an image button is clicked. When you click an image button, the x,y coordinate of the mouse-click location is passed. Why would you use this information? You can use one ImageButton control to represent multiple buttons by creating a simple imagemap. The multiple buttons can be drawn in a single image. You can detect which buttons are clicked by examining the X and Y properties of the ImageClickEventArgs parameter.
Building Forms with Web Server Controls CHAPTER 2
75
The page in Listing 2.9, for example, contains one ImageButton control that represents three different buttons (see Figure 2.4). When you click any of the three buttons, the ImageButton_Click subroutine is executed. The X property of the ImageClickEventArgs parameter detects which of the three buttons is clicked. FIGURE 2.4 The ImageButton control’s X and Y properties.
2 BUILDING FORMS WITH WEB SERVER CONTROLS
LISTING 2.9
ImageButtonEventArgs.aspx
Sub ImageButton_Click( s As Object, e As ImageClickEventArgs ) Select Case e.X Case Is < 95 lblMessage.Text = txtSomeText.Text.ToUpper() Case Is < 185 lblMessage.Text = txtSomeText.Text.ToLower() Case Is < 289 lblMessage.Text = “” End Select End Sub ImageButtonEventArgs.aspx
76
Working with ASP.NET Web Forms PART I LISTING 2.9
continued
The LinkButton Control A LinkButton control is similar to both the Button and ImageButton controls except for the fact that a LinkButton control renders a hypertext link. When you click the hypertext link, all the fields in the form that contains the link button are submitted to the server. All the properties, methods, and events of this control are listed in Table 2.5. TABLE 2.5
LinkButton Properties, Methods, and Events
Properties
Description
Text
Gets or sets the text displayed as the label for the link.
CommandName
Passes this value to the Command event when the link is clicked.
CommandArgument
Passes this value to the Command event when the link is clicked.
CausesValidation
When False, the form submitted by the button is not validated. Default value is True.
Building Forms with Web Server Controls CHAPTER 2 TABLE 2.5
77
continued
Description
OnClick
Raises the Click event.
OnCommand
Raises the Command event.
Events
Description
Click
This event is raised when the link is clicked and the form containing the link is submitted to the server.
Command
This event also is raised when the link is clicked. However, the values of the CommandName and CommandArgument properties are passed to this event.
The LinkButton control does not work on browsers that do not support JavaScript (or browsers that have JavaScript disabled). If you attempt to use this type of control on a browser that does not support JavaScript, nothing happens when you click the link button. Listing 2.10 demonstrates how you can use a link button to submit a form. This page tells you your fortune. When you enter your name and click the link button, the LinkButton_Click subroutine is executed, and one of four random fortunes is displayed. LISTING 2.10
LinkButton.aspx
Sub LinkButton_Click( s As Object, e As EventArgs ) Dim RanNum As New Random Select Case RanNum.Next( 4 ) Case 0 lblFortune.Text = txtUsername.Text & “, good things are coming!” Case 1 lblFortune.Text = txtUsername.Text & “, you are doomed!” Case 2 lblFortune.Text = txtUsername.Text & “, invest in Microsoft!” Case 3 lblFortune.Text = txtUsername.Text & “, this book will change your life!” End Select End Sub LinkButton.aspx
2 BUILDING FORMS WITH WEB SERVER CONTROLS
Methods
78
Working with ASP.NET Web Forms PART I LISTING 2.10
continued
Enter your name to receive your fortune:
Using the RadioButton and RadioButtonList Controls Radio buttons represent a group of mutually exclusive options. Each radio button can be either checked or unchecked. No more than one radio button in a group can be checked at the same time. You can use two Web controls to add radio buttons to a page: RadioButton and RadioButtonList. The RadioButton control provides you with more control over the layout of individual radio buttons. The RadioButtonList control, on the other hand, enables you to easily display a list of radio buttons from a database table or collection.
The RadioButton Control You can add individual radio buttons one by one to a page by using the RadioButton control. Radio buttons are grouped together using the GroupName property. Only one radio button in a group can be checked at a time. However, nothing is wrong with having multiple groups of radio buttons with different GroupName properties. All the properties, methods, and events of this control are listed in Table 2.6.
Building Forms with Web Server Controls CHAPTER 2 TABLE 2.6
79
RadioButton Properties, Methods, and Events
Description
AutoPostBack
When True, automatically posts the form containing the radio button whenever a new radio button is selected from the radio button group.
Checked
Has the value True when the radio button is checked and False otherwise.
GroupName
Specifies the name of the group that contains the radio button.
Text
Gets or sets the text label associated with the radio button.
TextAlign
Determines how the text label for the radio button is aligned relative to the radio button. Possible values are Left and Right; the default value is Right.
Methods
Description
OnCheckedChanged
Raises the CheckedChanged event.
Events
Description
CheckedChanged
This event is raised when the radio button changes from being checked to unchecked or unchecked to checked.
You can determine when a radio button is checked in two ways. First, you can use the Checked property. In Listing 2.11, a group of RadioButton controls representing cities is displayed. When a button is clicked, the Button_Click subroutine is executed. The Checked property of each RadioButton control is examined one by one to find the radio button that was checked. LISTING 2.11
RadioButton.aspx
Sub Button_Click( s As Object, e As EventArgs ) If radSeattle.Checked Then lblCity.Text = radSeattle.Text ElseIf radSanFran.Checked Then lblCity.Text = radSanFran.Text ElseIf radBoston.Checked Then lblCity.Text = radBoston.Text End If End Sub
2 BUILDING FORMS WITH WEB SERVER CONTROLS
Properties
80
Working with ASP.NET Web Forms PART I LISTING 2.11
continued
RadioButton.aspx Enter the name of your favorite city, please select only one:
Building Forms with Web Server Controls CHAPTER 2
81
You also can use the CheckChanged event to determine which radio button has been checked. It’s much easier to use the CheckChanged event when you have a large group of radio buttons. The page in Listing 2.12 illustrates how you can use this event. LISTING 2.12
RadioButtonChanged.aspx
Sub City_Changed( s As Object, e As EventArgs ) lblCity.Text = s.Text End Sub
RadioButtonChanged.aspx Enter the name of your favorite city, please select only one:
Each RadioButton control in Listing 2.12 is associated with the City_Changed subroutine by using the OnCheckedChanged method. If a new radio button is selected, the City_Changed subroutine will be executed when the form is posted. The first parameter passed to the City_Changed subroutine represents the particular control that raised the event. You can use this parameter to retrieve the Text property of the newly checked radio button and display the proper city. RadioButton
Warning Remember that the CheckedChanged event occurs only when a new RadioButton control is checked. If you submit a form with the default radio button still checked, the CheckedChanged event doesn’t happen.
Finally, the RadioButton control supports the AutoPostBack property. When AutoPostBack is enabled for a RadioButton control, the form containing the radio button is automatically posted to the server whenever a new radio button is selected. Warning The AutoPostBack property requires client-side JavaScript. If a browser does not support JavaScript or JavaScript is disabled on the browser, AutoPostBack doesn’t work.
The page in Listing 2.13 displays different background colors depending on which radio button is selected. Notice that the page does not contain a submit button. Because the
Building Forms with Web Server Controls CHAPTER 2
83
property has the value True for each RadioButton control, the page is automatically posted whenever a new radio button is selected.
AutoPostBack
LISTING 2.13
RadioButtonAutoPostBack.aspx
Dim strBackGroundColor As String = “white” Sub BackColor_Changed( s As Object, e As EventArgs ) strBackGroundColor = s.Text End Sub
RadioButtonAutoPostBack.aspx Select a background color:
BUILDING FORMS WITH WEB SERVER CONTROLS
2
84
Working with ASP.NET Web Forms PART I LISTING 2.13
continued
The RadioButtonList Control The RadioButtonList control, like the RadioButton control, represents radio buttons. However, the RadioButtonList control represents a list of radio buttons. Each list item in a RadioButtonList represents an individual radio button. All the properties, methods, and events of this control are listed in Table 2.7. TABLE 2.7
RadioButtonList Properties, Methods, and Events
Properties
Description
AutoPostBack
When True, automatically posts the form containing the RadioButtonList whenever a new radio button is selected.
CellPadding
Sets the number of pixels between the border and a particular radio button.
CellSpacing
Sets the number of pixels between individual radio buttons in the RadioButtonList.
DataMember
Identifies the particular table in a data source to bind to the control.
DataSource
Identifies the data source for the items listed in the RadioButtonList.
DataTextField
Identifies the field from the data source to use for the radio button labels.
DataTextFormatString
Gets or sets a format string that determines how the DataTextField is displayed.
DataValueField
Identifies the field from the data source to use for the radio button values.
Items
Represents the collection of items in the RadioButtonList.
RepeatColumns
Determines the number of columns used to display the radio buttons.
RepeatDirection
Indicates the direction the radio buttons should be repeated. Possible values are Horizontal and Vertical.
Building Forms with Web Server Controls CHAPTER 2 TABLE 2.7
85
continued
Description
RepeatLayout
Determines how the radio buttons are formatted. Possible values are Table and Flow; Table is the default.
SelectedIndex
Specifies the index number of the currently checked radio button.
SelectedItem
Represents the selected radio button.
TextAlign
Indicates the alignment of the radio button label relative to the radio button. Possible values are Left and Right.
Methods
Description
DataBind
Binds the RadioButtonList to its data source. Loads the items from the data source into the items collection.
OnSelectedIndexChanged
Raises the SelectedIndexChanged event.
Events
Description
SelectedIndexChanged
This event is raised when a new radio button is selected in the RadioButtonList.
You can add radio buttons to a RadioButtonList in three ways: You can list the radio buttons when you declare the RadioButtonList control, you can add items directly to the ListItemCollection collection of the RadioButtonList control, or you can bind a RadioButtonList to a data source. First, you can simply list the radio buttons as list items when you declare the control like this:
This RadioButtonList contains three radio buttons: one labeled Red, one labeled Blue, and one labeled Green. The Blue radio button is selected (checked) by default. Second, you can add list items directly to the ListItemCollection collection of a For example, in Listing 2.14, three colors are added to a RadioButtonList in the Page_Load subroutine. RadioButtonList.
2 BUILDING FORMS WITH WEB SERVER CONTROLS
Properties
86
Working with ASP.NET Web Forms PART I LISTING 2.14
RadioButtonList.aspx
Sub Page_Load( s As Object, e As EventArgs ) If Not IsPostBack Then radlFavoriteColor.Items.Add( “Red” ) radlFavoriteColor.Items.Add( “Blue” ) radlFavoriteColor.Items.Add( “Green” ) End If End Sub RadioButtonlist.aspx
Finally, you can bind the RadioButtonList control to a data source such as a database table or an existing collection. Data binding is covered in detail in Chapter 10, “Binding Data to Web Controls,” but I’ll show you a quick example in Listing 2.15. LISTING 2.15
RadioButtonListDataBind.aspx
Sub Page_Load Dim colArrayList As New ArrayList If Not IsPostBack Then colArrayList.Add( “Red” ) colArrayList.Add( “Blue” ) colArrayList.Add( “Green” ) radlFavoriteColor.DataSource = colArrayList radlFavoriteColor.DataBind() End If End Sub
Building Forms with Web Server Controls CHAPTER 2 LISTING 2.15
87
continued
RadioButtonListDataBind.aspx
In the Page_Load subroutine in Listing 2.15, an ArrayList named colArrayList is created. The values Red, Blue, and Green are added to the ArrayList. Next, the ArrayList is assigned to the RadioButtonList control’s DataSource property, and the DataBind() method is called. After this method is called, all the items in the ArrayList are loaded into the ListItemCollection collection of the RadioButtonList. Note An ArrayList is a collection. Think of it as an array without any fixed size. Collections are discussed in detail in Chapter 24, “Working with Collections and Strings.”
Tip When you bind a RadioButtonList to a data source, you can use the DataTextFormatString property to format the text of each radio button. For example, the following RadioButtonList prefixes the string “The color is” to the text of each radio button displayed:
To learn more about format strings see Chapter 24.
BUILDING FORMS WITH WEB SERVER CONTROLS
2
88
Working with ASP.NET Web Forms PART I
Detecting the Selected Item in a RadioButtonList You can detect which radio button is checked in a RadioButtonList in two ways. You can use either the SelectedIndex or SelectedItem property. The SelectedIndex property returns the index number of the currently checked radio button. For example, if the first radio button in the list is checked, SelectedIndex returns 0; if the second radio button is checked, SelectedIndex returns 1; and so on. The SelectedItem property, on the other hand, returns the actual list item in the collection that is currently selected. You can use this property to return the Text property for the selected radio button. ListItemCollection
When you select a radio button in Listing 2.16, for example, the Button_Click subroutine is executed, and the SelectedIndex and SelectedItem properties for the selected radio button are displayed (see Figure 2.5). FIGURE 2.5 The RadioButtonList
control’s SelectedIndex
and SelectedItem properties.
LISTING 2.16
RadioButtonListSelected.aspx
Sub Button_Click( s As Object, e As EventArgs ) lblSelectedIndex.Text = radlFavoriteColor.SelectedIndex lblSelectedItem.Text = radlFavoriteColor.SelectedItem.Text End Sub
Building Forms with Web Server Controls CHAPTER 2 LISTING 2.16
89
continued
RadioButtonListSelected.aspx
Selected Index:
Selected Item:
Assigning Different Text and Values to a RadioButtonList Imagine that you are using a RadioButtonList to display a list of states. For example, you might display a list of radio buttons for California, Massachusetts, and Ohio. Now, imagine that you want the RadioButtonList to return the state abbreviation rather than the full state name when a radio button is selected. You can do so by using the Value property of a list item.
2 BUILDING FORMS WITH WEB SERVER CONTROLS
90
Working with ASP.NET Web Forms PART I
You can assign the Value property to list items when you create a RadioButtonList like this:
You also can assign a different Value property than Text property when explicitly adding list items to the ListItem collection, as illustrated in Listing 2.17. LISTING 2.17
RadioButtonListValue.aspx
Sub Page_Load If Not IsPostBack Then radlStateOfResidence.Items.Add( New ListItem( “California”, “CA” ) ) radlStateOfResidence.Items.Add( New ListItem( “Massachusetts”, “MA” ) ) radlStateOfResidence.Items.Add( New ListItem( “Ohio”, “OH” ) ) End If End Sub RadioButtonListValue.aspx
Building Forms with Web Server Controls CHAPTER 2
91
If you want a different Text property than Value property when working with a data source, you must specify both the DataTextField and DataValueField properties. The page in Listing 2.18 demonstrates how you can use these properties when binding to a DataTable. LISTING 2.18
RadioButtonListDataValueField.aspx
dtblStates.Columns.Add(New DataColumn( “StateName”, GetType( String ) ) ) dtblStates.Columns.Add(New DataColumn( “StateAbbr”, GetType( String ) ) ) drowNewState = dtblStates.NewRow() drowNewState( “StateName” ) = “California” drowNewState( “StateAbbr” ) = “CA” dtblStates.Rows.Add( drowNewState ) drowNewState = dtblStates.NewRow() drowNewState( “StateName” ) = “Massachusetts” drowNewState( “StateAbbr” ) = “MA” dtblStates.Rows.Add( drowNewState ) drowNewState = dtblStates.NewRow() drowNewState( “StateName” ) = “Ohio” drowNewState( “StateAbbr” ) = “OH” dtblStates.Rows.Add( drowNewState ) radlStateOfResidence.DataSource = dtblStates radlStateOfResidence.DataTextField = “StateName” radlStateOfResidence.DataValueField = “StateAbbr” radlStateOfResidence.DataBind End If End Sub RadioButtonListDataValueField.aspx
2 BUILDING FORMS WITH WEB SERVER CONTROLS
Sub Page_Load If Not IsPostBack Then Dim dtblStates As New DataTable Dim drowNewState As DataRow
92
Working with ASP.NET Web Forms PART I LISTING 2.18
continued
After you specify a distinct Value property, you can use it in your code. When you click a radio button in Listing 2.19, the list item’s Index, Text, and Value properties are displayed. LISTING 2.19
RadioButtonListValueSelected.aspx
Sub Button_Click( s As Object, e As EventArgs ) lblSelectedIndex.Text = radlStateOfResidence.SelectedIndex lblSelectedText.Text = radlStateOfResidence.SelectedItem.Text lblSelectedValue.Text = radlStateOfResidence.SelectedItem.Value End Sub RadioButtonListValueSelected.aspx
Selected Index:
Selected Value:
Controlling the Layout of a RadioButtonList A RadioButtonList has several properties that you can use to control its layout. Two of the more interesting ones are RepeatLayout and RepeatDirection. You can use these properties to display the radio buttons in a RadioButtonList in multiple columns. For example, the page in Listing 2.20 displays 50 radio buttons. (The 50 radio buttons are created in the For..Next loop in the Page_Load subroutine.) The RadioButtonList control’s RepeatColumns property is assigned the value 3. When the radio buttons are displayed, they appear in three columns (see Figure 2.6). In Listing 2.20, the RepeatDirection property has the value Vertical. The radio buttons are rendered down each column and then across to a new column. You also can assign the value Horizontal to the RepeatDirection property. In that case, the radio buttons would be rendered horizontally across the columns and then down to a new row.
2 BUILDING FORMS WITH WEB SERVER CONTROLS
Selected Text:
94
Working with ASP.NET Web Forms PART I
FIGURE 2.6 Displaying a RadioButtonList
with multiple columns.
LISTING 2.20
RadioButtonListColumns.aspx
Sub Page_Load Dim intCounter As Integer Dim strSomeListItem As String If Not IsPostBack Then For intCounter = 1 To 50 strSomeListItem = “Radio Button “ & intCounter radlRadioButtons.Items.Add( strSomeListItem ) Next End If End Sub RadioButtonListColumns.aspx
Building Forms with Web Server Controls CHAPTER 2 LISTING 2.20
95
continued
The RadioButtonList Control’s AutoPostBack Property
Warning The AutoPostBack property uses client-side JavaScript. If a browser does not support JavaScript or JavaScript is disabled on the browser, AutoPostBack does not work.
The page contained in Listing 2.21 contains a RadioButtonList control with its AutoPostBack property enabled. When a new radio button is checked, the SelectedIndexChanged event is raised. This event is associated with the radlColors_ SelectedIndexChanged subroutine. So, whenever a new button is checked, the radlColors_SelectedIndexChanged subroutine is executed. LISTING 2.21
RadioButtonListAutoPostBack.aspx
Dim strBackGroundColor As String = “white” Sub radlColors_SelectedIndexChanged( s As Object, e As EventArgs ) strBackGroundColor = radlColors.SelectedItem.Text End Sub RadioButtonListAutoPostBack.aspx
Using the CheckBox and CheckBoxList Controls You can use a check box to represent a simple yes or no value. If you group multiple check boxes together, you can use the check boxes to represent a list of nonmutually exclusive options. Multiple check boxes, unlike multiple radio buttons, can be checked at the same time. You can use two Web controls to represent check boxes in your ASP.NET pages: CheckBox and CheckBoxList.
The CheckBox Control You can use a single CheckBox control to create a simple check box on a Web Forms Page. All the properties, methods, and events of this control are listed in Table 2.8. TABLE 2.8
CheckBox Properties, Methods, and Events
Properties
Description
AutoPostBack
When True, automatically posts the form containing the check box whenever it is checked or unchecked.
Checked
Has the value True when the check box is checked and False otherwise.
Text
Gets or sets the text label associated with the check box.
TextAlign
Determines how the text label for the check box is aligned relative to the check box. Possible values are Left and Right; the default value is Right.
Building Forms with Web Server Controls CHAPTER 2 TABLE 2.8
97
continued
Methods
Description
OnCheckedChanged
Raises the CheckedChanged event.
Events
Description
CheckedChanged
This event is raised when the check box changes from being checked to unchecked or unchecked to checked.
Listing 2.22 illustrates how you can add a CheckBox control to a page. CheckBox.aspx
Sub Button_Click( s As Object, e As EventArgs ) If chkLikeSite.Checked Then lblResponse.Text = “Thank you!” Else lblResponse.Text = “Go Away!” End If End Sub CheckBox.aspx
BUILDING FORMS WITH WEB SERVER CONTROLS
LISTING 2.22
2
98
Working with ASP.NET Web Forms PART I LISTING 2.22
continued
When the form is submitted, the Checked property of the CheckBox control is examined to determine whether the check box was checked. Depending on the value of Checked, different messages are displayed. You can use the AutoPostBack property to automatically submit the form containing a check box whenever it is checked or unchecked. For example, the page in Listing 2.23 contains two check boxes that control the formatting of the text in the Label control. When the first check box is checked, the text in the label appears in bold, and when the second check box is checked, the text in the label appears in italics. Warning The AutoPostBack property uses client-side JavaScript. If a browser does not support JavaScript or JavaScript is disabled on the browser, the AutoPostBack property does not work.
LISTING 2.23
CheckBoxAutoPostBack.aspx
Sub Page_Load If chkFormatBold.Checked Then lblMessage.Font.Bold = True Else lblMessage.Font.Bold = False End If If chkFormatItalic.Checked Then lblMessage.Font.Italic = True Else lblMessage.Font.Italic = False End If End Sub CheckBoxAutoPostBack.aspx
Building Forms with Web Server Controls CHAPTER 2 LISTING 2.23
99
continued
Select among the following formatting options:
2 BUILDING FORMS WITH WEB SERVER CONTROLS
The CheckBoxList Control The CheckBoxList control, like the CheckBox control, represents check boxes. However, the CheckBoxList control represents a list of check boxes. Each list item in a CheckBoxList represents an individual check box. All the properties, methods, and events of this control are listed in Table 2.9. TABLE 2.9
CheckBoxList Properties, Methods, and Events
Properties
Description
AutoPostBack
When True, automatically posts the form containing the CheckBoxList whenever a new check box is selected or unselected.
CellPadding
Sets the number of pixels between the border and a particular check box.
CellSpacing
Sets the number of pixels between individual check boxes in the CheckBoxList.
100
Working with ASP.NET Web Forms PART I TABLE 2.9
continued
Properties
Description
DataMember
Identifies the particular table in a data source to bind to the control.
DataSource
Indicates the data source for the items listed in the CheckBoxList.
DataTextField
Indicates the field from the data source to use for the check box labels.
DataTextFormat String
Gets or sets a format string that determines how the DataTextField is displayed.
DataValueField
Indicates the field from the data source to use for the check box values.
Items
Represents the collection of items in the CheckBoxList.
RepeatColumns
Determines the number of columns used to display the check boxes.
RepeatDirection
Indicates the direction the check boxes should be repeated. Possible values are Horizontal and Vertical.
RepeatLayout
Determines how the check boxes are formatted. Possible values are Table and Flow; Table is the default.
SelectedIndex
Specifies the index number of the currently checked check box.
SelectedItem
Represents the selected check box.
TextAlign
Indicates the alignment of the check box label relative to the check box. Possible values are Left and Right.
Method
Description
DataBind
Binds the CheckBoxList to its data source. Loads the items from the data source into the ListItem collection.
OnSelectedIndexChanged
Raises SelectedIndexChanged event.
Events
Description
SelectedIndexChanged
This event is raised whenever a new check box is checked or unchecked in the CheckBoxList.
Building Forms with Web Server Controls CHAPTER 2
101
Unlike a RadioButtonList, a CheckBoxList can have multiple check boxes checked at the same time. A CheckBoxList can represent nonmutually exclusive options. You can add individual check boxes to a CheckBoxList in three ways: You can list the check boxes when you declare the CheckBoxList control, you can add items directly to the ListItem collection of the CheckBoxList control, or you can bind a CheckBoxList to a data source. First, you can simply list the check boxes as list items when you declare the control like this:
This CheckBoxList contains three check boxes: one labeled Pizza, one labeled Hamburgers, and one labeled Pasta. The Hamburgers check box is selected (checked) by default. Second, you can add list items directly to the ListItemCollection collection of a CheckBoxList. For example, in Listing 2.24, types of food are added to a CheckBoxList in the Page_Load subroutine. LISTING 2.24
CheckBoxList.aspx
Sub Page_Load If Not IsPostBack Then chklFavoriteFoods.Items.Add( “Pizza” ) chklFavoriteFoods.Items.Add( “Hamburgers” ) chklFavoriteFoods.Items.Add( “Pasta” ) End If End Sub CheckBoxList.aspx
2 BUILDING FORMS WITH WEB SERVER CONTROLS
102
Working with ASP.NET Web Forms PART I LISTING 2.24
continued
Finally, you can bind the CheckBoxList control to a data source such as a database table or an existing collection. Data binding is covered in detail in Chapter 10, but I’ll show you a quick example of binding a collection to a CheckBoxList in Listing 2.25. LISTING 2.25
checkboxlist2.aspx
Sub Page_Load Dim colArrayList As New ArrayList If Not IsPostBack Then colArrayList.Add( “Pizza” ) colArrayList.Add( “Hamburgers” ) colArrayList.Add( “Pasta” ) chklFavoriteFoods.DataSource = colArrayList chklFavoriteFoods.DataBind() End If End Sub CheckBoxListDataBind.aspx
In the Page_Load subroutine in Listing 2.25, an ArrayList named colArrayList is created. The values Pizza, Hamburgers, and Pasta are added to the ArrayList. Next, the ArrayList is assigned to the CheckBoxList control’s DataSource property, and the
Building Forms with Web Server Controls CHAPTER 2
103
method is called. After this method is called, all the items in the ArrayList are loaded into the ListItem collection of the CheckBoxList. DataBind()
Note An ArrayList is a collection. Think of it as an array without any fixed size. Collections are discussed in detail in Chapter 24.
Like the RadioButtonList control, the CheckBoxList control supports the SelectedIndex and SelectedItem properties. The SelectedIndex property returns the index number of the currently selected item in the CheckBoxList, and the SelectedItem property returns the actual item that has been selected. Unlike a RadioButtonList, however, a CheckBoxList can have multiple items selected at a time. If multiple items are checked in a CheckBoxList, the SelectedIndex property returns the index number of the first item selected, and the SelectedItem property returns the first item selected. To retrieve a list of all the items selected, you need to iterate through the ListItemCollection collection itself. For each list item, you can determine whether the item is checked by examining the item’s Selected property. The page in Listing 2.26 illustrates how you can check whether multiple check boxes are checked. LISTING 2.26
CheckBoxListMultiSelect.aspx
Sub Button_Click( s As Object, e As EventArgs ) Dim itmFood As ListItem Dim strList As String For each itmFood in chklFavoriteFoods.Items If itmFood.Selected Then strList &= “
You picked:
Assigning Different Text and Values to a CheckBoxList Imagine that you are using a CheckBoxList to display a list of products that someone can purchase from your Web site. Now, imagine that you want the CheckBoxList to display the names of the products but return product codes when a product is selected. You can do so by using the Value property of a list item. You can assign the Value property to list items when you create a CheckBoxList like this:
You also can assign a different Value property than Text property when explicitly adding list items to the ListItemCollection collection, as illustrated in Listing 2.27. CheckBoxListValue.aspx
Sub Page_Load If Not IsPostBack Then chklProducts.Items.Add( New ListItem( “Hair Dryer”, “A8999” ) ) chklProducts.Items.Add( New ListItem( “Shaving Cream”, “S7777” ) ) chklProducts.Items.Add( New ListItem( “Electric Comb”, “E23234” ) ) End If End Sub CheckBoxListValue.aspx
If you want a different Text property than Value property when working with a data source, you must specify both the DataTextField and DataValueField properties. The page in Listing 2.28 demonstrates how you can use these properties when binding to a collection of classes.
BUILDING FORMS WITH WEB SERVER CONTROLS
LISTING 2.27
2
106
Working with ASP.NET Web Forms PART I LISTING 2.28
CheckBoxListDataValue.aspx
Public Class Product Private _strProductName, _strProductCode As String Sub New( strProductName As String, strProductCode As String ) _strProductName = strProductName _strProductCode = strProductCode End Sub Public ReadOnly Property ProductName As String Get Return _strProductName End Get End Property Public ReadOnly Property ProductCode As String Get Return _strProductCode End Get End Property End Class Sub Page_Load Dim colProducts As New ArrayList If Not IsPostBack Then colProducts.Add( New Product( “Hair Dryer”, “A8999” ) ) colProducts.Add( New Product( “Shaving Cream”, “S7777” ) ) colProducts.Add( New Product( “Electric Comb”, “E23234” ) ) chklProducts.DataSource = colProducts chklProducts.DataTextField = “ProductName” chklProducts.DataValueField = “ProductCode” chklProducts.DataBind End If End Sub CheckBoxListDataValue.aspx
Building Forms with Web Server Controls CHAPTER 2 LISTING 2.28
107
continued
After you specify a distinct Value property, you can use it in your code. When you submit the form contained in Listing 2.29, the list item’s Text and Value properties are displayed for each of the check boxes selected.
2 CheckBoxListValueSelected.aspx
Sub Button_Click( s As Object, e As EventArgs ) Dim itmProduct As ListItem Dim strTextList, strValueList As String For each itmProduct in chklProducts.Items If itmProduct.Selected Then strTextList &= “
Selected Text:
Selected Value:
Controlling the Layout of a CheckBoxList You can control the layout of the check boxes in a CheckBoxList by modifying the values of the RepeatLayout and RepeatDirection properties. These properties enable you to specify the number of columns to use when displaying the check boxes, and whether the check boxes are ordered horizontally or vertically. The page in Listing 2.30, for example, displays 50 check boxes arranged in three columns. LISTING 2.30
CheckBoxListColumns.aspx
Sub Page_Load Dim intCounter As Integer Dim strListItem As String
Building Forms with Web Server Controls CHAPTER 2 LISTING 2.30
109
continued
If Not IsPostBack Then For intCounter = 1 To 50 strListItem = “Checkbox “ & intCounter chklCheckBoxList.Items.Add( strListItem ) Next End If End Sub
The CheckBoxList Control’s AutoPostBack Property If you enable the AutoPostBack property of the CheckBoxList control, the form containing the CheckBoxList is submitted automatically whenever a check box is checked or unchecked. Warning The AutoPostBack property uses client-side JavaScript. If a browser does not support JavaScript, or JavaScript is disabled on the browser, AutoPostBack does not work.
The page in Listing 2.31 contains a CheckBoxList control with its AutoPostBack property enabled. When a new check box is checked, the SelectedIndexChanged event is raised. This event is associated with the chklFavoriteFoods_SelectedIndexChanged subroutine. So, whenever a new check box is checked, the page is automatically submitted to the server, and the chklFavoriteFoods_SelectedIndexChanged subroutine is executed.
2 BUILDING FORMS WITH WEB SERVER CONTROLS
CheckBoxListColumns.aspx
110
Working with ASP.NET Web Forms PART I LISTING 2.31
CheckBoxListAutoPostBack.aspx
Sub chklFavoriteFoods_SelectedIndexChanged( s As Object, e As EventArgs ) Dim itmFood As ListItem Dim strList As String For each itmFood in chklFavoriteFoods.Items If itmFood.Selected Then strList &= “
2
116
Working with ASP.NET Web Forms PART I
You also can assign a different Value property than Text property when explicitly adding list items to the ListItemCollection collection, as illustrated in Listing 2.35. LISTING 2.35
DropDownListValue.aspx
Sub Page_Load If Not IsPostBack Then dropCategory.Items.Add( New ListItem( “Country Music”, “country” ) ) dropCategory.Items.Add( New ListItem( “Rock Music”, “rock” ) ) dropCategory.Items.Add( New ListItem( “Classical Music”, “classical” ) ) End If End Sub DropDownListValue.aspx
If you want a different Text property than Value property when working with a data source, you must specify both the DataTextField and DataValueField properties. The page in Listing 2.36 demonstrates how you can use these properties when binding to a DataTable. Note To learn more about the DataTable class see Chapter 12, “Working with DataSets.”
LISTING 2.36
DropDownListDataValue.aspx
Building Forms with Web Server Controls CHAPTER 2 LISTING 2.36
117
continued
Sub Page_Load If Not IsPostBack Then Dim dtbMusic As New DataTable Dim drowNewRows As DataRow
dropCategory.DataSource = dtbMusic dropCategory.DataTextField = “CategoryName” dropCategory.DataValueField = “CategoryID” dropCategory.DataBind End If End Sub DropDownListDataValue.aspx
After you specify a distinct Value property, you can use it in your code. When you select an option from the DropDownList in Listing 2.37, the Index, Text, and Value properties of the selected option are displayed.
2 BUILDING FORMS WITH WEB SERVER CONTROLS
dtbMusic.Columns.Add( _ New DataColumn( “CategoryName”, GetType( String ) ) ) dtbMusic.Columns.Add( _ New DataColumn( “CategoryID”, GetType( String ) ) ) drowNewRows = dtbMusic.NewRow() drowNewRows( “CategoryName” ) = “Country Music” drowNewRows( “CategoryID” ) = “country” dtbMusic.Rows.Add( drowNewRows ) drowNewRows = dtbMusic.NewRow() drowNewRows( “CategoryName” ) = “Rock Music” drowNewRows( “CategoryID” ) = “rock” dtbMusic.Rows.Add( drowNewRows ) drowNewRows = dtbMusic.NewRow() drowNewRows( “CategoryName” ) = “Classical Music” drowNewRows( “CategoryID” ) = “classical” dtbMusic.Rows.Add( drowNewRows )
118
Working with ASP.NET Web Forms PART I LISTING 2.37
DropDownListValueSelected.aspx
Sub Button_Click( s As Object, e As EventArgs ) lblSelectedIndex.Text = dropCategory.SelectedIndex lblSelectedText.Text = dropCategory.SelectedItem.Text lblSelectedValue.Text = dropCategory.SelectedItem.Value End Sub DropDownListValueSelected.aspx Selected Index:
Selected Text:
Building Forms with Web Server Controls CHAPTER 2 LISTING 2.37
119
continued
Selected Value:
2 If you enable the AutoPostBack property of the DropDownList control, the form containing the DropDownList is submitted automatically whenever a new option is selected. Warning The AutoPostBack property uses client-side JavaScript. If a browser does not support JavaScript, or JavaScript is disabled on the browser, AutoPostBack does not work.
The page in Listing 2.38 contains a DropDownList control with its AutoPostBack property enabled. When a new option is selected, the SelectedIndexChanged event is raised. This event is associated with the dropColors_SelectedIndexChanged subroutine. So, whenever a new option is selected, the form is submitted, and the dropColors_SelectedIndexChanged subroutine is executed. LISTING 2.38
DropDownListAutoPostBack.aspx
Dim strBackGroundColor As String = “white” Sub dropColors_SelectedIndexChanged( s As Object, e As EventArgs ) strBackGroundColor = dropColors.SelectedItem.Text End Sub DropDownListAutoPostBack.aspx
BUILDING FORMS WITH WEB SERVER CONTROLS
The DropDownList Control’s AutoPostBack Property
120
Working with ASP.NET Web Forms PART I LISTING 2.38
continued
Using the ListBox Control You can use a ListBox control to present a list of options. You can create a list box that enables a user to select only one option at a time, or you can create a multiselect list box. All the properties, methods, and events of this control are listed in Table 2.11. TABLE 2.11
ListBox Properties, Methods, and Events
Properties
Description
AutoPostBack
When True, automatically posts the form containing the list box whenever a new option is selected.
DataMember
Identifies the particular table in a data source to bind to the control.
DataSource
Indicates the data source for the items listed in the list box.
DataTextField
Indicates the field from the data source to use for the option text.
DataTextFormatString
Gets or sets a format string that determines how the DataTextField is displayed.
DataValueField
Indicates the field from the data source to use for the option values.
Items
Represents the collection of items in the list box.
Rows
Indicates the number of rows to display in the list box. The default value is 4.
SelectedIndex
Specifies the index number of the selected option.
Building Forms with Web Server Controls CHAPTER 2 TABLE 2.11
121
continued
Description
SelectedItem
Represents the selected option.
SelectionMode
Determines whether a user can select more than one list item at a time. Possible values are Multiple and Single.
Method
Description
DataBind
Binds the ListBox to its data source. Loads the items from the data source into the ListItem collection.
OnSelectedIndexChanged
Raises the SelectedIndexChanged event.
Events
Description
SelectedIndexChanged
This event is raised whenever a new option is selected in the list box.
A ListBox control has a collection, represented by its Items property, that represents all the individual options in the list box. You can add options to a list box in three ways: You can list the options when you declare the ListBox control, you can add items directly to the ListItemCollection collection of the ListBox control, or you can bind a list box to a data source. First, you can simply list the options as list items when you declare the control like this:
This list box contains three options that represent different products: one labeled Hair Dryer, one labeled Shaving Cream, and one labeled Electric Comb. The Shaving Cream option is selected by default. Second, you can add list items directly to the ListItemCollection collection of a list box. For example, in Listing 2.39, three options are added to a list box in the Page_Load subroutine.
2 BUILDING FORMS WITH WEB SERVER CONTROLS
Properties
122
Working with ASP.NET Web Forms PART I LISTING 2.39
ListBox.aspx
Sub Page_Load If Not IsPostBack Then lstProducts.Items.Add( “Hair Dryer” ) lstProducts.Items.Add( “Shaving Cream” ) lstProducts.Items.Add( “Electric Comb” ) End If End Sub ListBox.aspx
Finally, you can bind the ListBox control to a data source such as a database table or an existing collection. Data binding is covered in detail in Chapter 10, but I’ll show you a quick example of binding a collection to a list box in Listing 2.40. LISTING 2.40
ListBoxDataBind.aspx
Sub Page_Load Dim colArrayList As New ArrayList If Not IsPostBack Then colArrayList.Add( “Hair Dryer” ) colArrayList.Add( “Shaving Cream” ) colArrayList.Add( “Electric Comb” ) lstProducts.DataSource = colArrayList lstProducts.DataBind() End If End Sub
Building Forms with Web Server Controls CHAPTER 2 LISTING 2.40
123
continued
ListBoxDataBind.aspx
In the Page_Load subroutine in Listing 2.40, an ArrayList named colArrayList is created. The values Hair Dryer, Shaving Cream, and Electric Comb are added to the ArrayList. Next, the ArrayList is assigned to the ListBox control’s DataSource property, and the DataBind() method is called. After this method is called, all the items in the ArrayList are loaded into the ListItemCollection collection of the ListBox. Note An ArrayList is a collection. Think of it as an array without any fixed size. Collections are discussed in detail in Chapter 24.
Creating a Multiselect List Box By default, you can select only one option in a list box. However, you can set the SelectionMode property to enable you to pick multiple options at a time. Listing 2.41 illustrates how you can create a multiselect list box. LISTING 2.41
ListBoxMultiSelect.aspx
ListBoxMultiSelect.aspx
BUILDING FORMS WITH WEB SERVER CONTROLS
2
124
Working with ASP.NET Web Forms PART I LISTING 2.41
continued
When SelectionMode is assigned the value Multiple, you can choose multiple options by using the Shift or Ctrl key. The Shift key enables you to choose a range of options at a time. The Ctrl key enables you to pick multiple options even if the options do not appear next to one another.
Detecting the Selected Item in a List Box If you are working with a single-select list box, you can use either the SelectedIndex or SelectedItem property to determine which option is selected. The SelectedIndex property returns the index number of the currently selected option. For example, if the first option in the list is selected, SelectedIndex returns 0; if the second option is selected, SelectedIndex returns 1; and so on. The SelectedItem property, on the other hand, returns the actual list item in the ListItemCollection collection that is currently selected. You can use this property to return the Text property for the selected option. When you select an option in Listing 2.42, for example, the Button_Click subroutine is executed, and both the SelectedIndex and SelectedItem properties for the selected option are displayed. LISTING 2.42
ListBoxSelected.aspx
Sub Button_Click( s As Object, e As EventArgs ) lblSelectedIndex.Text = lstProducts.SelectedIndex lblSelectedItem.Text = lstProducts.SelectedItem.Text End Sub
Building Forms with Web Server Controls CHAPTER 2 LISTING 2.42
125
continued
ListBoxSelected.aspx
Selected Index:
Selected Item:
When you’re working with a multiselect list box, the SelectedIndex and SelectedItem properties return only the first index and first item selected. If you need to determine all the items selected, you need to walk through the ListItem collection and check whether each item is selected. The page in Listing 2.43 illustrates how you would go through this list.
2 BUILDING FORMS WITH WEB SERVER CONTROLS
126
Working with ASP.NET Web Forms PART I LISTING 2.43
ListBoxSelectedMultiple.aspx
Sub Button_Click( s As Object, e As EventArgs ) Dim itmProduct As ListItem Dim strList As String For each itmProduct in lstProducts.Items If itmProduct.Selected Then strList &= “
Selected Index:
Selected Text:
Selected Value:
The ListBox Control’s AutoPostBack Property If you enable the AutoPostBack property of the ListBox control, the form containing the list box is submitted automatically whenever a new option is selected. Warning The AutoPostBack property uses client-side JavaScript. If a browser does not support JavaScript, or JavaScript is disabled on the browser, AutoPostBack does not work.
Building Forms with Web Server Controls CHAPTER 2
131
The page contained in Listing 2.47 contains a ListBox control with its AutoPostBack property enabled. When a new option is selected, the SelectedIndexChanged event is raised. This event is associated with the lstColors_SelectedIndexChanged subroutine. So, whenever a new option is selected, the form is submitted, and the subroutine is executed (the lstColors_SelectedIndexChanged subroutine displays a different background color). LISTING 2.47
ListBoxAutoPostBack.aspx
Sub lstColors_SelectedIndexChanged( s As Object, e As EventArgs ) strBackGroundColor = lstColors.SelectedItem.Text End Sub ListboxAutoPostBack.aspx
Controlling Page Navigation In the following sections, you learn how to control how a user moves from one ASP.NET page to another. First, you learn how to submit an HTML form to another page and retrieve form information. Next, you learn how to use the Redirect() method to automatically transfer a user to a new page. Finally, you learn how to link pages together with the HyperLink control.
BUILDING FORMS WITH WEB SERVER CONTROLS
Dim strBackGroundColor As String = “white”
2
132
Working with ASP.NET Web Forms PART I
Submitting a Form to a Different Page You might not have noticed, but all the ASP.NET pages discussed up to this point in the book have posted back to themselves. There is a good reason for this. Most of the ASP.NET controls take advantage of a page’s view state to retain information between posts. If you use the server-side version of the tag, you cannot post a form to a different page. You can, if you have a pressing need, post a form to a new page by using the standard HTML form tags instead of ASP.NET controls as illustrated in the page contained in Listing 2.48. LISTING 2.48
Form.aspx
Form.aspx Username:
Comments:
In Listing 2.48, the Action attribute of the tag is assigned the name of a new page, Results.aspx, where the form information is posted. Because the form information is posted to a different page, you cannot retrieve that information by using ASP.NET controls. For example, you cannot refer to the Username control within the Results.aspx page. Instead, you need to use the Params collection of the HTTPRequest class.
Building Forms with Web Server Controls CHAPTER 2
133
Note You also can use the Form collection of the HTTPRequest class to grab form data. The difference between the Params and Form collections is that the Params collection also represents QueryStrings, ServerVariables, and Cookies.
The Results.aspx page in Listing 2.49 demonstrates how you can use the Params collection to retrieve values of the Username and Comments form fields. Results.aspx
Sub Page_Load lblUsername.Text = Request.Params( “Username” ) lblComments.Text = Request.Params( “Comments” ) End Sub Results.aspx wrote
Using the Redirect() Method Normally, you do not want to post form data to a separate page. In most cases, you should take advantage of view state and have the form post back to itself. So, the question remains, how do you transfer a user to a new page after the user has successfully completed a form?
BUILDING FORMS WITH WEB SERVER CONTROLS
LISTING 2.49
2
134
Working with ASP.NET Web Forms PART I
The best way to transfer the user is to use the Response.Redirect() method. You can use this method to automatically transfer a user to any page on your Web site. You can even use it to transfer a user to a page on another Web site (such as the home page of Yahoo.com). The page in Listing 2.50 demonstrates how you use the Response.Redirect() method with a form. After the form is submitted, the Redirect() method transfers the user to a page named ThankYou.aspx. Typically, you would save the form data to a file or database table right before the Redirect() method is called. LISTING 2.50
Redirect.aspx
Sub Button_Click( s As Object, e As EventArgs ) ‘ Save Form Data to Database Table Response.Redirect( “ThankYou.aspx” ) End Sub Redirect.aspx Username:
Comments:
Building Forms with Web Server Controls CHAPTER 2
135
Working with the HyperLink Control You can link one ASP.NET page to another by adding a HyperLink control to a page. It can display either text or an image as a link. All the properties, methods, and events of this control are listed in Table 2.12. TABLE 2.12
HyperLink Properties, Methods, and Events
Description
ImageUrl
The URL of an image to display for the link.
NavigateUrl
The URL of the page to which the user is transferred when the link is clicked.
Target
The target window or frame for the link. Possible values are _top, _self, _parent, _search, or _blank.
Text
The text label for the link.
Methods
Description
None
Events
Description
None
The advantage of using a HyperLink control rather than a simple HTML link is that you can manipulate its properties in your code. For example, the HyperLink control in Listing 2.51 links to different pages depending on the time of day. After 5:00 p.m., the HyperLink control links to a page named AfterHoursHelp.aspx and before 5:00 p.m., it links to Help.aspx. LISTING 2.51
HyperLink.aspx
Sub Page_Load If TimeOfDay > #5:00pm# Then lnkHelpLink.NavigateUrl = “AfterHoursHelp.aspx” lnkHelpLink.Text = “After Hours Help” Else lnkHelpLink.NavigateURL = “Normalhelp.aspx” lnkHelpLink.Text = “Help” End If End Sub
2 BUILDING FORMS WITH WEB SERVER CONTROLS
Properties
136
Working with ASP.NET Web Forms PART I LISTING 2.51
continued
HyperLink.aspx Click on the link below to receive a list of contact phone numbers for help information:
Applying Formatting to Controls In the following sections, you learn how to make more attractive Web forms. First, you look at an overview of the formatting properties common to all Web controls; they are the formatting properties of the base control class. Next, you learn how to apply Cascading Style Sheet styles and classes to Web controls.
Base Web Control Properties All Web controls inherit from the base Web control class (the WebControl class). The base Web control class contains a number of properties that you can use to modify the appearance of any Web control. These properties are listed in Table 2.13. TABLE 2.13
Base Web Control Formatting Properties
Properties
Description
AccessKey
Indicates a keyboard shortcut for selecting a control. Use a single letter or number when holding down the Alt key.
Building Forms with Web Server Controls CHAPTER 2 TABLE 2.13
137
continued
Description
BackColor
Indicates the color that appears behind the text of a control. This value can be specified by name (red, blue) or by RGB value (#FF0000, #0000FF).
BorderStyle
Sets the appearance of the border. Possible values are Dashed, Dotted, Double, Groove, Inset, None, NotSet, Outset, Ridge, and Solid.
BorderWidth
Indicates the thickness of a control’s border in pixels.
Font-Bold
Displays text in bold.
Font-Italic
Displays text in italic.
Font-Name
Indicates the name of the typeface for formatting text.
Font-Names
Specifies a list of names of typefaces for formatting text. If the first typeface is not available, the second typeface is applied, and so on.
Font-Overline
Draws a line above the text.
Font-Size
Sets the size of a font in points or pixels.
Font-Strikeout
Draws a line through the text.
Font-Underline
Draws a line under the text.
ForeColor
Specifies the text color.
Height
Sets the height of the control in pixels.
TabIndex
Indicates the tab order of the controls (works only with Internet Explorer 4.0 and higher).
ToolTip
Sets the text that appears when the mouse pointer hovers over the control.
Width
Sets the width of the control in pixels or percentage.
Not all these properties work with all browsers. Some of them depend on Cascading Style Sheets, which not all browsers support. Furthermore, some of these properties depend on features specific to Microsoft Internet Explorer. The page in Listing 2.52 demonstrates how you can use several of these properties to format controls (see Figure 2.7).
2 BUILDING FORMS WITH WEB SERVER CONTROLS
Properties
138
Working with ASP.NET Web Forms PART I
FIGURE 2.7 Using base Web control properties.
LISTING 2.52
BaseWebControl.aspx
BaseWebControl.aspx Field1:
Field 2
Building Forms with Web Server Controls CHAPTER 2 LISTING 2.52
139
continued
The page in Listing 2.52 contains three TextBox controls. Each text box is assigned an access key so that you can navigate directly to it. For example, to move to the third text box, you would press Alt+3. Each TextBox control is assigned a yellow background and blue forecolor. When you type text into the text box, the text appears in a blue font. The first text box is assigned a dashed border. The BorderWidth property is assigned the value 4 so that the border is easier to see. The second text box is provided with a ToolTip. If you hover your mouse pointer over the text box, the text “Enter some data” appears in a bubble.
2 BUILDING FORMS WITH WEB SERVER CONTROLS
Field 3
140
Working with ASP.NET Web Forms PART I
The Width property of the third text box has the value 100%. When the text box is rendered, it fills the width of the screen. Finally, several of the formatting properties of the Button control are modified in Listing 2.52. For example, the button is assigned a double border. You also can modify the properties of a Web control within the code of the page. For example, the page in Listing 2.53 enables you to pick both the background color and font size of the text that appears in the text box. LISTING 2.53
BaseWebControlDynamic.aspx
Sub dropBackColor_SelectedIndexChanged( s As Object, e As EventArgs ) Dim strBackColor As Color strBackColor = Color.FromName( dropBackColor.SelectedItem.Text ) txtTextBox.BackColor = strBackColor End Sub Sub dropFontSize_SelectedIndexChanged( s As Object, e As EventArgs ) txtTextBox.Font.Size = FontUnit.Parse( dropFontSize.SelectedItem.Text ) End Sub BaseWebControlDynamic.aspx
BackColor:
Building Forms with Web Server Controls CHAPTER 2 LISTING 2.53
141
continued
Applying Styles to Web Controls The Cascading Style Sheets standard contains many more attributes than can be captured by the limited number of properties discussed in the previous sections. Fortunately, you can use any Cascading Style Sheet attribute with a Web control in the same way as you would use the attribute with a normal HTML tag. For example, you can use the Text-Transform attribute to automatically capitalize the first letter of every word in a string of text. You can use this attribute with the Style property of the Label Web control like this:
When this Label control is rendered, a tag that includes the Style attribute is generated. The following tag is displayed: this is some text
You also can use the CssClass property to assign a Cascading Style Sheet class to a Web control. The page in Listing 2.54 demonstrates how to use the CssClass property.
2 BUILDING FORMS WITH WEB SERVER CONTROLS
Font Size:
142
Working with ASP.NET Web Forms PART I LISTING 2.54
Style.aspx
.myClass { text-align: justify; font: 14pt Script; } Style.aspx
Finally, you can modify either the Style or CssClass properties within the code of an ASP.NET page. For example, the page in Listing 2.55 contains three link buttons. Clicking each link button applies a different style to the Label control. Different styles are applied by modifying the control’s Style property (see Figure 2.8). LISTING 2.55
StyleDynamic.aspx
Sub lbtnCapitalize_Click( s As Object, e As EventArgs ) lblLabel.Style( “text-transform” ) = “capitalize” End Sub Sub lbtnUppercase_Click( s As Object, e As EventArgs ) lblLabel.Style( “text-transform” ) = “uppercase” End Sub Sub lbtnLowercase_Click( s As Object, e As EventArgs ) lblLabel.Style( “text-transform” ) = “lowercase”
Building Forms with Web Server Controls CHAPTER 2 LISTING 2.55
143
continued
End Sub StyleDynamic.aspx
In the same manner in which you can modify the Style property programmatically, you can modify the CssClass property programmatically. The page in Listing 2.56 demonstrates how you can programmatically modify the Cascading Style Sheet class assigned to a control. The page contains two classes named myClass1 and myClass2. When the first LinkButton control is clicked, the myClass1 class is assigned to the Label control. When the second LinkButton control is clicked, the myClass2 class is assigned to the Label control.
2 BUILDING FORMS WITH WEB SERVER CONTROLS
144
Working with ASP.NET Web Forms PART I
FIGURE 2.8 Applying different Style classes.
LISTING 2.56
CssClassDynamic.aspx
Sub lbtnScript_Click( s As Object, e As EventArgs ) myLabel.CssClass = “myClass1” End Sub Sub lbtnVerdana_Click( s As Object, e As EventArgs ) myLabel.CssClass = “myClass2” End Sub .myClass1 { font: 18pt script; color: blue; } .myClass2 {
Building Forms with Web Server Controls CHAPTER 2 LISTING 2.56
145
continued
font: 24pt verdana; color: red; } CssClassDynamic.aspx
The Style Class In the previous section, you learned how to apply client-side style sheets to controls. The ASP.NET Framework also supports server-side styles through the Style class. If you want to apply the same formatting properties to several controls, you can explicitly create an instance of the Style class and apply it to multiple controls. For example, in Listing 2.57, the same instance of the Style class is applied to three TextBox controls.
BUILDING FORMS WITH WEB SERVER CONTROLS
2
146
Working with ASP.NET Web Forms PART I LISTING 2.57
StyleClass.aspx
Sub Page_Load( s As Object, e As EventArgs ) Dim myStyle As New Style myStyle.BackColor = myStyle.ForeColor = myStyle.BorderStyle myStyle.BorderWidth
Color.Yellow Color.Green = BorderStyle.Dashed = New Unit(4)
txtTextBox1.ApplyStyle( myStyle ) txtTextBox2.ApplyStyle( myStyle ) txtTextBox3.MergeStyle( myStyle ) End Sub StyleClass.aspx
Building Forms with Web Server Controls CHAPTER 2
147
In Listing 2.57, the Style class is applied to the first two TextBox controls with the ApplyStyle() method. This method overrides any existing properties of the controls. In this example, the ApplyStyle() method overrides the red back color of the TextBox controls and assigns the new back color yellow. The MergeStyle() method applies the style to the third test box. This method does not override existing properties. Therefore, the third text box continues to appear with a red back color (see Figure 2.9). FIGURE 2.9
Summary This chapter discussed all the basic Web controls. You learned how to add Web controls such as TextBox, DropDownList, and RadioButton to your ASP.NET pages. You also learned how to write subroutines to capture the events that these controls raise. Next, you learned how to work with multiple pages in your ASP.NET application and use the Response.Redirect() method to automatically redirect a user to a new page. You also learned how to link two pages together with the HyperLink control. Finally, you learned how to apply formatting to Web controls by using the formatting properties in the base Web control class. You also learned how to apply Cascading Style Sheet classes and attributes to Web controls.
2 BUILDING FORMS WITH WEB SERVER CONTROLS
Using the Style class.
CHAPTER 3
Performing Form Validation with Validation Controls IN THIS CHAPTER • Using Client-side Validation
150
• Requiring Fields: The RequiredFieldValidator Control
153
• Validating Expressions: The RegularExpressionValidator
Control
159
• Comparing Values: The CompareValidator Control
171
• Checking for a Range of Values: The RangeValidator Control 177 • Summarizing Errors: The ValidationSummary Control
181
• Performing Custom Validation: The CustomValidator Control 188 • Disabling Validation
194
150
Working with ASP.NET Web Forms PART I
In this chapter, you learn how to use the Validation Web controls to validate the information that a user enters into an HTML form. You can use the Validation controls to perform very different types of form validation tasks. For example, you can use the Validation controls to check whether a form field has a value, check whether the data in a form field falls in a certain range, or check whether a form field contains a valid e-mail address or phone number. Note You can view “live” versions of many of the code samples in this chapter by visiting the Superexpert Web site at: http://www.Superexpert.com/AspNetUnleashed/
At the end of this chapter, you also learn how to create custom validation functions by using the CustomValidator control. Even if you need to perform a very specialized form validation task, you can do so by using this control.
Using Client-side Validation Traditionally, Web developers have faced a tough choice when adding form validation logic to their pages. You can add form validation routines to your server-side code, or you can add the validation routines to your client-side code. The advantage of writing validation logic in client-side code is that you can provide instant feedback to your users. For example, if a user neglects to enter a value in a required form field, you can instantly display an error message without requiring a roundtrip back to the server. People really like client-side validation. It looks great and creates a better overall user experience. The problem, however, is that it does not work with all browsers. Not all browsers support JavaScript, and different versions of browsers support different versions of JavaScript, so client-side validation is never guaranteed to work. For this reason, in the past, many developers decided to add all their form validation logic exclusively to server-side code. Because server-side code functions correctly with any browser, this course of action was safer. Fortunately, the Validation controls discussed in this chapter do not force you to make this difficult choice. The Validation controls automatically generate both client-side and
Performing Form Validation with Validation Controls CHAPTER 3
151
server-side code. If a browser is capable of supporting JavaScript, client-side validation scripts are automatically sent to the browser. If a browser is incapable of supporting JavaScript, the validation routines are automatically implemented in server-side code. You should be warned, however, that client-side validation works only with Microsoft Internet Explorer version 4.0 and higher. In particular, the client-side scripts discussed in this chapter do not work with any version of Netscape Navigator.
Configuring Client-side Validation The Validation controls make use of a JavaScript script library that is automatically installed on your server when you install the .NET framework. This library is located in a file named WebUIValidation.js.
FIGURE 3.1 Error from missing validation file.
3 PERFORMING FORM VALIDATION
By default, WebUIValidation.js is installed in a directory named aspnet_client located beneath your Web server’s wwwroot directory. If you change the location of your root directory, you need to copy the aspnet_client directory to the new directory; otherwise, the validation script will not work. If WebUIValidation.js can’t be found, you receive the error Warning! Unable to find script library ‘WebUIValidation.js’ (see Figure 3.1).
152
Working with ASP.NET Web Forms PART I
Note The exact location of the WebUIValidation.js file is determined by your machine.config file (in the section). To learn more about the machine.config file, see Chapter 15, “Creating ASP.NET Applications.”
Microsoft includes a command line tool with the ASP.NET Framework named aspnet_regiis that you can use to automatically install and uninstall the script library. To install the script library execute aspnet_regiis -c, to uninstall the library execute aspnet_regiis -e. The aspnet_regiis tool is located in your \WINNT\Microsoft.NET\Framework\[version]\ directory.
Enabling and Disabling Client-side Validation If you request a page that contains a validation control, and you are using Microsoft Internet Explorer version 4.0 or higher, JavaScript code is automatically sent to your browser. If, for whatever reason, you want to disable client-side form validation, you can do so by adding the following directive at the top of your page:
This directive disables client-side form validation. Unfortunately, however, it also prevents all the ASP.NET controls on the page from rendering any non-HTML 3.2 compatible content. For example, the directive also prevents the rendering of Cascading Style Sheet attributes to the page. Note The ClientTarget attribute accepts a string value that corresponds to one of the entries in the section of the machine.config file. In the machine.config file, uplevel is defined as Internet Explorer 4.0 and downlevel is defined as the Unknown browser. The Unknown browser is assumed to not support Cascading Style Sheets.
Alternatively, you can disable client-side validation for individual validation controls by setting the EnableClientScript property to the value False. Since all validation
Performing Form Validation with Validation Controls CHAPTER 3
153
controls share the EnableClientScript property, you can use this property to disable client-side scripts for particular validation controls or for all validation controls. Finally, you can disable validation, both client and server validation, when certain buttons are pushed. You’ll need to do this when creating a Cancel button. See the last section of this chapter, Disabling Validation, for sample code that demonstrates how to do this.
Requiring Fields: The RequiredFieldValidator
Control You use RequiredFieldValidator in a Web form to check whether a control has a value. Typically, you use this control with a TextBox control. However, nothing is wrong with using RequiredFieldValidator with other input controls such as RadioButtonList. All the properties and methods of this control are listed in Table 3.1. RequiredFieldValidator Properties, Methods, and Events
Properties
Description
ControlToValidate
Specifies the ID of the control that you want to validate.
Display
Sets how the error message contained in the Text property is displayed. Possible values are Static, Dynamic, and None; the default value is Static.
EnableClientScript
Enables or disables client-side form validation. This property has the value True by default.
Enabled
Enables or disables both server and client-side validation. This property has the value True by default.
ErrorMessage
Specifies the error message that is displayed in the ValidationSummary control. This error message is displayed by the validation control when the Text property is not set.
InitialValue
Gets or sets the initial value of the control specified by the ControlToValidate property.
IsValid
Has the value True when the validation check succeeds and False otherwise.
Text
Sets the error message displayed by the control.
PERFORMING FORM VALIDATION
TABLE 3.1
3
154
Working with ASP.NET Web Forms PART I TABLE 3.1
continued
Methods
Description
Validate
Performs validation and updates the IsValid property.
Events
Description
None
The page in Listing 3.1, for example, contains two TextBox controls named txtUsername and txtComments. Each TextBox control has a RequiredFieldValidator control associated with it. If you attempt to submit the form without entering data into both TextBox controls, you get an error. LISTING 3.1
RequiredFieldValidator.aspx
Sub Button_Click( s As Object, e As EventArgs ) If IsValid Then Response.Redirect( “ThankYou.aspx” ) End If End Sub RequiredFieldValidator.aspx Username:
Comments:
Performing Form Validation with Validation Controls CHAPTER 3 LISTING 3.1
155
continued
If you do not enter any text into the TextBox controls and you submit the form, the RequiredFieldValidator controls display error messages, and the IsValid property has the value False. Otherwise, the Button_Click subroutine automatically redirects you to a page named ThankYou.aspx.
3
When performing form validation, you can check the IsValid property for each Validation control to check whether each form field was completed successfully. Alternatively, you can simply check the IsValid property of the page. The Page.IsValid property is True only if the IsValid property is True for every Validation control on the page.
PERFORMING FORM VALIDATION
The RequiredFieldValidator controls are associated with the TextBox controls through the ControlToValidate property. For example, the first RequiredFieldValidator control is associated with the txtUsername control because its ControlToValidate property has the value txtUsername. The error message that each RequiredFieldValidator displays is determined by the Text property. By default, the text is displayed in a red font. You can determine precisely how the error message is formatted by using the formatting properties discussed in the final section of the preceding chapter, “Building Forms with Web Server Controls.” If you want to display a blue error message using a Script font, for example, you would declare RequiredFieldValidator with the following properties:
156
Working with ASP.NET Web Forms PART I
You also can control how the error messages appear by modifying the Display property. By default, screen real estate is reserved for an error message, even if the error message is not displayed. However, if you assign the value Dynamic to the Display property, the error message pushes away any content surrounding it. The page in Listing 3.2, for example, contains two RequiredFieldValidator controls. The first RequiredFieldValidator control has its Display property set to Static; the second has its Display property set to Dynamic. LISTING 3.2
RequiredFieldValidatorDisplay.aspx
RequiredFieldValidatorDisplay.aspx Field 1: Here is Some Text
Field 2: Here is Some Text
Performing Form Validation with Validation Controls CHAPTER 3 LISTING 3.2
157
continued
The text, Here is Some Text, is written next to each RequiredFieldValidator. Notice how the text after the first RequiredFieldValidator control is pushed to the right (see Figure 3.2). FIGURE 3.2 The difference between Dynamic and Static.
3 PERFORMING FORM VALIDATION
Comparing to an Initial Value You also can use RequiredFieldValidator to check whether a user has entered a value other than an initial value. You might want to display a sample of the type of input that a user should enter into a form field, but require a different value to be entered into the form field before the form is submitted. For example, you might want to display the text Enter Some Text within a text box. However, you would not want the user to actually submit the value Enter Some Text when the form is submitted. The page in Listing 3.3 demonstrates how to use the InitialValue property of the RequiredFieldValidator control.
158
Working with ASP.NET Web Forms PART I LISTING 3.3
RequiredFieldValidatorInitialValue.aspx
Sub Button_Click( s As Object, e As EventArgs ) If IsValid Then Response.Redirect( “ThankYou.aspx” ) End If End Sub RequiredFieldValidatorInitialValue.aspx Comments:
When the form in Listing 3.3 is first requested, the text Enter Some Text appears in the TextBox control. If you attempt to submit the form without changing this value, an error is displayed.
Performing Form Validation with Validation Controls CHAPTER 3
159
Validating Expressions: The RegularExpressionValidator
Control You can use RegularExpressionValidator to match the value entered into a form field to a regular expression. You can use this control to check whether a user has entered, for example, a valid e-mail address, telephone number, or username or password. Samples of how to use a regular expression to perform all these validation tasks are provided in the following sections. The properties and methods of this control are listed in Table 3.2. TABLE 3.2
RegularExpressionValidator Properties, Methods, and Events
Description
ControlToValidate
Specifies the id of the control that you want to validate.
Display
Sets how the error message contained in the Text property is displayed. Possible values are Static, Dynamic, and None; the default value is Static.
EnableClientScript
Enables or disables client-side form validation. This property has the value True by default.
Enabled
Enables or disables both server and client-side validation. This property has the value True by default.
ErrorMessage
Specifies the error message that is displayed in the ValidationSummary control. This error message is displayed by the control when the Text property is not set.
IsValid
Has the value True when the validation check succeeds and False otherwise.
Text
Sets the error message displayed by the control.
ValidationExpression
Specifies the regular expression to use when performing validation.
Methods
Description
Validate
Performs validation and updates the IsValid property.
Events
Description
None
3 PERFORMING FORM VALIDATION
Properties
160
Working with ASP.NET Web Forms PART I
Note Regular expressions are discussed in detail in Chapter 24, “Working with Collections and Strings.”
You assign the regular expression that you want to use when performing validation to the ValidationExpression property. You can perform complex types of validation by using the correct regular expression. Warning Regular expressions work somewhat differently in JavaScript than they do in the .NET framework. So, in certain circumstances, the client-side validation code used for matching regular expressions might return different results than the server-side validation code. Even worse, a valid .NET regular expression might generate a JavaScript error. For example, the syntax for using regular expression options inline—such as the i option—differs between JavaScript and the .NET classes.
You can submit the form in Listing 3.4, for example, only if you enter a product code that starts with the uppercase letter P and contains no more, or no fewer, than four numerals in a row. The RegularExpressionValidator control uses the regular expression P[0-9]{4}. LISTING 3.4
RegularExpressionValidator.aspx
Sub Button_Click( s As Object, e As EventArgs ) If IsValid Then Response.Redirect( “ThankYou.aspx” ) End If End Sub RegularExpressionValidator.aspx
Performing Form Validation with Validation Controls CHAPTER 3 LISTING 3.4
161
continued
Product Code:
3
The only way to require a value is to combine RegularExpressionValidator with RequiredFieldValidator. Nothing prevents you from associating multiple Validator controls with the same control.
Validating E-Mail Addresses One of the most common and difficult validation tasks that arise when performing form validation is validating an e-mail address. This task is actually much more difficult than you might assume because the e-mail standard is so complicated. Note You can find the specification for e-mail addresses in RFC 822, Standard for ARPA Internet Text Messages, at the following location: ftp://ftp.rfc-editor.org/in-notes/rfc822.txt
PERFORMING FORM VALIDATION
If you experiment with the page contained in Listing 3.4, you’ll quickly discover that you can submit the form without entering any text into the text box. The RegularExpressionValidator control does not require a value.
162
Working with ASP.NET Web Forms PART I
Even if a perfect e-mail validation regular expression is beyond your grasp, however, you can still hope to validate simple e-mail addresses. For example, you can use the following regular expression to check that an e-mail address starts with one or more nonwhitespace characters, followed by an @ sign, followed by one or more nonwhitespace characters, followed by a period, followed by one or more nonwhitespace characters: \S+@\S+\.\S+
So, this regular expression would match the following e-mail addresses: [email protected] [email protected] [email protected]
However, it would fail to match e-mail addresses like steve@aol steve smith@aol
which is what you want. If you want to exclude all e-mail addresses that do not end with a top-level domain name between two and three characters—such as .com, .net, and .ws—you would use this expression: \S+@\S+\.\S{2,3}
The page in Listing 3.5 demonstrates how you would use this regular expression with the RegularExpressionValidator control. LISTING 3.5
RegularExpressionValidatorEmail.aspx
Sub Button_Click( s As Object, e As EventArgs ) If IsValid Then Response.Redirect( “ThankYou.aspx” ) End If End Sub RegularExpressionvalidatorEmail.aspx Email Address:
Performing Form Validation with Validation Controls CHAPTER 3 LISTING 3.5
163
continued
3 Typically, Web sites require you to enter a username and password containing only alphanumeric characters or the underscore character. You can perform this type of validation using a regular expression that looks like this: \w+
This regular expression matches any expression that contains one or more word characters (a word character can be a letter, number, or the underscore character). You also can specify a minimum and maximum length for a password by using a regular expression that looks like this: \w{8,20}
This regular expression matches only expressions that are between 8 and 20 characters long. Finally, some Web sites require you to use at least one number and one letter in your password. You can use the following regular expression to satisfy this requirement: [a-zA-Z]+\w*\d+\w*
This regular expression requires you to enter at least one letter, followed by any number of word characters, followed by at least one number, followed by any number of word characters.
PERFORMING FORM VALIDATION
Validating Usernames and Passwords
164
Working with ASP.NET Web Forms PART I
Listing 3.6 demonstrates how you can combine two RegularExpressionValidator controls and a RequiredFieldValidator control to validate a password. The Validation controls require you to enter a password that starts with at least one letter and contains one number and between 3 and 20 characters (special characters, such as # and ?, are not allowed). LISTING 3.6
RegularExpressionValidatorPassword.aspx
Sub Button_Click( s As Object, e As EventArgs ) If IsValid Then Response.Redirect( “thankyou.aspx” ) End If End Sub RegularExpressionValidatorPassowrd.aspx Password:
Validating Phone Numbers Phone numbers are difficult to validate—especially when you take into consideration foreign area codes and phone number extensions. Even if you ignore these problems and concentrate on U.S. phone numbers without extensions, many different formats still are used when entering a phone number. For example, the following formats are all commonly used:
You can create a regular expression that matches all three of the preceding expressions, as follows: \(?\s*\d{3}\s*[\)\.\-]?\s*\d{3}\s*[\-\.]?\s*\d{4}
The page in Listing 3.7 illustrates how you can use this regular expression with RegularExpressionValidator. LISTING 3.7
RegularExpressionValidatorPhone.aspx
Sub Button_Click( s As Object, e As EventArgs ) If IsValid Then Response.Redirect( “ThankYou.aspx” ) End If End Sub RegularExpressionValidatorPhone.aspx
3 PERFORMING FORM VALIDATION
(555) 555-5555 555.555.5555 555 555-555
166
Working with ASP.NET Web Forms PART I LISTING 3.7
continued
Phone Number:
Validating Web Addresses You also might need to validate URLs that users enter into a form at your Web site. For example, you might have a registration form that contains a field for the URL of a user’s home page. You can use the following regular expression to check for a valid URL: http://\S+\.\S+
This regular expression matches any string that begins with the characters http:// followed by one or more nonwhitespace characters, followed by a period, followed by one or more nonwhitespace characters. The page in Listing 3.8 demonstrates how you can use this regular expression with RegularExpressionValidator.
Performing Form Validation with Validation Controls CHAPTER 3 LISTING 3.8
167
RegularExpressionWeb.aspx
Sub Button_Click( s As Object, e As EventArgs ) If IsValid Then Response.Redirect( “thankyou.aspx” ) End If End Sub RegularExpressionValidatorWeb.aspx
One problem with the code in Listing 3.8 concerns case-sensitivity. The RegularExpressionValidator control uses case-sensitive comparisons. So, the regular expression discussed in this section matches http://www.superexpert.com
3 PERFORMING FORM VALIDATION
Enter the address of your homepage:
168
Working with ASP.NET Web Forms PART I
but does not match HTTP://www.superexpert.com
Unfortunately, RegularExpressionValidator does not have a property that you can set to enable regular expression options such as the i option (an option which enables caseinsensitive matches). You cannot use the i option inline because the syntax for doing so with JavaScript is different from the syntax for doing so with the .NET classes. A not completely satisfactory workaround to this problem is illustrated by the page in Listing 3.9. The RegularExpressionValidator in this page performs a case insensitive match by using the i option. Furthermore, the RegularExpressionValidator disables client-side validation to prevent conflicts with JavaScript. This is not a perfect workaround to the problem since the page must be submitted back to the server before the RegularExpressionValidator will validate the expression. LISTING 3.9
RegularExpressionIgnoreCase.aspx
Sub Button_Click( s As Object, e As EventArgs ) If IsValid Then Response.Redirect( “thankyou.aspx” ) End If End Sub RegularExpressionValidatorIgnoreCase.aspx Enter the address of your homepage:
Checking for Entry Length
To check for a certain entry length, use regular expression quantifiers like this: .{0,10}
This expression matches any entry (including spaces) that contains between 0 and 10 characters. If you want to restrict the entry to a string of nonwhitespace characters that has a certain length, you would use a regular expression that looks like this: \S{0,10}
The page in Listing 3.10 demonstrates how you can use this regular expression with RegularExpressionValidator. LISTING 3.10
RegularExpressionValidatorLength.aspx
Sub Button_Click( s As Object, e As EventArgs ) If IsValid Then Response.Redirect( “ThankYou.aspx” ) End If End Sub
3 PERFORMING FORM VALIDATION
You also can use RegularExpressionValidator to check whether a form field contains more than a certain number of characters. This type of validation is especially useful when you’re working with MultiLine TextBox controls. Because a MultiLine TextBox control does not have a MaxLength property, there is nothing to prevent a user from typing any number of characters in the text box.
170
Working with ASP.NET Web Forms PART I LISTING 3.10
continued
RegularExpressionValidatorLength.aspx Enter your last name: (no more than 10 characters)
Validating ZIP Codes You also can use the RegularExpressionValidator control to validate ZIP codes. For example, if you want to require that a ZIP code entered into a form field contains exactly five digits, you would use the following regular expression: \d{5}
This expression matches strings that have no more or no fewer than five digits. The page in Listing 3.11 demonstrates how you would use this regular expression with the RegularExpressionValidator control.
Performing Form Validation with Validation Controls CHAPTER 3 LISTING 3.11
171
RegularExpressionValidatorZip.aspx
Sub Button_Click( s As Object, e As EventArgs ) If IsValid Then Response.Redirect( “ThankYou.aspx” ) End If End Sub RegularExpressionValidatorZip.aspx ZIP Code:
Comparing Values: The CompareValidator Control The CompareValidator control performs comparisons between the data entered into a form field and another value. The other value can be a fixed value, such as a particular
PERFORMING FORM VALIDATION
3
172
Working with ASP.NET Web Forms PART I
number, or a value entered into another control. All the properties and methods of the CompareValidator are listed in Table 3.3. TABLE 3.3
CompareValidator Properties, Methods, and Events
Properties
Description
ControlToCompare
Specifies the id of the control to use for comparing values.
ControlToValidate
Specifies the id of the control that you want to validate.
Display
Sets how the error message contained in the Text property is displayed. Possible values are Static, Dynamic, and None; the default value is Static.
EnableClientScript
Enables or disables client-side form validation. This property has the value True by default.
Enabled
Enables or disables both server and client-side validation. This property has the value True by default.
ErrorMessage
Specifies the error message that is displayed in the ValidationSummary control. This error message is displayed by the control when the Text property is not set.
IsValid
Has the value True when the validation check succeeds and False otherwise.
Operator
Gets or sets the comparison operator to use when performing comparisons. Possible values are Equal, NotEqual, GreaterThan, GreaterThanEqual, LessThan, LessThanEqual, DataTypeCheck.
Text
Sets the error message displayed by the control.
Type
Gets or sets the data type to use when comparing values. Possible values are Currency, Date, Double, Integer, and String.
ValueToCompare
Specifies the value used when performing the comparison.
Methods
Description
Validate
Performs validation and updates the IsValid property.
Events
Description
None
You can use CompareValidator, for example, to check whether a user entered a number greater than 7, check to see whether a date entered into one control is greater than a date
Performing Form Validation with Validation Controls CHAPTER 3
173
entered into another control, or check whether a currency amount is less than a certain specified amount. also can be used to check whether a form field contains a particular data type. For example, you can use the control to check whether a user entered a valid date, number, or currency value.
CompareValidator
Comparing the Value of One Control to the Value of Another Imagine that you have two form fields for entering dates: one labeled Start Date and one labeled End Date. Now, imagine that you want to make sure that any date entered into the second form field is later than any date entered in the first form field. You can perform this type of form validation by using the CompareValidator control. To compare two dates, you need to set the ControlToValidate, ControlToCompare, Operator, and Type properties of the CompareValidator control. The page in Listing 3.12 illustrates how you can check whether one date is later than another.
3 CompareValidator.aspx
Sub Button_Click( s As Object, e As EventArgs ) If IsValid Then Response.Redirect( “ThankYou.aspx” ) End If End Sub CompareValidator.aspx Start Date: End Date:
PERFORMING FORM VALIDATION
LISTING 3.12
174
Working with ASP.NET Web Forms PART I LISTING 3.12
continued
The CompareValidator control in Listing 3.12 uses the ControlToValidate and ControlToCompare properties to indicate the controls to use for the comparison. The Operator property has the value GreaterThan. CompareValidator checks whether the value entered into the txtEndDate control is greater than the value entered into the txtStartDate control. Finally, the Type property has the value Date. The control compares the two values as date values rather than string or integer values. does not check whether values are actually entered into the controls that it is comparing. If either the txtStartDate or txtEndDate controls are left blank, the form passes the validation check. CompareValidator
Comparing the Value of a Control to a Fixed Value Instead of using CompareValidator to compare the values of two controls, you can use it to compare a value in one control to a fixed value. For example, suppose that you are building an auction Web site, and you want a user to enter a bid higher than a certain amount. You can use CompareValidator to compare a bid to the previous highest bid. The page in Listing 3.13 illustrates how you would do so.
Performing Form Validation with Validation Controls CHAPTER 3 LISTING 3.13
175
CompareValidatorValue.aspx
Sub Button_Click( s As Object, e As EventArgs ) If IsValid Then Response.Redirect( “ThankYou.aspx” ) End If End Sub CompareValidatorValue.aspx
If the user attempts to enter a bid less than $2,344.89, an error message is displayed. The fixed value is assigned to CompareValidator by using its ValueToCompare property.
3 PERFORMING FORM VALIDATION
Minimum Bid: $2,344.89 Enter your bid:
176
Working with ASP.NET Web Forms PART I
Performing a Data Type Check A common validation task involves performing data type checks. For example, you might need to check whether a user has entered a date in a date field, a string in a string field, or a number in a number field. You can perform this type of validation with the CompareValidator control by using the DataTypeCheck value of the Operator property. Imagine that you have a form field for a user’s birth date. The page in Listing 3.14 illustrates how you can check for a valid date. LISTING 3.14
CompareValidatorDataTypeCheck.aspx
Sub Button_Click( s As Object, e As EventArgs ) If IsValid Then Response.Redirect( “ThankYou.aspx” ) End If End Sub CompareValidatorDataTypeCheck.aspx Enter your birth date:
Performing Form Validation with Validation Controls CHAPTER 3 LISTING 3.14
177
continued
In Listing 3.14, the CompareValidator control’s Operator property is assigned the value DataTypeCheck, and the Type property is assigned the value Date. If an invalid date is entered into the txtBirthDate form field, the CompareValidator control displays an error. Warning The CompareValidator is pretty particular about the dates that it will accept. For example, the following dates are not considered valid: January 1, 2001 Jan 1, 2001
The CompareValidator requires a date that looks like this: 1-1-2001
If you want to be more inclusive when performing date validation, then you’ll need to use the CustomValidator (described later in this chapter in the section entitled “Performing Custom Validation: The CustomValidator Control”).
Checking for a Range of Values: The RangeValidator Control You can use the RangeValidator control to check whether the value of a form field falls between a minimum and maximum value. The minimum and maximum values can be dates, numbers, currency amounts, or strings. All the properties and methods of this control are listed in Table 3.4. TABLE 3.4
RangeValidator Properties, Methods, and Events
Properties
Description
ControlToValidate
Specifies the ID of the control that you want to validate.
PERFORMING FORM VALIDATION
1/1/2001
3
178
Working with ASP.NET Web Forms PART I TABLE 3.4
continued
Properties
Description
Display
Sets how the error message contained in the Text property is displayed. Possible values are Static, Dynamic, and None; the default value is Static.
EnableClientScript
Enables or disables client-side form validation. This property has the value True by default.
Enabled
Enables or disables both server and client-side validation. This property has the value True by default.
ErrorMessage
Specifies the error message that is displayed in the ValidationSummary control. This error message is displayed by the control when the Text property is not set.
IsValid
Has the value True when the validation check succeeds and False otherwise.
MaximumValue
Specifies the maximum value in the range of permissible values.
MinimumValue
Specifies the minimum value in the range of values.
Text
Sets the error message displayed by the control.
Type
Gets or sets the data type to use when comparing values. Possible values are Currency, Date, Double, Integer, and String.
Methods
Description
Validate
Performs validation and updates the IsValid property.
Events
Description
None
You can use RangeValidator, for example, to check whether a form field contains a date that falls within a certain range. The page in Listing 3.15 checks whether the date entered is greater or equal to today’s date, but less than three months in the future. LISTING 3.15
RangeValidator.aspx
Sub Page_Load valgMeetingDate.MinimumValue = Now.Date valgMeetingDate.MaximumValue = Now.Date.AddMonths( 3 ) End Sub
Performing Form Validation with Validation Controls CHAPTER 3 LISTING 3.15
179
continued
Sub Button_Click( s As Object, e As EventArgs ) If IsValid Then Response.Redirect( “ThankYou.aspx” ) End If End Sub RangeValidator.aspx Choose a meeting date in the next three months:
The Page_Load subroutine in Listing 3.15 assigns values to the MinimumValue and MaximumValue properties of the RangeValidator control. Today’s date is assigned to the MinimumValue property, and a date three months in the future is assigned to the MaximumValue property. If you need to detect whether a value entered into a form field falls within a certain range determined by the values of other form fields, you should use the CompareValidator
3 PERFORMING FORM VALIDATION
180
Working with ASP.NET Web Forms PART I
rather than the RangeValidator. The CompareValidator, unlike RangeValidator, enables you to compare the values of different controls. For example, the page in Listing 3.16 contains three TextBox controls. You must enter a number greater than or equal to the value entered into the first control, and less than or equal to the value entered into the last control; otherwise, you receive an error. LISTING 3.16
CompareValidatorRange.aspx
Sub Button_Click( s As Object, e As EventArgs ) If IsValid Then Response.Redirect( “Thankyou.aspx” ) End If End Sub CompareValidatorRange.aspx Minimum Value:
Maximum Value:
Value:
3
Summarizing Errors: The ValidationSummary Control Imagine that you have a form with 50 form fields. If you use only the Validation controls discussed in the previous sections of this chapter to display errors, seeing an error message on the page might be difficult. For example, you might have to scroll down to the 48th form field to find the error message. Fortunately, Microsoft includes a ValidationSummary control with the Validation controls. You can use this control to summarize all the errors at the top of a page, or wherever else you wish. All the properties and methods of this control are listed in Table 3.5. TABLE 3.5
ValidationSummary Properties, Methods, and Events
Properties
Description
DisplayMode
Sets the formatting for the error messages displayed by the control. Possible values are BulletList, List, and SingleParagraph.
PERFORMING FORM VALIDATION
182
Working with ASP.NET Web Forms PART I TABLE 3.5
continued
Properties
Description
EnableClientScript
Enables or disables client-side form validation. This property has the value True by default.
Enabled
Enables or disables both server and client-side validation. This property has the value True by default.
HeaderText
Sets the text that is displayed at the top of the summary.
ShowMessageBox
When True, displays error messages in a pop-up message box.
ShowSummary
Enables or disables the summary of error messages.
Methods
Description
None
Events
Description
None
The page in Listing 3.17 illustrates how you can use the ValidationSummary control to display a summary of errors (see Figure 3.3). FIGURE 3.3 Summarizing errors with the ValidationSummary
control.
Performing Form Validation with Validation Controls CHAPTER 3 LISTING 3.17
183
ValidationSummary.aspx
Sub Button_Click( s As Object, e As EventArgs ) If IsValid Then Response.Redirect( “ThankYou.aspx” ) End If End Sub ValidationSummary.aspx
First Name:
Last Name:
3 PERFORMING FORM VALIDATION
184
Working with ASP.NET Web Forms PART I LISTING 3.17
continued
Occupation:
Notice how each Validation control is assigned an error message with the ErrorMessage property. These error messages are displayed in the ValidationSummary control whenever a problem occurs with a form field. Don’t confuse the ErrorMessage and Text properties. Typically, you use the Text property of a Validation control to display an error message next to a form field, and you use the ErrorMessage property to display a message in the ValidationSummary control. By default, the ValidationSummary control displays error messages in a bulleted list. However, you also have the option of displaying the messages in a nonbulleted list or within a single paragraph. To control how the ValidationSummary control formats its summary of errors, modify its DisplayMode property. Figure 3.4 shows how the ValidationSummary control displays error messages with each of the different settings of the DisplayMode property.
Performing Form Validation with Validation Controls CHAPTER 3
185
FIGURE 3.4 Different ValidationSummary
display modes.
3 Have you ever seen those obnoxious pop-up error messages that some sites use to report validation errors? You can provide these messages for your users, too! You can enable the ValidationSummary control’s ShowMessageBox property to display error messages in a dialog box. Listing 3.18 demonstrates how you can enable this property (see Figure 3.5 for the output). Note If you want to show the error message summary only in the pop-up dialog box and not on the page itself, set the ValidationSummary control’s ShowSummary property to False.
PERFORMING FORM VALIDATION
Displaying Pop-Up Error Messages
186
Working with ASP.NET Web Forms PART I
FIGURE 3.5 Enabling the ValidationSummary
control’s ShowMessageBox
property.
LISTING 3.18
ValidationSummaryPopUp.aspx
Sub Button_Click( s As Object, e As EventArgs ) If IsValid Then Response.Redirect( “ThankYou.aspx” ) End If End Sub ValidationSummaryPopUp.aspx
First Name:
Performing Form Validation with Validation Controls CHAPTER 3 LISTING 3.18
187
continued
Last Name:
Occupation:
3 PERFORMING FORM VALIDATION
188
Working with ASP.NET Web Forms PART I
Performing Custom Validation: The CustomValidator Control The Validation controls included with ASP.NET enable you to handle a wide range of validation tasks. However, you cannot perform certain types of validation with the included controls. To handle any type of validation that is not covered by the standard validation controls, you need to use the CustomValidator control. All the properties, methods, and events of this control are listed in Table 3.6. TABLE 3.6
CustomValidator Properties, Methods, and Events
Properties
Description
ClientValidationFunction
Specifies the name of a client-side validation function.
ControlToValidate
Specifies the ID of the control that you want to validate.
Display
Sets how the error message contained in the Text property is displayed. Possible values are Static, Dynamic, and None; the default value is Static.
EnableClientScript
Enables or disables client-side form validation. This property has the value True by default.
Enabled
Enables or disables both server and client-side validation. This property has the value True by default.
ErrorMessage
Specifies the error message that is displayed in the ValidationSummary control. This error message is displayed by the control when the Text property is not set.
IsValid
Has the value True when the validation check succeeds and False otherwise.
Text
Sets the error message displayed by the control.
Methods
Description
OnServerValidate
Raises the ServerValidate event.
Validate
Performs validation and updates the IsValid property.
Events
Description
ServerValidate
Represents the function for performing the server-side validation.
Performing Form Validation with Validation Controls CHAPTER 3
189
You cannot use any of the included Validation controls, for example, with information that is stored in a database table. Typically, you want users to enter a unique username and/or e-mail address when completing a registration form. To determine whether a user has entered a unique username or e-mail address, you must perform a database lookup. Using the CustomValidator control, you can write any subroutine for performing validation that you wish. The validation can be performed completely server-side. Alternatively, you can write a custom client-side validation function to use with the control. You can write the client-side script using either JavaScript or VBScript. To create the server-side validation routine, you’ll need to create a Visual Basic subroutine that looks like this: Sub CustomValidator_ServerValidate( s As object, e As ServerValidateEventArgs ) End Sub
This subroutine accepts a special ServerValidateEventArgs parameter that has the following two properties: IsValid—When this property is assigned the value True, the control being validated passes the validation check.
•
Value—The
value of the control being validated.
Warning The custom validation subroutine is not called when the control being validated does not contain any data. The only control that you can use to check for an empty form field is the RequiredFieldValidator control.
For this example, create a custom validation subroutine that checks for the string ASP.NET Unleashed. If you enter text into a TextBox control and it does not contain the name of this book, it fails the validation test. First, you need to create a server-side version of the validation subroutine: Sub CustomValidator_ServerValidate( s As Object, e As ServerValidateEventArgs ) Dim strValue As String strValue = e.Value.ToUpper() If strValue.IndexOf( “ASP.NET UNLEASHED” ) > -1 Then e.IsValid = True Else e.IsValid = False End If End Sub
3 PERFORMING FORM VALIDATION
•
190
Working with ASP.NET Web Forms PART I
This subroutine checks whether the string ASP.NET Unleashed appears in the value of the control being validated. If it does, the value True is assigned to the IsValid property; otherwise, the value False is assigned. You could just implement custom validation with the server-side validation subroutine, which is all you need to get the CustomValidator control to work. However, if you want to get really fancy, you could implement it in a client-side script as well. Be fancy. This VBScript client-side script implements the validation subroutine: Sub CustomValidator_ClientValidate( s, e ) Dim strValue strValue = UCase( e.Value ) If Instr( strValue, “ASP.NET UNLEASHED” ) > 0 Then e.IsValid = True Else e.IsValid = False End If End Sub
This subroutine performs exactly the same task as the previous server-side validation subroutine; however, it is written in VBScript rather than Visual Basic. After you create your server-side and client-side subroutines, you can hook them up to your CustomValidator control by using the OnServerValidate and ClientValidationFunction properties. The page in Listing 3.19 illustrates how you can use the custom validation subroutines you just built. LISTING 3.19
CustomValidator.aspx
Sub Button_Click( s As Object, e As EventArgs ) If IsValid Then Response.Redirect( “ThankYou.aspx” ) End If End Sub Sub CustomValidator_ServerValidate( s As Object, e As ServerValidateEventArgs ) Dim strValue As String strValue = e.Value.ToUpper() If strValue.IndexOf( “ASP.NET UNLEASHED” ) > -1 Then e.IsValid = True
Performing Form Validation with Validation Controls CHAPTER 3 LISTING 3.19
191
continued
Else e.IsValid = False End If End Sub Sub CustomValidator_ClientValidate( s, e ) Dim strValue
CustomValidator.aspx Enter the name of your favorite book:
0 Then e.IsValid = True Else e.IsValid = False End If End Sub
192
Working with ASP.NET Web Forms PART I LISTING 3.19
continued
Text=”Submit!” OnClick=”Button_Click” Runat=”Server” />
Validating Credit Card Numbers In this section, you look at a slightly more complicated but more realistic application of the CustomValidator control. You learn how to write a custom validation function that checks whether a credit card number is valid. The function uses the Luhn algorithm xto check whether a credit card is valid. This function does not check whether credit is available in a person’s credit card account. However, it does check whether a string of credit card numbers satisfies the Luhn algorithm, an algorithm that the numbers in all valid credit cards must satisfy. The page in Listing 3.20 contains a TextBox control that is validated by a If you enter an invalid credit card number, the validation check fails and an error message is displayed. CustomValidator.
Note You can test the form contained in Listing 3.20 by entering your personal credit card number or by entering the number 8 repeated 16 times.
LISTING 3.20
CustomValidatorLuhn.aspx
Sub Button_Click( s As Object, e As EventArgs) If IsValid Then Response.Redirect( “Thankyou.aspx” ) End If End Sub Sub ValidateCCNumber( s As Object, e As ServerValidateEventArgs ) Dim intCounter As Integer Dim strCCNumber As String Dim blnIsEven As Boolean = False
Performing Form Validation with Validation Controls CHAPTER 3 LISTING 3.20
193
continued
Dim strDigits As String = “” Dim intCheckSum As Integer = 0 ‘ Strip away everything except numerals For intCounter = 1 To Len( e.Value ) If IsNumeric( MID( e.Value, intCounter, 1 ) ) THEN strCCNumber = strCCNumber & MID( e.Value, intCounter, 1 ) End If Next ‘ If nothing left, then fail If Len( strCCNumber ) = 0 Then e.IsValid = False Else
‘ Calculate CheckSum For intCounter = 1 To Len( strDigits ) intCheckSum = intCheckSum + cINT( MID( strDigits, intCounter, 1 ) ) Next ‘ Assign results e.IsValid = (( intCheckSum Mod 10 ) = 0 ) End If End Sub CustomValidatorLuhn.aspx Enter your credit card number:
Disabling Validation Typically, a form includes a Cancel button that enables you to stop working on the form and navigate to a new page. Implementing a Cancel button in a form that includes validation controls, however, is more difficult than you might expect. The problem is that the client-side validation scripts can prevent any subroutine associated with the Cancel button from ever executing. To get around this problem, you need to use a special property of the Button, LinkButton, and ImageButton controls named the CausesValidation property. You can use the CausesValidation property to enable or disable validation when a particular button is clicked. For example, the page in Listing 3.21 contains both a Submit and Cancel button. The CausesValidation property is assigned the value False in the case of the Cancel button. LISTING 3.21
CausesValidation.aspx
Sub btnSubmit_Click( s As Object, e As EventArgs ) If IsValid Then Response.Redirect( “ThankYou.aspx” ) End If End Sub
Performing Form Validation with Validation Controls CHAPTER 3 LISTING 3.21
195
continued
Sub btnCancel_Click( s As Object, e As EventArgs ) Response.Redirect( “Cancel.aspx” ) End Sub CausesValidation.aspx Enter your first name:
Summary This chapter covered all the ASP.NET validation controls. You learned how to make required form fields by using the RequiredFieldValidator control, how to perform
3 PERFORMING FORM VALIDATION
196
Working with ASP.NET Web Forms PART I
complex pattern validation by using the RegularExpressionValidator control, how to compare values and data types by using the CompareValidator control, and how to check for a range of values by using the RangeValidator control. You also learned how to summarize validation errors by using the ValidationSummary control. You learned how to summarize errors within the page itself and how to display the summary in a pop-up dialog box. Finally, you learned how to implement custom server-side and client-side validation functions by using the CustomValidator control. You should now be ready to handle any conceivable Web site validation task.
CHAPTER 4
Advanced Control Programming
IN THIS CHAPTER • Working with View State
198
• Displaying and Hiding Content • Using Rich Controls
225
203
198
Working with ASP.NET Web Forms PART I
In this chapter, you examine several advanced methods of working with controls in your ASP.NET pages. First, you delve deeper into the details of view state. You learn how to enable and disable view state for particular controls on a page, and how to disable view state for a whole page. You also learn how to store your own variables in view state. Next, you learn how to gain programmatic access to the controls in your pages and set control properties to hide and display controls. You also learn how to programmatically add new controls to a page. This skill enables you to create pages with complex interactions between the form elements. Note You can view “live” versions of many of the code samples in this chapter by visiting the Superexpert Web site at: http://www.Superexpert.com/AspNetUnleashed/
Finally, you examine several of the rich controls included with ASP.NET. You learn how to use the Calendar control to create interactive calendars, use the AdRotator control to display rotating banner advertisements, and use the HTMLInputFile control to accept file uploads at your Web site.
Working with View State By default, almost all ASP.NET controls retain the values of their properties between form posts. For example, if you assign text to a Label control and submit the form, when the page is rendered again, the contents of the Label control are preserved. The magic of view state is that it does not depend on any special server or browser properties. In particular, it does not depend on cookies, session variables, or application variables. View state is implemented with a hidden form field called __VIEWSTATE that is automatically created in every Web Forms Page. When used wisely, view state can have a dramatic and positive effect on the performance of your Web site. For example, if you display database data in a control that has view state enabled, you do not have to return to the database each time the page is posted back to the server. You can automatically preserve the data within the page’s view state between form posts.
Advanced Control Programming CHAPTER 4
199
Disabling View State In certain circumstances, you might want to disable view state for an individual control or for an ASP.NET page as a whole. For example, you might have a control that contains a lot of data (imagine a RadioButtonList control with 1,000 options). You might not want to load the data into the hidden __VIEWSTATE form field if you are worried that the form data would significantly slow down the rendering of the page. Note In Chapter 18, “Application Tracing and Error Handling,” you learn how to determine how each control affects the total size of a page’s view state.
You also might want to disable view state if you need to automatically reinitialize a control every time a page is loaded. For example, through view state, a Label control retains whatever text you assign to it across multiple form posts. Note Form controls, such as TextBox and RadioButtonList, retain their values between posts even when view state is disabled. The values of these controls do not need to be preserved in the __VIEWSTATE hidden form field because they are actually being submitted to the server on each form post.
LISTING 4.1
ViewState.aspx
Sub Page_Load If Not IsPostBack Then lblMessage.Text = “Hello!” End If End Sub
4 ADVANCED CONTROL PROGRAMMING
Consider the page in Listing 4.1. It contains a single Label control and a single Button control. When the page is first loaded, a message is assigned to the Label control’s Text property. If you click the button and reload the page, however, the label continues to display the message. The Label control’s Text property is automatically preserved through view state.
200
Working with ASP.NET Web Forms PART I LISTING 4.1
continued
ViewState.aspx
You can disable view state for an individual control by modifying the EnableViewState property. You can use this property to disable view state for both HTML and Web controls. The page in Listing 4.2, for example, uses the EnableViewState property to disable view state for a Label control. When the page is first loaded, a message is assigned to the Label control and the message is displayed. If you click the button, however, and reload the page, the text disappears from the label. LISTING 4.2
EnableViewState.aspx
Sub Page_Load If Not IsPostBack Then lblMessage.Text = “Hello!” End If End Sub EnableViewState.aspx
Advanced Control Programming CHAPTER 4 LISTING 4.2
201
continued
Instead of disabling view state control by control, you also can disable view state for the whole page. You should do so when you are not taking advantage of view state and the controls in a page contain a lot of data. To disable view state for an entire page, modify the EnableViewState attribute of the Page directive. The page in Listing 4.3 demonstrates how you would do so. LISTING 4.3
PageViewState.aspx
PageViewState.aspx
4 ADVANCED CONTROL PROGRAMMING
Sub Page_Load If Not IsPostBack Then lblMessage.Text = “Hello!” End If End Sub
202
Working with ASP.NET Web Forms PART I LISTING 4.3
continued
Adding Values to View State You can take advantage of view state within your code by adding values to the state bag class. If you add values to this class, they are automatically added to the hidden VIEWSTATE form variable; therefore, they are available across multiple form posts. To add a value to the state bag, use a statement such as the following: ViewState( “SomeItem” ) = “Some Value”
This statement adds a new item to the state bag class named SomeItem with the value Some Value. Warning ViewState is case-sensitive. The following two statements are not equivalent: Response.Write( ViewState( “Count” ) ) Response.Write( ViewState( “count” ) )
The page in Listing 4.4, for example, uses the state bag class to record the number of times the button on the form is clicked. After you click the button five times, a message is displayed. LISTING 4.4
StateBag.aspx
Sub Button_Click( s As Object, e As EventArgs ) ViewState( “TotalCount” ) += 1 If ViewState( “TotalCount” ) = 5 Then lblCount.Text = “You clicked 5 times!” End If End Sub
Advanced Control Programming CHAPTER 4 LISTING 4.4
203
continued
StateBag.aspx
Displaying and Hiding Content Imagine that you are creating a form with an optional section. For example, imagine that you are creating an online tax form, and you want to display or hide a section that contains questions that apply only to married tax filers.
Finally, imagine that you want to break the tax form into multiple pages so that a person views only one part of the tax form at a time. In the following sections, you learn about the properties that you can use to hide and display controls in a form. You learn how to use the Visible and Enabled properties with individual controls and groups of controls to hide and display page content.
Using the Visible and Enabled Properties Every control, including both HTML and Web controls, has a Visible property that determines whether the control is rendered. When a control’s Visible property has the value False, the control is not displayed on the page; the control is not processed for either prerendering or rendering.
4 ADVANCED CONTROL PROGRAMMING
Or, imagine that you want to add an additional help button to the tax form. You might want to hide or display detailed instructions for completing form questions depending on a user’s preferences.
204
Working with ASP.NET Web Forms PART I
Web controls (but not every HTML control) have an additional property named Enabled. When Enabled has the value False and you are using Internet Explorer version 4.0 or higher, the control appears ghosted and no longer functions. When used with other browsers, such as Netscape Navigator, the control might not appear ghosted, but it does not function. The page in Listing 4.5 illustrates how both the Visible and Enabled properties affect the appearance of controls. See Figure 4.1 for the output of the page on Internet Explorer 5.5. FIGURE 4.1 The Visible and Enabled properties.
LISTING 4.5
Display.aspx
Sub lbtnVisible_Click( s As Object, e As EventArgs ) If txtTextBox.Visible = True Then txtTextBox.Visible = False lnkHyperLink.Visible = False lbtnLinkButton.Visible = False btnButton.Visible = False lbtnVisible.Text = “Make Visible!” Else txtTextBox.Visible = True lnkHyperLink.Visible = True lbtnLinkButton.Visible = True
Advanced Control Programming CHAPTER 4 LISTING 4.5
205
continued
btnButton.Visible = True lbtnVisible.Text = “Make Invisible!” End If End Sub Sub lbtnEnabled_Click( s As Object, e As EventArgs ) If txtTextBox.Enabled = True Then txtTextBox.Enabled = False lnkHyperLink.Enabled = False lbtnLinkButton.Enabled = False btnButton.Enabled = False lbtnEnabled.Text = “Make Enabled!” Else txtTextBox.Enabled = True lnkHyperLink.Enabled = True lbtnLinkButton.Enabled = True btnButton.Enabled = True lbtnEnabled.Text = “Make Disabled!” End If End Sub Display.aspx
HyperLink:
LinkButton:
Button:
The page in Listing 4.5 contains two LinkButton controls initially labeled Make and Make Disabled! If you click the Make Invisible! link, the Visible property of all the controls is set to False. All the controls disappear from the page when the page is rendered.
Invisible!
Clicking the Make Disabled! link has different effects when used with different browsers. When you use this link with Internet Explorer, you cannot enter any text into form controls that have been disabled. Furthermore, the Button control appears ghosted, and it no longer can be clicked. Note Exactly how the Enabled property affects the rendering of a form control depends on how a browser interprets the HTML Disabled attribute, which is part of the HTML 4.0 standard (see http://www.w3.org/TR/html4/). According to the standard, it should apply to all the standard HTML form tags.
Advanced Control Programming CHAPTER 4
207
When used with Netscape Navigator, on the other hand, the Disabled property has no effect on the appearance of any form elements. In particular, you can still click the form button and the form will be submitted. Regardless of the browser you use, the Disabled property never has an effect on the function of the HyperLink control. If you disable a hyperlink, you can still click it and navigate to a page (Internet Explorer version 6.0 and higher displays a ghosted hyperlink in gray, but it still functions). Finally, the Disabled property always modifies the appearance of a link button. When a link button is disabled on either Microsoft Internet Explorer or Netscape Navigator, the LinkButton control is rendered as text and not as a HyperText link.
Using the Panel Control Instead of setting the Visible property for controls one by one, you can use the Panel control to hide controls as a group. All the properties of this control are listed in Table 4.1. TABLE 4.1
Panel Properties, Methods, and Events
Description
BackImageURL
Gets or sets the URL of a background image to be displayed behind the contents of the panel.
HorizontalAlign
Sets the horizontal alignment of the contents of a panel. Possible values are Center, Justify, Left, NotSet, and Right.
Wrap
When True, wraps the contents of the panel. The default value is True.
Methods
Description
None
Events
Description
None
Suppose that you have a form which contains a RadioButtonList control that enables users to pick their favorite Web site. Now, imagine that one option on the list is labeled Other Site. If someone picks Other Site, you want to display a text box that enables the person to enter the name of the new Web site.
4 ADVANCED CONTROL PROGRAMMING
Properties
208
Working with ASP.NET Web Forms PART I
You can hide and display a group of controls by using the Panel control. The page in Listing 4.6 demonstrates how you can set the Visible property of a Panel control to display and hide a text box when the Other Site option is selected (see Figure 4.2). FIGURE 4.2 Hiding and displaying content with the Panel control.
LISTING 4.6
Panel.aspx
Sub Button_Click( s As Object, e As EventArgs ) If dropFavSite.SelectedIndex = 3 Then pnlOtherSite.Visible = True Else pnlOtherSite.Visible = False End If End Sub Panel.aspx Select your favorite ASP Web site:
Other Site:
4
Simulating Multipage Forms Imagine that you have a form, which contains 50 questions that you want to break up into multiple pages. One way to do so would be to actually create a separate ASP.NET page for each group of questions. Creating a form that spans multiple ASP.NET pages, however, would take a lot of work. You would need some method of storing the answers that a user entered for page 1 when the user is on page 5. You could use hidden form fields or Session variables, but there is an easier way.
ADVANCED CONTROL PROGRAMMING
The Panel control acts as a container for other controls. Hiding a panel by setting its Visible property to False also hides all the controls that the panel contains.
210
Working with ASP.NET Web Forms PART I
Instead of breaking the form into multiple ASP.NET pages, you can place all the questions within a single ASP.NET page. You can use the Panel control to display and hide different sections of the form at a time. The advantage of this approach is that all the answers entered into the form are automatically preserved. When the Panel control displays the questions for page 5, the answers to the questions for page 1 are still safely tucked away in an invisible panel. The page in Listing 4.7 demonstrates how you would use this method to create a simulated three-page form. LISTING 4.7
PanelMultiPage.aspx
Sub Page_Load If Not IsPostBack Then ViewState( “CurrentPage” ) = 1 End If End Sub Sub btnNextPage_Click( s As Object, e As EventArgs ) Dim pnlPanel As Panel Dim strPanelName AS String ‘ Hide Previous Panel strPanelName = “pnlForm” & ViewState( “CurrentPage” ) pnlPanel = FindControl( strPanelName ) pnlPanel.Visible = False ‘ Show Current Panel ViewState( “CurrentPage” ) += 1 strPanelName = “pnlForm” & ViewState( “CurrentPage” ) pnlPanel = FindControl( strPanelName ) pnlPanel.Visible = True End Sub Sub btnPrevPage_Click( s As Object, e As EventArgs ) Dim pnlPanel As Panel Dim strPanelName AS String ‘ Hide Current Panel strPanelName = “pnlForm” & ViewState( “CurrentPage” ) pnlPanel = FindControl( strPanelName ) pnlPanel.Visible = False ‘ Show Previous Panel ViewState( “CurrentPage” ) -= 1 strPanelName = “pnlForm” & ViewState( “CurrentPage” )
Advanced Control Programming CHAPTER 4 LISTING 4.7
211
continued
pnlPanel = FindControl( strPanelName ) pnlPanel.Visible = True End Sub Sub btnFinish_Click( s As Object, e As EventArgs ) pnlForm3.Visible = False pnlForm4.Visible = True lblSummary.Text = “You entered:” lblSummary.Text &= “
Product “ & strFieldNum & “: “ plhProductFields.Controls.Add( litLabel ) ‘ Add TextBox Control txtTextBox = New TextBox txtTextBox.ID = “txtProduct” & strFieldNum plhProductFields.Controls.Add( txtTextBox ) End Sub Sub btnSubmit_Click( s As Object, e As EventArgs ) Response.Redirect( “ThankYou.aspx” ) End Sub DynamicForm.aspx Customer Name:
Product 1:
Dynamically Generating List Items The various list controls—such as RadioButtonList, CheckBoxList, DropDownList, and ListBox—discussed in Chapter 2, “Building Forms with Web Server Controls,” present a special case. You can dynamically add and remove items from these controls by working with the properties and methods of the ListItemCollection collection. The properties and methods of the ListItemCollection collection are described in Table 4.2. TABLE 4.2
ListItemCollection Collection Properties and Methods
Properties
Description
Count
Returns a count of the number of list items in the collection
Item
Returns a list item at the specified index
Methods
Description
Add
Adds a new list item to the ListItem collection
AddRange
Adds an array of list items
Clear
Removes all the items from the ListItem collection
Contains
Returns True if the ListItem collection contains the specified list item
CopyTo
Copies all the list items to an array
FindByText
Returns a list item by its Text value
FindByValue
Returns a list item by its Value value
GetEnumerator
Returns an enumerator for the list items
IndexOf
Returns the index number of the list item
Advanced Control Programming CHAPTER 4 TABLE 4.2
223
continued
Methods
Description
Insert
Inserts a list item at a particular index location in the ListItem collection
Remove
Removes a list item from the ListItem collection
RemoveAt
Removes a list item at a particular index location in the ListItem collection
You can use the properties and methods of the list controls to create complex interactions between the elements of a form. For example, the page contained in Listing 4.13 contains two ListBox controls: one labeled Products and one labeled Shopping Cart. When you click an item listed in the Products list box, the item is automatically added to the Shopping Cart list box (see Figure 4.7). FIGURE 4.7 Manipulating the ListItem collection.
4 ADVANCED CONTROL PROGRAMMING
The Shopping Cart list box has Remove Item and Remove All buttons. Clicking Remove Item removes the selected item from the Shopping Cart list box, and clicking Remove All removes all the items from the list box.
224
Working with ASP.NET Web Forms PART I LISTING 4.13
ListItemCollection.aspx
Sub lstProducts_SelectedIndexChanged( s As Object, e As EventArgs ) lstCart.Items.Add( lstProducts.SelectedItem ) End Sub Sub btnRemove_Click( s As Object, e As EventArgs ) lstCart.Items.Remove( lstCart.SelectedItem ) End Sub Sub btnRemoveAll_Click( s As Object, e As EventArgs ) lstCart.Items.Clear() End Sub ListItemCollection.aspx Products:
Shopping Cart:
Advanced Control Programming CHAPTER 4 LISTING 4.13
225
continued
Using Rich Controls In the following sections, you learn how to use three of the more feature-rich controls in the ASP.NET framework. You learn how to use the Calendar control to display interactive calendars, the AdRotator control to display rotating banner advertisements, and the HTMLInputFile control to accept file uploads.
Displaying Interactive Calendars with the Calendar Control You can use the ASP.NET Calendar control to display an interactive calendar on a page. This control enables you to select particular days, weeks, or months. You can also display custom content on each day. All the properties, methods, and events of this control are listed in Table 4.3. TABLE 4.3
Calendar Properties, Methods, and Events
Description
CellPadding
Specifies the number of pixels between the contents of a cell and its border.
CellSpacing
Specifies the number of pixels between cells.
DayNameFormat
Sets the format of the day names. Possible values are FirstLetter, FirstTwoLetters, Full, and Short; the default is Short.
FirstDayOfWeek
Specifies the day of the week that is displayed in the calendar’s first column. Possible values are Default, Friday, Monday, Saturday, Sunday, Thursday, Tuesday, and Wednesday; the default is Default, which uses the server’s local settings.
4 ADVANCED CONTROL PROGRAMMING
Properties
226
Working with ASP.NET Web Forms PART I TABLE 4.3
continued
Properties
Description
NextMonthText
If ShowNextPrevMonth is True, specifies the text for the Next Month hyperlink.
NextPrevFormat
Specifies the format of the Next and Previous hyperlinks. Possible values are CustomText, FullMonth, and ShortMonth; the default is CustomText.
PrevMonthText
If ShowNextPrevMonth is True, specifies the text for the Previous Month hyperlink.
SelectedDate
Contains a DateTime value that specifies the highlighted day. The default value is TodaysDate.
SelectedDates
Contains a collection of DateTime items that represent the highlighted days on the calendar.
SelectionMode
Determines whether days, weeks, or months can be selected. Possible values are Day, DayWeek, DayWeekMonth, and None; the default is Day.
SelectMonthText
Contains HTML text displayed for selecting months when SelectionMode has the value DayWeekMonth.
SelectWeekText
Contains HTML text displayed for selecting weeks when SelectionMode has the value DayWeek or DayWeekMonth.
ShowDayHeader
If True, displays the names of the days of the week.
ShowGridLines
If True, renders the calendar with lines around the days.
ShowNextPrevMonth
If True, displays Next Month and Previous Month links.
ShowTitle
If True, displays the calendar’s title.
TitleFormat
Determines how the month name appears in the title bar. Possible values are Month and MonthYear.
TodaysDate
Specifies a DateTime value that sets the calendar’s current date.
VisibleDate
Specifies a DateTime value that sets the month to display.
Methods
Description
OnDayRender
Raises DayRender event.
OnSelectionChanged
Raises the SelectionChanged event.
OnVisibleMonthChanged
Raises the VisibleMonthChanged event.
Advanced Control Programming CHAPTER 4 TABLE 4.3
227
continued
Events
Description
DayRender
Raised before each day cell is rendered on the calendar.
SelectionChanged
Raised when a new day, month, or week is selected.
VisibleMonthChanged
Raised by clicking the Next Month or Previous Month link.
Typical uses for the Calendar control include displaying a schedule of events. You can customize the appearance of each cell in the calendar as it is displayed. You can even display a hyperlink to more information within each calendar day. Another common use of the Calendar control is scheduling. Because you can select particular days, weeks, or months on the calendar, you can use the control to reserve dates for meetings or events. A complex calendar can be added to a page with very few lines of code. For example, Listing 4.14 displays the default calendar displayed by the Calendar control (see Figure 4.8). FIGURE 4.8 Calendar control with default properties.
4 ADVANCED CONTROL PROGRAMMING
228
Working with ASP.NET Web Forms PART I LISTING 4.14
Calendar.aspx
calendar.aspx
Customizing the Appearance of the Calendar Control You can customize the appearance of the Calendar control in three ways: You can use the formatting properties common to all controls, you can use the general formatting properties of the Calendar control, or you can apply one or more custom style objects to the control. The Calendar control supports all the common Web control formatting properties described in Chapter 2. For example, you can modify Calendar properties such as BackColor, ForeColor, Border, Font, Height, and Width by modifying the base Web control formatting properties. Note All the common Web control formatting properties are listed under the base Web control properties in Appendix C, “Web Control Reference.”
The page contained in Listing 4.15 illustrates how you can modify several of these properties (see Figure 4.9 for the output). LISTING 4.15
CalendarBaseFormatting.aspx
CalendarBaseFormatting.aspx
Advanced Control Programming CHAPTER 4 LISTING 4.15
229
continued
FIGURE 4.9 A calendar with different base Web control formatting properties.
4
To change the way the next and previous month links are displayed, for example, you can modify the NextPrevFormat, NextMonthText, PrevMonthText, and ShowNextPrevMonth properties. You can even use images for the link to the next and previous months by setting the properties like this:
ADVANCED CONTROL PROGRAMMING
You also can modify the general formatting properties of the Calendar control itself. Examples of these properties are ShowGridLines, NextMonthText, PrevMonthText, and TitleFormat. You can find a list of all these properties in Table 4.3.
230
Working with ASP.NET Web Forms PART I
To modify the way the list of weekdays appears in the calendar, modify the DayNameFormat property. When this property has the value FirstLetter, only the first letter of each day is shown. When this property has the value FirstTwoLetters, only the first two letters of each day are shown. When this property has the value Full, the full day name is shown. When this property has the value Short, three letters are shown for each day. You also can modify the format of a Calendar control by applying any one of several style objects to the calendar. The Calendar control supports the style objects listed in Table 4.4. TABLE 4.4
Calendar Style Objects
Object
Description
DayHeaderStyle
The style of the weekdays listed at the top of the calendar
DayStyle
The style applied to each day in the calendar
NextPrevStyle
The style applied to the next and previous month links
OtherMonthDayStyle
The style applied to days from other months that appear in the current month
SelectedDayStyle
The style applied to the currently selected day
SelectorStyle
The style applied to the link for selecting the week or month
TitleStyle
The style applied to the calendar’s title bar
TodayDayStyle
The style applied to the current date
WeekendDayStyle
The style applied to weekend days
All the style objects, with the exception of the TitleStyle object, support the properties of the TableItemStyle style class. (The properties of this class are listed in Appendix C.) The TitleStyle object supports all the styles of the base style class (also listed in Appendix C). Suppose that you want to display most days in blue, but show weekends in green, today in yellow, and the selected date in orange. You can do so by modifying the style objects. Listing 4.16 contains a (very ugly) Calendar control formatted with these properties.
Advanced Control Programming CHAPTER 4 LISTING 4.16
231
CalendarStyle.aspx
CalendarStyle.aspx
Selecting Days, Weeks, and Months with the Calendar Control By default, the Calendar control enables you to pick a particular day on the calendar. However, you can modify the properties of the Calendar control so that it enables you to select weeks or months. You can even set the Calendar control so that it does not allow you to pick anything. To pick weeks or months, modify the SelectionMode property. You can assign the values Day, DayWeek, DayWeekMonth, or None to this property. For example, the value DayWeekMonth enables you to select either a day, week, or month.
To determine the dates that have been selected on the calendar, use the SelectionChanged event. When this event is raised, you can use the SelectedDate property to determine what date is selected or the SelectedDates property to determine what dates are selected.
4 ADVANCED CONTROL PROGRAMMING
After you modify the SelectionMode property to enable week or month selection, you can modify the SelectWeekText and SelectMonthText properties to display different links for selecting the day or month. You can even assign images to these properties by using syntax like this:
232
Working with ASP.NET Web Forms PART I
The page in Listing 4.17, for example, demonstrates how you can retrieve and display the dates selected on a calendar (see Figure 4.10). FIGURE 4.10 Selecting calendar dates.
LISTING 4.17
CalendarSelected.aspx
Sub Calendar_SelectionChanged( s As Object, e As EventArgs ) Dim dtmDate As DateTime lblDates.Text = “You selected the following date(s):” For Each dtmDate in calCalendar.SelectedDates lblDates.Text &= “
” ) ) ctlCell.Controls.Add( New LiteralControl( strHoroscope ) ) End Sub CalendarHoroscope.aspx Horoscope:
Notice how the Controls collection of the calendar cell is used. To add new content to a day, you add new controls to the Controls collection of a calendar cell. In Listing 4.18, you add two LiteralControl controls: One LiteralControl represents an HTML paragraph break, and the other represents the text of the horoscope. Note You can also add text to a particular calendar cell by using the TableCell’s Text property. This works fine for plain text or HTML content. However, you cannot use the Text property to add ASP.NET controls, such as TextBox controls.
4 ADVANCED CONTROL PROGRAMMING
The Calendar_RenderDay subroutine is called for each day rendered on the calendar. This subroutine randomly selects from four possible horoscopes and displays one for the particular day being rendered (see Figure 4.11).
236
Working with ASP.NET Web Forms PART I
FIGURE 4.11 Handling the DayRender event.
Creating an Interactive Meeting Scheduler In this section, you build a more complex application using the Calendar control. You create a Web application that enables you to add notes to any day on the calendar and save the notes between visits. The complete code for this application is contained in Listing 4.19. The ASP.NET page in Listing 4.19 contains Calendar, TextBox, and Button controls. If you want to add a note to a date, you select the date on the calendar, enter the note in the text box, and click the Save Changes button. Every date on the calendar is represented by a two-dimensional array named arrCalendar. The first index of the array represents the month, and the second index represents the day. The Calendar_RenderDay subroutine is called for each day that is rendered on the calendar. This subroutine displays the day with an orange background color if a note is associated with the day. When you click Save Changes, the contents of the arrCalendar array are saved to disk. This is accomplished in the btnSave_Click subroutine. The array is saved to a file named schedule.bin located in the root of the C: drive.
Advanced Control Programming CHAPTER 4
237
If you save changes and return to the page on a later date, the notes are preserved. When the page is first loaded, the notes are retrieved from the schedule.bin file saved on disk. After the notes are loaded the first time, they are subsequently retrieved from the cache to improve performance. LISTING 4.19
Dim arrCalendar( 13, 32 ) As String ‘ Get schedule from cache or disk Sub Page_Load Dim strmFileStream As FileStream Dim fmtrBinaryFormatter As BinaryFormatter
‘ Save schedule to file Sub btnSave_Click( s As Object, e As EventArgs ) Dim dtmDate As DateTime Dim strmFileStream As FileStream Dim fmtrBinaryFormatter As BinaryFormatter dtmDate = calSchedule.SelectedDate arrCalendar( dtmDate.Month, dtmDate.Day ) = txtNotes.Text strmFileStream = _ New FileStream( “c:\schedule.bin”, FileMode.Create ) fmtrBinaryFormatter = New BinaryFormatter fmtrBinaryFormatter.Serialize( strmFileStream, arrCalendar ) strmFileStream.Close()
4 ADVANCED CONTROL PROGRAMMING
If Cache( “arrCalendar” ) Is Nothing Then If File.Exists( “c:\schedule.bin” ) Then strmFileStream = _ New FileStream( “c:\schedule.bin”, FileMode.Open ) fmtrBinaryFormatter = New BinaryFormatter arrCalendar = _ CType( fmtrBinaryFormatter.Deserialize( strmFileStream ), Array ) strmFileStream.Close() Cache( “arrCalendar” ) = arrCalendar End If Else arrCalendar = Cache( “arrCalendar” ) End If End Sub
238
Working with ASP.NET Web Forms PART I LISTING 4.19
continued
Cache( “arrCalendar” ) = arrCalendar End Sub ‘ Pick a date Sub Calendar_SelectionChanged( s As Object, e As EventArgs ) Dim dtmDate As DateTime dtmDate = calSchedule.SelectedDate txtNotes.Text = arrCalendar( dtmDate.Month, dtmDate.Day ) End Sub ‘ Display each calendar day Sub Calendar_RenderDay( s As Object, e As DayRenderEventArgs ) Dim dtmDate As DateTime Dim ctlCell As TableCell dtmDate = e.Day.Date ctlCell = e.Cell If arrCalendar( dtmDate.Month, dtmDate.Day ) “” Then ctlCell.BackColor = Color.FromName( “Orange” ) End If End Sub CalendarSchedule.aspx
Displaying Banner Advertisements with the AdRotator Control You can use the AdRotator control to randomly display banner advertisements on your Web pages. In most cases, you use the AdRotator control with an advertisement file that contains a list of the properties of banner advertisements to display. All the properties, methods, and events of this control are listed in Table 4.6. TABLE 4.6
AdRotator Control Properties, Methods, and Events
Properties
Description
AdvertisementFile
The path to an XML file that contains a list of banner advertisements to display.
KeywordFilter
When set, only those banner advertisements that match the filter are returned.
Target
When you click the banner advertisement, the page is displayed in this window or frame. Possible values are _top, _new, _child, _self, _parent, and _blank.
Description
OnAdCreated
Raises the AdCreated event.
Events
Description
AdCreated
Raised after an advertisement is retrieved from the advertisement file and before the banner advertisement is rendered.
The Advertisement File is an XML file with the following format: URL of image to display URL of page to link to
ADVANCED CONTROL PROGRAMMING
Methods
4
240
Working with ASP.NET Web Forms PART I Text to show as ToolTip keyword used to filter relative weighting of ad
The file enables you to specify the properties for each banner advertisement described in Table 4.7: •
ImageUrl—The
URL of the image to display for the advertisement. It can be either a relative or absolute path.
•
NavigateUrl—The
•
AlternateText—The alternative text that is displayed for browsers that do not support images. This property is also commonly used to display ToolTip text.
•
Keyword—An
optional keyword that you can use to categorize the banner advertisement. It can be used with the KeywordFilter property to display different categories of advertisements from the file.
•
Impressions—The
page you go to when you click an advertisement.
relative frequency that a particular advertisement should be shown in relation to other advertisements in the file. All things being equal, the higher this number, the more times this advertisement will be shown.
All the preceding properties are optional except ImageUrl. For example, if you do not specify a NavigateUrl, the banner advertisement is displayed without a link. Tip You can add your own custom properties to the advertisement file. For example, you can add a Campaign property that indicates the particular banner campaign for the advertisement. An example of a custom property is included in the next section, Tracking AdRotator Impressions.
The page contained in Listing 4.20 illustrates how you can use the AdRotator control with an Advertisement File named myAds.xml, which is included in Listing 4.21. LISTING 4.20
AdRotator.aspx
AdRotator.aspx
Advanced Control Programming CHAPTER 4 LISTING 4.20
241
continued
LISTING 4.21
myAds.xml
AspWorkshopsBanner.gif http://www.AspWorkshops.com Need ASP.NET Training? 2 SuperexpertBanner.gif http://www.superexpert.com Click here to visit Superexpert.com! 1
4 If you add a subroutine to handle the AdCreated event, you can track the number of times each banner advertisement is displayed. You can use this subroutine to record advertisement statistics to a database table, an XML file, or the file system. When the AdCreated event is raised, an instance of the AdCreatedEventArgs class is passed to the subroutine that handles the event. The properties of the AdCreatedEventArgs class are listed in Table 4.8.
ADVANCED CONTROL PROGRAMMING
Tracking AdRotator Impressions
242
Working with ASP.NET Web Forms PART I TABLE 4.8
AdCreatedEventArgs Properties
Properties
Description
AdProperties
A collection of all the attributes for the current advertisement retrieved from the advertisement file. This collection includes any custom properties in the advertisement file.
AlternateText
Gets or sets any alternative text for the advertisement.
ImageUrl
Gets or sets the path to the image for the current advertisement.
NavigateUrl
Gets or sets the path to the page you go to when you click the current advertisement.
For example, the page in Listing 4.22 records statistics on the number of times each advertisement is displayed to the file system. The page uses the advertisement file in Listing 4.23. Each advertisement in the advertisement file has a custom Campaign property. When the AdRotator in Listing 4.22 displays an advertisement, the AdRotator_ subroutine updates the statistics for the advertisement. For example, if the AspWorkshops advertisement is displayed, then the number of impressions for this advertisement is recorded to a file named AspWorkshops.imp. The name of the file corresponds to the Campaign property in the advertisement file.
AdCreated
LISTING 4.22
AdRotatorImpressions.aspx
Sub AdRotator_AdCreated( s As Object, e As AdCreatedEventArgs ) Dim intImpressions As Integer = 0 Dim strFileName As String Dim strmStreamReader As StreamReader Dim strmStreamWriter As StreamWriter ‘ Assign file name strFileName = e.AdProperties( “Campaign” ).Trim & “.imp” If strFileName = “” Then strFileName = “default.imp” End If ‘ Read current impressions If File.Exists( MapPath( strFileName ) ) Then strmStreamReader = File.OpenText( MapPath( strFileName ) ) intImpressions = Cint( strmStreamReader.ReadLine )
Advanced Control Programming CHAPTER 4 LISTING 4.22
243
continued
strmStreamReader.Close End If ‘ Write current impressions strmStreamWriter = File.CreateText( MapPath( strFileName ) ) strmStreamWriter.Write( intImpressions + 1 ) strmStreamWriter.Close End Sub AdRotatorImpressions.aspx
LISTING 4.23
AdCampaigns.xml
4 ADVANCED CONTROL PROGRAMMING
AspWorkshops AspWorkshopsBanner.gif http://www.AspWorkshops.com Need ASP.NET Training? 2 Superexpert SuperexpertBanner.gif http://www.superexpert.com Click here to visit Superexpert.com! 1
244
Working with ASP.NET Web Forms PART I
Accepting File Uploads with the HTMLInputFile Control In many situations, you need to enable the users of your Web site to upload files to your Web server. For example, you might want to create a form that provides your users with a means to upload images if you are constructing an image library. Or you might want to build a central document repository to store Microsoft Word documents. You can use the HTMLInputFile control to enable users to transfer any type of file from their hard drive to your Web site. You can upload images, HTML files, Microsoft Word documents, MPEG files, or video files with the HTMLInputFile control. All the properties, methods, and events of this control are listed in Table 4.9. TABLE 4.9
HTMLInputFile Properties, Methods, and Events
Properties
Description
Accept
Specifies a comma-delimited list of the MIME types of files acceptable for upload. For example, use image/gif for GIF files or image/* for any type of image file.
MaxLength
Specifies the maximum number of characters that can be entered into the upload text box.
PostedFile
Returns the file uploaded by the user.
Size
Indicates the size of the upload text box.
Methods
Description
None
Events
Description
None
The page in Listing 4.24 illustrates how you can create a simple upload form that enables you to upload an image file (GIF file). Any file uploaded with this form is saved with the name NewFile.gif in a folder named c:\uploads (You’ll need to create this folder yourself). LISTING 4.24
HtmlInputFile.aspx
Sub Button_Click( s As Object, e As EventArgs ) inpFileUp.PostedFile.SaveAs( “c:\Uploads\NewFile.gif” ) End Sub
Advanced Control Programming CHAPTER 4 LISTING 4.24
245
continued
HtmlInputFile.aspx
The HTMLInputFile is an HTML control (not a Web control). The control is declared on the page with the following tag:
On Internet Explorer (version 3.02 and higher), or Netscape Navigator (version 3.0 or higher), this tag renders a text box field accompanied by a Browse button that enables you to select a file to upload from your hard drive (see Figure 4.12). FIGURE 4.12
4 ADVANCED CONTROL PROGRAMMING
Creating a file upload form with the HTMLInputFile control.
246
Working with ASP.NET Web Forms PART I
If you include an HTMLInputFile control in a form, you must change the default encoding format used by the form. You can change the encoding format by modifying the form’s EncType property. The form in Listing 4.24 is created with the following tag:
When the button is clicked and the file is uploaded, the Button_Click subroutine saves the file to disk. The file is saved with the following statement: inpFileUp.PostedFile.SaveAs( “c:\Uploads\NewFile.gif” )
The PostedFile property refers to whatever file was uploaded. Technically, this property refers to an instance of the HttpPostedFile class. Here, you are calling the SaveAs() method of the HttpPostedFile class to save the uploaded file to disk. The properties and methods of the HttpPostedFile class are listed in Table 4.10. TABLE 4.10
HttpPostedFile Properties, Methods, and Events
Properties
Description
ContentLength
Specifies the size of the uploaded file in bytes
ContentType
Specifies the MIME content type of the uploaded file
FileName
Specifies the full path of the uploaded file on the client’s machine
InputStream
Returns a stream that represents the raw contents of the uploaded file
Methods
Description
SaveAs
Saves the uploaded file to disk
Events
Description
None
You can use the properties of the HttpPostedFile class to retrieve additional information about an uploaded file. For example, you can use the ContentLength property to check the size of a file and not save it when the file is too big. You can use the ContentType property to determine whether an uploaded file is an image file, a Microsoft Word document, or some other type of file.
Advanced Control Programming CHAPTER 4
247
Summary In this chapter, you learned how to use several of the advanced methods and properties of ASP.NET controls. First, you learned how to take advantage of view state in your ASP.NET pages and disable view state for a single control or a whole page. You also learned how to save the values of your own variables in view state. Next, you learned how to hide and disable individual controls in a page by using the Visible and Enabled properties. You also learned how to use the Panel control to hide and display a group of controls at a time. You also delved deeper into the structure of an ASP.NET page. You learned how to add and remove controls from a page through code. You also examined methods of adding and removing items from list controls such as PlaceHolder, DropDownList and ListBox. Finally, you examined three of the more advanced controls included in the .NET framework. You learned how to build interactive calendars by using the Calendar control, randomly display banner advertisements by using the AdRotator control, and accept files uploaded by using the HtmlFileInput control.
4 ADVANCED CONTROL PROGRAMMING
Advanced ASP.NET Page Development
PART
II IN THIS PART 5 Creating Custom Controls with User Controls 6 Separating Code from Presentation
277
7 Targeting Mobile Devices with Mobile Controls 309 8 Using Third-Party Controls
359
251
CHAPTER 5
Creating Custom Controls with User Controls IN THIS CHAPTER • Including Standard Content with User Controls 252 • Exposing Properties and Methods in User Controls 257 • Exposing Web Controls in User Controls 260 • Exposing Events in User Controls • Loading User Controls Programmatically 269
268
252
Advanced ASP.NET Page Development PART II
In this chapter, you learn how to create and work with user controls. A user control is an ASP.NET page that has been converted into a control. User controls enable you to easily reuse the same content and programming logic on multiple ASP.NET pages. In the first section, you learn how to use user controls to display the same content on multiple ASP.NET pages. For example, you learn how to construct a standard page header and footer with a user control and display them on multiple pages. Next, you learn how to use user controls to package multiple controls into a single control. For example, you learn how to package multiple Web controls into a single user control that represents an address form. Finally, you learn how to dynamically load user controls into an ASP.NET page. You also learn how to set properties and call methods of a dynamically loaded user control. Note You can view “live” versions of many of the code samples in this chapter by visiting the Superexpert Web site at: http://www.Superexpert.com/AspNetUnleashed/
Including Standard Content with User Controls You can use a user control to redisplay static content on multiple ASP.NET pages. For example, most Web sites have a standard header and footer that appear at the top and bottom of every page. With user controls, you can create the standard header and footer once and use the same user controls on multiple pages. The advantage of using user controls to display the same content on multiple pages is that you can easily update the content if it ever changes. For example, if your Web site logo changes, you need to change only one file rather than hundreds of files. ASP Classic Note In previous versions of Active Server Pages, #INCLUDE files were used to display static content on multiple pages. You can still use #INCLUDE files with ASP.NET, but user controls offer much more functionality.
Creating Custom Controls with User Controls CHAPTER 5
253
To build a user control, you simply need to create a file that has the extension .ascx. You can place any static content in a user control file that you wish. Note User controls have the special .ascx extension so that they cannot be opened directly in a Web browser. Being able to open a user control file directly would create potential security risks. If you attempt to open a user control file, you receive a This type of page is not served. message.
Listing 5.1 contains a simple user control that represents a standard Web site page header. LISTING 5.1
SimpleHeader.ascx
Global Super Company Global Super Company We mean business!
Notice that Listing 5.1 does not contain anything special. It includes standard opening HTML tags and a little bit of text. After you create a user control, you can include it in any other ASP.NET page in your Web site. For example, the page in Listing 5.2 contains the SimpleHeader.ascx user control. LISTING 5.2
HomePage.aspx
5
Welcome to our home page!
CREATING CUSTOM CONTROLS
254
Advanced ASP.NET Page Development PART II
When the page in Listing 5.2 is displayed, the contents of the user control are also displayed (see Figure 5.1). FIGURE 5.1 Registering a user control.
The first line of Listing 5.2 contains a Register directive. You must register a user control on a page before you can start using it. The page in this listing uses three attributes of the Register directive: •
TagPrefix—The
•
TagName—The
•
Src—The
alias to associate with the user control’s namespace
alias to associate with the user control’s class
virtual path to the file containing the user control
The TagPrefix attribute enables you to assign a unique namespace to the user control. If you add another user control to the page with the same TagName, you could still distinguish between the controls with the TagPrefix. The TagName attribute enables you to name your custom control. You use this name when creating an instance of the user control on the page. The Src attribute specifies the path to the user control file. If the user control is located in the same directory as the containing page, you can simply provide the filename. If the user control is located in another directory, you need to provide either a relative or absolute path to the file.
Creating Custom Controls with User Controls CHAPTER 5
255
After you register the user control on the page, you can start using the control just like any other ASP.NET control. In Listing 5.2, you declared the control on the page by using the following tag:
When the page is rendered, the control displays the contents of the SimpleHeader.ascx file. Note You do not always need to include a user control within a tag. You need to include a user control within a form tag only when you want the control to participate in view state or when it includes other controls that require it.
You can use the same user control multiple times within a single ASP.NET page. The only requirement is that each instance has a unique ID. Imagine that you created a complicated page divider using multiple HTML elements. You can place all the HTML elements into a user control and redisplay the divider multiple times on a page. Listing 5.3 contains a user control that represents a page divider built from an HTML
CREATING CUSTOM CONTROLS
This divider is displayed multiple times in the page contained in Listing 5.4 (see Figure 5.2 for the output of this page).
5
256
Advanced ASP.NET Page Development PART II LISTING 5.4
UserControlsDivider.aspx
UserControlsDivider.aspx Here is some text... Here is some more text... Yet some more text...
FIGURE 5.2 Displaying a single user control multiple times.
Creating Custom Controls with User Controls CHAPTER 5
257
Exposing Properties and Methods in User Controls In the preceding section, you learned how to use a user control to display a standard header on multiple ASP.NET pages. There is one problem with this header user control; however, it displays the exact same page title on each page. Fortunately, you can find an easy way around this problem, and the solution illustrates the flexibility and power of user controls. User controls do not need to be simple static files. You can expose properties in a user control and set and read these properties in the containing page. You can, for example, expose a property in the header user control that represents the title of the page. The modified header user control in Listing 5.5 demonstrates how you can do so. LISTING 5.5
HeaderTitle.ascx
Public PageTitle As String = “Global Super Company” Global Super Company We mean business!
The header user control in Listing 5.5 contains a script that declares a public variable named PageTitle. Because the variable is declared as public, it can be accessed within any page that contains the user control. The PageTitle variable displays the title of the page. The value of the variable is rendered within the opening and closing tags. The variable is given a default value of Global Super Company. If no other value is assigned to the variable, it displays this default title.
CREATING CUSTOM CONTROLS
Now that you’ve declared the public variable, you can set the variable in any page that contains the user control, as illustrated in Listing 5.6.
5
258
Advanced ASP.NET Page Development PART II LISTING 5.6
HomePageTitle.aspx
Welcome to our home page!
Notice how a value is assigned to the header user control when the user control tag is declared. The value is assigned within the declaration of the header user control. When the page in Listing 5.6 is displayed, the title of the page is The Home Page. You also can assign a value to the PageTitle user control property programmatically. Suppose that you want to display the current date in the page title. The page in Listing 5.7 assigns a value to the PageTitle variable in the Page_Load subroutine. LISTING 5.7
HomePageDate.aspx
Sub Page_Load ctlHeader.PageTitle = “Date: “ & Now.ToString( “D” ) End Sub Welcome to our home page!
All public variables declared in the user control file are exposed as properties of the user control. Functions and subroutines contained in the user control file can also be exposed.
Creating Custom Controls with User Controls CHAPTER 5
259
However, functions and subroutines are exposed not as properties, but as methods of the user control. The user control contained in Listing 5.8, for example, repeats a word a certain number of times. It has one property called Word, which represents the word to be repeated, and one method called Repeat(), which causes the word to repeated. LISTING 5.8
WordRepeater.ascx
Public Word As String Sub Repeat( intNumTimes As Integer ) Dim intCounter As Integer For intCounter = 0 to intNumTimes - 1 Response.Write( Word ) Next End Sub
The Repeat() method is implemented in the user control as a subroutine. It’s a subroutine that has one parameter that represents the number of times the word should be repeated. The page in Listing 5.9 demonstrates how you call the Repeat() method to display the word Hello! 50 times. LISTING 5.9
DisplayWordRepeater.aspx
Sub Page_Load myWordRepeater.Repeat( 50 ) End Sub
Exposing Web Controls in User Controls A user control can contain both HTML and Web controls. For example, you can place multiple Web Form controls in a user control and expose them as a single control. Why would you want to use controls this way? Imagine that you need to display the same group of Web controls over and over again in different pages at your Web site. For example, many forms contain multiple form fields for address information. You can build a single Web control that contains all the necessary form fields and use the control whenever you need to request address information. The user control in Listing 5.10 illustrates how you can create one control for address information. LISTING 5.10
Address.ascx
Street Address:
City:
State:
ZIP:
Creating Custom Controls with User Controls CHAPTER 5 LISTING 5.10
261
continued
After you create the address user control, you can use it in multiple forms, or you can even use it multiple times in the same form. The page in Listing 5.11 uses the address control twice, once for the shipping address and once for the billing address (see Figure 5.3). LISTING 5.11
DisplayAddress.aspx
DisplayAddress.aspx Billing Address Shipping Address
CREATING CUSTOM CONTROLS
Notice how both address controls are declared within the form contained in Listing 5.11. You should never declare a form within the user control itself. Instead, you should always create the form in the page that contains the user control.
5
262
Advanced ASP.NET Page Development PART II
FIGURE 5.3 Displaying multiple address forms with a single user control.
Now that you have added the address controls to the page, you need a method of accessing the values of the form fields from within the containing page. For example, you might want to submit the customer’s shipping and billing address information to a database table. Retrieving the values of the address form fields is more difficult than you might think. The TextBox controls for the address form fields are located within the user control. You cannot access the properties of these controls outside the user control. For example, you cannot read the Text property of the TextBox control named txtCity directly from the containing page. To retrieve the values from the TextBox controls within the address user control, you need to add properties to the user control that expose the properties of the form controls. The page contained in Listing 5.12 illustrates how you can create the necessary properties. LISTING 5.12
AddressProperties.ascx
Public Property Street As String Get Return txtStreet.Text End Get
Creating Custom Controls with User Controls CHAPTER 5 LISTING 5.12
263
continued
Set txtStreet.Text = Value End Set End Property Public Property City As String Get Return txtCity.Text End Get Set txtCity.Text = Value End Set End Property Public Property State As String Get Return txtState.Text End Get Set txtState.Text = Value End Set End Property Public Property ZIP As String Get Return txtZIP.Text End Get Set txtZIP.Text = Value End Set End Property
Street Address:
City:
CREATING CUSTOM CONTROLS
State:
ZIP:
The modified version of the address user control in Listing 5.12 uses property accessor functions to expose the properties of the TextBox controls. Each property on the page is declared with both Get and Set functions. When you read a property, the Get function is called, and when you set a property, the Set function is called. The Street property, for example, acts as a proxy for the Text property of the TextBox control named txtStreet. The Street property is declared as follows: Public Property Street As String Get Return txtStreet.Text End Get Set txtStreet.Text = Value End Set End Property
When you read the Street property, the Steet’s Get function retrieves the correct value from the Text property of the TextBox control named txtStreet. When you set the Street property, the Street’s Set function sets the Text property of the TextBox control. After you create proxy properties for each control contained within the user control, you can access the address information from the containing page. The page contained in Listing 5.13 illustrates how you can do so. LISTING 5.13
DisplayAddressProperties.aspx
Sub Button_Click( s As Object, e As EventArgs ) lblOutput.Text = “You entered the following values:” lblOutput.Text &= “Billing Address:” lblOutput.Text &= “
” “Billing Address:” “
CREATING CUSTOM CONTROLS
When you click the Submit button on the page contained in Listing 5.13, the Button_Click subroutine is executed. This subroutine displays the values of the properties of the shipping and billing address user controls within a Label control.
5
266
Advanced ASP.NET Page Development PART II
Creating a proxy property for every TextBox control in a user control can be a long and tedious process. For example, if a user control contains 20 form fields, you would have to create 20 properties. Instead of creating a separate property for each control, you can create one property that represents the values of all the controls. The page in Listing 5.14 illustrates how you would do so. LISTING 5.14
AddressHashTable.ascx
Public ReadOnly Property Values As HashTable Get Dim colHashTable As New HashTable Dim ctlControl As Control Dim txtTextBox As TextBox For each ctlControl in Controls If TypeOf ctlControl Is TextBox Then txtTextBox = ctlControl colHashTable.Add( txtTextBox.ID, txtTextBox.Text ) End If Next Return colHashTable End Get End Property
Street Address:
City:
State:
ZIP:
Creating Custom Controls with User Controls CHAPTER 5 LISTING 5.14
267
continued
The address user control in Listing 5.14 returns the values of the TextBox controls in a HashTable. The user control has a single read-only property named Values. In the Get function of the Values property, a HashTable is constructed by looping through all the TextBox controls. Note A HashTable is a collection of key and value pairs. The HashTable collection is covered in detail in Chapter 24, “Working with Collections and Strings.”
To retrieve the TextBox control values in the containing page, you must read the values from the HashTable returned by the Values property. The page in Listing 5.15 retrieves and displays each value from the HashTable returned by the user control. The values of the ctlShippingAddress are retrieved by name. The values of the ctlBillAddress are retrieved within a For...Each loop. LISTING 5.15
DisplayAddressHashTable.aspx
Sub Button_Click( s As Object, e As EventArgs ) Dim objItem As Object ‘ Show Billing lblOutput.Text lblOutput.Text lblOutput.Text lblOutput.Text lblOutput.Text lblOutput.Text lblOutput.Text
Information = “You entered the following values:” &= “Billing Address:” &= “
”
5 CREATING CUSTOM CONTROLS
‘ Show Shipping Information lblOutput.Text &= “Shipping Address:”
“Street” ) “City” ) “State” ) “ZIP” )
268
Advanced ASP.NET Page Development PART II LISTING 5.15
continued
For Each objItem in ctlShippingAddress.Values lblOutput.Text &= “
Exposing Events in User Controls A user control can contain the same event-handling subroutines as a normal ASP.NET page. For example, you can add a Page_Load subroutine to a user control that executes before any content in the body of the control is rendered. The Page_Load subroutine
Creating Custom Controls with User Controls CHAPTER 5
269
contained in the user control is completely distinct from the Page_Load subroutine in the parent page. The user control in Listing 5.16 randomly displays one of three quotes every time it is displayed. LISTING 5.16
UserControlEvents.ascx
Sub Page_Load Dim RanNum As New Random Select Case RanNum.Next( 3 ) Case 0 lblQuote.Text = “A penny saved is a penny earned” Case 1 lblQuote.Text = “Look before you leap” Case 2 lblQuote.Text = “He who hesitates is lost” End Select End Sub
The page in Listing 5.17 uses this user control to randomly display a quotation. LISTING 5.17
DisplayControlEvents.aspx
Loading User Controls Programmatically
CREATING CUSTOM CONTROLS
Instead of declaring a user control in a page, you can add the user control programmatically in your code. For example, imagine that you want to add a featured product section to the home page of your Web site. Imagine, furthermore, that each featured product has
5
270
Advanced ASP.NET Page Development PART II
different layout requirements. You can create user controls that correspond to each featured product and randomly load one of the controls each time the home page is displayed. Listing 5.18 and 5.19 contain two featured products. LISTING 5.18
FeaturedProduct1.ascx
Public BackColor As String = “lightgreen”
Hammers on Sale! Quality hammers are now on sale in the hardware department. |
Blenders on Sale! See our selection of blenders in the home appliance deparment. |
Hammers on Sale! Quality hammers are now on sale in the hardware department. |
Blenders on Sale! Creating Custom Controls with User Controls CHAPTER 5 LISTING 5.24 275 continued See our selection of blenders in the home appliance deparment. |
Value 2:
Output:
You also can expose properties in a component by using property accessor syntax. Using this syntax, you can define a Set function that executes every time you assign a value to a property and a Get function that executes every time you read a value from a property. You can then place validation logic into the property’s Get and Set functions to prevent certain values from being assigned or read.
FROM
AddValues.aspx
SEPARATING CODE
6 PRESENTATION
lblOutput.Text = myAdder.AddValues() End Sub
285
286
Advanced ASP.NET Page Development PART II
The modified version of the adder component in Listing 6.5, for example, uses property accessor syntax. LISTING 6.5
AdderProperties.vb
Imports System Namespace myComponents Public Class AdderProperties Private _firstValue As Integer Private _secondValue As Integer Public Property FirstValue As Integer Get Return _firstValue End Get Set _firstValue = Value End Set End Property Public Property SecondValue As Integer Get Return _secondValue End Get Set _secondValue = Value End Set End Property Function AddValues() As Integer Return _firstValue + _secondValue End Function End Class End Namespace
The modified version of the adder component in Listing 6.5 works in exactly the same way as the original adder component. When you assign a new value to the FirstValue property, the Set function executes and assigns the value to a private variable named _firstValue. When you read the FirstValue property, the Get function executes and returns the value of the private _firstvalue variable. The advantage of using accessor functions with properties is that you can add validation logic into the functions. For example, if you never want someone to assign a value less than 0 to the FirstValue property, you can declare the FirstValue property like this:
Separating Code from Presentation CHAPTER 6
You can use components to move some, but not all, of the application logic away from an ASP.NET page to a separate compiled file. Imagine, for example, that you want to create a simple user registration form with an ASP.NET page. When someone completes the user registration form, you want the information to be saved in a file. You also want to use a component to encapsulate the logic for saving the form data to a file. Listing 6.6 contains a simple user registration component. This component has a subroutine named doRegister that accepts three parameters: FirstName, LastName, and FavColor. The component saves the values of these parameters to a file named userlist.txt. Note File access in the .NET framework is discussed in detail in Chapter 25, “Working with the File System.”
LISTING 6.6
Register.vb
Imports System Imports System.IO Namespace myComponents Public Class Register
FROM
Using a Component to Handle Events
SEPARATING CODE
This Set function checks whether the value passed to it is less than 0. If the value is, in fact, less than 0, the value 0 is assigned to the private _firstValue variable.
6 PRESENTATION
Public Property firstValue As Integer Get Return _firstValue End Get Set If value < 0 Then _firstValue = 0 Else _firstValue = Value End If End Set End Property
287
288
Advanced ASP.NET Page Development PART II LISTING 6.6
continued
Shared Sub doRegister( FirstName As String, LastName As String, FavColor As String ) Dim strPath As String = “c:\userlist.txt” Dim strmFile As StreamWriter strmFile = File.AppendText( strPath ) strmFile.Write( “first name: “ & firstname & Environment.NewLine ) strmFile.Write( “last name: “ & lastname & Environment.NewLine ) strmFile.Write( “favorite Color: “ & favColor & Environment.NewLine ) strmFile.Write( “=============” & Environment.NewLine ) strmFile.Close End Sub End Class End Namespace
After you compile the Register.vb file and copy the compiled component to the /BIN directory, you can use the component in an ASP.NET page. The page in Listing 6.7 demonstrates how you can use the register component. LISTING 6.7
UserRegistration.aspx
Sub Button_Click( s As Object, e As EventArgs ) Register.doRegister( txtFirstName.Text, txtLastName.Text, txtFavColor.Text ) End Sub UserRegistration.aspx First Name:
Last Name:
Separating Code from Presentation CHAPTER 6 LISTING 6.7
continued
When someone completes the form and clicks the button, the Button_Click subroutine is executed. This subroutine calls the doRegister() method to save the form data to a file. Note In Listing 6.7, you do not need to create an instance of the Register component before calling its doRegister() method. You don’t need to create an instance of the component since the doRegister() method is declared as a shared method. Typically, when you declare a method, you declare instance methods. Instance methods operate on a particular instance of a class. A shared method, however, operates on the class itself.
Notice that you must still place some of the application logic in the UserRegistration. aspx page. In the Button_Click subroutine, you must pass the values entered into each of the TextBox controls to the doRegister() method. You are forced to do so because you cannot refer directly to the controls in UserRegistration.aspx from the register component.
FROM
SEPARATING CODE
Favorite Color:
6 PRESENTATION
289
290
Advanced ASP.NET Page Development PART II
Later in this chapter, in the discussion of code-behind, you learn how to move all your application logic into a separate compiled file.
Creating Multitiered Web Applications You can use components to divide your Web application into multiple layers, thereby creating a multitiered application. For example, in a two-tiered application, you might have a user interface layer built from ASP.NET pages and a data layer built from components. In a three-tiered application, you might introduce a third layer, representing your application’s business logic, between the user interface and data layers. Why would you want to create multiple layers? The advantage of dividing an application into multiple layers is that you can more easily manage and change the application. In the preceding section, for example, you created a component that enables you to save form information to a file. In essence, you created a single component for the data layer. Now suppose that your needs change, and you decide to save the data to a database table instead of a file. In that case, you need to modify only the component. You don’t have to touch a line of code in the ASP.NET page itself. In other words, you can isolate changes in the data layer from changes in the user interface layer. An additional benefit of dividing an application into multiple layers is that doing so makes it easier for multiple teams of programmers to work on the same application at the same time. If all the application logic is contained in a single page, you face the problem of multiple programmers stumbling over each other while performing modifications. To illustrate these points, you can construct a simple three-tiered Web application consisting of user interface, business, and data layers to create a simple order entry application. The application consists of the following three objects: •
OrderForm.aspx—This ASP.NET
•
BizObject—This
•
DataObject—This
page represents the user interface layer.
component represents the business layer. component represents the data layer.
First, build the user interface layer. The ASP.NET page for the user interface is contained in Listing 6.8. The page requests five pieces of information: the customer name, product, unit price of the product, quantity, and customer’s state of residence (see Figure 6.3).
Separating Code from Presentation CHAPTER 6
FIGURE 6.3
6
Sub Button_Click( s As Object, e As EventArgs ) Dim myBizObject As New BizObject If isValid Then Try myBizObject.CheckOrder( _ txtCustomer.Text, _ dropProduct.SelectedItem.Text, _ txtUnitPrice.Text, _ txtQuantity.Text, _ dropState.SelectedItem.Text ) Catch expException As Exception lblError.Text = expException.Message End Try End If End Sub OrderForm.aspx
FROM
OrderForm.aspx
SEPARATING CODE
PRESENTATION
A simple order form.
LISTING 6.8
291
292
Advanced ASP.NET Page Development PART II LISTING 6.8
continued
Enter an Order:
Customer Name:
Product:
Unit Price:
Separating Code from Presentation CHAPTER 6 LISTING 6.8
continued
Customer State:
When all the information is entered into the form, and the Place Order button is clicked, the Button_Click subroutine executes. This subroutine creates an instance of the BizObject component and passes all the form information to the component’s CheckOrder() method. The business component is contained in Listing 6.9.
FROM
SEPARATING CODE
6 PRESENTATION
Quantity:
293
294
Advanced ASP.NET Page Development PART II LISTING 6.9
BizObject.vb
Imports System Namespace myComponents Public Class BizObject Sub CheckOrder( _ Customer As String, _ Product As String, _ UnitPrice As Double, _ Quantity As Integer, _ State As String ) If Quantity 100 Then Throw New ArgumentException( “Invalid Quantity!” ) End If If State = “California” And Product=”Hair Dryer” Then Throw New ArgumentException( “Californians cannot own Hair Dryers!” ) End IF If State = “Washington” Then unitPrice += unitPrice * .06 End If Dim myDataObject As New DataObject myDataObject.SaveOrder( customer, product, unitPrice, quantity, state ) End Sub End Class End Namespace
The business component contains all the business rules for the application. The component encapsulates the following rules: 1. The product quantity must be greater than 0 and less than 100. 2. People who live in California cannot buy hair dryers. 3. People who live in Washington must pay an additional 6% sales tax. If an order fails either of the first two requirements, an exception is raised. The error is caught in the OrderEntry.aspx page and displayed in a Label control. For example, Figure 6.4 displays what happens when you try to order more than 100 products.
Separating Code from Presentation CHAPTER 6
FIGURE 6.4
6
Exceptions and Try...Catch blocks are discussed in detail in Chapter 18, “Application Tracing and Error Handling.”
If an order satisfies all the business rules, it is passed to the SaveOrder() method of the DataObject component, which saves the order to disk. This component is contained in Listing 6.10. DataObject.vb
Imports System Imports System.IO Namespace myComponents Public Class DataObject Sub SaveOrder( _ Customer As String, _ Product As String, _ UnitPrice As Double, _ Quantity As Integer, _ State As String )
FROM
Note
SEPARATING CODE
PRESENTATION
Raising an exception in the order form.
LISTING 6.10
295
296
Advanced ASP.NET Page Development PART II LISTING 6.10
continued
Dim strPath As String = “c:\orders.txt” Dim strmFile As StreamWriter strmFile = File.AppendText( strPath ) strmFile.Write( “customer: “ & customer & Environment.NewLine ) strmFile.Write( “product: “ & product & Environment.NewLine ) strmFile.Write( “unit price: “ & unitPrice.ToString() & Environment.NewLine ) strmFile.Write( “quantity: “ & quantity.ToString() & Environment.NewLine ) strmFile.Write( “state: “ & state & Environment.NewLine ) strmFile.Write( “=============” & Environment.NewLine ) strmFile.Close End Sub End Class End Namespace
Remember that you must compile both the BizObject and DataObject components and copy them to your application’s /BIN directory before you can use them. The order of compilation is important. Because the BizObject component refers to the DataObject component, you must create the DataObject component first. You can compile this component by using the following statement (executed from a DOS prompt): vbc /t:library DataObject.vb
After you copy the DataObject.dll file to your application’s /BIN directory, you can compile the BizObject component by using the following statement: vbc /t:library /r:DataObject.dll bizObject.vb
Notice the /r option used when compiling the BizObject component; it references the DataObject.dll file. If you don’t include this reference, you receive the error User-defined type not defined: DataObject.
Using Code-Behind Using components is an excellent method of moving your application logic outside your ASP.NET pages. However, as you have seen, components have one serious shortcoming. Because you cannot directly refer to the controls contained in an ASP.NET page from a component, you cannot move all your application logic out of the page. In this section, you learn about a second method of dividing presentation content from code that remedies this deficiency. You learn how to take advantage of a feature of ASP.NET pages called code-behind.
Separating Code from Presentation CHAPTER 6
Jumble.aspx
Sub Button_Click( s As Object, e As EventArgs ) lblMessage.Text = “Hello!” End Sub Jumble.aspx
The page in Listing 6.11 contains one Button control and one Label control. When you click the button, the Button_Click subroutine executes and a message is assigned to the label. Now, you can divide this page into separate presentation and code-behind files. The presentation file is simple; it just contains everything but the Button_Click subroutine. It also contains an additional page directive at the top of the file.
FROM
LISTING 6.11
SEPARATING CODE
Start with a page that has both its presentation content and application code jumbled together in one file. This page is shown in Listing 6.11.
6 PRESENTATION
You can use code-behind to cleanly divide an ASP.NET page into two files. One file contains the presentation content, and the second file, called the code-behind file, contains all the application logic.
297
298
Advanced ASP.NET Page Development PART II LISTING 6.12
Presentation.aspx
Presentation.aspx
The code-behind file, contained in Listing 6.13, is a little more complicated. It consists of an uncompiled Visual Basic class file.
Listing 6.13 Imports Imports Imports Imports
myCodeBehind.vb
System System.Web.UI System.Web.UI.WebControls System.Web.UI.HtmlControls
Public Class myCodeBehind Inherits Page Protected WithEvents lblMessage As Label Sub Button_Click( s As Object, e As EventArgs ) lblMessage.Text = “Hello!” End Sub End Class
That’s it. That’s all you need to do to cleanly divide a page into separate files for presentation content and application logic. If you save the Presentation.aspx and
Separating Code from Presentation CHAPTER 6 myCodeBehind.vb
If you look closely at the Visual Basic class declared in the code-behind file in the preceding section, you notice that the myCodeBehind class inherits from the Page class. The class definition for myCodeBehind begins with the following statement: Inherits Page
When you create a code-behind file, you are actually creating an instance of an ASP.NET page. You’re explicitly building a new ASP.NET page in a class file by inheriting from the Page class. Code-behind pages use an additional level of inheritance. The page used for the presentation content in the preceding section, Presentation.aspx, inherits from the code-behind file. The first line of Presentation.aspx is as follows:
The presentation page inherits all the properties, methods, and events of the code-behind file. When you click the button, the Button_Click subroutine executes because the presentation page is inherited from a class that contains this subroutine. So, the code-behind file inherits from the Page class, and the presentation file inherits from the code-behind file. Because this hierarchy of inheritance exists, all the properties, methods, and events of the Page class are available in the code-behind file, and all the properties, methods, and events in the code-behind file are available to the presentation file. When using code-behind files, you must be careful to declare an instance of each control used in the presentation file within the code-behind file. For example, in the code-behind file, you refer to the lblMessage control in the Button_Click subroutine. So, you have to explicitly declare the control in the code-behind file like this: Protected WithEvents lblMessage As Label
If you neglected to include this declaration, you would receive the error The name when attempting to open the presentation page in a browser. ‘lblMessage’ is not declared
FROM
How Code-Behind Really Works
PRESENTATION
Notice that you do not even have to compile the code-behind file. The Visual Basic class contained in the code-behind file is compiled automatically when you request the Presentation.aspx page.
6 SEPARATING CODE
files anywhere on your Web server, you can immediately open the file in a Web browser, and the page will work as expected.
Presentation.aspx
299
300
Advanced ASP.NET Page Development PART II
Compiling Code-Behind Files You don’t need to compile a code-behind file. The class file contained in a code-behind file is compiled automatically when you request the presentation page. If you prefer, however, there is nothing to prevent you from compiling it. For example, to compile a code-behind file named CodeBehind.vb, execute the following statement from a DOS prompt in the same directory as your code-behind file: vbc /t:library /r:system.dll,system.web.dll CodeBehind.vb
This statement compiles a code-behind file named CodeBehind.vb into a file named CodeBehind.dll. The /t option indicates that a DLL file should be created. The /r option references the system.dll assembly and system.web.dll assembly. (All the Web control classes inhabit this assembly.) After you compile the code-behind file into CodeBehind.dll, you need to move this file to your application’s /BIN directory. Finally, modify the page directive on the presentation page. Change the page directive from
to
You no longer need the src attribute because the compiled code-behind file is located in the /BIN directory. The compiled classes in the /BIN directory are automatically available to use in all the ASP.NET pages in an application.
Inheriting Multiple Pages from a Code-Behind File You can inherit multiple presentation pages from the same code-behind file. In fact, if you really want to, you could inherit all the pages in your Web site from a single codebehind file. Why is this capability useful? Imagine that you have a set of functions and subroutines that you need to call in almost every page. You can place the functions and subroutines in your code-behind file, and they are immediately available in any page that inherits from the code-behind file. The code-behind file in Listing 6.14, for example, has one subroutine and one function. The subroutine is the standard Page_Load subroutine that executes whenever a page is first loaded. The utility function named HeaderMessage() returns the current date.
Separating Code from Presentation CHAPTER 6 LISTING 6.14
Protected WithEvents lblHeader As Label Overridable Sub Page_Load lblHeader.Text = HeaderMessage() End Sub
Function HeaderMessage() As String Dim strMessage As String strMessage = “Welcome to this Web site!” strMessage &= “The current date is “ strMessage &= DateTime.Now.ToString( “D” ) Return strMessage End Function End Class
Any page that derives from the code-behind file in Listing 6.14 inherits both the Page_Load subroutine and HeaderMessage() function. For example, the presentation page in Listing 6.15 automatically uses the Page_Load subroutine from the code-behind file. LISTING 6.15
PresentationMultiple1.aspx
PresentationMultiple1.aspx Page 1
FROM
Public Class CodeBehindMultiple Inherits Page
6 SEPARATING CODE
System System.Web.UI System.Web.UI.WebControls System.Web.UI.HtmlControls
PRESENTATION
Imports Imports Imports Imports
CodeBehindMultiple.vb
301
302
Advanced ASP.NET Page Development PART II LISTING 6.15
continued
When you open the page contained in Listing 6.15, the Page_Load subroutine from the code-behind file executes, and the header Label is assigned the text from the HeaderText() function. Now, imagine that you want to use the Page_Load subroutine declared in the code-behind file in some pages that inherit from the code-behind file but not others. Because you declared the Page_Load subroutine as Overridable, you can override it in a presentation page. The page in Listing 6.16, for example, overrides the Page_Load subroutine with its own Page_Load subroutine and displays a custom message in the header Label. LISTING 6.16
PresentationMultiple2.aspx
Overrides Sub Page_Load lblHeader.Text = “Who cares about the current date?” End Sub
PresentationMultiple2.aspx Page 2
Separating Code from Presentation CHAPTER 6
LISTING 6.17
PresentationMultiple3.aspx
Overrides Sub Page_Load myBase.Page_Load lblHeader.Text &= “And the current time is “ lblHeader.Text &= Now.ToString( “t” ) End Sub PresentationMultiple3.aspx.aspx Page 3
In Listing 6.17, the Page_Load subroutine overrides the Page_Load subroutine from the code-behind file. However, notice this statement: myBase.Page_Load
FROM
The presentation page in Listing 6.17 illustrates how you would do so.
SEPARATING CODE
Now, try one last example. Imagine that you want to create a page that executes the subroutine in the code-behind file, but also executes a Page_Load subroutine of its own. You can extend the subroutine in the code-behind file within a presentation file by overriding the original subroutine and calling the original subroutine in the new subroutine. In other words, you can extend the code-behind subroutine in your presentation file.
Page_Load
6 PRESENTATION
Notice that the Page_Load subroutine uses the Overrides modifier, which overrides the Page_Load subroutine declared in the code-behind file. You can set it up this way because the Page_Load subroutine was declared with the Overridable modifier.
303
304
Advanced ASP.NET Page Development PART II
This statement invokes the Page_Load subroutine in the code-behind file. The Visual Basic myBase keyword refers to the base class that the current class is derived from. Therefore, when the page contained in Listing 6.17 is opened in a browser, the Page_Load subroutines in both the code-behind file and presentation file are executed. The page in Figure 6.5 is displayed. FIGURE 6.5 Extending a codebehind subroutine.
Compiling a Complete ASP.NET Page In this chapter, you have examined various methods of moving the application logic out of an ASP.NET page and placing it in a separate file. In this section, I want to discuss an extreme case. How would you go about compiling all of an ASP.NET page, including both the presentation content and application code? You might want to compile a complete page in several circumstances. For example, imagine that you have developed an e-Commerce Web site, and you want to sell the site to multiple clients. When you distribute the site, you might not want the clients to be able to easily view the source code. If you compile all the pages in your application, all the Visual Basic source code is hidden from view. You can compile an ASP.NET page by placing all its contents into a code-behind file. In the code-behind file, you must explicitly declare all the controls and all the static content that appears in the page.
Separating Code from Presentation CHAPTER 6
System System.Web.UI System.Web.UI.WebControls System.Web.UI.HtmlControls
Public Class myPage Inherits Page Dim txtTextBox As New TextBox Dim lblLabel As New Label Protected Overrides Sub CreateChildControls ‘ Add Opening HTML Tags Dim strOpenHTML As String strOpenHTML = “My Page” strOpenHTML &= “Enter some text:” Controls.Add( New LiteralControl( strOpenHTML ) ) ‘ Add HTMLForm Tag Dim frmForm As New HTMLForm frmForm.ID = “myForm” Controls.Add( frmForm )
‘ Add a TextBox txtTextBox.ID = “myTextBox” frmForm.Controls.Add( txtTextBox ) ‘ Add a Button Dim btnButton As New Button btnButton.Text = “Click Here!” AddHandler btnButton.Click, AddressOf Button_Click frmForm.Controls.Add( btnButton ) ‘ Add Page Break frmForm.Controls.Add( New LiteralControl( “
” ) ) ‘ Add a Label lblLabel.ID = “myLabel” frmForm.Controls.Add( lblLabel ) ‘ Add Closing HTML Tags Dim strCloseHTML As String
FROM
Imports Imports Imports Imports
myPage.vb
PRESENTATION
LISTING 6.18
6 SEPARATING CODE
The code-behind file in Listing 6.18 contains a complete ASP.NET page in a Visual Basic class.
305
306
Advanced ASP.NET Page Development PART II LISTING 6.18
continued
strCloseHTML = “” Controls.Add( New LiteralControl( strCloseHTML ) ) End Sub Sub Button_Click( s As Object, e As EventArgs ) lblLabel.Text = txtTextBox.Text End Sub End Class
The bulk of the code in Listing 6.18 is contained in a subroutine called CreateChildControls. In this subroutine, you create each of the controls that you want to appear in your ASP.NET page. To add static content to the page, you use the LiteralControl control. For example, you use LiteralControl to create the opening HTML tags at the beginning of the CreateChildControls subroutine and the closing HTML tags at the end of the subroutine. You also add all the Web controls to the page within the CreateChildControls subroutine. First, you add an HTMLForm control to the page’s Controls collection. Next, you add TextBox, Button, and Label controls to the HTMLForm’s Controls collection. Finally, your page class contains a subroutine named Button_Click. This subroutine is executed when you click the button. The Button_Click subroutine assigns whatever text was entered into the TextBox control to the Label control. To wire up the Button_Click control to the Button control, you use the following statement: AddHandler btnButton.Click, AddressOf Button_Click
This statement adds an event handler to the Click event of the Button control. It associates the Button_Click subroutine with the Click event. So, whenever the button is clicked, the Button_Click subroutine is executed. After you create the page class in Listing 6.18, you can compile it by using the following statement: vbc /t:library /r:system.dll,system.web.dll myPage.vb
After the page is compiled, you need to move it to your application’s /BIN directory. Finally, to request the compiled page in a Web browser, you have to create one additional file. This file is contained in Listing 6.19.
Separating Code from Presentation CHAPTER 6 LISTING 6.19
ShowMyPage.aspx
307
6
In this chapter, you examined two methods of dividing a page’s application logic from its design content. First, you learned how to create custom business components using Visual Basic class files and discovered how to use components to create multitiered Web applications. In the second half of this chapter, you learned how to take advantage of code-behind. You learned how to divide an ASP.NET page into a presentation file and a code-behind file. Finally, you examined a method of compiling a complete ASP.NET page.
FROM
Summary
PRESENTATION
As you can see, the ShowMyPage.aspx file contains a single line; it’s a page directive indicating that the current page should inherit from the myPage class. When you open ShowMyPage.aspx in a Web browser, the ASP.NET page created in the code-behind file is displayed.
SEPARATING CODE
CHAPTER 7
Targeting Mobile Devices with Mobile Controls IN THIS CHAPTER • Using Mobile Device Software Simulators 310 • Introduction to the Wireless Application Protocol 312 • Building WML Pages
313
• Using ASP.NET Mobile Controls
317
• Creating Cross-Device-Compatible Mobile Pages 350
310
Advanced ASP.NET Page Development PART II
In this chapter, you’ll learn how to write ASP.NET pages that work with mobile devices such as cellular phones, Pocket PC devices, and the Palm Pilot Web browser. First, you’ll be provided with an overview of the Wireless Application Protocol (WAP), a standard for communicating with wireless devices. You’ll also learn the basics of the Wireless Markup Language (WML), the wireless version of HTML. Next, you’ll learn how to leverage your existing ASP.NET skills to create ASP.NET pages that generate content compatible with mobile devices. You’ll be given an overview of the controls included with the Microsoft Mobile Internet Toolkit. Finally, you’ll learn how to create ASP.NET pages that are cross-device compatible. You’ll learn how to detect features of specific devices and exploit their special capabilities. In this chapter, you will learn: • How to configure Internet Information Server to work with WML files • How to create multiple mobile forms in a single page • How to display interactive lists of information • How to accept and validate user input • How to create device-compatible mobile ASP.NET pages
Using Mobile Device Software Simulators You can download software simulators for mobile devices so that you can test your ASP.NET pages from the comfort of your desktop computer. These simulators are a lot of fun to use. They display a mock telephone interface so you can test your pages by clicking phone buttons. One of the easiest simulators to set up is the one provided by Openwave. The Openwave simulator enables you to display a number of different phones by configuring different skins. For example, you can simulate phones from Samsung, Mitsubishi, and Ericsson by applying the proper skin (see Figure 7.1). To download the Openwave phone simulator, go to http://developer.Openwave. and download the OpenWave SDK.
com
Targeting Mobile Devices with Mobile Controls CHAPTER 7
311
FIGURE 7.1 Openwave simulator with the Alcatel skin.
7 TARGETING MOBILE DEVICES
Note This chapter assumes that you have the OpenWave SDK 3.2 installed. Currently, there are two versions of the Openwave SDK available for download: OpenWave SDK 3.2 and OpenWave SDK 4.1. The mobile controls have not been tested with every one of these phone simulators. See the documentation for the Mobile Internet Toolkit for more information.
You can also download phone simulators from Nokia by going to forum.nokia.com. The Nokia Wap Toolkit 2.0 includes simulators for the Nokia 7110 phone and a “Blueprint” phone that has no counterpart in the actual world (it’s a “concept” phone). Currently, the mobile controls will only work with the Nokia 7110 and not the Blueprint simulator (see Figure 7.2). Finally, you can use the Microsoft Mobile Explorer version 2.01 software emulator (see Figure 7.3). This simulator is a free download from the Microsoft Web site (go to www. microsoft.com/mobile/phones/mme/mmemulator.asp). For installation instructions, see http://www.microsoft.com/mobile/phones/mme/MME_2.01_Installation.doc.
312
Advanced ASP.NET Page Development PART II
FIGURE 7.2 The Nokia 7110 phone simulator.
FIGURE 7.3 The Microsoft Mobile Explorer emulator.
Introduction to the Wireless Application Protocol Developing pages for mobile devices is a very different challenge than developing pages for standard Web browsers. One of the biggest differences is the available screen space. A typical mobile device (such as a cell phone) can only display 15 characters across and four characters down. A typical desktop Web browser, in contrast, can display 80 characters across and 40 characters down.
Targeting Mobile Devices with Mobile Controls CHAPTER 7
313
Another challenge concerns bandwidth. Most mobile devices do not have blazing fast connections to a network. When you use a mobile device, you typically get to enjoy watching each character slowly walk across the network and be displayed on your screen. Therefore, sending pages full of rich graphics and sound effects simply won’t work. In response to the unique characteristics of mobile devices, several alternative protocols to HTML were developed that are tailored specifically for mobile devices. In this chapter, we’ll be discussing the Wireless Application Protocol (WAP). The WAP standard is an open standard developed and maintained by the WAP Forum (www.wapforum.org).
7
The WAP standard encompasses a number of different protocols. It includes a transportlevel protocol named WSP, which performs the same function in the world of mobile devices that HTTP does on the Internet. It also includes an application level-protocol, the Wireless Markup Language (WML), which performs the same function in the wireless world that HTML does on the Internet.
TARGETING MOBILE DEVICES
Strictly speaking, you don’t need to know anything about WML to use the mobile controls discussed in this chapter. The controls automatically render the correct WML content for you. However, it helps. The content in a WML file has a special organization, and it is useful to know what is happening under the hood when working with the mobile controls.
Building WML Pages WML is similar to HTML. When building WML pages, you can continue to use familiar tags, such as the , , and tags, with the same effect as in HTML pages. However, there are several significant differences between a WML page and an HTML page. In this section, you’ll learn how to create and use simple WML pages. Note You can read the complete specification for WML at the WAP Forum Web site at www.wapforum.org.
Configuring Internet Information Server You don’t need to configure Internet Information Server to use the ASP.NET mobile controls. So, ignore this section if you plan to generate WML content only with the mobile
314
Advanced ASP.NET Page Development PART II
controls. However, if you plan to serve static WML pages directly from your Web site, you’ll need to configure Internet Information Server to use an additional MIME type for WML files. To configure Internet Information Server 5.0 to serve static WML files, follow these steps: 1. Go to Start, Administrative Tools, Internet Services Manager. 2. Open the property sheet for your default Web site. 3. Choose the tab labeled HTTP Headers. 4. Click the button labeled File Types in the MIME Map section. 5. Enter a new file type with the extension .wml and content type text/vnd.wap.wml. After you perform these steps, your server will send WML files with the correct MIME type.
WML and XML Unlike HTML, WML is based on XML (the Extensible Markup Language). This fact has some inconvenient implications. First, because WML is based on XML and XML is case sensitive, WML is case sensitive. Therefore, using the tag in a page will not have the same effect as using the tag. In general, you must use all the tags in lowercase. Second—and this requirement should be familiar to ASP.NET developers—you must be careful to close all the tags in a WML file. This requirement is very strict. For example, if you use the
tag, you must end it with a
tag. If you want to add a line break, you must use the tag. If you neglect to close a tag, an error will be generated. Finally, all WML files must begin with the following document type declaration:Select Movie: Star Wars Gone with the Wind Citizen Kane
You selected: Star Wars
You selected: Gone with the Wind
You selected:
7 TARGETING MOBILE DEVICES
You can view the page in Listing 7.1 in a phone simulator such as the OpenWave SDK phone simulator. It won’t display property in a normal Web Browser since it is a WML file.
316
Advanced ASP.NET Page Development PART II LISTING 7.1
continued
Citizen Kane
Application: Movies Contacts
•
•
•
•
For example, the following declaration of a TextView control is perfectly valid: Here is some text: Hello World!
7 TARGETING MOBILE DEVICES
324
Advanced ASP.NET Page Development PART II
Paging through Text If you need to display a relatively large amount of text—such as a movie description or driving directions—you should enable pagination for a form. You enable pagination by setting the Paginate property of a Mobile Form to the value True. For example, the text in Listing 7.7 will be displayed automatically on multiple pages on devices that have small screens (see Figure 7.8). LISTING 7.7
Paginate.aspx
FIGURE 7.8 The output of a Mobile Form with pagination enabled.
Controlling Text Alignment and Wrapping You can control the alignment of the text in either a Label or TextView control by modifying the control’s Alignment property. Possible values for Alignment are NotSet, Left, Center, and Right. The text displayed in the Label control in Listing 7.8 uses Center alignment.
Targeting Mobile Devices with Mobile Controls CHAPTER 7 LISTING 7.8
325
Alignment.aspx
Hello!
You can control the way that text word-wraps in either a Label or TextView control by modifying the control’s Wrapping property. Possible values for the Wrapping property are NotSet, Wrap, and NoWrap. If you set the Wrapping property to NoWrap, then the lines of text contained in a form are not automatically wrapped to a new line. For example, the page in Listing 7.9 contains a long line of text in a form with wrapping disabled (see Figure 7.9 for the output of this page). LISTING 7.9
Controlling the Wrapping of Text
This is a line of text that goes off the side of the page ➥because it is very long
Displaying Lists Included with the mobile controls is a List control that enables you to display lists of items. Imagine, for example, that you need to display a list of movies currently playing at a movie theater. Listing 7.10 demonstrates how you can display a list of information using the List mobile control.
7 TARGETING MOBILE DEVICES
326
Advanced ASP.NET Page Development PART II
FIGURE 7.9 Setting Wrapping to NoWrap.
LISTING 7.10
CurrentMovies.aspx
Now Playing:
The List control is used in Listing 7.10 to display a list of three movies. When the page is opened in a device that supports HTML 3.2, the list is displayed within an HTML table. When displayed in a WML-compatible device, each list item is automatically separated by tags. You can modify the manner in which each list item is displayed by changing the List control’s Decoration property. This property can have the value None, Bulleted, or Numbered. For example, the page in Listing 7.11 displays the three movies in a numbered list.
Targeting Mobile Devices with Mobile Controls CHAPTER 7 LISTING 7.11
327
ListNumbered.aspx
Figure 7.10 illustrates how the Decoration property modifies the appearance of a List on Internet Explorer 6.0. FIGURE 7.10 Setting Decoration to Numbered on Internet Explorer 6.0.
Note Currently, the Decoration property only affects how a page is rendered in an HTML-compatible device. The Decoration property is ignored when rendering WML content.
7 TARGETING MOBILE DEVICES
Now Playing:
328
Advanced ASP.NET Page Development PART II
Binding a List to a Data Source You can bind a List control to a data source such as a database table or an ArrayList collection. For example, if you were really creating a page that displays a list of movies, you would most likely retrieve the list of movies from a database table rather than include a static list of movies. We cover data binding in detail in Chapter 10, “Binding Data to Web Controls.” However, I’ll include a quick example using mobile controls here. The page in Listing 7.12 demonstrates how you can bind a List control named movies to an ArrayList collection named myArrayList. LISTING 7.11
ListBinding.aspx
Sub Page_Load Dim colArrayList As New ArrayList colArrayList.Add( “Star Wars” ) colArrayList.Add( “Gone with the Wind” ) colArrayList.Add( “Citizen Kane” ) lstMovies.DataSource = colArrayList lstMovies.DataBind() End Sub Now Playing:
In the Page_Load subroutine, an ArrayList collection is created and bound to the List control named lstMovies. When DataBind is called, the items in the List control are populated from the items in ArrayList.
Paging Through a List If you want to display a list that contains a lot of items, you can divide the list into multiple pages. When you enable paging for a form that contains a list, Next and Previous links are automatically created at set intervals in the list.
Targeting Mobile Devices with Mobile Controls CHAPTER 7
329
For example, the List control in Listing 7.12 displays 50 movies (named movie 1, movie 2, and so on). Because the Mobile Form’s Paginate property has the value True, the list items are divided into multiple pages. LISTING 7.12
ListPaginate.aspx
7
Sub Page_Load Dim colArrayList As New ArrayList Dim intCounter As Integer
TARGETING MOBILE DEVICES
For intCounter = 1 to 50 colArrayList.Add( “movie “ & intCounter.ToString() ) Next lstMovies.DataSource = colArrayList lstMovies.DataBind() End Sub Now Playing:
You can control the user interface displayed for paging through a form by setting properties of the PagerStyle element. For example, if you want to display a page number that indicates the current page, you can do this by displaying the value of the PageLabel property of the PagerStyle element. If you want to specify the text used for the next and previous page links, you can set the value of the NextPageText and PreviousPageText properties (see Figure 7.11). The page in Listing 7.13 illustrates how to set all three of these properties in a form. LISTING 7.13
ListPagerStyle.aspx
330
Advanced ASP.NET Page Development PART II LISTING 7.13
continued
Sub Page_Load Dim colArrayList As New ArrayList Dim intCounter As Integer For intCounter = 1 to 50 colArrayList.Add( “movie “ & intCounter.ToString() ) Next lstMovies.DataSource = colArrayList lstMovies.DataBind() End Sub Now Playing:
FIGURE 7.11 Controlling pagination style.
Targeting Mobile Devices with Mobile Controls CHAPTER 7
331
Creating Interactive Lists You can also use the List control to display a list of choices. The choices appear as links when displayed in either WML or HTML devices. Note As an alternative to the List control, you can use the SelectionList control to render an interactive list. The SelectionList control, unlike the List control, supports selecting multiple items at a time.
LISTING 7.14
ListLinks.aspx
Sub List_ItemCommand( s As object, e As ListCommandEventArgs ) lblMovie.Text = e.ListItem.Text ActiveForm = form2 End Sub Select Movie: You selected:
TARGETING MOBILE DEVICES
For example, the page in Listing 7.14 uses the List control to display a list of movie choices.
7
332
Advanced ASP.NET Page Development PART II
When a List control has an event handler, each list item is displayed as a link (see Figure 7.12). In Listing 7.14, the OnItemCommand method associates the List_ItemCommand subroutine with the ItemCommand event. When you click any of the movie links, the ItemCommand event is raised and the List_ItemCommand subroutine is executed. FIGURE 7.12 Displaying a List of Links.
The List_ItemCommand subroutine retrieves the value of the Text property of the selected item and assigns the text to a Label control named lblMovie. Next, the List_ItemCommand subroutine activates form2, so that the Label control is displayed. Individual list items can have distinct Text and Value properties. The Text property is always displayed when an item is displayed and the Value property is hidden. You can use the Value property to pass additional information when an item is selected. For example, in Listing 7.15, the Value property is used to pass the category of each movie. LISTING 7.15
ListValue.aspx
Sub List_ItemCommand( s As object, e As ListCommandEventArgs ) lblMovie.Text = e.ListItem.Text
Targeting Mobile Devices with Mobile Controls CHAPTER 7 LISTING 7.15
333
continued
lblMovieCategory.Text = e.ListItem.Value ActiveForm = form2 End Sub
You selected: Category:
Each of the three items contained in the List control in Listing 7.15 has both a Text and a Value property. When an item is selected, the ItemCommand event is raised and the List_ItemCommand subroutine is executed. This subroutine assigns the value of the Text property to a Label control named lblMovie and the value of the Value property to a Label control named lblMovieCategory.
Creating Object Lists One significant limitation of the List control is that it enables you to select only one option for each item in the list. You click a list item and something happens. Imagine, however, that you need to display multiple options for each list item. For example, when displaying a list of movies, you might want the user to be able to view the description of the movie or buy a ticket for the movie. In other words, you might want two options associated with each list item.
7 TARGETING MOBILE DEVICES
Select Movie:
334
Advanced ASP.NET Page Development PART II
To create more complicated lists, you need to use the ObjectList control instead of the List control. The ObjectList control is similar to the List control in that it enables you to display a list of links. However, unlike the List control, the ObjectList control enables you to associate multiple commands with each link. Also, unlike the List control, the ObjectList control requires you to bind the list to a data source. Note Data binding is discussed in Chapter 10, “Binding Data to Web Controls.”
The page in Listing 7.16 illustrates how you can use the ObjectList control to associate multiple commands with each list item. LISTING 7.16
ObjectList.aspx
Sub Page_Load Dim colArrayList As New ArrayList colArrayList.Add( “Star Wars” ) colArrayList.Add( “Citizen Kane” ) lstMovies.Datasource = colArrayList lstMovies.Databind() End Sub Sub ObjectList_ItemCommand( s As Object, e As ObjectListCommandEventArgs ) If e.CommandName = “Buy” Then ActiveForm = frmBuyTicket Else ActiveForm = frmShowDesc End If End Sub Great Movie!
The ObjectList control in Listing 7.16 is bound to an ArrayList collection named colArrayList. This ArrayList collection consists of movie titles. Within the ObjectList control, two Command controls are declared. The first command is labeled Buy, and the second command is labeled Desc. If you select a movie title or you select either of the two commands, the ObjectList_ItemCommand subroutine is executed. Exactly how an ObjectList is displayed depends on the device. When the page in Listing 7.16 is displayed in Internet Explorer 6.0, each list item is rendered as a link (see Figure 7.13). When you click a link, the page in Figure 7.14 is displayed. FIGURE 7.13 Displaying ObjectList
Links.
TARGETING MOBILE DEVICES
Buy Ticket!
7
336
Advanced ASP.NET Page Development PART II
FIGURE 7.14 Output of after Clicking Link.
ObjectList
When displayed in a cell phone, such as the Alcatel cell phone, the Go command is displayed. When you press the Go button, a menu displaying Buy and Desc options is displayed (see Figure 7.15). FIGURE 7.15 Output of on Alcatel Phone Simulator.
ObjectList
Unlike the List control, the ObjectList control can display multiple fields from a data source. For example, you can bind an ObjectList to a database table that contains
Targeting Mobile Devices with Mobile Controls CHAPTER 7
337
movieTitle, movieCat, and ticketPrice columns and displays all these columns by using the ObjectList.
The ObjectList in Listing 7.17 is bound to an ArrayList collection. Each item in the ArrayList collection is an instance of a class defined within the page named the Movie class (we need to create a class so that we can represent multiple fields for each item in the ArrayList collection). The Movie class has movieTitle, movieCat, and ticketPrice properties. When you select an item in the ObjectList, the values of all three of the properties are displayed.
7 ObjectListMultiple.aspx
Public class Movie Private _movieTitle, _movieCat, _ticketPrice As String ReadOnly Property movieTitle As String Get Return _movieTitle End Get End Property ReadOnly Property movieCat As String Get Return _movieCat End Get End Property ReadOnly Property ticketPrice As String Get Return _ticketPrice End Get End Property Sub New( movieTitle As String, _ movieCat As String, _ ticketPrice As String ) _movieTitle = movieTitle _movieCat = movieCat _ticketPrice = ticketPrice End Sub
TARGETING MOBILE DEVICES
LISTING 7.17
338
Advanced ASP.NET Page Development PART II LISTING 7.17
continued
End Class
Sub Page_Load Dim colArrayList As New ArrayList colArrayList.Add( new Movie( “Star Wars”, “SciFi”, “$7.98” ) ) colArrayList.Add( new Movie( “Citizen Kane”, “Drama”, “$4.00” ) ) lstMovies.Datasource = colArrayList lstMovies.Databind End Sub Sub ObjectList_ItemCommand( s As Object, e As ObjectListCommandEventArgs ) ActiveForm = frmSelectMovie End Sub Movie Selected!
Creating Text Boxes Entering text into most mobile devices is a cumbersome experience. For example, entering large amounts of text with a cell phone’s numeric keypad is close to impossible. In most cases, you should enable users to make choices with the List controls discussed in the previous section.
Targeting Mobile Devices with Mobile Controls CHAPTER 7
339
However, there are certain circumstances in which you have no choice but to force the users of your application to enter text. For example, you might need the user to enter a password, a phone number, or some other type of data that cannot be selected from a list. In these situations, you will need to use the mobile TextBox control. The mobile TextBox control is similar to the ASP.NET TextBox control. However, it does not support multiline input. All text must be entered into a single line (see Figure 7.16). FIGURE 7.16
7
Entering Text on a Cell Phone.
TARGETING MOBILE DEVICES
The page in Listing 7.18 illustrates how you can use the TextBox control. The first mobile form contains two mobile TextBox controls: one for a contact name and one for a contact phone. A Command control is used to enable the user to submit the form. When the Command control is clicked, the Command_Click subroutine is executed and the second Mobile Form is displayed. This form simply redisplays the values entered into the txtContactName and txtContactPhone TextBox controls. LISTING 7.18
TextBox.aspx
Sub Command_Click( s As Object, e As EventArgs ) lblContactName.Text = txtContactName.Text
340
Advanced ASP.NET Page Development PART II LISTING 7.18
continued
lblContactPhone.Text = txtContactPhone.Text ActiveForm = frmDisplayContact End Sub Name: Phone: You Entered:
Displaying Different TextBox Types The Mobile TextBox control has two properties, Numeric and Password, that indicate the type of information that can be entered into the text box. The Password value works with both HTML and WML clients. The Numeric value is unique to WML devices. When the Password property is assigned the value True, any text entered into the text box is hidden (typically, the asterisk character is used to echo each character entered). When the Numeric property is assigned the value True, and the text box is rendered on a WML device, only numerals can be entered into the text box.
Targeting Mobile Devices with Mobile Controls CHAPTER 7
341
The page in Listing 7.19 illustrates how to use both of these properties. The first TextBox control, labeled txtUsername, has the default values for Password and Numeric (False). The txtUsername TextBox control can accept any text input. The second TextBox control, labeled txtPassword, will hide any text entered into it. The final TextBox control, labeled txtPIN, accepts only numeric values when rendered on a WML device. When rendered on an HTML device, a Numeric TextBox control behaves like a normal text box. LISTING 7.19
TextBoxTypes.aspx
User Name: Password: PIN:
Validating User Input All the ASP.NET Validation controls are shadowed in the mobile controls. For each of the ASP.NET Validation controls, there is a corresponding Mobile Validation control. The six Mobile Validation controls are detailed in the following list: •
CompareValidator.
Compares the value entered into a control to a value or performs a data type check
•
CustomValidator.
Enables you to write a custom function to perform validation
7 TARGETING MOBILE DEVICES
342
Advanced ASP.NET Page Development PART II
•
RangeValidator.
Checks whether the value entered into a control falls between a
certain range •
RegularExpressionValidator.
Matches the value entered into a control against a
regular expression •
RequiredFieldValidator.
•
ValidationSummary.
Checks whether a value has been entered into a control
Displays a summary of validation errors
Note To learn more about the standard ASP.NET Validation controls, see Chapter 3, “Performing Form Validation with Validation Controls.”
These mobile Validation controls work almost, but not exactly, like the standard Validation controls. First, unlike the standard Validation controls, the Mobile Validation controls do not use client-side JavaScript to display error messages. Error messages are not automatically displayed as you move from TextBox control to TextBox control. Furthermore, the Mobile ValidationSummary control behaves differently than the standard ASP.NET ValidationSummary control. The Mobile ValidationSummary control is typically placed in a separate form and has a property named FormToValidate that indicates the form against which to perform validation. The page in Listing 7.20 illustrates how to use the mobile RequiredFieldValidator, RangeValidator, and ValidationSummary validation controls. LISTING 7.20
Validation.aspx
Sub Command_Click( s As Object, e As EventArgs ) If Not isValid Then ActiveForm = frmBad Else ActiveForm = frmGood End If End Sub
Targeting Mobile Devices with Mobile Controls CHAPTER 7 LISTING 7.20
343
continued
Enter a number between 3 and 12:
Good Job!
The first form in Listing 7.20 contains a mobile TextBox control. Both RequiredFieldValidator and RangeValidator mobile validation controls are associated with the TextBox control. The RequiredFieldValidator validation control is used to check whether a value has been entered into the control. The RangeValidator validation control is used to ensure that the entered value is between 3 and 12.
TARGETING MOBILE DEVICES
7
344
Advanced ASP.NET Page Development PART II
When you submit the form, the Command_Click subroutine executes. If the IsValid property is not true, there is an error in the form. In that case, the second form is displayed. This form contains a ValidationSummary control that displays all the validation errors (see Figure 7.17). FIGURE 7.17 Displaying a Validation Summary.
If there are no validation errors, the third form is displayed, which simply displays the message “Good Job!”
Displaying Images Displaying images is a complicated proposition when it comes to mobile devices. The problem is that there are several different incompatible image formats that mobile devices support. Most Web browsers support GIF and JPEG image files. Many mobile devices, on the other hand, only support BMP or WBMP. The mobile controls need to support all these formats. Microsoft’s solution to this problem is elegant—a Mobile Image control. The Image control can detect different devices and display the proper image file format for the detected device. The page in Listing 7.21 illustrates how the Image control can be used with multiple devices. LISTING 7.21
Image.aspx
Targeting Mobile Devices with Mobile Controls CHAPTER 7 LISTING 7.21
345
continued
By default, the Image control in Listing 7.21 displays the myImage.gif image. However, the Image control also contains a tag. When a device that prefers a BMP image requests the page, a BMP image is displayed instead of the GIF image. Note The element makes use of a filter section in the Web.Config file. (This Web.Config file is included on the CD-ROM.) For more information on the element, see the last section of this chapter, “Creating Cross-Device-Compatible Mobile Pages.”
When an Openwave (Phone.com) compatible cell phone is detected, a BMP image is displayed (see Figure 7.18). When an Internet Explorer 6.0 browser is detected, a GIF image is displayed. Note You have to create an image in each of the formats. The Image control isn’t smart enough to generate the different image types for you.
If a device does not support any of the image formats, text is displayed instead. This alternative text is specified by the AlternateText property of the Image control.
7 TARGETING MOBILE DEVICES
346
Advanced ASP.NET Page Development PART II
FIGURE 7.18 Image Displayed in a Cell Phone.
Placing Phone Calls One interesting thing you can do with mobile controls that you cannot do in a normal ASP.NET page is place phone calls. When you use the Call control with a device such as a cell phone, the control enables you to place a call. When the Call control is used with a device that doesn’t support placing phone calls, such as Internet Explorer, either a label or link is displayed. You can use the Call control to automatically call a phone number in a list of contacts. Or, you can imagine adding a customer service number to your mobile application. The page in Listing 7.22 illustrates how to use the Call control. LISTING 7.22
Call.aspx
Targeting Mobile Devices with Mobile Controls CHAPTER 7
347
Displaying Advertisements with Mobile Controls The Mobile controls include an AdRotator control that corresponds to the standard ASP.NET AdRotator control. The mobile AdRotator control displays links on WML devices and images on HTML devices. Like the standard AdRotator control, the mobile AdRotator control uses an external XML file to list the banner advertisements that it displays. The XML file is contained in Listing 7.23. myAds.xml
ad1.gif ad1.bmp http://www.AspWorkshops.com Click here for ASP.NET Training! 60 ad2.gif ad2.bmp http://www.superexpert.com Click here to visit superexpert.com! 60
This advertisement file contains two banner advertisements. The ImageURL, TargetURL, and AlternateText, as well as the relative number of impressions to display are declared for both banner advertisements. Notice that two images are specified for each advertisement. A GIF image is contained in the ImageUrl tag and a BMP image is contained in the BmpImageUrl tag. After you have created an advertisement file, you can use the file with the mobile AdRotator control by setting the AdRotator control’s AdvertisementFile property. This is illustrated in Listing 7.24. LISTING 7.24
AdRotator.aspx
TARGETING MOBILE DEVICES
LISTING 7.23
7
348
Advanced ASP.NET Page Development PART II LISTING 7.24
continued
The output of Listing 7.24 is displayed in Figure 7.19. The AdRotator control in Listing 7.24 uses the DeviceSpecific tag to display the appropriate image for different devices. This page depends on the Filter section declared in the Web.Config file. (This Web.Config file is included on the CD-ROM that accompanies this book.) FIGURE 7.19 Displaying a Banner Advertisement.
Displaying Calendars with Mobile Controls The mobile controls include a Calendar control that corresponds to the standard ASP.NET Calendar control. You can use the Calendar control to select particular days, weeks, or months. It’s useful for performing such activities as scheduling meetings and appointments.
Targeting Mobile Devices with Mobile Controls CHAPTER 7
349
When the Mobile Calendar control is displayed in an HTML-compatible device, a full calendar is rendered on the screen (see Figure 7.20). When the Calendar control is displayed in a WML device, you have a couple of options for selecting the date (see Figure 7.21). For example, you can select a date by typing the date or you can go through a series of menus and select a date without ever pressing a single number key. FIGURE 7.20 Calendar Control displayed in Internet Explorer 6.0.
7 TARGETING MOBILE DEVICES
FIGURE 7.21 Calendar Control displayed in Cell Phone.
350
Advanced ASP.NET Page Development PART II
The page in Listing 7.25 illustrates how to add a mobile Calendar control to a form. LISTING 7.25
Calendar.aspx
Sub Calendar_SelectionChanged( s As Object, e As EventArgs ) lblDate.Text = calSchedule.SelectedDate ActiveForm = frmResults End Sub Schedule Meeting You selected:
When the page in Listing 7.25 is opened, you can select a date. Once a date is selected, the Calendar_SelectionChanged subroutine is executed and the selected date is displayed in the second form.
Creating Cross-Device-Compatible Mobile Pages One of the most difficult challenges that a developer faces when designing HTML pages is the problem of browser compatibility. A page that works well with Internet Explorer
Targeting Mobile Devices with Mobile Controls CHAPTER 7
351
6.0 on a PC will not necessarily work well with Netscape Navigator 3.0 on the Macintosh. Well, HTML designers have it easy. If you really want a tough challenge, try creating pages that are cross-device compatible rather than simply cross-browser and platform compatible. Making a page that looks good on a cell phone and on a full browser is a real challenge. In this section, you’ll learn about some of the features of mobile controls that you can exploit to detect the capabilities of different mobile devices. You can use these features to construct mobile pages that are cross-device compatible.
When you install the mobile controls, your server’s system machine.config file is also automatically modified. Additional entries are added to the section, which describes the capabilities of various mobile devices. If you are curious, go ahead and open the machine.web file to look at these definitions (you can open the file with Notepad). Note The config.web file is discussed in detail in Chapter 15, “Creating ASP.NET Applications.”
These entries in the machine.config file are used by the MobileCapabilities class. For example, the page in Listing 7.26 displays a number of features of the current device by displaying properties from the MobileCapabilities class. LISTING 7.26
MobileCapabilities.aspx
Sub Page_Load Dim caps AS system.Web.Mobile.MobileCapabilities caps = Request.Browser lblBrowser.Text = caps.Browser lblType.Text = caps.Type
TARGETING MOBILE DEVICES
Detecting Mobile Capabilities
7
352
Advanced ASP.NET Page Development PART II LISTING 7.26
continued
lblPreferredRenderingType.Text = caps.PreferredRenderingType lblScreenCharactersWidth.Text = caps.ScreenCharactersWidth lblScreenCharactersHeight.Text = caps.ScreenCharactersHeight End Sub Browser: Type: PreferredRenderingType: ScreenCharactersWidth: ScreenCharactersHeight:
The page in Listing 7.26 displays five properties of the current device: Browser, Type, PreferredRenderingType, ScreenCharactersWidth, and ScreenCharactersHeight. The properties are retrieved by retrieving an instance of the MobileCapabilities class from the Browser class. Each property is displayed by assigning the value of each property to a Mobile Label control (see Figure 7.22).
Targeting Mobile Devices with Mobile Controls CHAPTER 7
353
FIGURE 7.22 Displaying Device Capabilities.
7 TARGETING MOBILE DEVICES
The Browser property returns the type of browser used by the device—for example, IE or Phone.com. The Type property returns the general type of the device—IE5 or Pocket Internet Explorer, for example. The PreferredRenderingType property returns the MIME type of the rendering language of the device. Examples are html32 and wml11. Finally, the ScreenCharactersWidth and ScreenCharactersHeight properties return the number of characters that a device can display horizontally and vertically. You can retrieve many more device properties than those returned in Listing 7.26. For example, you can use the IsColor property to detect whether a device is capable of displaying color or the CanInitiateVoiceCall property to detect whether the device can place phone calls. For a complete list of properties supported by the MobileCapabilities class, consult the Mobile Internet Toolkit documentation.
Choosing Devices with DeviceSpecific The MobileCapabilities class can be used to display different content for different devices. You can use the properties from the MobileCapabilities class with a special element named the element. The element works (very roughly) like a Visual Basic Select Case statement. The element can contain multiple elements that match different device criteria. When a element is matched, its content is returned.
354
Advanced ASP.NET Page Development PART II
Earlier in this chapter, in the section titled “Displaying Images,” you saw how the element can be used with the Image control to display different image files, depending on the nature of the device that requests the page. You can also use with other controls, such as the List and ObjectList controls. For example, the page in Listing 7.27 displays different content to HTML and WML devices. If the device is an HTML-compatible device, special formatting is applied to the List control’s header and footer. Furthermore, each list item is formatted in the List control’s ItemTemplate. LISTING 7.27
DeviceSpecific.aspx
Sub List_ItemCommand( s As object, e As ListCommandEventArgs ) lblMovie.Text = e.ListItem.Text ActiveForm = form2 End Sub Welcome to the Movie Reservation System!
Targeting Mobile Devices with Mobile Controls CHAPTER 7 LISTING 7.27
355
continued
All Contents © copyright 2002 by the Company
When the page in Listing 7.27 is displayed in an HTML 3.2–compatible browser, the special formatting in HeaderTemplate and FooterTemplate is displayed. For example, the ItemTemplate shows a Button control for each list item. You should also notice that all the templates use HTML tags that are not WML compatible (see Figure 7.23). FIGURE 7.23 Using Templates with Internet Explorer 6.0.
When the page in Listing 7.27 is displayed in a WML-compatible browser, all the templates are ignored. The List control is displayed with its default formatting.
7 TARGETING MOBILE DEVICES
You selected:
356
Advanced ASP.NET Page Development PART II
Using Form Template Sets As you saw in the previous section, the element can be used with Image, List, and ObjectList controls. You can also use the element with a Mobile Form control. The Mobile Form control has both a HeaderTemplate and a FooterTemplate. You can create multiple HeaderTemplates and FooterTemplates and place them within different elements. By creating multiple templates, you can display different content for different devices. The page in Listing 7.28 demonstrates how to create three sets of templates. The first set of templates will render on WML-compatible devices, the second set on HTML 3.2–compatible devices, and the final set will render on other devices. LISTING 7.28
FormTemplates.aspx
Welcome to this Web site! All Content Copyrighted 2001
Welcome to this Web site! |
In Listing 8.2, you add a property and method to the TreeView control declaration. You set AutoPostBack to the value True and associate the TreeView_SelectedIndexChanged subroutine with the OnSelectedIndexChanged method. When you select a new node in the tree view, the TreeView_SelectedIndexChanged subroutine executes. This subroutine assigns the text from the selected tree node to a Label control named lblSelectedNode. The TreeViewSelectEventArgs parameter has two properties: •
NewNode—Returns
a string representing the index of the selected node
•
OldNode—Returns
a string representing the index of the previous node
Notice that both the NewNode and OldNode properties return a string, not an integer. For example, if you select the first book under the ASP.NET Books node, the NewNode property returns the string value 0.0. If you select the second child node, the property returns the value 0.1. The string represents the location of the node in the tree view hierarchy. In Listing 8.2, the GetNodeFromIndex method returns a TreeView node from its index. After you have the node, you can display its label by using the node’s Text property.
Associating URLs with TreeView Nodes You can associate a URL with any TreeView node. This capability is useful, for example, when you need to create a tree view for navigating your Web site. Listing 8.3 demonstrates how you can use the NavigateUrl property to create a tree view that enables you to display links to other Web sites.
Using Third-Party Controls CHAPTER 8 LISTING 8.3
365
TreeViewNavigateUrl.aspx
TreeViewNavigateUrl.aspx
Associating Images with TreeView Nodes You can associate an image with each node in a tree view by using the TreeView control’s ImageUrl property. By also specifying an ExpandedImageUrl property, you display different images depending on whether a node is expanded or collapsed. The page in Listing 8.4, for example, uses the ImageUrl and ExpandedImageUrl properties to display different folder images next to each node (see Figure 8.2).
USING THIRDPARTY CONTROLS
8
366
Advanced ASP.NET Page Development PART II
FIGURE 8.2 Tree view with images.
LISTING 8.4
TreeViewImageUrl.aspx
TreeViewImageUrl.aspx
Using Third-Party Controls CHAPTER 8 LISTING 8.4
367
continued
If you want to display different images for different types of nodes, you can use the TreeNodeType control. For example, the page in Listing 8.5 displays different images for the parent and child nodes (see Figure 8.3). FIGURE 8.3 Tree view with multiple images.
8 USING THIRDPARTY CONTROLS
LISTING 8.5
TreeViewNodeType.aspx
TreeViewNodeType.aspx
The TreeNodeType control associates a particular image with a particular type. Any node declared with the specified type displays the associated image.
Associating Check Boxes with TreeView Nodes You can render a check box next to any node in a tree view by using the CheckBox property. You might, for example, want to use a tree view to display a hierarchical list of products for an online store. The page in Listing 8.6 demonstrates how you can display check boxes next to a list of ice cream flavors (see Figure 8.4).
Using Third-Party Controls CHAPTER 8
369
FIGURE 8.4 Tree view with check boxes.
8 TreeViewCheckBox.aspx
Sub Page_Load ShowChecked( treeIceCream.Nodes ) End Sub Sub ShowChecked( colNodes As TreeNodeCollection ) Dim tnNode As TreeNode For each tnNode in colNodes If tnNode.Checked = True Then lblCheckedNodes.Text &= “
The Page_Load subroutine in Listing 8.6 iterates recursively through each node in the tree view. If a node is checked, the value of the node’s Text property is appended to the text displayed by a Label control. The result that is every checked node is listed in the Label control.
Binding the TreeView Control to an External XML File Instead of specifying the nodes of a TreeView within the TreeView control itself, you can bind the TreeView control to an external XML file. To bind a TreeView to an XML file, you use the TreeNodeSrc property.
Using Third-Party Controls CHAPTER 8
371
The TreeView control in Listing 8.7, for example, is bound to an XML file named TreeNodes.xml. LISTING 8.7
TreeViewNodeSrc.aspx
TreeViewNodeSrc.aspx
8
LISTING 8.8
TreeNodes.xml
You also can bind XML files to individual nodes in a TreeView control. You can even bind a single TreeView control to multiple XML files, as illustrated in Listing 8.9. LISTING 8.9
TreeViewNodeSources.aspx
USING THIRDPARTY CONTROLS
The contents of the TreeNode.xml file, contained in Listing 8.8, are used to create all the nodes in the tree.
372
Advanced ASP.NET Page Development PART II LISTING 8.9
continued
TreeViewNodeSources.aspx
Notice that the TreeNodeSrc property is used with both TreeNode controls instead of the TreeView control. Also, notice that AutoPostBack is set to True. The TreeNode controls bind to the two XML files contained in Listings 8.10 and 8.11. LISTING 8.10
MicrosoftASPNET.xml
LISTING 8.11
CommunityASPNET.xml
Binding the TreeView Control to a Database Table The TreeView control does not directly support binding to a database table. If you want to display database data with the TreeView control, you must trick it into thinking that you are binding it to an XML file. The page in Listing 8.12 illustrates how you can bind the TreeView control to the Categories and Products tables in the Northwind database (see Figure 8.5). FIGURE 8.5 Tree view of the DataSet.
8 USING THIRDPARTY CONTROLS
LISTING 8.12
TreeViewDataSet.aspx
TreeViewDataSet.aspx
374
Advanced ASP.NET Page Development PART II LISTING 8.12
continued
In Listing 8.12, the TreeNodeSrc property binds the TreeView control to a file named Categories.aspx. The Categories.aspx file dynamically generates an XML file representing all the categories from the Categories table. The Categories.aspx file is included in Listing 8.13. LISTING 8.13
Categories.aspx
Sub Page_Load Dim conNorthwind As SqlConnection Dim cmdCategories As SqlCommand Dim dstCategories As DataSet Dim strQuery As String conNorthwind = New SqlConnection( “Server=localhost;UID=sa;PWD=secret; ➥Database=Northwind” ) strQuery = “select CategoryName As Text, ‘products.aspx?catid=’ ➥ + LTRIM( STR( CategoryID ) ) “ & _ “As TreeNodeSrc from Categories As TreeNode for xml auto, XMLDATA” cmdCategories = New SqlCommand( strQuery, conNorthwind ) conNorthwind.Open() dstCategories = New DataSet dstCategories.ReadXml( cmdCategories.ExecuteXmlReader(), ➥ XmlReadMode.Fragment ) dstCategories.DataSetName = “TREENODES” dstCategories.WriteXml( Response.OutputStream ) conNorthwind.Close() End Sub
The Categories.aspx file renders an XML file within its Page_Load subroutine. First, a SQL SELECT statement that contains a for xml clause is created (this clause works only
Using Third-Party Controls CHAPTER 8
375
with Microsoft SQL Server 2000 and higher). The SELECT statement retrieves the contents of the Categories database table as an XML document. Next, a DataSet is created to represent the database query’s results. Finally, the method is called to output the contents of the DataSet as XML.
WriteXml
When the Categories.aspx page is requested, it renders the following XML document:
Notice that each TreeNode contains a TreeNodeSrc attribute, which points to another file named Products.aspx. Furthermore, a query string parameter representing the category ID is passed to the file.
LISTING 8.14
Products.aspx
Sub Page_Load Dim conNorthwind As SqlConnection Dim cmdProducts As SqlCommand Dim dstProducts As DataSet Dim strQuery As String conNorthwind = New SqlConnection( “Server=localhost;UID=sa;PWD=secret; ➥Database=Northwind” ) strQuery = “select ProductName As Text from Products As TreeNode “ & _ “Where CategoryID=@categoryID for xml auto, XMLDATA “ cmdProducts = New SqlCommand( strQuery, conNorthwind ) cmdProducts.Parameters.Add( New SqlParameter( “@categoryID”, ➥ Request.QueryString( “catID” ) ) ) conNorthwind.Open() dstProducts = New DataSet dstProducts.ReadXml( cmdProducts.ExecuteXmlReader(), XmlReadMode.Fragment ) dstProducts.DataSetName = “TREENODES”
8 USING THIRDPARTY CONTROLS
The Products.aspx file is contained in Listing 8.14.
376
Advanced ASP.NET Page Development PART II LISTING 8.14
continued
dstProducts.WriteXml( Response.OutputStream ) conNorthwind.Close() End Sub
The Products.aspx page is similar to the Categories.aspx page. Like the Categories.aspx page, the Products.aspx page renders an XML document. The Products.aspx page displays product names from the Products database table. When you pass a category ID to the page, an XML document representing the products that match the indicated category is displayed.
Using the Toolbar Control The Toolbar control enables you to add a standard application toolbar to your ASP.NET pages. Like the TreeView control, the Toolbar control supports both up-level and downlevel clients. When used with Internet Explorer 5.5 or higher, the control uses DHTML behavior. When it is used with a down-level browser, such as Netscape, standard HTML 3.2–compliant content is generated. The Toolbar control itself acts as a container for one or more of the following controls: •
ToolbarButton—Renders
a standard image or text button
•
ToolbarCheckButton—Renders
•
ToolbarCheckGroup—Groups
•
ToolbarDropDownList—Renders
a button that can be selected or unselected
multiple ToolbarCheckButton controls and causes them to function like radio buttons (only one can be selected at a time) a drop-down list that can be used as a simple
menu •
ToolbarLabel—Renders
a text or image label
•
ToolbarSeparator—Renders
•
ToolbarTextBox—Renders
a separator between items in the Toolbar control
a text box
In the following sections, you learn how to assemble a toolbar from these elements.
Creating a Simple Toolbar To add a simple toolbar to a page (see Figure 8.6), you need to complete the following steps: 1. Import the proper namespace and register the Toolbar control. 2. Add a Toolbar control to your page.
Using Third-Party Controls CHAPTER 8
377
3. Add one or more ToolbarButton, ToolbarCheckButton, ToolbarCheckGroup, ToolbarDropDownList, ToolbarLabel, ToolbarSeparator, or ToolbarTextBox controls to the Toolbar control. FIGURE 8.6 A simple toolbar.
8
LISTING 8.15
SimpleToolbar.aspx
SimpleToolbar.aspx
Each ToolbarButton control in Listing 8.15 has a Text property that contains the label for the button. You can optionally supply an ImageUrl property, which adds an image to the button.
Handling Toolbar Events If you want to perform an action when a user interacts with the Toolbar control, you need to create subroutines that handle Toolbar events. The Toolbar control raises two events: •
ButtonClick—This
event is raised when you click a button in the toolbar.
•
CheckChange—This
event is raised when a ToolbarCheckButton changes state.
The page in Listing 8.16, for example, contains a Toolbar control that contains a ToolbarTextBox, a ToolbarButton, and three ToolbarCheckButton controls. (The three ToolbarCheckButton controls are grouped in a ToolbarCheckGroup control.) Clicking the different buttons modifies the text displayed in a Label control. Clicking the ToolbarButton control assigns new text to the Label control. Clicking the ToolbarCheckButton control changes the color of the text (see Figure 8.7).
Using Third-Party Controls CHAPTER 8
379
FIGURE 8.7 Capturing toolbar events.
8 ToolbarEvents.aspx
Sub Toolbar_ButtonClick( s As Object, e As EventArgs ) lblText.Text = txtSampleText.Text Select Case chkgColor.SelectedCheckButton.Text Case “Red” lblText.ForeColor = System.Drawing.Color.Red Case “Green” lblText.ForeColor = System.Drawing.Color.Green Case “Blue” lblText.ForeColor = System.Drawing.Color.Blue End Select End Sub ToolbarEvents.aspx
USING THIRDPARTY CONTROLS
LISTING 8.16
380
Advanced ASP.NET Page Development PART II LISTING 8.16
continued
When you click the ToolbarButton control or any of the ToolbarCheckButton controls, the Toolbar_ButtonClick subroutine is executed. This subroutine retrieves the text assigned to the ToolbarTextBox control and assigns it to a Label control named lblText. Next, the subroutine changes the foreground color of the Label control by using the SelectedCheckButton() method to retrieve the currently selected ToolbarCheckButton.
Formatting the Toolbar Control The Toolbar control supports standard style properties, such as Font, Width, Height, BorderWidth, and BorderColor. It also supports a special formatting property named Orientation, which enables you to create a vertical toolbar instead of the default horizontal toolbar. These formatting properties are illustrated by the page in Listing 8.17.
Using Third-Party Controls CHAPTER 8 LISTING 8.17
381
ToolbarStyle.aspx
ToolbarStyle.aspx
8 USING THIRDPARTY CONTROLS
The toolbar rendered by the page in Listing 8.17 is oriented vertically with an orange background. It also renders text with a 16-point Impact typeface.
Formatting Toolbar Buttons You can format the buttons displayed in a Toolbar control by assigning style attributes to the following properties: • •
DefaultStyle—The
style applied to the Toolbar button when the button is not selected and the mouse pointer is not hovering over the button
SelectedStyle—The
selected
style applied to the Toolbar button when the button is
382
Advanced ASP.NET Page Development PART II
•
HoverStyle—The
style applied to the Toolbar button when the mouse pointer is hovering over the button
The page in Listing 8.18, for example, displays unselected buttons with a blue font and selected buttons with a green font. When you hover the mouse pointer over a button, the button appears with a red font. LISTING 8.18
ToolbarButtonStyle.aspx
ToolbarButtonStyle.aspx
Using the TabStrip Control You can use the TabStrip control to create a group of tabs in an ASP.NET page. When used with the MultiPage control, the TabStrip control can display different pages of content when different tabs are selected.
Using Third-Party Controls CHAPTER 8
383
Like the TreeView and Toolbar controls, the TabStrip control renders different content on up-level and down-level browsers. When used with Internet Explorer 5.5 or higher, the TabStrip control uses DHTML behavior. When used with other browsers, such as Netscape Navigator, the TabStrip control renders standard HTML 3.2–compliant content. The TabStrip control contains one or more of the following controls: •
Tab—Creates
an individual tab in the TabStrip
•
TabSeparator—Separates
tabs in a TabStrip
Creating a Simple TabStrip To add a TabStrip to a page, you need to complete the following steps: 1. Import the proper namespace and register the TabStrip control. 2. Add a TabStrip control to your page. 3. Add one or more Tab or TabSeparator controls to the TabStrip control. If you want to display different pages of content when different tabs are selected, you also need to add a MultiPage control to your page.
FIGURE 8.8 A simple TabStrip.
USING THIRDPARTY CONTROLS
Listing 8.19 illustrates how you can add a simple TabStrip to an ASP.NET page (see Figure 8.8).
8
384
Advanced ASP.NET Page Development PART II LISTING 8.19
SimpleTabStrip.aspx
SimpleTabStrip.aspx Page 1 Page 2 Page 3
Using Third-Party Controls CHAPTER 8 LISTING 8.19
385
continued
The TabStrip in Listing 8.19 contains three Tab controls labeled Tab 1 through Tab 3. A TabSeparator control appears after each Tab control to prevent the tabs from being scrunched together. Finally, the TargetID property of the TabStrip control points to the MultiPage control named mpgPages. The MultiPage control contains three pages that correspond to each of the three tabs. The pages are contained in PageView controls. If you click a tab in an up-level browser, such as Internet Explorer 5.5 or higher, the current page is switched on the client. No roundtrip to the server is made. If you click a tab in a down-level browser, such as Netscape, on the other hand, a roundtrip is made.
Formatting a TabStrip
8
Warning The implementation of Cascading Style Sheets in Netscape Navigator is different from the implementation in Internet Explorer. Many of these style properties do not render correctly in the case of Netscape Navigator.
You can set the following style properties for the TabStrip control: •
Style—Specifies
the style attributes applied to the TabStrip control
•
TabDefaultStyle—Specifies
the style attributes applied to each Tab control by
default •
TabSelectedStyle—Specifies
•
TabHoverStyle—Specifies
the style attributes applied to the selected Tab
the style attributes applied to a Tab when the mouse
pointer hovers over it •
SepDefaultStyle—Specifies
control by default
the style attributes applied to each TabSeparator
USING THIRDPARTY CONTROLS
By default, a TabStrip looks pretty ugly. To make a nicer looking TabStrip, you need to set some style properties. Each TabStrip, Tab, and TabSeparator control has style properties that you can modify.
386
Advanced ASP.NET Page Development PART II
In Listing 8.19, for example, you set the style attributes to the following values: Style=”width:400px;font: bold 10pt Arial;height:100%” TabDefaultStyle=”width:50px;border:solid 1px blue;background:#dddddd; ➥padding:5px” TabHoverStyle=”color:red” TabSelectedStyle=”background:white;border-bottom:none” SepDefaultStyle=”width:10px;border-bottom:solid 1px blue;”
You also can set style attributes for individual Tab and TabSeparator controls in a TabStrip. Both the Tab and TabSeparator controls support the following style properties: • •
DefaultStyle—Specifies
the style attributes applied to the control by default
SelectedStyle—Specifies
the style attributes applied to the control when the con-
trol is selected •
HoverStyle—Specifies
the style attributes applied to a control when the mouse
pointer hovers over it
Creating a TabStrip with Images As an alternative to using plain text to label the tabs in a TabStrip, you can use images (see Figure 8.9). The page in Listing 8.20 illustrates how you can create images for both the default and selected tabs. FIGURE 8.9 TabStrip
images.
with
Using Third-Party Controls CHAPTER 8 LISTING 8.20
387
TabStripImage.aspx
TabStripImage.aspx
Page 1 Page 2 Page 3
8 USING THIRDPARTY CONTROLS
388
Advanced ASP.NET Page Development PART II LISTING 8.20
continued
Two properties of the Tab control set the image used for each tab. The DefaultImageUrl property specifies the default image for the tab, and the SelectedImageUrl property specifies the image used when the tab is selected. You can also, optionally, provide a HoverImageUrl that specifies an image to show when the mouse pointer hovers over a tab.
Creating a Vertical TabStrip By default, the tabs in a TabStrip are rendered horizontally. You can also create a vertical TabStrip by modifying the value of the TabStrip control’s Orientation property (see Figure 8.10). A vertical TabStrip is illustrated in Listing 8.21. FIGURE 8.10 Vertical TabStrip.
LISTING 8.21
TabStripVertical.aspx
Using Third-Party Controls CHAPTER 8 LISTING 8.21
389
continued
TabStripVertical.aspx
Page 1 Page 2 Page 3 8 USING THIRDPARTY CONTROLS 390 Advanced ASP.NET Page Development PART II LISTING 8.21 continued |
Age:
Birthdate:
406
Advanced ASP.NET Page Development PART II LISTING 8.36
continued
Comments:
The DataForm control in Listing 8.36 has both ConnectionString and TableName properties. The ConnectionString property enables the DataForm control to connect to a database. The TableName property indicates the name of the table to which the form data is saved. The DataForm control contains several DataTextBox controls. Each control has a DataField property, which maps the control to a database column. For example, when the form is saved, the contents of the first DataTextBox control are saved to the Customer column. Finally, the DataForm control has a FormSubmit event, which is raised when the is submitted. In Listing 8.36, the FormSubmit event is handled by a subroutine named Form_Submit. The Form_Submit subroutine calls the Save() method, which saves all the data entered into the DataForm. Next, the Form_Submit subroutine redirects the user to a page named ThankYou.aspx. DataForm
Adding DataForm Form Controls In the preceding section, you saw how to customize the appearance of the DataForm control by using DataTextBox controls. You also can add other standard form controls to the DataForm control. The DataForm control can act as a container for one or more of the following controls:
Using Third-Party Controls CHAPTER 8
•
DataTextBox
•
DataRadioButton
•
DataCheckBox
•
DataDropDownList
•
DataRadioButtonList
•
DataCheckBoxList
•
DataLabel
407
These controls extend the standard ASP.NET Web controls with three additional properties: •
DataField—The
name of a field from a database table to update when the DataForm is submitted
•
DataReadOnly—A
•
DataType—A SqlDBType
Boolean value indicating whether the value of this form field is used when updating a database table value indicating the SQL data type of the form field
Displaying Database Errors with the DataForm Control By default, if you use the DataForm control to submit form data to a database table and the database generates an error, the error message is hidden. To display a database error, you need to use the DataError property. The DataForm control’s Save() method returns a Boolean value indicating whether the operation of saving the form data was successful. You can use the DataError property to display the actual text of the database error that occurred. If you submit the form contained in Listing 8.37 with the value apple entered into the Age form field, for example, an error is displayed (see Figure 8.14).
8 USING THIRDPARTY CONTROLS
Typically, you need to specify the value of the DataField property only to update a database table with the DataForm control.
408
Advanced ASP.NET Page Development PART II
FIGURE 8.14 Displaying DataForm database errors.
LISTING 8.37
DataFormDataError.aspx
Sub Form_Submit( s As Object, e As EventArgs ) If dfmSurvey.Save() Then Response.Redirect( “ThankYou.aspx” ) Else lblError.Text = dfmSurvey.DataError End If End Sub DataFormDataError.aspx
Using Third-Party Controls CHAPTER 8 LISTING 8.37
409
continued
Customer:
Age:
Comments:
8 USING THIRDPARTY CONTROLS
Birthdate:
410
Advanced ASP.NET Page Development PART II
Automatically Updating a Database Table with the DataForm Control You also can use the DataForm control to update an existing record in a database table. To do so, you need to specify values for the following two properties: •
DataKeyField—The
name of a primary key field from the database table being
updated •
DataKeyValue—The
value of the primary key field from the database table being
updated Setting DataKeyField to the value Customer_ID and DataKeyValue to the value 3, for example, updates the third record in the CustomerSurvey table. The page in Listing 8.38 illustrates how to create a form that enables users to update the second record in the CustomerSurvey table. LISTING 8.38
DataFormUpdate.aspx
Sub Form_Submit( s As Object, e As EventArgs ) dfmSurvey.Update() Response.Redirect( “ThankYou.aspx” ) End Sub DataFormUpdate.aspx Customer:
Age:
Birthdate:
In Listing 8.38, the DataKeyField property is assigned the value Customer_ID, the DataKeyValue property is assigned the value 2, and the Update() method is called in the Form_Submit subroutine. The Update() method automatically updates the table specified by the TableName property.
Automatically Generating a Form with the DataForm Control You can use the DataForm control to automatically generate a form that corresponds to the columns in any database table. For example, suppose that you want to add new records to the Titles table in the Pubs database. The DataForm in Listing 8.39 automatically displays the necessary form.
8 USING THIRDPARTY CONTROLS
Comments:
412
Advanced ASP.NET Page Development PART II LISTING 8.39
DataFormAutoGenerate.aspx
DataFormAutoGenerate.aspx
The DataForm in Listing 8.39 automatically generates the form displayed in Figure 8.15. Notice that validation logic is automatically added to this form. For example, if a database table column does not allow Null values, data must be entered into the corresponding form field before the form can be submitted. If a database table column accepts integer values, the corresponding form field must contain an integer. If you attempt to submit the form without entering the proper values, error messages are automatically displayed. FIGURE 8.15 Automatically generating a form.
Using Third-Party Controls CHAPTER 8
413
In Listing 8.39, the Mode property is assigned the value AddRecord. The Mode property accepts the following values: •
AddRecord—Adds
a new record to the database table specified by the TableName
property. •
UpdateRecord—Updates
a record in the database table specified by the TableName property. The DataKeyField and DataKeyValue properties indicate the particular record to update.
•
UpdateTable—Enables
you to update any of the records in the database table specified by the TableName property.
•
Custom—Enables you to perform a custom action when the form is submitted by handling the DataForm control’s Submit event.
When you set Mode to UpdateTable, for example, you can update every record in a database table. The page in Listing 8.40 enables you to update any record in the Titles table (see Figure 8.16). LISTING 8.40
DataFormUpdateTable.aspx
DataFormUpdateTable.aspx
8 USING THIRDPARTY CONTROLS
414
Advanced ASP.NET Page Development PART II
FIGURE 8.16 Automatically updating a table.
Summary This chapter discussed two groups of third-party controls that you can use with the ASP.NET Framework. In the first half of this chapter, you learned how to use the Internet Explorer WebControls. You learned how to use the TreeView control to display a hierarchical menu of options and the Toolbar control to create an interactive button toolbar. You also learned how to use the TabStrip control to add text and image tabs to an ASP.NET page. In the second half of this chapter, you learned how to use three Superexpert controls. First, you learned how to randomly display content by using the Content Rotator control. Next, you learned how to use the SuperDataGrid control to automate the process of displaying, sorting, paging through, and editing data. Finally, you learned how to use the DataForm control to automate the process of saving form data to a database table.
Working with ADO.NET
PART
III IN THIS PART 9 Introduction to ADO.NET
417
10 Binding Data to Web Controls
473
11 Using the DataList and DataGrid Controls 12 Working with DataSets 13 Working with XML
513
587
637
14 Using ADO.NET to Create a Search Page
667
CHAPTER 9
Introduction to ADO.NET
IN THIS CHAPTER • An Overview of ADO.NET
418
• Performing Common Database Tasks 420 • Improving Database Performance • Advanced Database Topics
462
449
418
Working with ADO.NET PART III
Database access is a crucial component of almost any ASP.NET application. Fortunately, the ASP.NET framework includes a rich set of classes and controls for working with database data in your ASP.NET pages. This chapter introduces ADO.NET, the data access technology built into the .NET framework. It gives detailed explanations of the classes that you need to use most often in your ASP.NET pages. You learn how to retrieve records from a database table, insert new records, update records, and delete records. You also learn how to use the ADO.NET classes when working with Web forms. You learn how to build forms that enable you to insert and modify database records. Finally, this chapter delves into some of the more advanced features of ADO.NET. You learn how to increase the performance of your ADO.NET applications using stored procedures and connection pooling. You also learn how to execute commands in transactions and retrieve database table schema information. Note You can view “live” versions of many of the code samples in this chapter by visiting the Superexpert Web site: http://www.Superexpert.com/AspNetUnleashed/
An Overview of ADO.NET We’ll begin with a quick tour of ADO.NET. The .NET framework contains several namespaces with dozens of classes devoted to database access. However, this chapter concentrates on the classes from just two of the namespaces: System.Data.SqlClient and System.Data.OleDb. The System.Data.SqlClient namespace includes the following three classes: •
SqlConnection
•
SqlCommand
•
SqlDataReader
If you plan to build your ASP.NET application with Microsoft SQL Server (version 7.0 or higher), you’ll use these classes most often. These classes enable you to execute SQL statements and quickly retrieve data from a database query.
Introduction to ADO.NET CHAPTER 9
419
The SqlConnection class represents an open connection to a Microsoft SQL Server database. The SqlCommand class represents a SQL statement or stored procedure. Finally, the SqlDataReader class represents the results from a database query. The next section of this chapter goes into the details of using each of these classes. ASP Classic Note If you have used ActiveX Data Objects (ADO), these three classes should be familiar to you. The SqlConnection and SqlCommand classes are similar to the ActiveX Data Objects Connection and Command objects, with the important exception that they work only with Microsoft SQL Server. The SqlDataReader class is similar to an ActiveX Data Objects Recordset object opened with a fast, forward-only cursor. However, unlike Recordset, SqlDataReader does not support alternative cursor types, and it works only with Microsoft SQL Server.
The first group of classes, from the System.Data.SqlClient namespace, works only with Microsoft SQL Server. If you need to work with another type of database, such as an Access or Oracle database, you need to use the classes from the System.Data.OleDb namespace. The System.Data.OleDb namespace includes the following classes: •
OleDbConnection
•
OleDbCommand
•
OleDbDataReader
The OleDbConnection class represents an open database connection to a database, the OleDbCommand class represents a SQL statement or stored procedure, and the OleDbReader class represents the results from a database query. Why did Microsoft duplicate these classes, creating one version for SQL Server and one version for every other database? By creating two sets of classes, Microsoft was able to optimize one set of classes for SQL Server. The OleDb classes use OLE DB providers to connect to a database. The SQL classes, on the other hand, communicate with Microsoft SQL Server directly on the level of the Tabular Data Stream (TDS) protocol. TDS is the low-level proprietary protocol used by
INTRODUCTION TO ADO.NET
Notice that these classes have the same names as the ones in the previous group, except that these class names start with OleDb rather than Sql.
9
420
Working with ADO.NET PART III
SQL Server to handle client and server communication. By bypassing OLE DB and ODBC and working directly with TDS, you get dramatic performance benefits. Note You can use the classes from the System.Data.OleDb namespace with Microsoft SQL Server. You might want to do so if you want your ASP.NET page to be compatible with any database. For example, you might want your page to work with both Microsoft SQL Server and Oracle. However, you lose all the speed advantages of the SQL specific classes if you use the System.Data.OleDb namespace.
In general, the classes in the two namespaces duplicate the same functionality. Just remember to use the classes from the System.Data.SqlClient namespace when working with Microsoft SQL Server 7.0 and higher and use the classes from the System.Data.OleDb namespace when working with any other database. Note You examine another important set of ADO.NET classes in Chapter 12, “Working with Data Sets.” The classes in this group are used to represent memory-resident, disconnected database data. These classes include DataSet, DataTable, and DataAdapter.
Performing Common Database Tasks In the following sections, you learn how to perform common database tasks using ADO.NET. First, you learn how to create and open a database connection. Next, you learn how to retrieve and display database records, add new database records, update existing database records, and delete database records. You learn how to perform all these tasks using classes from both the System.Data.SqlClient and System.Data.OleDb namespaces. When you work with SQL Server, you need to import the System.Data.SqlClient namespace by adding the following page directive at the top of your ASP.NET page:
Introduction to ADO.NET CHAPTER 9
421
When working with other databases, such as Microsoft Access or Oracle databases, you need to import the System.Data.OleDb namespace by using the following page directive:
Opening a Database Connection To access a database, you first need to create and open a database connection. Once again, you create the connection in different ways depending on the type of database that you want to access. You would create and open a connection for a Microsoft SQL Server database as shown in Listing 9.1. LISTING 9.1
SqlConnection.aspx
Sub Page_Load Dim conPubs As SqlConnection conPubs = New SqlConnection( “Server=localhost;uid=sa;pwd=secret;database=pubs” ) conPubs.Open() End Sub Connection Opened!
9
The connection string contains all the necessary location and authentication information to connect to SQL Server. In Listing 9.1, the connection string contains the name of the server, name of the database, SQL Server login, and password.
INTRODUCTION TO ADO.NET
The first line in Listing 9.1 imports the necessary namespace, System.Data.SqlClient, for working with SQL Server. The connection to SQL Server is created and opened in the Page_Load subroutine. First, an instance of the SqlConnection class named conPubs is created. The conPubs class is initialized by passing a connection string as a parameter to the constructor for the SqlConnection class. Finally, the connection is actually opened by calling the Open() method of the SqlConnection class.
422
Working with ADO.NET PART III
Note You do not specify a Provider parameter for the connection string when using the SqlConnection class. The classes in the System.Data.SqlClient namespace do not use an OLE DB provider, ADO, ODBC, or any other intermediate interface to SQL Server. The classes work directly with the TDS stream. Furthermore, you cannot use a Data Source Name (DSN) when opening a connection with the SqlConnection class. If you really want to use a DSN with SQL Server, you must use the classes in the System.Data.OleDb namespace instead.
You would use similar code to create a connection to a Microsoft Access database. In Listing 9.2, a database connection is created and opened for a Microsoft Access database named Authors. (This Authors database is included on the CD that accompanies this book.) LISTING 9.2
OleDbConnection.aspx
Sub Page_Load( s As Object, e As EventArgs ) Dim conAuthors As OleDbConnection conAuthors = New OleDbConnection( “PROVIDER=Microsoft.Jet.OLEDB.4.0;DATA Source=c:\Authors.mdb” ) conAuthors.Open() End Sub Connection Opened!
Because you are creating a connection for Microsoft Access, you must import the System.Data.OleDb namespace rather than the System.Data.SqlClient namespace. Next, you must create an instance of the OleDbConnection class and initialize it with a connection string appropriate for Microsoft Access. Finally, calling the Open() method of the OleDbConnection class actually opens the database connection. In Listing 9.2, you pass the name of the OLE DB provider for Microsoft Access (Microsoft.Jet.OLEDB.4.0) and the path to the Access database on the server. If you want to connect to another type of database, you need to specify a different provider. For example, to connect to an Oracle database, you use the MSDAORA provider.
Introduction to ADO.NET CHAPTER 9
423
Note If you prefer, you can use a Data Source Name (DSN) with the OleDbConnection class to open a database connection. For example, after you create a System DSN named myDSN, you can connect using the following line: conPubs = New OleDbConnection( “DSN=myDSN” )
Realize, however, that opening a connection in this way forces you to use the OLE DB for ODBC provider rather than the native OLE DB provider for your database. Typically, but not always, this results in slower performance.
By default when you call the Open() method with either the SqlConnection or OleDbConnection class, the connection is given 15 seconds to open before timing out. You can override this default behavior by supplying a Connect_Timeout attribute in the connection string. For example, to allow up to 90 seconds for a connection to open in a SQL Server database, you would initialize the connection like this myConnection = New SqlConnection( “Server=localhost;UID=sa;PWD=secret;Connect ➥Timeout=90” )
Finally, when you are done using a database connection, closing it as quickly as possible is important. A database has a limited number of connections; closing the connection frees it so that it can be used for other pages. To close either a SqlConnection or OleDbConnection, use a statement like the following: myConnection.Close()
Retrieving Records from a Database Table
9
The SQL statement that you will use most often in your ASP.NET pages is Select. The Select statement enables you to retrieve records that match a certain condition from a database table. The syntax for a basic Select statement is as follows:
INTRODUCTION TO ADO.NET
SELECT column1, column2... FROM tablename1, tablname2... WHERE search condition
If you want to retrieve the au_fname and au_lname columns, for example, from the Authors table where the au_lname column has the value Smith, you would use the following Select statement: Select au_fname, au_lname FROM Authors WHERE au_lname = ‘Smith’
424
Working with ADO.NET PART III
Note Notice that you use single quotation marks to indicate the start and end of a string with SQL. This is unlike Visual Basic in which you use double quotation marks.
If you simply want to retrieve all the columns and all the rows from the Authors table, you would use the following Select statement: Select * FROM Authors
The asterisk (*) is a wildcard character that represents all the columns. If you don’t use a WHERE clause, all the rows from the Authors table are automatically returned. Follow these four steps to execute a Select statement in an ASP.NET page: 1. Create and open a database connection. 2. Create a database command that represents the SQL Select statement to execute. 3. Execute the command with the ExecuteReader() method returning a DataReader. 4. Loop through the DataReader displaying the results of the query. When you execute a query using ADO.NET, the results of that query are returned in a DataReader. More accurately, the results of that query are represented by either a SqlDataReader or OleDbDataReader, depending on the database from which you are retrieving the records. A DataReader represents a forward-only stream of database records. This means that the represents only a single record at a time. To fetch the next record in the stream, you must call the Read() method. To display all the records returned from a query, you must call the Read() method repeatedly until you reach the end of the stream. Once you pass a record, there’s no going back.
DataReader
ASP Classic Note If you have used earlier versions of the ADO, you might find it helpful to think of a DataReader as a Recordset opened with a forward-only cursor.
The ASP.NET page in Listing 9.3, for example, displays all the records from a SQL Server database table named Authors (see Figure 9.1).
Introduction to ADO.NET CHAPTER 9 LISTING 9.3
FIGURE 9.1 Displaying the contents of a SqlDataReader.
9 INTRODUCTION TO ADO.NET
The first line in Listing 9.3 imports the necessary namespace to use the ADO.NET classes for SQL Server. Next, a connection is created and opened for the database located on the local server named Pubs. After the database connection is opened, a SqlCommand object is initialized with a SQL string that contains a SQL Select statement. This statement retrieves all the records from a database table named Authors.
426
Working with ADO.NET PART III
Next, the command is executed by calling the ExecuteReader() method of the SqlCommand class. This method returns a SqlDataReader class that represents the results of executing the SQL Select statement. When you have a SqlDataReader, you need to loop through its contents to display all the records returned by the query. In Listing 9.3, this is accomplished with a While...End While loop. All the records returned by the Select statement are displayed with the following block of code: While dtrAuthors.Read() Response.Write( “
432
Working with ADO.NET PART III
You represent parameters in ADO.NET with either the SqlParameter class (in the case of Microsoft Sql Server) or the OleDbParameter class (in the case of every other database). A Command object has a parameters collection that represents all of its parameters. There are several ways you can create a new parameter and associate it with a Command. For example, the following two statements create and add a new parameter to the SqlCommand object: cmdSelect.Parameters.Add( “@firstname”, “Fred” ) cmdSelect.Parameters.Add( New SqlParameter( “@firstname”, “Fred” ) )
These two statements are completely equivalent. Both statements create a new SqlParameter with the name @firstname and the value Fred and add the new parameter to the parameters collection of the SqlCommand object. Notice that we do not specify the SQL data type of the parameter in the case of either statement. If you don’t specify the data type, it is automatically inferred from the value assigned to the parameter. For example, since the value Fred is a String, the SQL data type NVarchar is inferred. In the case of an OleDbParameter, the data type VarWChar would be automatically inferred. Note All the standard ASP.NET form controls, such as the TextBox control, return String values. If you do not explicitly specify the data type when creating a parameter, a String value will be interpreted as either a SqlClient NVarchar, or OleDb VarWChar data type.
In some cases, you’ll want to explicitly specify the data type of a parameter. For example, you might want to explicitly create a Varchar parameter instead of an NVarchar parameter. To do this, you can use the following statement: cmdSelect.Parameters.Add( “@lname”, SqlDbType.Varchar ).Value = “Johnson”
This statement specifies the SQL data type of the parameter by using a value from the SqlDbType enumeration. The SqlDbType enumeration is located in the System.Data namespace. Each of its values corresponds to a SQL data type. In the case of an OleDbParameter, you would use a value from the OleDbType enumeration like this: cmdSelect.Parameters.Add( “@lname”, OleDbType.Varchar ).Value = “Johnson”
Introduction to ADO.NET CHAPTER 9
433
The OleDbType enumeration can be found in the System.Data.OleDb namespace. Finally, you can specify the maximum size of a database parameter by using the following statement: cmdSelect.Parameters.Add( “@lname”, SqlDbType.Varchar, 15 ).Value = “Johnson”
This statement creates a parameter named @lname with a column size of 15 characters. If you don’t explicitly specify the maximum size of a parameter, the size is automatically inferred from the value assigned to the parameter.
Using Parameters with Microsoft SQL Server Queries When executing a SQL statement with the SqlCommand class, you represent parameters in the statement that you want to execute like this: Select phone From Authors Where au_fname = @firstname And au_lname = @lastname
In this statement, @firstname and @lastname represent parameters. You can assign different values to the parameters and execute the SQL Select statement, retrieving different phone numbers for different authors’ first and last names. The page in Listing 9.7, for example, demonstrates how you can execute this parameterized query from a Web form. You can enter the name Ann Dull and retrieve her phone number (see Figure 9.4). FIGURE 9.4 Executing a parameterized query.
9 INTRODUCTION TO ADO.NET
434
Working with ADO.NET PART III LISTING 9.7
SqlParameterSelect.aspx
Sub Button_Click( s As Object, e As EventArgs ) Dim conPubs As SqlConnection Dim strSelect As String Dim cmdSelect As SqlCommand conPubs = New SqlConnection( “server=localhost;UID=sa;PWD=secret;Database=Pubs” ) strSelect = “Select phone From Authors Where au_fname=@firstname And au_lname=@lastname” cmdSelect = New SqlCommand( strSelect, conPubs ) cmdSelect.Parameters.Add( “@firstname”, txtFirstname.Text ) cmdSelect.Parameters.Add( “@lastname”, txtLastname.Text ) conPubs.Open() lblPhone.Text = cmdSelect.ExecuteScalar() conPubs.Close() End Sub SqlParameterSelect.aspx Author Phone Lookup First Name:
Last Name:
Introduction to ADO.NET CHAPTER 9 LISTING 9.7
435
continued
Phone:
The page in Listing 9.8 contains two TextBox controls named txtFirstname and txtLastname. When you enter values into the two Textbox controls and click Lookup!, the Button_Click subroutine is executed. The Button_Click subroutine creates an instance of the SqlCommand class with a parameterized Select statement. Two instances of the SqlParameter class are created and added to the Parameters collection of the SqlCommand class. For example, the @firstname parameter is created and added with the following statement: cmdSelect.Parameters.Add( “@firstname”, txtFirstname.Text )
This statement creates a new SqlParameter named @firstname and adds it to the Parameters collection of the SqlCommand class. The parameter is created by passing its name and value. In this case, the value of the parameter is the value of the txtFirstname TextBox control.
cmdSelect.Parameters.Add( _ “@firstname”, SqlDbType.Varchar, 20 ).Value = txtFirstname.Text cmdSelect.Parameters.Add( _ “@lastname”, SqlDbType.Varchar, 20 ).Value = txtLastname.Text
These statements explicitly create Varchar parameters with a maximum size of 20 characters.
Using Parameters with Other Databases When using the classes from the System.Data.OleDb namespace, you need to create the parameters a little differently. For example, to retrieve a phone number for an author from the Authors table, you would write the SQL statement like this:
9 INTRODUCTION TO ADO.NET
In Listing 9.7, we allowed ADO.NET to infer the data type and size of both the and @lastname parameters. If we wanted to be explicit about the data type and size, we could have created the parameters like this:
@firstname
436
Working with ADO.NET PART III Select phone From Authors Where au_fname = ? And au_lname = ?
Notice that you use a ? character to represent the parameter instead of using a named parameter. The page in Listing 9.8 illustrates how you would use parameterized queries with a Microsoft Access database. You can lookup the phone number for Bertrand Russell. LISTING 9.8
OleDbParameterSelect.aspx
Sub Button_Click( s As Object, e As EventArgs ) Dim conAuthors As OleDbConnection Dim strSelect As String Dim cmdSelect As OleDbCommand conAuthors = New OleDbConnection( “PROVIDER=Microsoft.Jet.OLEDB.4.0;DATA Source=c:\Authors.mdb” ) strSelect = “Select phone From Authors Where au_fname=? And au_lname=?” cmdSelect = New OleDbCommand( strSelect, conAuthors ) cmdSelect.Parameters.Add( “@firstname”, txtFirstname.Text ) cmdSelect.Parameters.Add( “@lastname”, txtLastname.Text ) conAuthors.Open() lblPhone.Text = cmdSelect.ExecuteScalar() conAuthors.Close() End Sub OleDbParameterSelect.aspx Author Phone Lookup First Name:
Introduction to ADO.NET CHAPTER 9 LISTING 9.8
437
continued
Last Name:
Phone:
Adding Records to a Database You can add new records to a database table by using the SQL Insert command. The syntax for a basic Insert command is as follows: INSERT tablename ( column1, column2... ) VALUES ( value1, value2... )
INSERT Authors ( au_fname, au_lname ) VALUES ( ‘Bertrand’, ‘Russell’ )
Follow these three steps to execute a SQL Insert command in an ASP.NET page: 1. Create and open a database connection. 2. Create a database command that represents the SQL Insert statement to execute. 3. Execute the command with the ExecuteNonQuery() method. The ASP.NET page in Listing 9.9 uses the classes from the System.Data.SqlClient namespace to insert a new record into a SQL Server database table named Products.
9 INTRODUCTION TO ADO.NET
You insert new records into a table by listing the table columns and values that you want to insert into the columns. For example, imagine that you have a table named Authors that has both au_fname and au_lname columns. The following statement inserts a new author named Bertrand Russell:
438
Working with ADO.NET PART III LISTING 9.9 New Product Added!
The first statement in Listing 9.9 imports the necessary namespace to use the SqlClient classes. Next, a connection to a SQL database is initialized. Then, a variable named strInsert that has a SQL Insert statement as its value is created. The Insert statement inserts a new record into a table named Products. In the statement that follows, an instance of the SqlCommand class is created. This class is initialized with two parameters: the command to execute and the connection to use for executing the command. Finally, the command is executed by calling the ExecuteNonQuery() method of the SqlCommand class. This method sends the SQL command to the database server, and the database server executes the command. Notice that you use ExecuteNonQuery() rather than ExecuteReader() to execute the command. You need to use the ExecuteNonQuery() method because you are not returning any records from the database. The code in Listing 9.9 works only with Microsoft SQL Server (version 7.0 or higher). To use other databases, you need to modify the code to use the classes from the System.Data.OleDb namespace rather than the System.Data.SqlClient namespace. The ASP.NET page in Listing 9.10 demonstrates how you would add a new record to a Microsoft Access database table.
Introduction to ADO.NET CHAPTER 9 LISTING 9.10 New Author Added!
The code in Listing 9.10 executes an Insert statement that adds a new record to a Microsoft Access table named Authors.mdb. Notice that Microsoft Access requires you to use the keyword Into with the Insert statement. (You use Insert Into Authors rather than Insert Authors.) Note The apostrophe character (‘) can cause problems when you’re inserting data into a database table. For example, imagine that you want to add a new author named O’Leary to the Authors table. You might try to execute the following statement: This statement generates an error because the apostrophe in O’Leary is interpreted as marking the end of the SQL string. To get around this problem, you need to double up your apostrophes. For example, use the following statement to add O’Leary to a database table: INSERT INTO Authors ( Author ) Values ( ‘O’’Leary’ )
Creating a Form to Insert New Records Typically, you insert new records into a database table by using a Web form. The page in Listing 9.11 illustrates how you can create a form that enables you to add new products to the Products database table.
9 INTRODUCTION TO ADO.NET
INSERT INTO Authors ( Author ) Values ( ‘O’Leary’ )
440
Working with ADO.NET PART III LISTING 9.11
SqlFormInsert.aspx
Sub Button_Click( s As Object, e As EventArgs ) Dim conNorthwind As SqlConnection Dim strInsert As String Dim cmdInsert As SqlCommand conNorthwind = New SqlConnection( “Server=localhost;UID=sa;PWD=secret;database=Northwind” ) strInsert = “Insert Products ( ProductName, UnitPrice ) Values ( @ProductName, @UnitPrice )” cmdInsert = New SqlCommand( strInsert, conNorthwind ) cmdInsert.Parameters.Add( “@ProductName”, txtProductName.Text ) cmdInsert.Parameters.Add( “@UnitPrice”, SqlDbType.Money ).Value = txtUnitPrice.Text conNorthwind.Open() cmdINsert.ExecuteNonQuery() conNorthwind.Close() End Sub SqlFormInsert.aspx Add New Product Product Name:
Unit Price:
Introduction to ADO.NET CHAPTER 9
441
The form in Listing 9.11 has two TextBox controls named txtProductName and txtUnitPrice. When you click the Add! Button, the Button_Click subroutine is executed, and the values entered into the two TextBox controls are inserted into the Products database table. Notice that the data type for the @UnitPrice parameter is explicitly specified. If the data type were not explicitly specified, the SqlCommand would attempt to insert the value of the txtUnitPrice TextBox as an NVarchar value. This would generate an error since SQL Server cannot automatically convert an NVarchar value to a Money value.
Updating Database Records To update existing records in a database table, you use the SQL Update command. The syntax for the basic Update command is as follows: UPDATE tablename SET column1 = value1, column2 = value2... WHERE search condition
You update a table by setting certain columns to certain values where a certain search condition is true. For example, imagine that you have a database table named Authors that has a column named au_lname. The following statement sets the value of the au_lname column to Smith wherever the column has a value of Bennet: UPDATE Authors SET au_lname = ‘Smith’ WHERE au_lname = ‘Bennet’
Updating a row that doesn’t exist does not raise an error. You execute an Update command within an ASP.NET page by completing the following steps:
2. Create a database command that represents the SQL Update statement to execute. 3. Execute the command with the ExecuteNonQuery() method. The ASP.NET page contained in Listing 9.12, for example, updates a record in a SQL Server database table named Authors. LISTING 9.12 Author Updated!
The first statement in Listing 9.12 imports the necessary namespace for working with the SQL ADO.NET classes. Next, a database connection is initialized with the correct connection string for the local server. In the statement that follows, an instance of the SqlCommand class is created by passing a SQL Update command and SqlConnection to the constructor for the class. The SQL Update command is executed when the ExecuteNonQuery() method of the class is called. At this point, the Update statement is transmitted to SQL Server and executed.
SqlCommand
The code in Listing 9.12 works only with Microsoft SQL Server (version 7.0 and higher). If you want to update a record in another type of database, you need to use the ADO.NET classes from the System.Data.OleDb namespace rather than the System.Data.SqlClient namespace. The ASP.NET page contained in Listing 9.13 modifies a record in a Microsoft Access database. LISTING 9.13 Author Updated!
Introduction to ADO.NET CHAPTER 9
443
One difference between the SQL Update and Insert commands is that the SQL Update command might affect more than one record at a time. When you execute an Update command, it changes every record that satisfies the command’s WHERE clause. You can determine the number of records affected by an Update command within an ASP.NET page by grabbing the value returned by the ExecuteNonQuery() method. The page contained in Listing 9.14 illustrates this method. LISTING 9.14 Records Updated!
Creating a Form to Update Records
LISTING 9.15
SqlFormUpdate.aspx
Sub Button_Click( s As Object, e As EventArgs ) Dim conPubs As SqlConnection Dim strUpdate As String Dim cmdUpdate As SqlCommand Dim intUpdateCount As Integer conPubs = New SqlConnection( “Server=localhost;UID=sa;PWD=secret;database=Pubs” ) strUpdate = “Update Authors Set phone=@phone Where au_fname=@firstname And
9 INTRODUCTION TO ADO.NET
Typically, you execute a SQL Update statement from a Web form. The page in Listing 9.15 illustrates how you can create a Web form that enables you to update records in the Authors database table.
444
Working with ADO.NET PART III LISTING 9.15
continued
au_lname=@lastname” cmdUpdate = New SqlCommand( strUpdate, conPubs ) cmdUpdate.Parameters.Add( “@phone”, txtPhone.Text ) cmdUpdate.Parameters.Add( “@firstname”, txtFirstName.Text ) cmdUpdate.Parameters.Add( “@lastname”, txtLastName.Text ) conPubs.Open() intUpdateCount = cmdUpdate.ExecuteNonQuery() conPubs.Close() lblResults.Text = intUpdateCount & “ records updated!” End Sub SqlFormUpdate.aspx Update Phone Number First Name:
Last Name:
New Phone:
Introduction to ADO.NET CHAPTER 9
445
The form in Listing 9.15 has three TextBox controls: one for a first name, last name, and new phone number. When you click the Update Phone Number! button, the Button_Click subroutine is executed, and the phone number for the proper author is updated. Finally, the number of records updated is retrieved from the ExecuteNonQuery() method of the SqlCommand class. This result is assigned to a Label control (see Figure 9.5). FIGURE 9.5 Updating database records.
You delete data from a database by using the SQL Delete statement. The syntax for a basic Delete statement is as follows: DELETE tablename WHERE search condition
If, for example, you want to delete all the rows from a table named Authors where the au_lname column has the value Bennet, you would use the following statement: DELETE Authors WHERE au_lname = ‘Bennet’
Deleting rows that do not exist does not result in an error. To execute a SQL Delete statement from within an ASP.NET page, you must complete the following steps:
9 INTRODUCTION TO ADO.NET
Deleting Database Records
446
Working with ADO.NET PART III
1. Create and open a database connection. 2. Create a database command that represents the SQL Delete statement to execute. 3. Execute the command by calling the ExecuteNonQuery() method. The ASP.NET page in Listing 9.16, for example, demonstrates how you can delete a record from a SQL Server database table named Authors. LISTING 9.16 Records Deleted!
The first line in Listing 9.16 imports the necessary namespace to work with SQL Server. Next, a connection to the SQL Server running on the local machine is initialized. The SqlCommand class is initialized with two parameters: a SQL Delete statement and an instance of the SqlConnection class. Next, the connection is opened, the command is executed by calling ExecuteNonQuery(), and the connection is closed. If you need to work with a database other than Microsoft SQL Server, you would use similar code. However, you must use the classes from the System.Data.OleDb namespace rather than the classes from System.Data.SqlClient. The page contained in Listing 9.17 illustrates how you would delete a record from a Microsoft Access database table named Authors. LISTING 9.17 Records Deleted!
Notice that you must use Delete Microsoft Access database.
From
rather than just Delete when working with a
A SQL Delete statement is similar to a SQL Update statement in that it might affect an unknown number of records. A SQL Delete statement deletes all the records that match the condition specified by the command’s WHERE clause. If you need to determine the number of records affected by a Delete statement, you can grab the value returned by the ExecuteNonQuery() method. The page contained in Listing 9.18 illustrates how to do so. LISTING 9.18 Records Deleted!
448
Working with ADO.NET PART III
FIGURE 9.6 Deleting database records.
LISTING 9.19
SqlFormDelete.aspx
Sub Button_Click( s As Object, e As EventArgs ) Dim conPubs As SqlConnection Dim strDelete As String Dim cmdDelete As SqlCommand Dim intDeleteCount As Integer conPubs = New SqlConnection( “Server=localhost;UID=sa;PWD=secret;database=Pubs” ) strDelete = “Delete Authors Where au_lname=@lastname” cmdDelete = New SqlCommand( strDelete, conPubs ) cmdDelete.Parameters.Add( “@lastname”, txtLastName.Text ) conPubs.Open() intDeleteCount = cmdDelete.ExecuteNonQuery() conPubs.Close() lblResults.Text = intDeleteCount & “ records deleted!” End Sub SqlFormDelete.aspx
Introduction to ADO.NET CHAPTER 9 LISTING 9.19
449
continued
Delete Authors Last Name:
The form contains one TextBox control named txtLastName. When you click the Delete Author! button, the Button_Click subroutine is executed, and the author with the specified last name is deleted from the Authors database table. A count of the number of authors deleted is retrieved from the ExecuteNonQuery() method and assigned to a Label control named lblResults.
Improving Database Performance
Next, you look at both OLE DB and SQL connection pooling. You learn how to configure and take advantage of connection pooling in your Web applications to open database connections faster.
Using SQL Stored Procedures You can execute a SQL statement from within an ASP.NET page in two ways. You can execute the statement directly from code in the page, or you can package the SQL statement as a stored procedure and execute the stored procedure from the page.
9 INTRODUCTION TO ADO.NET
In the following sections, you learn two methods of increasing the database performance of your ASP.NET pages. First, you learn how to use SQL stored procedures from an ASP.NET page. By packaging your SQL statements in stored procedures, you get faster database performance.
450
Working with ADO.NET PART III
Building stored procedures takes a little more work than executing SQL statements directly on a page. However, you can significantly increase the performance of a database-driven Web site by using stored procedures. A SQL statement must be parsed, compiled, and optimized by SQL Server whenever it is executed from an ASP.NET page. A stored procedure, on the other hand, needs to be parsed, compiled, and optimized only once. Another advantage of using stored procedures is that you can package multiple SQL statements in a stored procedure and execute them as a group. For example, you can create one stored procedure that contains multiple SQL Update statements so that you can update the records in multiple tables at once. You can use all the programming features of the Transact-SQL language when building stored procedures. Transact-SQL includes support for variables, conditionals, loops and functions. Using these features, you can build very complex mini-programs within a stored procedure. Stored procedures can also be used to shield your ASP.NET pages from the particular implementation of the tables in your database. If you make changes in the database tables that you use in your application, you can change your stored procedures without making any changes to your ASP.NET pages. In any case, after you set up the parameters for a SQL command, executing the command as a stored procedure requires a trivial amount of work. We’ll start with a page that doesn’t use a stored procedure and modify it to use one. Listing 9.20 contains a simple ASP.NET page that inserts a new record into the Products table. LISTING 9.20 New Product Added!
Introduction to ADO.NET CHAPTER 9
451
The page in Listing 9.20 adds a new product to the Products database table. The new record is added by passing values for the ProductName and UnitPrice table columns. To modify the page in Listing 9.20 so that it uses a stored procedure, you need to complete the following four steps: 1. Create a SQL stored procedure that contains the statement that you want to execute. 2. Import the System.Data namespace. 3. Pass the name of the stored procedure to the instance of the SqlCommand class. 4. Set the CommandType property of the SqlCommand class to the value CommandType.StoredProcedure. The first step is to actually create the SQL stored procedure. You can create a stored procedure by using either the Microsoft SQL Server Enterprise Manager or Query Analyzer. For this example, use Query Analyzer. Run Query Analyzer by going to Start, Programs, Microsoft SQL Server, and then Query Analyzer. Enter your login name and password, and select from the drop-down list the database where you want to create your stored procedure. Next, type the following statement in the Query window: Create Procedure InsertProducts ( @ProductName NVarchar( 80 ), @UnitPrice Money ) As Insert Products ( ProductName, UnitPrice ) Values ( @ProductName, @UnitPrice )
The InsertProducts stored procedure has two input parameters named @ProductName and @UnitPrice. The @ProductName parameter is declared as an NVarchar parameter with a maximum length of 80 characters. The @UnitPrice parameter is declared as a Money parameter. The body of the stored procedure appears after the As keyword. The InsertProducts stored procedure simply executes a SQL Insert statement using the two parameters.
INTRODUCTION TO ADO.NET
After you enter this statement, click the Execute Query button (the green VCR play button) to execute the statement and create a new stored procedure named InsertProducts (see Figure 9.7).
9
452
Working with ADO.NET PART III
FIGURE 9.7 Creating a stored procedure with Query Analyzer.
You need to make three modifications to the page in Listing 9.20 to execute your InsertProducts stored procedure. The modified page appears in Listing 9.21. LISTING 9.21 New Product Added!
Introduction to ADO.NET CHAPTER 9
453
First, notice that you import the System.Data namespace on the second line of the page. You need to import the System.Data namespace to use the CommandType enumeration that you use later in the page. Next, when initializing the SqlCommand class, you pass the name of the InsertProduct stored procedure. When you execute the command, this stored procedure will be executed. Finally, you set the CommandType property of the SqlCommand class to the value CommandType.StoredProcedure. Setting the CommandType causes the command to be interpreted as the name of a stored procedure rather than a standard SQL statement. (The default value of CommandType is Text.) You also can use stored procedures when executing queries with a SQL Select statement and a DataReader. The steps for doing so are exactly the same. For example, the page in Listing 9.22 displays the value of the au_lname column for all the records in the Authors database table. LISTING 9.22
454
Working with ADO.NET PART III
The SelectAuthors stored procedure takes no parameters. It simply returns all the author last names from the Authors table. The SelectAuthors stored procedure can be created with the following statement: Create Procedure SelectAuthors As Select au_lname From Authors
Retrieving Return Values and Output Parameters In the preceding section, you saw how you can use input parameters with a stored procedure to pass values to a stored procedure from an ASP.NET page. SQL stored procedures also support return values and output parameters for passing information back from a stored procedure.
Using Return Values Every stored procedure exits with a return value, even if you don’t capture it. By default, every stored procedure returns the value 0. However, you can return any integer you please from a stored procedure by using the Return statement. Suppose that you want to retrieve a count of the number of records in the Authors database table by using a stored procedure. One way to do so would be to create the following stored procedure: Create Procedure GetAuthorCount As Return ( Select Count(*) From Authors )
The GetAuthorCount stored procedure returns a count of the number of rows in the Authors database table. This value is passed back from the Return statement. Warning You can use only integers with Return values. You cannot return a Null value.
After you create the GetAuthorCount stored procedure, you can use it in an ASP.NET page by creating a Return parameter. The page in Listing 9.23 illustrates how to do so. LISTING 9.23
SqlGetAuthorCount.aspx
There are records in the Authors table
In Listing 9.23, an instance of the SqlParameter class is created to represent the return value from the GetAuthorCount stored procedure. This parameter is created with the following two statements: parmReturnValue = cmdAuthorCount.Parameters.Add( “ReturnValue”, SqlDbType.Int ) parmReturnValue.Direction = ParameterDirection.ReturnValue
The first statement creates a parameter named ReturnValue with a SQL Int data type. This parameter is added to the Parameters collection of the SqlCommand class and assigned to a variable named parmReturnValue.
Using Output Parameters Using output parameters is similar to using return values. However, output parameters have a couple of important advantages. You can use an output parameter to pass Varchar, Int, Money, or values of any other data type from a stored procedure. A return value, on the other hand, can return only integer values. Another advantage of output parameters is that you can include multiple output parameters in a stored procedure. A Microsoft SQL Server stored procedure can have a maximum of 1,024 parameters (including both input and output parameters), but it can have only one return value.
9 INTRODUCTION TO ADO.NET
The second line assigns a direction to the parameter. By default, parameters are input parameters. Because you want your parameter to represent a return value, you must assign the value ParameterDirection.ReturnValue to the parameter’s Direction property.
456
Working with ADO.NET PART III
The following stored procedure, for example, can be used to retrieve an author’s last name given the author’s first name. The stored procedure has one input parameter named @firstname and one output parameter named @lastname. (The output parameter is marked with the special SQL keyword Output.) Create Procedure GetLastname ( @firstname Varchar( 20 ), @lastname Varchar( 20 ) Output ) As Select @lastname = au_lname From Authors Where au_fname = @firstname
The page in Listing 9.24 illustrates how you would use this stored procedure in an ASP.NET page. LISTING 9.24 The last name is
In Listing 9.24, the @lastname output parameter is declared with three lines of code. The first line creates the parameter, assigns the parameter a Varchar data type, and adds the parameter to the Parameters collection of the SqlCommand class. The next line specifies the maximum size of the output parameter. Because the @lastname parameter can return 40 characters, the Size property is assigned the value 40. Finally, the direction of the parameter, ParameterDirection.Output, is assigned to the Direction property. After the command is executed, you can capture the value of the output parameter from the Parameters collection. Notice how the IsDBNull() method is used to check whether the output parameter has a value. You don’t want to attempt to assign the value of the parameter to a string variable if nothing is returned. You want to assign the value of the output parameter to the strLastname variable only when an author with that first name exists.
Executing Complex Stored Procedures One important benefit of using stored procedures is that they enable you to package together multiple SQL statements and execute them as a group. In this section, you examine two ASP.NET code samples that use multiple SQL statements.
Create Procedure AddProduct ( @ProductName NVarchar( 80 ), @UnitPrice Money ) As Insert Products ( ProductName, UnitPrice ) Values ( @ProductName, @UnitPrice ) Return @@Identity
9 INTRODUCTION TO ADO.NET
The first sample demonstrates how to use a stored procedure to get the identity value of a new row entered into a table. The following stored procedure adds a new product to the Products database table and returns the value of the identity column for the new record entered in the table:
458
Working with ADO.NET PART III
The @@Identity global variable contains the last value inserted into a SQL table’s identity column. The AddProduct stored procedure adds a new product to the Products table and returns the value of the @@Identity variable. The page in Listing 9.25 illustrates how you would use the AddProduct stored procedure from an ASP.NET page (see Figure 9.8). FIGURE 9.8 Retrieving the @@Identity value.
LISTING 9.25 The ID for the new product is
The page in Listing 9.25 contains three parameters: two input parameters and a return value. The two input parameters represent the new product name and price. The return value retrieves the value of the identity column when the new product is inserted into the database table. Another situation in which using a stored procedure to execute multiple statements is valuable concerns inserting nonduplicate records in a database. Suppose that you want to insert a product into the Products table only if a record for the product doesn’t already exist. The following stored procedure uses the SQL Exists keyword to check for the existence of a product before inserting a record:
The AddUniqueProduct stored procedure checks whether a product with a product name already exists by using the Exists keyword. If Exists returns True, the stored procedure exits with the value 1. If the product doesn’t already exist, the product is added and the stored procedure exits with its default value of 0.
9 INTRODUCTION TO ADO.NET
Create Procedure AddUniqueProduct ( @ProductName NVarchar( 80 ), @UnitPrice Money ) As If Exists ( Select ProductName From Products Where ProductName = @ProductName ) Return 1 Else Insert Products ( ProductName, UnitPrice ) Values ( @ProductName, @UnitPrice )
460
Working with ADO.NET PART III
The page in Listing 9.26 illustrates how to use this stored procedure from within an ASP.NET page. LISTING 9.26
The page in Listing 9.26 passes two input parameters to the addUniqueProduct stored procedure and retrieves the return value. If the return value is 0, the message New Product Added! is displayed. Otherwise, the message Product already exists! is displayed.
Introduction to ADO.NET CHAPTER 9
461
Improving Performance with Connection Pooling Opening a connection is a database-intensive task. It can be one of the slowest operations that you perform in an ASP.NET page. Furthermore, a database has a limited supply of connections, and each connection requires a certain amount of memory overhead (approximately 40 kilobytes per connection). If you plan to have hundreds of users hitting your Web site simultaneously, the process of opening a database connection for each user can have a severe impact on the performance of your Web site. Fortunately, you can safely ignore these dire warnings if you take advantage of connection pooling. When database connections are pooled, a set of connections is kept open so that they can be shared among multiple users. When you request a new connection, an active connection is removed from the pool. When you close the connection, the connection is placed back in the pool. Connection pooling is enabled for both OleDb and SqlClient connections by default. OleDb connection pooling is handled by the OLE DB .NET provider. SqlClient connection pooling is handled by Windows 2000 component services. To take advantage of connection pooling, you must be careful to do two things in your ASP.NET pages. First, you must be careful to use the same exact connection string whenever you open a database connection. Only those connections opened with the same connection string can be placed in the same connection pool.
Note The web.config file and Application state are both discussed in Chapter 15, “Creating ASP.NET Applications.”
9 INTRODUCTION TO ADO.NET
Realize that even very small differences in the connection string can thwart connection pooling. Connections are pooled only when they are opened with connection strings that exactly match character by character. For this reason, it is wise to create your connection string in one place and use the same connection string within all your ASP.NET pages. For example, you can place your connection string in the web.config file and retrieve it from this file whenever you need to open a connection. Another option is to place the connection string in Application state within the Global.asax file.
462
Working with ADO.NET PART III
To take advantage of connection pooling in your ASP.NET pages, you also must be careful to explicitly close whatever connection you open as quickly as possible. If you do not explicitly close a connection with the Close() method, the connection is never added back to the connection pool. When using SQL connection pooling, you can place additional options in a connection string to modify how connection pooling works. For example, you can specify the minimum and maximum size of the connection pool or even completely disable connection pooling. Here’s a list of the connection pooling options that you can add to the SQL Server connection string: •
Connection Lifetime Destroys a connection after a certain number of seconds. The default value is 0, which indicates that connections should never be destroyed.
•
Connection Reset
•
Enlist Indicates whether a connection should be automatically enlisted in the current transaction context. The default value is true.
•
Max Pool Size
•
Min Pool Size
•
Pooling
Indicates whether connections should be reset when they are returned to the pool. The default value is true.
The maximum number of connections allowed in a single connection pool. The default value is 100. The minimum number of connections allowed in a single connection pool. The default value is 0.
Determines whether connection pooling is enabled or disabled. The default value is true.
Advanced Database Topics In the following sections, we explore three advanced database topics. You learn how to execute database commands in the context of a transaction. You also learn how to modify the behavior of a database command by specifying a CommandBehavior. Finally, you learn how to retrieve schema information from a database table.
Executing Database Commands in a Transaction A database transaction is a series of statements that either succeed or fail as a whole. The standard example of a transaction is transferring money between two bank accounts. If one of the following two statements fail, it would be better if both statements fail: Update BankAccountA Set Balance = Balance - 9999.99 WHERE Customer=’Smith’ Update BankAccountB Set Balance = Balance + 9999.99 WHERE Customer=’Smith’
Introduction to ADO.NET CHAPTER 9
463
The first statement removes $9,999.99 from the balance of bank account A. The second statement adds this amount to the balance of bank account B. If the first statement executes, but the second statement fails, the customer is going to be very unhappy. If the first statement fails, but the second statement executes, the bank is going to be very unhappy. To keep both parties happy, it would be better to execute these statements as a transaction. There are three approaches that you can take to creating transactions. You can create transactions at the database level, at the level of ADO.NET, or at the level of a whole ASP.NET page.
Creating a Database Transaction Microsoft SQL Server itself supports transactions through the BEGIN TRANSACTION, COMMIT TRANSACTION, and ROLLBACK TRANSACTION statements. For example, the following SQL Stored procedure updates Account A and Account B in a transaction: Create Procedure UpdateAccounts As BEGIN TRANSACTION Update AccountA Set Balance = Balance - 999.99 WHERE Customer=’Smith’ Update AccountB Set Balance = Balance + 999.99 WHERE Customer=’Smith’ COMMIT TRANSACTION
If someone unplugs your database server after the first update statement but before the second update statement, the first statement will be automatically rolled back (when the server restarts). In other words, any modifications made by the first statement will be undone. By using transactions in this way, you can prevent your tables from containing inconsistent data.
ADO.NET also supports transactions through the Connection and Transaction classes. You create a new transaction with the BeginTransaction method of the Connection class. You can then associate the transaction with multiple commands with the Transaction property of the Command class. For example, the page in Listing 9.27 contains two commands that are executed within a single transaction. If either command fails, both commands will fail. LISTING 9.27
SqlTransaction.aspx
INTRODUCTION TO ADO.NET
Creating an ADO.NET Transaction
9
464
Working with ADO.NET PART III LISTING 9.27
Introduction to ADO.NET CHAPTER 9
465
Creating an ASP.NET Page Transaction Finally, you can create a transaction at the level of an ASP.NET page. You can enroll an ASP.NET page in a transaction by adding one of the following page directives to the ASP.NET page: •
Disabled
Transactions are disabled for the page. This is the default value.
•
NotSupported
•
Supported
•
Required If a transaction already exists, the page will execute within the context of the transaction. If a transaction does not exist, it will create a new one.
•
RequiresNew
Indicates that the page does not execute within a transaction.
If a transaction already exists, the page will execute within the context of the transaction. However, it will not create a new transaction.
Creates a new transaction for each request.
After you enable transactions for an ASP.NET page, you can use two methods from the ContextUtil class to explicitly commit or rollback a transaction: the SetComplete and SetAbort methods. The page in Listing 9.28 illustrates how to use these methods: LISTING 9.28
ASPTransaction.aspx
Transaction=”RequiresNew” %> Import Namespace=”System.EnterpriseServices” %> Import Namespace=”System.Data” %> Import Namespace=”System.Data.SqlClient” %>
Authors table added to DataSet!
DataSets
dstAuthors As DataSet conPubs As SqlConnection dadAuthors As SqlDataAdapter
12 WORKING
0’ )
The first parameter passed to the OPENQUERY function represents the linked server to perform the query against, and the second parameter represents the actual query. This query retrieves a list of all the indexed documents that contain the phrase ASP.NET.
Retrieving Document Properties When you perform queries by using the Indexing Service, you can return any of a number of standard document properties. For example, the following query returns the document filename, document file size, and author name for each document that matches the search expression:
The Indexing Service supports several standard properties. However, additional custom properties can be added by new applications. A list of standard properties appears in Table 14.1. (This table contains a partial list; see the Windows 2000 Help file for a complete list.) TABLE 14.1
Standard Document Properties
Access
Date and time the document was last accessed.
Characterization
Summary of the document automatically generated by the Indexing Service.
Created
Date and time the document was created.
14 USING ADO.NET TO CREATE A SEARCH PAGE
SELECT * FROM OPENQUERY(FileSystem, ‘SELECT FileName, Size, DocAuthor FROM SCOPE() WHERE FREETEXT(‘’ASP.NET’’) > 0 ORDER BY RANK DESC’ )
696
Working with ADO.NET PART III TABLE 14.1
continued
Directory
Physical path to the document, not including the document name.
DocAppName
Name of the application that created the document.
DocAuthor
Author of the document.
DocByteCount
Number of bytes in the document.
DocCategory
Type of document (such as a memo, schedule, or white paper).
DocCharCount
Number of characters in the document.
DocComments
Comments about the document.
DocCompany
Name of the company for which the document was written.
DocCreatedTm
Time the document was created.
DocEditTime
Total time spent editing the document.
DocHiddenCount
Number of hidden slides in a Microsoft PowerPoint document.
DocKeywords
Document keywords.
DocLastAuthor
User who edited the document most recently.
DocLastPrinted
Time the document was last printed.
DocLastSavedTm
Time the document was last saved.
DocLineCount
Number of lines contained in the document.
DocPageCount
Number of pages in the document.
DocParaCount
Number of paragraphs in the document.
DocPartTitles
Names of document parts, such as spreadsheet names in a Microsoft Excel document or slide titles in a Microsoft PowerPoint slide show.
DocSubject
Subject of the document.
DocTitle
Title of the document.
DocWordCount
Number of words in the document.
FileIndex
Unique ID of the document.
FileName
Name of the document.
HitCount
Number of hits (elements in the results list) in the document.
Path
Full physical path to the document, including the document name.
Rank
Rank of how well an item in a result list matches query criteria. The range is from 0 to 1000; larger numbers indicate better matches.
ShortFileName
Short (8.3 format) document name.
Size
Size of the document, in bytes.
VPath
Full virtual path to the document, including the document name. If more than one path is possible, the best match for the specific query is chosen.
WorkId
Internal ID for the document used within the Indexing Service.
Write
Last time the document was modified (written).
Using ADO.NET to Create a Search Page CHAPTER 14
697
Performing Free Text Queries with File System Data You can perform free text queries with the Microsoft Indexing Service by using the FREETEXT function. A free text query can contain any word, phrase, or sentence. Any Boolean operators or wildcard characters that appear in a free text query are ignored. You also can search for a particular phrase in a free text query by enclosing the phrase in quotation marks. For example, the search phrase secret computers matches only those documents that contain the exact phrase secret computers. It does not match documents that contain The secret to making money from computers. You can use the following query, for example, to retrieve a list of all documents that match the search phrase How do you use ASP.NET?: SELECT * FROM OPENQUERY(FileSystem, ‘SELECT RANK, FileName, Characterization FROM SCOPE() WHERE FREETEXT(‘’How do you use ASP.NET?’’) > 0 ORDER BY RANK DESC’ )
This query returns three properties for each search result: RANK, FileName, and Characterization. The RANK represents how well each result matched the search phrase. The FileName represents the name of the matching document. Finally, the Characterization contains a brief summary of the document.
SELECT * FROM OPENQUERY(FileSystem, ‘SELECT RANK, FileName, Characterization FROM SCOPE(‘’””/Products””’’) WHERE FREETEXT(‘’How do you use ASP.NET?’’) > 0 ORDER BY RANK DESC’ ) Verified by JBN
The page in Listing 14.9 illustrates how you can create a search form for performing free text queries within an ASP.NET page (see Figure 14.9).
14 USING ADO.NET TO CREATE A SEARCH PAGE
Notice that the query is performed with a particular scope. In this case, all documents in all directories enabled for indexing are searched. You could, however, provide a path with the SCOPE() function to limit the search to a particular directory. For example, if you want to limit your search to only those files in the Products virtual directory, you would specify your query like this:
698
Working with ADO.NET PART III
FIGURE 14.9 Performing a free text query with the file system.
LISTING 14.9
FileFreeText.aspx
Sub Button_Click( s As Object, e As EventArgs ) Dim conMyData As SqlConnection Dim strSearch As String Dim cmdSearch As SqlCommand Dim dtrSearch As SqlDataReader conMyData = New SqlConnection( “Server=Localhost;UID=sa;PWD=secret; ➥Database=myData” ) strSearch = “SELECT * FROM “ & _ “OPENQUERY(FileSystem, “ & _ “‘SELECT RANK, FileName, VPath, Characterization “ & _ “FROM SCOPE() “ & _ “WHERE FREETEXT( ‘’” & txtSearchPhrase.Text & _ “‘’) > 0 “ & _ “ORDER BY RANK DESC’ ) “ cmdSearch = New SqlCommand( strSearch, conMyData ) conMyData.Open() Try dtrSearch = cmdSearch.ExecuteReader() lblResults.Text = “
” End While Catch exc As Exception lblResults.Text = “Please rephrase your query” End Try conMyData.Close() End Sub FileFreeText.aspx
In Listing 14.9, the FREETEXT function is used in the query executed in the Button_Click subroutine. The RANK, filename, and characterization are displayed for each result. Furthermore, each query result links to the document associated with the result. Notice that a TRY...CATCH block is used when displaying the query results. Certain search phrases generate errors when used with the Microsoft Indexing Service.
14 USING ADO.NET TO CREATE A SEARCH PAGE
File Free Text Search:
700
Working with ADO.NET PART III
For example, a search phrase that contains the single word The would generate an error because the search phrase would contain only words that are ignored by the Indexing Service.
Performing Boolean Queries with File System Data You can perform Boolean queries with the Microsoft Indexing Service by using the CONTAINS function. The Indexing Service supports searches that contain the Boolean operators AND, OR, and AND NOT. The following sample query returns the names of documents that contain the word apple but not the word green: SELECT * FROM OPENQUERY(FileSystem, ‘SELECT FileName, Characterization FROM SCOPE() WHERE CONTAINS(‘’apple AND NOT green’’) > 0 ORDER BY RANK DESC’ )
The page in Listing 14.10 illustrates how you can execute a query by using the CONTAINS function in an ASP.NET page (see Figure 14.10). FIGURE 14.10 Performing a contains query with the file system.
Using ADO.NET to Create a Search Page CHAPTER 14 LISTING 14.10
701
FileContains.aspx
Sub Button_Click( s As Object, e As EventArgs ) Dim conMyData As SqlConnection Dim strSearch As String Dim cmdSearch As SqlCommand Dim dtrSearch As SqlDataReader
FileContains.aspx File Contains Search: 0 “ & _ “ORDER BY RANK DESC’ ) “ cmdSearch = New SqlCommand( strSearch, conMyData ) conMyData.Open() Try dtrSearch = cmdSearch.ExecuteReader() lblResults.Text = “
” End While Catch exc As Exception lblResults.Text = “Please rephrase your query” End Try conMyData.Close() End Sub
702
Working with ADO.NET PART III LISTING 14.10
continued
Runat=”Server” />
The page in Listing 14.10 is almost exactly the same as the page in Listing 14.9 except for the fact that the query uses the CONTAINS function rather than the FREETEXT function.
Performing Queries with Document Properties When you perform a query with the Microsoft Indexing Service, you can use any of the document properties to limit the results returned by the query. For example, suppose that you want to return a list of all Microsoft Word documents that contain the word Apple, which were authored by Stephen Walther. To do so, you could create the query as follows: SELECT * FROM OPENQUERY(FileSystem, ‘SELECT FileName, Characterization FROM SCOPE() WHERE CONTAINS(‘’Apple’’) > 0 AND DocAuthor=’’Stephen Walther’’ AND DocAppName LIKE ‘’Microsoft Word%’’ ORDER BY RANK DESC’ )
This query uses the DocAuthor property to limit the results to only those documents authored by Stephen Walther. A LIKE operator is used with the DocAppName property to return only Microsoft Word documents (any version). To see a list of additional document properties you can use when performing a query, refer to the section “Retrieving Document Properties” earlier in the chapter.
Using ADO.NET to Create a Search Page CHAPTER 14
703
Summary In this chapter, you learned how to create search pages with ADO.NET. In the first part, you learned how to work with the Microsoft SQL Server Full-Text Search Service to search through data stored in a database table. You learned how to perform both free text and Boolean queries. You also learned how to store and index binary documents—such as Microsoft Word documents and PowerPoint presentations—in an image column. Next, you learned how to work with the Microsoft Indexing Service. You examined how to use the OLE DB Provider for Indexing Service with ADO.NET by sending queries to the Indexing Service through Microsoft SQL Server. You learned about the standard document properties supported by the Indexing Service and how to perform both free text and Boolean queries against documents stored in the file system.
14 USING ADO.NET TO CREATE A SEARCH PAGE
Working with ASP.NET Applications
PART
IV IN THIS PART 15 Creating ASP.NET Applications 16 Tracking User Sessions
707
747
17 Caching ASP.NET Applications
771
18 Application Tracing and Error Handling
811
CHAPTER 15
Creating ASP.NET Applications
IN
THIS CHAPTER • Overview of ASP.NET Applications 708 • Using Application State • Using the Web.Config File • Using HTTP Handlers and Modules 733
710 725
708
Working with ASP.NET Applications PART IV
In this chapter, you learn how to work with ASP.NET applications. First, you learn how to create global variables and objects by storing information in application state. Next, you learn how to use the Global.asax file, a special file that represents the current application so that you can use it to handle application wide events. You also examine how to configure an application with the Web.Config and configuration files. You examine the standard sections of the Web.Config file so that you understand how to add new sections of your own. Machine.Config
Finally, you learn how to create custom HTTP handlers and modules. You can use a custom HTTP handler to perform custom logic when a request is made to a certain page. A custom HTTP module can be used to perform custom logic on each page request. At the end of this chapter, you learn how to create a WhosOn application with a custom HTTP handler and module that display statistics on the current users at your Web site.
Overview of ASP.NET Applications An ASP.NET application consists of all files located within a particular virtual directory. The virtual directory can be the default directory (the wwwroot directory), or it can be a new virtual directory that you have defined by using the Internet Services Manager. To create a new virtual directory, follow these steps: 1. Launch Internet Services Manager by going to Start, Administrative Tools, Internet Services Manager. 2. Right-click your default Web site and choose New, Virtual Directory. 3. Provide the virtual directory with an alias (for example, myApp). 4. Choose a physical directory for the virtual directory. It can be located anywhere on your hard drive. 5. Choose the access permissions for the virtual directory. You need to enable both Read and Run scripts to execute ASP.NET pages. After you create a new application by creating a virtual directory, you can access pages in the application by using URLs that look like this: http://yourSite.com/myApp/default.aspx
This URL accesses a page located in the myApp application. All the pages running in an application are isolated within an application domain. Separating a Web site into multiple application domains, by creating virtual directories, has a number of benefits:
Creating ASP.NET Applications CHAPTER 15
709
• Code that executes within one application cannot directly access code or resources in another application. So, you can host multiple applications on a single Web server without worrying about unintended interactions between the different applications. • If one application is shut down (or it crashes), it has no effect on other applications running on the same server. Therefore, flaky code executing in a preproduction Web site can be isolated from code running on a live Web site. • Assemblies in one domain are not shared with other domains. For example, one application can use a different version of a component without any conflict with other applications. • You can set application wide security policies. The code executing in a trusted application domain can be subject to a different set of permissions than code running in another untrusted application domain. • You can specify configuration settings that apply to one application and not another. For example, you can enable tracing and debugging for one application and not another application. Every application can contain a special directory and two special files. The special directory is named /bin. This directory must be located in the root directory of the application. Note After you create a new virtual directory, you need to add the /bin directory to the application yourself.
The /bin directory contains custom components and controls (in the form of assemblies). Any component or control added to the /bin directory is automatically visible to all pages executing within the application. For example, if you create a new custom control named SuperDataGrid, you need to place this control in the /bin directory so that you can use it in your ASP.NET pages. ASP Classic Note
CREATING ASP.NET APPLICATIONS
In versions of Active Server Pages prior to ASP.NET, you needed to shut down the current application before you modified any of the components in an application. When using ASP.NET, in contrast, you can simply replace an old component with the new component in the /bin directory.
15
710
Working with ASP.NET Applications PART IV
An application can also contain a special file named Global.asax. This file also must be located in the root directory of an application. The Global.asax file contains subroutines that handle application wide events and objects declared with application scope. You learn how to create this file later in this chapter in the section titled “Using the Global.asax File.” Finally, an application can contain in its root directory a Web.Config file that specifies configuration information for the entire application. You learn how to use this file later in this chapter in the section titled “Using the Web.Config File.”
Using Application State You can store variables and objects directly in application state. An item that has been added to application state is available within any ASP.NET page executing in the application. Items stored in application state function like global variables for an application. Note Application state is represented by the HttpApplicationState class.
The following statement, for example, creates a new item named myItem with the value Hello! in application state: Application( “myItem” ) = “Hello!”
After this statement is executed in an ASP.NET page, the value of myItem can be retrieved within any other ASP.NET page contained in the same application. To read the value of an application variable, you can use a statement like this: Response.Write( Application( “myItem” ) )
This statement displays the value of the application item named myItem. You can add more complex objects, such as collections and DataSets, in an application state. (You should read the warnings in the next section before doing so, however.) For example, you can add an existing DataSet to application state like this: Application( “DataSet” ) = dstDataSet
After you add the DataSet with this statement, you can retrieve it from application state within any ASP.NET page and display its contents.
Creating ASP.NET Applications CHAPTER 15
711
Classic ASP In previous versions of Active Server Pages, the Application object was frequently used to cache data. You can still use application state for this purpose. However, the ASP.NET framework includes a new object, named the Cache object, that provides you with a richer set of methods and properties for working with cached data. To learn more details about caching data, see Chapter 17, “Caching ASP.NET Applications.”
You can remove an item from application state by using the Remove method like this: Application.Remove( “myItem” )
If you want to remove all items from application state, you can use either the Clear or RemoveAll method. For example, you would use the RemoveAll method as follows: Application.RemoveAll()
After an item is added to application state, it remains there until the application is shut down, the Global.asax file is modified, or the item is explicitly removed. So, be cautious about storing too much data in application state. Warning You can configure ASP.NET so that it automatically shuts down the currently running application and replaces it with a new one on a timed basis. (Shutting down an application on a timed basis can make your site more stable.) When the application shuts down, you lose all data stored in application state.
Understanding Application State and Synchronization When you add items to application state, the items can be accessed within multiple pages at the same time. This fact can result in conflicts and problems. Consider the sample page in Listing 15.1. BadPageCounter.aspx
Sub Page_Load Application( “PageCounter” ) += 1
CREATING ASP.NET APPLICATIONS
LISTING 15.1
15
712
Working with ASP.NET Applications PART IV LISTING 15.1
continued
lblCount.Text = Application( “pageCounter” ) End Sub BadPageCounter.aspx This page has been requested: times!
The page in Listing 15.1 uses an item stored in application state to create a simple page counter. Every time the page is requested, the application item named PageCounter is incremented by one (see Figure 15.1). FIGURE 15.1 Simple page view counter.
Creating ASP.NET Applications CHAPTER 15
713
This page has one important problem, however. Suppose that the current value of PageCounter is 590. Now imagine that two people, named Fred and Jane, request the page at the same time. When both Fred and Jane request the page, PageCounter has the value 590. When Fred’s request for the page finishes processing, the value of PageCounter is incremented by one so that it has the new value 591. When Jane’s request for the page finishes processing, the value of PageCounter is incremented by one so that it has the new value 591. See the problem? Because items stored in application state are shared among multiple pages, conflicts can occur. In this simple case, the worst outcome is inaccurate data. However, when users are working with more complicated objects in application state—such as collections— concurrent access to items stored in application state can result in deadlocks, race conditions, and access violations. In other words, storing items in application state can cause your application to crash in a very messy manner. Fortunately, you can find ways to get around this problem. You can force access to items in application state to take place in an orderly manner by using two methods of the application state object: Lock and Unlock. The Lock method locks the application state object so that it can be accessed only by the current thread. Typically, this means that only the current page can access objects in application state. The Unlock method releases the lock on application state. It enables other pages to access values in application state again. Note You should always unlock the application state object as quickly as possible. However, if you do forget to call Unlock after Lock, the lock is automatically released when the current request finishes processing, an error occurs, or the request times out.
The page in Listing 15.2 illustrates how you can use the Lock and Unlock methods. LISTING 15.2
GoodPageCounter.aspx
Sub Page_Load Application.Lock Application( “PageCounter” ) += 1
CREATING ASP.NET APPLICATIONS
15
714
Working with ASP.NET Applications PART IV LISTING 15.2
continued
lblCount.Text = Application( “pageCounter” ) Application.Unlock End Sub GoodPageCounter.aspx This page has been requested: times!
In Listing 15.2, the Lock method is called immediately before PageCounter is updated. The Unlock method is called immediately after PageCounter is assigned to the Label control. Notice that the Lock method locks the application state object as a whole. You cannot selectively lock items in application state. Because locking application state blocks all other access to any item, using application state unwisely in a high volume Web site can have a serious impact on the performance of the site. You must be cautious about using complex objects, such as collections, in application state. Collections such as ArrayLists and HashTables are not designed to be accessed by multiple threads at the same time. This means that you should either use the Lock and Unlock methods whenever accessing collections in application state, or you should create thread-safe versions of the collections. You can create thread-safe versions of the collection objects by using the Synchronized() method, which returns a thread-safe wrapper for the collection. For example, the following statements return a thread-safe instance of an ArrayList: Dim colArrayList As ArrayList colArrayList = New ArrayList colArrayList.Add( “Plato” ) colArrayList.Add( “Frege” ) colArrayList.Add( “Carnap” ) colArrayList = ArrayList.Synchronized( colArrayList )
Creating ASP.NET Applications CHAPTER 15
715
In this example, a new ArrayList containing three items is created. When first created, the ArrayList is not thread-safe. However, in the final statement, the Synchronized method is used to return a thread-safe wrapper for the ArrayList.
Using the Global.asax File Every ASP.NET application can contain a single file named Global.asax in its root directory. This special file can be used to handle application wide events and declare application wide objects. Warning The Global.asax file is not used to display content. If you request this file, you receive an error. This error is intentional. Enabling users to view the contents of the Global.asax file would constitute a serious security hole.
Every ASP.NET application supports a certain number of events. The following is a list of the most important of these events: •
Application_AuthenticateRequest—Raised
•
Application_AuthorizeRequest—Raised
•
Application_BeginRequest—Raised
•
Application_End—Raised
•
Application_EndRequest—Raised
•
Application_Error—Raised
•
before authenticating a user.
before authorizing a user.
by every request to the server.
immediately before the end of all application instances. at the end of every request to the server.
by an unhandled error in the application.
Application_PreSendRequestContent—Raised
before sending content to the
browser. •
Application_PreSendRequestHeaders—Raised
before sending headers to the
browser. •
Application_Start—Raised immediately after the first application is created. This event is guaranteed to occur only once.
•
Dispose—Raised
•
Init—Raised
immediately before the end of a single application instance.
This list contains the standard application events. If you add other modules to your application, additional events are exposed in the Global.asax file. For example, FormsAuthenticationModule exposes a Forms_Authenticate event, and SessionStateModule exposes both Session_Start and Session_End events.
15 CREATING ASP.NET APPLICATIONS
immediately after each application instance is created. This event might occur multiple times.
716
Working with ASP.NET Applications PART IV
In the section titled “Using HTTP Handlers and Modules” later in this chapter, you learn how to add your own modules to the Global.asax file. Classic ASP The Global.asax file is backward-compatible with the Global.asa file. To retain compatibility with the Global.asa file, you can refer to events by using the syntax Application_OnEventName. For example, you can use either Application_ Start or Application_OnStart in the Global.asax file.
You can handle any of these events in the Global.asax file by adding the appropriate subroutine. In general, the subroutine should look like this: Sub Application_EventName ... application code End Sub
Modifying the Global.asax file restarts the application. Any information stored in application (or session) state is lost. So, be cautious about modifying the Global.asax file on a production Web site.
Understanding Context and Using the Global.asax File Within an ASP.NET page, the Page object is the default object. In many cases, if you do not specify an object when you call a method or access a property, you are implicitly calling the method or accessing the property from the Page object. For example, when you call the MapPath method (which maps virtual paths to physical paths), you are actually calling the Page.MapPath method. Or, when you access the Cache property, you are implicitly accessing the Page.Cache property. The Global.asax file does not have the Page object as its default object. This means that you cannot simply call a method such as MapPath and expect it to work. Fortunately, in most cases, you can use a simple workaround. Instead of calling the MapPath method, you can call the Context.MapPath method. Or, instead of accessing the Page.Cache object, you can access the Context.Cache object. If a familiar property or method does not work in the Global.asax file, you should immediately try calling the method or property by using the Context object instead.
Creating ASP.NET Applications CHAPTER 15
717
Handling the Application Start and Init Events Imagine that you have just plugged your Web server into a power outlet and the Web server has booted up. When the first user makes a request to your Web site, the Application_Start event occurs. The Application_Start event is guaranteed to occur only once throughout the lifetime of the application. It’s a good place to initialize global variables. For example, you might want to retrieve a list of products from a database table and place the list in application state or the Cache object. Note To learn more details about the Cache object, see Chapter 17, “Caching ASP.NET Applications.”
The page in Listing 15.3 illustrates how you can add the contents of the Products database table to the Cache object within a subroutine that handles the Application_Start event. LISTING 15.3
AppCache/Global.asax
Sub Application_Start Dim conNorthwind As SqlConnection Dim strSelect As String Dim dadProducts As SqlDataAdapter Dim dstProducts As DataSet
15 CREATING ASP.NET APPLICATIONS
conNorthwind = New SqlConnection( “Server=localhost;UID=sa;PWD=secret;Database=Northwind” ) strSelect = “Select * From Products” dadProducts = New SqlDataAdapter( strSelect, conNorthwind ) dstProducts = New DataSet() dadProducts.Fill( dstProducts, “Products” ) Context.Cache( “Products” ) = dstProducts End Sub
718
Working with ASP.NET Applications PART IV
Note Within this Global.asax file, you must use Context.Cache, rather than Cache, because the Page object is no longer the default object.
You can find the Global.asax file in Listing 15.3 in the AppCache subdirectory on the CD. To test how the page works, you can open the DisplayProducts.aspx page, which displays the list of products added to the Cache object (see Figure 15.2). FIGURE 15.2 Displaying Cached products.
The ASP.NET framework uses a pool of application instances to process each request to the server. When a request is made, an application instance is assigned to the request. Immediately after any of these application instances are created, the Init event is raised. Requesting the first page from the Web site raises both the Application_Start and events. The Application_Start event won’t be raised again for the lifetime of the application. The Init event, on the other hand, might be raised multiple times. Init
You can use the Init event to initialize any variables or objects that you’ll need to use throughout the lifetime of a particular application instance. If you assign values to local variables, the variables retain their values across multiple requests. The page in Listing 15.4, for example, illustrates how you can automatically add a random banner advertisement to the bottom of every page (see Figure 15.3).
Creating ASP.NET Applications CHAPTER 15
719
FIGURE 15.3 Displaying random banner advertisement.
LISTING 15.4
AppInit/Global.asax
Dim dtblBannerAds As DataTable Overrides Sub Init() Dim dstBannerAds As DataSet dstBannerAds = New DataSet dstBannerAds.ReadXml( Server.MapPath( “adFile.xml” ) ) dtblBannerAds = dstBannerAds.Tables( 0 ) End Sub Sub Application_PreSendRequestContent Dim strPageFooter As String Dim objRan As Random Dim drowSelectedAd As DataRow
15 CREATING ASP.NET APPLICATIONS
objRan = New Random() drowSelectedAd = dtblBannerAds.Rows( _ objRan.Next( dtblBannerAds.Rows.Count ) ) strPageFooter = “” Response.Write( strPageFooter ) End Sub
You can find the Global.asax file in Listing 15.4 in the AppInit subdirectory on the CD that accompanies this book. You can open the page named DisplayAd.aspx to see how a banner advertisement is automatically appended to every page. In the Init subroutine in Listing 15.4, a list of banner advertisements is loaded from an XML file named adFile.xml into a DataSet. This subroutine is executed only once for each application instance. Next, within the Application_PreSendRequestContent subroutine, one advertisement is randomly selected from the DataSet and sent to the browser. The PreSendRequestContent subroutine is executed just before content being sent to the browser. Therefore, outputting content within this subroutine guarantees that it will be displayed at the bottom of the page. Notice that initializing variables in the Init subroutine is similar to adding items to application state. The crucial difference is that items added to application state are guaranteed to survive across multiple application instances, whereas variables created in the Init subroutine survive only one application instance.
Handling the Application_BeginRequest Event The Application_BeginRequest event is raised when the request starts to be processed. You can exploit this event in several ways. Suppose, for example, that you want to create vanity URLs at your Web site. You want registered users to be able to enter a URL, such as http://www.superexpert.com/swalther, and view a page customized only for them. You would not, however, want to manually create a new page for each user. Instead, you would want to secretly transfer the user to a single page that handles all user requests for the page. In that case, you can capture the Application_BeginRequest event and automatically transfer the user to the new page by using the RewritePath method. The Global.asax page in Listing 15.5 demonstrates how you can use the RewritePath method with the Application_BeginRequest event. (You can find this file in the AppVanity subdirectory on the CD that accompanies this book.)
Creating ASP.NET Applications CHAPTER 15 LISTING 15.5
721
AppVanity/Global.asax
Sub Application_BeginRequest Dim strUsername Dim strCustomPath As String If INSTR( Request.Path.ToLower, “custom” ) = 0 Then strUsername = Request.Path.ToLower strUsername = strUsername.Replace( “.aspx”, “” ) strUsername = strUsername.Remove( _ 0, _ InstrRev( strUsername, “/” ) ) If Request.ApplicationPath = “/” Then strCustomPath = _ String.Format( _ “/custom/default.aspx?username={0}”, _ strUsername ) Else strCustomPath = _ String.Format( _ “{0}/custom/default.aspx?username={1}”, _ Request.ApplicationPath, _ strUsername ) End If Context.RewritePath( strCustomPath ) End If End Sub
The Global.asax file in Listing 15.5 contains an Application_BeginRequest subroutine. This subroutine checks whether the current URL contains the name of the custom subdirectory in its path. If not, the beginning and end of the URL are stripped, and the RewritePath method transfers the user to the /custom/default.aspx page. If you enter the URL http://superexpert.com/swalther.aspx in the browser address bar, it is automatically rewritten like this: http://superexpert.com/custom/default.aspx?username=swalther
15 CREATING ASP.NET APPLICATIONS
Within the Default.aspx file in the custom subdirectory, you can grab the username query string and customize the page for the particular user. The page in Listing 15.6 simply displays the username in the body of the page. (You can find this page in the /AppVanity/Custom subdirectory on the CD that accompanies this book.)
722
Working with ASP.NET Applications PART IV LISTING 15.6
AppVanity/Custom/Default.aspx
Dim strUsername As String Sub Page_Load strUsername = Request.Params( “username” ) End Sub Welcome! Hello and welcome to your Web site!
The page in Listing 15.6 retrieves the username parameter from the query string and displays it (see Figure 15.4). A more complicated page might grab data from the database associated with the particular user and display the information. FIGURE 15.4 A vanity page.
Creating ASP.NET Applications CHAPTER 15
723
You also could use the RewritePath method with the Application_BeginRequest event to fix broken links. For example, you could create an XML file that maps bad links to good links. Say that your Web site included products and services pages in the past, but now you have removed them and you want to redirect all users to the default page. The XML file in Listing 15.7 illustrates how you could create this XML file. (You can find this file in the AppFixLinks subdirectory on the CD that accompanies this book.) LISTING 15.7
AppFixLinks/BadLinks.xml
/products.aspx /default.aspx /services.aspx /default.aspx
The XML file in Listing 15.7 maps the URLs /products.aspx and /services.aspx to the URL /default.aspx. The Global.asax file in Listing 15.8 uses the RewritePath() method to automatically redirect users from bad links to good links. LISTING 15.8
AppFixLinks/Global.asax
Sub Application_BeginRequest Dim dtblBadLinks As DataTable Dim strThisUrl As String Dim strSelect As String Dim arrMatches() As DataRow Dim strGoodLink As String
15 CREATING ASP.NET APPLICATIONS
dtblBadLinks = GetBadLinks() strThisUrl = Request.Path.ToLower() If Request.ApplicationPath “/” Then strThisUrl = strThisUrl.Remove( 0, Request.ApplicationPath.Length ) End If strSelect = “badlink=’” & strThisURL & “‘“ arrMatches = dtblBadLinks.Select( strSelect, “badlink” ) If arrMatches.Length > 0 Then
724
Working with ASP.NET Applications PART IV LISTING 15.8
continued
strGoodLink = arrMatches( 0 )( “goodlink” ) strGoodLink = Request.ApplicationPath & strGoodLink Context.RewritePath( strGoodLink ) End If End Sub Function GetBadLinks() As DataTable Dim dstBadLinks As DataSet Dim dtblBadLinks As DataTable dtblBadLinks = Context.Cache( “badlinks” ) If dtblBadLinks Is Nothing Then dstBadLinks = New DataSet dstBadLinks.ReadXml( Server.MapPath( “badlinks.xml” ) ) dtblBadLinks = dstBadLinks.Tables( 0 ) Context.Cache.Insert( “badlinks”, _ dtblBadLinks, _ New CacheDependency( Server.MapPath( “badlinks.xml” ) ) ) End If Return dtblBadLinks End Function
The Badlinks.xml file is retrieved in the GetBadLinks() function. If the Badlinks.xml file is not already cached in memory, it is added to the Cache object. (The Badlinks.xml file is inserted with a Cache file dependency so that the Cache object is automatically updated if the Badlinks.xml file is modified.) Note To learn more about creating file dependencies see Chapter 17, “Caching ASP.NET Applications.”
In the Application_BeginRequest subroutine, the path of the current URL is checked against the list of bad links contained in the Badlinks.xml file. If a match is made, the RewritePath() method automatically transfers the user from the bad link to the good link.
Creating ASP.NET Applications CHAPTER 15
725
If a user enters the address http://superexpert.com/products.aspx in a Web browser, he or she is automatically transferred to the following new page specified in the Badlinks.xml file: http://superexpert.com/default.aspx
You can use this method to fix any bad links on your Web site. All you need to do is maintain the Badlinks.xml file.
Using the Web.Config File ASP.NET applications are configured with Web.Config files, which are standard, humanreadable XML files that you can open and modify with any text editor. The ASP.NET framework uses a hierarchical configuration system. You can use files to specify the settings for every ASP.NET application on a machine, a particular ASP.NET application, the ASP.NET pages in a particular directory, or a single ASP.NET page.
Web.Config
At the top of the hierarchy sits the Machine.Config file, which specifies the settings that are global to a particular machine. This file is located at the following path: \WINNT\Microsoft.NET\Framework\[Framework Version]\CONFIG\machine.config
You can override settings in the Machine.Config file for all the applications in a particular Web site by placing a Web.Config file in the root directory of the Web site as follows: \InetPub\wwwroot\Web.Config
This optional file applies to all applications (virtual directories) for a particular Web site. If you don’t include it, all the settings from the Machine.Config file apply. You also can place a Web.Config file in the root directory of any particular application— in other words, in the root directory of a particular virtual directory. The following Web.Config file applies to all pages within the application: \InetPub\myApplication\Web.Config
Finally, you can place a Web.Config file in any subdirectory of an application. The file then applies to any pages located in the same directory or any subdirectories.
To learn how to apply a Web.Config file to a single file, see the section titled “Setting the Configuration Location” later in this chapter.
CREATING ASP.NET APPLICATIONS
Note
15
726
Working with ASP.NET Applications PART IV
You need to understand that child Web.Config files override configuration settings specified by their parents. This means that you do not need to copy the complete contents of a parent Web.Config file when creating a Web.Config lower in the hierarchy. You can specify only the configuration settings that you need to modify. When you modify the settings in the Web.Config file, you do not need to restart the Web service for the modifications to take effect. The ASP.NET framework caches the settings in the Web.Config file. When you modify the file, the framework automatically detects the file change and reloads the settings from the file into the cache. The Web.Config file doesn’t secretly use the computer Registry or the Metabase to save configuration information. The ASP.NET framework reads all the configuration information directly from the Web.Config files. This means that if you need to move an ASP. NET application to a new server, you can simply copy all the files (including the Web. Config files) to the new server, and all the configuration settings are carried over.
Examining the Configuration Sections This section provides a brief overview of all the standard sections of the Web.Config (and Machine.Config) configuration files that apply to ASP.NET applications. Because many of the configuration sections are described more fully in other parts of this book, I have also provided references to other parts of this book where you can find more information. Note The Machine.Config and Web.Config files are case-sensitive. The section names in these files use a naming convention called camel casing. According to this convention, the first letter of the first word of a section name is lowercase.
The configuration settings that affect ASP.NET applications are contained within the System.Web section of the Machine.Config and Web.Config files. The following is a partial list of the settings found in each of these sections: •
trace—This
section contains configuration settings for page and application tracing. For more information, see Chapter 18, “Application Tracing and Error Handling.”
•
globalization—This
section specifies the character encoding to use with both requests and responses.
Creating ASP.NET Applications CHAPTER 15
•
httpRunTime—This section specifies the maximum amount of time that a page will execute without timing out, the maximum size of a request, and whether fully qualified URLs should be used for client redirects. (Some mobile devices require fully qualified client redirects.)
•
compilation—This
727
section contains configuration information for compiling pages. You can specify the default language to use for compiling ASP.NET pages, such as Visual Basic, C#, or JavaScript. (If you are fond of C#, you might want to specify that language as the default.) This section can also be used to indicate whether pages should be compiled in debug mode so that you can view error information. You can also use this section to specify the assemblies available in an application. By default, all assemblies located in the /bin directory are available. However, you can modify this configuration section to make only select assemblies available.
•
pages—This
section specifies configuration information for ASP.NET pages. For example, you can use this section to disable page buffering, session state, or view state.
•
customErrors—This
•
authentication—This
•
identity—This section configures user account impersonation. For more information, see Chapter 20.
•
authorization—This
section specifies the users and roles authorized to access files. For more information, see Chapters 19 and 20.
•
machineKey—This
•
trust—This
section specifies how error information should be displayed. To learn more information, see Chapter 18.
section specifies information for authenticating users. For more information, see Chapter 19, “Using Forms-Based Authentication,” and Chapter 20, “Using Windows-Based Authentication.”
section is used for sharing a standard encryption key across machines in a Web farm. For more information, see Chapter 19. section is used for setting security policies. For more information, see
Chapter 20. •
securityPolicy—This
section contains a list of available security policies. For more information, see Chapter 20.
•
sessionState—This
15 CREATING ASP.NET APPLICATIONS
section contains configuration information for session state. You can use this section to enable in-process, out-of-process, or cookieless sessions. For more information, see Chapter 16, “Tracking User Sessions.”
728
Working with ASP.NET Applications PART IV
•
httpHandlers—This
section associates a particular HTTP handler with a particular page path and request verb. For more information, see “Using HTTP Handlers and Modules” later in this chapter.
•
httpModules—This section lists the modules that are involved in every page request. For more information, see “Using HTTP Handlers and Modules.”
•
processModel—This
•
webControls—This section specifies the location of the client-side script library used with Web controls, such as the validation controls. For more information, see Chapter 3, “Performing Form Validation with Validation Controls.”
•
clientTarget—This
section configures the process model settings on Internet Information Server. For more information, see Chapter 18.
ClientTarget
section lists the values that you can use with the property. See Chapter 3.
•
browserCaps—This
section lists information on the capabilities of different browsers. The HttpBrowserCapabilities class uses this information to report on the features of different browsers.
•
webServices—This section contains configuration information for Web services. For more information, see Part VI of this book, “Building ASP.NET Web Services.”
Modifying Configuration Settings You can modify configuration settings directly in the Machine.Config file. However, by modifying the Machine.Config file, you modify the configuration settings for every application on your machine. For this reason, typically, you should add a new Web. Config file to a particular subdirectory, rather than modify the global Machine.Config file. Suppose that you are developing a new ASP.NET application, and you want all the pages in the application to display error information. In that case, you can add the Web.Config file in Listing 15.9 to the root directory of the application. (This file is located in the ConfigErrors subdirectory on the CD that accompanies this book.) LISTING 15.9
ConfigErrors/Web.Config
Creating ASP.NET Applications CHAPTER 15
729
The file in Listing 15.9 overrides a single configuration setting in the Machine.Config file; it sets debug mode to true for the application. If you request an ASP.NET page that contains a runtime error, the error message is displayed along with the source code listing that indicates the exact line where the error occurred. You can test the Web.Config file in Listing 15.9 by requesting the DisplayError.aspx page from the ConfigErrors subdirectory. This page intentionally generates a runtime error. Warning Because Web.Config and Machine.Config are XML files, they are case sensitive. You therefore receive an error if you create a Web.Config file that changes the Debug mode, rather than the debug mode.
Setting the Configuration Location By default, the Web.Config file applies to all the pages in the current directory and its subdirectories. You can modify this default behavior by adding a location section to a configuration file. You can use a location section to specify a path for configuration settings. The path can be used to specify an application, a particular directory, or even an individual file. The Web.Config file in Listing 15.10, for example, disables view state for all files in a subdirectory named NoViewState. (You can find this file in the ConfigLocationDir subdirectory on the CD that accompanies this book.) LISTING 15.10
ConfigLocationDir/Web.Config
CREATING ASP.NET APPLICATIONS
The configuration settings in Listing 15.10 apply to a directory named NoViewState. They do not modify the settings for the current directory.
15
730
Working with ASP.NET Applications PART IV
You can test the Web.Config file by requesting the DisplayViewState.aspx file located in both the ConfigLocationDir subdirectory and the ConfigLocationDir/NoViewState directory. You also can use the tag to modify the configuration settings for a particular page. For example, the Web.Config file in Listing 15.11 denies authorization on a single page named Secret.aspx. (You can find this file in the ConfigLocationFile subdirectory on the CD that accompanies this book.) LISTING 15.11
ConfigLocationFile/Web.Config
Because the Web.Config file in Listing 15.11 modifies the authentication mode, you must place it in the root directory of your application. The tag denies access to the Secret.aspx page. If you request the Secret. aspx page, you are automatically redirected to the Login.aspx page. The location tag applies to only a single file in the directory. You can request any file other than the Secret.aspx page.
Locking Configuration Settings You can use the tag to lock configuration settings in the Web.Config file so that they cannot be overridden by a Web.Config file located below it. You can use the allowOverride attribute to lock configuration settings. This attribute is especially valuable if you are hosting untrusted applications on your server. The Web.Config file in Listing 15.12, for example, locks the identity configuration setting for two subdirectories. The Web application runs under the app1 identity in the directory named application1, and the Web application runs under the app2 identity in
Creating ASP.NET Applications CHAPTER 15
731
the directory named application2. (You can find the Web.Config file in Listing 15.12 in the ConfigLock directory on the CD that accompanies this book.) LISTING 15.12
ConfigLock/Web.Config
Because the Web.Config file in Listing 15.12 locks the settings with the allowOverride attribute, you cannot place a Web.Config file that alters the identity settings in either the application1 or application2 subdirectory. If you attempt to modify these settings, you get an error. You can test this by attempting to retrieve the Default.aspx file from the directory. This directory contains a Web.Config file that attempts to override the locked identity setting. When you attempt to request the Default.aspx file, an error is generated. /ConfigLock/Application1
Typically, you lock configuration settings by placing a Web.Config file similar to the one in Listing 15.12 in the Web site root directory, or by modifying the Machine.Config file.
Adding Custom Configuration Information You can add your own application configuration information within the appSettings section of the Web.Config file. Adding information to this section is useful for storing such information as database connection strings. The Web.Config file in Listing 15.13, for example, contains a database connection string that connects to the Northwind database. (You can find this file in the ConfigCustom directory on the CD that accompanies this book.) ConfigCustom/Web.Config
CREATING ASP.NET APPLICATIONS
LISTING 15.13
15
732
Working with ASP.NET Applications PART IV LISTING 15.13
continued
You can retrieve configuration information from the Config.Web file with the AppSettings property of the ConfigurationSettings class. For example, the page in Listing 15.14 uses the value conString to open a database connection. LISTING 15.14
Default.aspx
Sub Page_Load Dim strConString As String Dim conNorthwind As SqlConnection Dim cmdSelect As SqlCommand strConString = ConfigurationSettings.AppSettings( “conString” ) conNorthwind = New SqlConnection( strConString ) cmdSelect = New SqlCommand( “select * From Products”, conNorthwind ) conNorthwind.Open() dgrdProducts.DataSource = cmdSelect.ExecuteReader() dgrdProducts.DataBind() conNorthwind.Close() End Sub Default.aspx
Notice that the value of conString is retrieved from the Web.Config file with the following single line of code: strConString = ConfigurationSettings.AppSettings( “conString” )
Creating ASP.NET Applications CHAPTER 15
733
Using HTTP Handlers and Modules In the following sections, you learn how to create custom HTTP handlers and modules. Both HTTP handlers and modules enable you to gain low-level access to HTTP requests and responses. Later, you learn how to implement a simple statistics application for your Web site by using a custom HTTP handler and module.
Working with HTTP Handlers An HTTP handler enables you to handle all requests made for a file with a certain extension, path, or request type. You can use HTTP handlers to handle requests for ASP.NET pages or any other file type, such as image or text files. Typical uses for handlers include implementations of custom authentication schemes and custom filters. For example, you can create a handler that authenticates requests for image files. Or you can create a handler that automatically transfers requests for one file to another file. Classic ASP HTTP handlers perform many of the same functions in the ASP.NET framework as ISAPI extensions performed in traditional Active Server Pages programming.
All requests made to an ASP.NET Web site are serviced by an HTTP handler. For example, by default, all requests made for files with the extension .aspx are handled by the PageHandlerFactory class. This class produces instances of the PageHandler class to service each request for an ASP.NET page. To create your own HTTP handler, complete the following steps: 1. Create a class that implements the IHttpHandler interface. 2. Add a reference to your HTTP handler in the Web.Config file. Now, create a simple HTTP handler that retrieves product information from a database table. The handler will take a URL like this:
It will retrieve information for that product from the Products database table.
CREATING ASP.NET APPLICATIONS
http://yourSite.com/products/product1.aspx
15
734
Working with ASP.NET Applications PART IV
The first step is to implement the IHttpHandler interface, which requires you to implement one property and one method: IsReusable and ProcessRequest(). The IsReusable property indicates whether the current handler can be reused for another request. The ProcessRequest() method contains the actual code to be executed in response to the request. The Visual Basic class in Listing 15.15 implements the products handler. LISTING 15.15
ProductsHandler/ProductsHandler.vb
Imports System.Data Imports System.Data.SqlClient Imports System.Web Public Class ProductsHandler Implements IHttpHandler Public Sub ProcessRequest( objContext As HttpContext ) _ Implements IHttpHandler.ProcessRequest Dim intProductID As Integer Dim conNorthwind As SqlConnection Dim strSelect As String Dim cmdSelect As SqlCommand Dim dtrProducts As SqlDataReader intProductID = GetProductID( objContext.Request.Path ) conNorthwind = New SqlConnection( _ “Server=localhost;UID=sa;PWD=secret;Database=Northwind” ) strSelect = “Select ProductName, UnitPrice From Products” & _ “ Where ProductID=@ProductID” cmdSelect = New SqlCommand( strSelect, conNorthwind ) cmdSelect.Parameters.Add( “@ProductID”, intProductID ) conNorthwind.Open() dtrProducts = cmdSelect.ExecuteReader( CommandBehavior.SingleRow ) If dtrProducts.Read Then objContext.Response.Write( “Product Name:” ) objContext.Response.Write( dtrProducts( “ProductName” ) ) objContext.Response.Write( “Product Price:” ) objContext.Response.Write( String.Format( _ “{0:c}”, _ dtrProducts( “UnitPrice” ) ) ) End If conNorthwind.Close() End Sub ReadOnly Property IsReusable() As Boolean _ Implements IHttpHandler.IsReusable Get Return True End Get End Property
Creating ASP.NET Applications CHAPTER 15 LISTING 15.15
735
continued
Function GetProductID( strPath As String ) As Integer Dim intCounter As Integer Dim strNumbers As String For intCounter = 0 To strPath.Length - 1 If Char.IsDigit( strPath.Chars( intCounter ) ) Then strNumbers &= strPath.Chars( intCounter ) End If Next If Not strNumbers = Nothing Then Return cINT( strNumbers ) Else Return -1 End If End Function End Class
The class in Listing 15.15 implements both the ProcessRequest() method and IsReusable property. The ProcessRequest() method uses the GetProductID() function to strip anything but numbers from the page requests. The resulting number is used to retrieve a product with a certain product ID from the Northwind database table. If the product is found, the name and price of the product are displayed (see Figure 15.5). FIGURE 15.5 Output of the ProductsHandler.
15 CREATING ASP.NET APPLICATIONS
736
Working with ASP.NET Applications PART IV
The IsReusable property simply returns the value True. This is all you need to implement for a simple HTTP handler. Before you can use the handler, you need to compile it. You can compile the class in Listing 15.15 by executing the following statement from the command line: vbc /t:library /r:System.dll,System.Data.dll,System.Web.dll ProductsHandler.vb
Next, you need to copy the compiled ProductsHandler class (ProductsHandler.dll) to your application /bin directory. The last step is to create the proper Web.Config file to associate the handler with a set of pages. The Web.Config file in Listing 15.16 associates the ProductsHandler class with all files requested in the current directory and all its subdirectories. LISTING 15.16
Web.Config
The Web.Config file in Listing 15.16 associates the ProductsHandler class with all files. You can limit the files associated with the handler by changing the value of the path attribute. For example, if you want to associate the handler with only those files that have a name starting with product, you would specify the path like this:
You can also add multiple entries for a single handler in the Web.Config file. You should be cautious about one thing: Only certain file extensions are handled by the ASP.NET framework. For example, by default, files with the extension .asp and .html are not handled by the ASP.NET framework. This means that you cannot, by default, create a handler for files with these extensions. You can associate any file with the ASP.NET framework by modifying the mapping for an extension with the Internet Services Manager. To do so, follow these steps: 1. Launch the Internet Services Manager. 2. Right-click the name of your application (virtual directory) and choose Properties.
Creating ASP.NET Applications CHAPTER 15
737
3. Choose the Virtual Directory tab and click the Configuration button. 4. Choose the App Mappings tab and make any desired changes. You can associate any file extension with the ASP.NET framework by mapping the file extension to the aspnet_isapi.dll file. For example, if you want to write a custom handler for HTML files, you need to map the .html extension to the aspnet_isapi.dll file.
Working with HTTP Modules An HTTP module is similar to a handler in that it enables you to gain low-level access to the HTTP requests and responses processed by the ASP.NET framework. However, an HTTP module, unlike a handler, enables you to participate in the processing of every request. The ASP.NET framework includes several standard modules for managing state and implementing authentication schemes. For example, the output cache, session state, forms authentication, and Windows authentication are all implemented as modules. You can replace any of the standard modules with your own module. For example, if you don’t like the way that the ASP.NET framework implements session state, you can replace the standard session state module with one of your own. Or you might decide to extend the ASP.NET framework by creating a completely new module. For example, you might want to implement a custom caching mechanism or create your own custom authentication system. Creating your own module requires completing the following two steps: 1. Create a class that implements the IHttpModule interface. 2. Add a reference to your module to a Web.Config file. For this next example, create a simple module that implements a custom authentication scheme. The module will require that a parameter named username be passed as part of the parameters collection to a page. If the username parameter is not present, you are automatically redirected to a login page. The first step is to implement the IHttpModule interface. This interface has two required methods: Init and Dispose. Within the Init subroutine, you can initialize any variables that you need in your module and initialize event handlers with the hosting application. The Dispose event cleans up any of your module’s instance variables.
CREATING ASP.NET APPLICATIONS
The module in Listing 15.17 implements both of these required methods. (You can find this module in the ModuleAuth subdirectory on the CD-ROM that accompanies this book.)
15
738
Working with ASP.NET Applications PART IV LISTING 15.17
ModuleAuth/AuthModule.vb
Imports System Imports System.Web Imports Microsoft.VisualBasic Namespace myModules Public Class AuthModule Implements IHttpModule Public Sub Init( ByVal myApp As HttpApplication ) _ Implements IHttpModule.Init AddHandler myApp.AuthenticateRequest, AddressOf Me.OnEnter AddHandler myApp.EndRequest, AddressOf Me.OnLeave End Sub Public Sub Dispose() _ Implements IHttpModule.Dispose End Sub Public Sub OnEnter( s As Object, e As EventArgs ) Dim objApp As HttpApplication Dim objContext As HttpContext Dim strPath As String objApp = CType( s, HttpApplication ) objContext = objApp.Context strPath = objContext.Request.Path.ToLower() If Right( strPath, 10 ) “login.aspx” Then If objContext.Request.Params( “username” ) = Nothing Then objContext.Response.Redirect( “login.aspx” ) End If End If End Sub Public Sub OnLeave( s As Object, e As EventArgs) End Sub End Class End Namespace
The file in Listing 15.17 contains the code for a module named AuthModule. In the Init subroutine, the OnEnter subroutine is associated with the Application_Authenticate event, and the OnLeave subroutine is associated with the Application_EndRequest event.
Creating ASP.NET Applications CHAPTER 15
739
Note You don’t actually use the OnLeave subroutine in the AuthModule module. Typically, you use OnLeave to clean up any resources that you have allocated for your module.
All the work happens in the OnEnter subroutine, which checks whether the current page is the login.aspx page. If it’s not, and the parameters collection does not contain a username parameter, the user is automatically redirected to the login.aspx page. So, if you request a page named secret like this http://yourSite.com/secret.aspx
you are automatically redirected to the login.aspx page. However, if you request the secret page like this http://yoursite.com/secret.aspx?username=bob
you can see the secret content in secret.aspx. You need to compile the file in Listing 15.17 before you can use AuthModule module. To do so, execute the following command from the command line: vbc /t:library /r:System.dll,System.Web.dll AuthModule.vb
After you compile the module, you need to move the AuthModule.dll file to your application’s /bin directory. Before you can use the module, you must add a reference to the module in the Web.Config file. The Web.Config file in Listing 15.18 contains the needed configuration settings. LISTING 15.18
Web.Config
15 CREATING ASP.NET APPLICATIONS
740
Working with ASP.NET Applications PART IV
The Web.Config file in Listing 15.18 includes an httpModules section that contains the custom AuthModule module. The value of the type attribute is the name of the class and the name of the assembly.
Creating the WhosOn Application In the following sections, you build a simple statistics application called WhosOn that uses both an HTTP handler and module. The WhosOn application displays statistics on the users of a particular page. For example, if your application contains a page at the path /myApp/Products.aspx, you can view statistics on the users who last accessed the page by requesting the page at /myApp/Products.axd (see Figure 15.6). FIGURE 15.6 Output of the WhosOn statistics application.
The WhosOn page lists the time, browser type, referrer, and remote IP address for the users who last accessed the page. You can configure the number of users that the application should track per page by modifying a setting in the Web.Config file. To create the WhosOn application, you need to create three files: •
Web.Config—You WhosOnHandler.
use the Web.Config file to configure both WhosOnModule and
Creating ASP.NET Applications CHAPTER 15
•
WhosOnModule—The WhosOn module executes every time a user requests a page. The module records information on the request by adding the information to the application cache.
•
WhosOnHandler—The WhosOn
741
handler displays statistics for a particular page. For example, requesting products.axd displays statistics on the products.aspx page.
You learn how to create each of these files in the following sections. Note You can find all the WhosOn files in the WhosOn subdirectory on the CD that accompanies this book.
Creating the WhosOn Web.Config File You can start this example by creating the Web.Config file contained in Listing 15.19. LISTING 15.19
Web.Config
The Web.Config file in Listing 15.19 adds the WhosOnModule to the current application. It associates the WhosOnHandler with all pages that end with the extension .axd. Finally, it sets the maximum number of entries tracked by WhosOnModule to the value 5.
The WhosOnModule, contained in Listing 15.20, grabs statistics for each request and adds them to the application cache.
CREATING ASP.NET APPLICATIONS
Creating the WhosOn Module
15
742
Working with ASP.NET Applications PART IV LISTING 15.20 Imports Imports Imports Imports Imports
WhosOnModule.vb
System System.Web System.Collections System.Configuration Microsoft.VisualBasic
Namespace WhosOn Public Class WhosOnModule Implements IHttpModule Public Sub Init( ByVal myApp As HttpApplication ) _ Implements IHttpModule.Init AddHandler myApp.BeginRequest, AddressOf Me.OnEnter End Sub Public Sub Dispose() _ Implements IHttpModule.Dispose End Sub Public Sub OnEnter( s As Object, e As EventArgs ) Dim objApp As HttpApplication Dim objContext As HttpContext Dim strPath As String Dim colPageStats As Queue Dim objStatsEntry As StatsEntry Dim intMaxEntries As Integer objApp = CType( s, HttpApplication ) objContext = objApp.Context strPath = objContext.Request.Path.ToLower() ‘ Don’t keep stats on .axd pages If Right( strPath, 4 ) = “.axd” Then Exit Sub End If strPath = strPath.SubString( 0, InstrRev( strPath, “.” ) - 1 ) ‘ Get Max Entries From Web.Config intMaxEntries = cINT( ConfigurationSettings.AppSettings( “whoson” ) ) ‘ Check whether Cache object exists colPageStats = CType( objContext.Cache( “whoson_” & strPath ), Queue ) If colPageStats Is Nothing Then colPageStats = New Queue() End If ‘ Add Current Request to the Queue objStatsEntry = New StatsEntry( _
Creating ASP.NET Applications CHAPTER 15 LISTING 15.20
743
continued
objContext.TimeStamp, _ objContext.Request.Browser.Type, _ objContext.Request.UserHostName, _ objContext.Request.ServerVariables( “HTTP_REFERER” ) ) colPageStats.Enqueue( objStatsEntry ) ‘ Delete Previous Entries If intMaxEntries Nothing Then While colPageStats.Count > intMaxEntries colPageStats.Dequeue() End While End If ‘ Update the Cache objContext.Cache( “whoson_” & strPath ) = colPageStats End Sub End Class Public Class StatsEntry Public Public Public Public
TimeStamp As DateTime BrowserType As String UserHostName As String Referrer As String
Public Sub New( _ TimeStamp As DateTime, _ BrowserType As String, _ UserHostName As String, _ Referrer As String ) Me.TimeStamp = TimeStamp Me.BrowserType = BrowserType Me.UserHostName = UserHostName Me.Referrer = Referrer End Sub End Class
End Namespace
15 CREATING ASP.NET APPLICATIONS
The WhosOn module is triggered by the Application_BeginRequest event. When this event fires, the OnEnter subroutine is executed.
744
Working with ASP.NET Applications PART IV
The OnEnter subroutine first checks whether the current page has the extension .axd. Because you don’t want to keep statistics on the statistics pages, the subroutine is exited if the current page is a statistics page. Next, the WhosOn key from the Web.Config appSetting section is retrieved. The value of the WhosOn key is assigned to a variable named intMaxEntries. The current page statistics are then retrieved from the application cache. The page statistics are represented in a Queue collection. Next, the OnEnter subroutine grabs the time stamp of the current request, the browser type, the remote host name, and the referrer server variable. This information is stored in a custom class named StatsEntry. The StatsEntry class is added to the page statistics queue. If the queue contains more than the maximum allowable number of entries, older entries are deleted. Finally, the page statistics are inserted back into the Cache object. Before you can use the WhosOnModule, you need to compile it. You can compile the file in Listing 15.20 by executing the following statement from the command line: vbc /t:library /r:System.dll,System.Web.dll WhosOnModule.vb
After you compile the module, remember to copy the compiled WhosOnModule.dll file to the application /bin directory.
Creating the WhosOn Handler The WhosOn handler, contained in Listing 15.21, displays the statistics for a particular page. You can view the output of the WhosOn handler by requesting a page with the extension .axd. For example, a request for the products.axd page would display user statistics for a page named products.aspx. LISTING 15.21
WhosOnHandler.vb
Imports System.Web Imports System.Collections Imports Microsoft.VisualBasic Namespace WhosOn Public Class WhosOnHandler Implements IHttpHandler Public Sub ProcessRequest( objContext As HttpContext ) _ Implements IHttpHandler.ProcessRequest
Creating ASP.NET Applications CHAPTER 15 LISTING 15.21
745
continued
Dim colPageStats As Queue Dim strPath As String Dim objStatsEntry As StatsEntry ‘ Get Page Path strPath = objContext.Request.Path strPath = strPath.SubString( 0, InstrRev( strPath, “.” ) - 1 ) ‘ Display the Stats colPageStats = CType( objContext.Cache( “whoson_” & strPath ), Queue ) If Not colPageStats Is Nothing Then objContext.Response.Write( “
” ) objContext.Response.Write( “Who’s On” ) objContext.Response.Write( “ | |||
Timestamp | ” ) objContext.Response.Write( “Browser Type | ” ) objContext.Response.Write( “Remote Address | ” ) objContext.Response.Write( “Referrer | ” ) objContext.Response.Write( “
---|---|---|---|
” & objStatsEntry.TimeStamp & “ | ” ) objContext.Response.Write( “” & “ | ” ) objContext.Response.Write( “” & “ | ” ) objContext.Response.Write( “” & Next objContext.Response.Write( “ |
Cookie Value:
Existing Cookies:
The page in Listing 16.1 displays a form with two TextBox controls. You can enter the name and value of a new cookie into the TextBox controls and create a new cookie. The new cookie is added with the Button_Click subroutine. This subroutine simply grabs the values from the two TextBox controls and adds the new cookie to the cookies collection of the Response object. Finally, the page displays all existing cookies in the Page_PreRender subroutine. Note In Listing 16.1, you display the cookies in the Page_PreRender subroutine instead of the Page_Load subroutine so that you can display any cookies added with the Button_Click subroutine. The Page_Load subroutine is executed before event-handling subroutines, such as Button_Click. The Page_PreRender subroutine isn’t executed, in contrast, until the page is about to be rendered.
Tracking User Sessions CHAPTER 16
Creating and Reading Persistent Cookies
Warning You have no guarantee that a persistent cookie will remain on a user’s computer for the period of time that you specify. A browser places a limit on the total number of cookies that it will store from all Web sites. After that limit is reached, the browser starts automatically deleting cookies. Users can also choose to delete cookies from the computer before they expire.
Persistent cookies are often used to store information such as usernames or user IDs. That way, the server can identify the same user when he or she returns to the Web site in the future. Warning Persistent cookies are stored in plain text files on a user’s computer. You therefore should never store sensitive information, such as passwords or credit card numbers, in a persistent cookie.
You can create a persistent cookie that lasts until December 25, 2005, as follows: Dim objCookie As New HttpCookie( “myPersistentCookie”, “Hello!” ) objCookie.Expires = #12/25/2005# Response.Cookies.Add( objCookie )
Notice that the only difference between a session and persistent cookie is the addition of expiration information. If you want to add a persistent cookie that will last the maximum amount of time, you can use the following statements: Dim objCookie As New HttpCookie( “myPersistentCookie”, “Hello!” ) objCookie.Expires = DateTime.MaxValue Response.Cookies.Add( objCookie )
16 TRACKING USER SESSIONS
A persistent cookie is similar to a session cookie, except for the fact that a persistent cookie has a definite expiration date. When a browser requests a page that creates a persistent cookie, the browser saves the cookie to the hard drive. You can create a persistent cookie that lasts months or even years on a user’s computer.
753
754
Working with ASP.NET Applications PART IV
After you create a persistent cookie, you can read it in the same way as you would a session cookie. You can access a persistent cookie through the Cookies property of the Request object like this: Response.Write( Request.Cookies( “myPersistentCookie” ).Value )
This statement displays the value of a persistent cookie named myPersistentCookie.
Setting Cookie Properties You can set any of the following properties of a cookie: •
Domain—The
domain associated with the cookie; for example, aspx.superexpert. com or .superexpert.
•
Expires—The
•
HasKeys—A
expiration date for a persistent cookie.
Boolean value that indicates whether the cookie is a cookie
dictionary. •
Name—The
name of the cookie.
•
Path—The
path associated with the cookie. The default value is /.
•
Secure—A value that indicates whether the cookie should be sent over an encrypted connection only. The default value is False.
•
Value—The
•
Values—A NameValueCollection
value of the cookie. that represents all the key and value pairs stored
in a cookie dictionary. The Domain property enables you to specify a particular domain for a cookie so that the cookie is sent only with requests to that domain. You cannot specify a domain that is different from the domain used by your Web server. For example, if your Web site is www.myCompany.com, you cannot assign the value www.Yahoo.com to the Domain property. However, you can specify different hostnames for the domain, such as products.myCompany.com, sales.myCompany.com, or www.myCompany.com. You should never change the Path property from its default value. By default, a browser sends a cookie for a domain with every request for a page at a Web site. If you specify a subdirectory by using the Path property, however, the cookie should be sent only with requests for pages in the subdirectory. Unfortunately, there is a known problem with the way the Netscape browser handles the Path property. The Netscape browser uses case-sensitive paths. Therefore, a request for a page at /myDirectory/myPage.aspx is treated differently than a request for a page at /MYDIRECTORY/mypage.aspx.
Tracking User Sessions CHAPTER 16
The following sample statements create a new cookie dictionary named Preferences: Dim objCookie As HttpCookie objCookie = New HttpCookie( “Preferences” ) objCookie.Values( “color” ) = “Red” objCookie.Values( “fontface” ) = “Arial” objCookie.Values( “fontsize” ) = “4” Response.Cookies.Add( objCookie )
These statements create a new cookie dictionary that contains three name and value pairs: color, fontface, and fontsize. Since no expiration information is set, the cookie is created as a session cookie. You can detect whether any cookie is a cookie dictionary by using the HasKeys property. For example, the following statements use the HasKeys property to detect whether a cookie is a cookie dictionary and display each of its entries: If objCookie.HasKeys Then Dim strItem As String For Each strItem in objCookie.Values Response.Write( “
Welcome to Our Web Site! Here is a list of our current products:
( Control generated: )
The user control in Listing 17.11 displays the text Welcome DataGrid that contains a list of products.
to Our Web Site!
and a
The PageContents user control includes an OutputCache directive, which causes the output of the user control to be cached for 60 seconds. The page that contains the user control, Home.aspx, is not cached. However, it contains a cached user control. The end result is that the banner advertisements in Home.aspx are not cached, but the database data displayed by the PageContents user control is cached.
Varying Page Fragment Caching by Parameter You can use the VaryByParam attribute with the OutputCache directive in a user control. This means that you can cache different versions of the output of a user control depending on the query string passed to the control. Imagine, for example, that you want to create a user control that displays a menu of options. When a user clicks an option, you want the currently selected option to be highlighted. The user control in Listing 17.12 illustrates how to do so (see Figure 17.4).
788
Working with ASP.NET Applications PART IV
FIGURE 17.4 Using VaryByParam
with
Partial Page Caching.
LISTING 17.12
Menu.ascx
Dim strMenuID As String Sub Page_Load strMenuID = Request.Params( “menuID” ) If strMenuID = Nothing Then strMenuID = “0” End If End Sub
Our Company Our Company | Our Services Caching ASP.NET Applications CHAPTER 17 LISTING 17.12 continued The user control in Listing 17.12 contains an OutputCache directive, which causes the output of the control to be cached for 300 seconds (5 minutes). The VaryByParam attribute indicates that different versions of the output of the control should be cached depending on the value of the menuID parameter. In the Page_Load subroutine, the value of the menuID parameter is retrieved from the query string passed to the page. The menuID highlights the currently selected menu option. The page in Listing 17.13 illustrates how you can use the Menu control in an ASP.NET page. OurCompany.aspx OurCompany.aspx Our Company Our company specializes in creating large scale Web sites for commercial clients. Visit the different areas of 17 CACHING ASP.NET APPLICATIONS Our Services | Our Products Our Products |
Caching ASP.NET Applications CHAPTER 17 LISTING 17.19
801
continued
The page in Listing 17.19 contains one TextBox and two Button controls. If you click the first button, the value of item1 in the cache is updated. If you click the second button, the value of item2 in the cache is updated. A key dependency exists between item1 and item2. If you update item1, item2 is automatically dropped from the cache. You can test this functionality by entering a value in the TextBox control and clicking the Update Item1 button when Item2 has a value. Note Key dependencies cascade. If you make a chain of key dependencies, a change in one item in the chain causes the other items in the chain to be dropped. You also can make key dependencies circular. In that case, a modification to any item in the circle drops all the cached items in the circle.
Creating an Absolute Expiration Policy When you add items to the cache, you can specify an absolute expiration policy. Items that are inserted with an absolute expiration policy are automatically dropped from the cache after a preset period of time.
17 CACHING ASP.NET APPLICATIONS
Item1 = Item2 =
802
Working with ASP.NET Applications PART IV
The following statement, for example, adds to the cache an item that will automatically expire in one minute: Cache.Insert( “myItem”, “Hello!”, Nothing, _ DateTime.Now.AddMinutes( 1 ), Cache.NoSlidingExpiration )
This statement adds an item named myItem with the value Hello! to the cache. The item is inserted with no file or key dependencies, and an absolute expiration date and time of one minute in the future. (I discuss the Cache.NoSlidingExpiration parameter in the next section.) The page in Listing 17.20 illustrates how to create an item with an absolute expiration policy within an ASP.NET page. LISTING 17.20
AbsoluteExpiration.aspx
Sub Page_Load Dim strTime As String strTime = Cache( “Time” ) If strTime Is Nothing Then strTime = DateTime.Now.ToString( “T” ) Cache.Insert(“Time”, strTime, Nothing, _ DateTime.Now.AddMinutes( 1 ), Cache.NoSlidingExpiration ) End If lblMessage.Text = strTime End Sub AbsoluteExpiration.aspx Data last cached:
In the Page_Load subroutine in Listing 17.20, an item named Time is pulled from the cache. If Time is Nothing, the item has expired. In that case, the item is added to the cache again with an absolute expiration policy of one minute in the future.
Caching ASP.NET Applications CHAPTER 17
803
Creating a Sliding Expiration Policy Instead of setting an absolute expiration policy for an item stored in the cache, you can set a sliding policy. When an item is cached with a sliding expiration policy, the item is removed from the cache a certain interval of time after it was last accessed. The following statement adds an item that will expire one minute after it is last accessed: Cache.Insert( “myItem”, “Hello!”, Nothing, _ Cache.NoAbsoluteExpiration, TimeSpan.FromMinutes( 1 ) )
Listing 17.21 illustrates how you can add an item with a sliding expiration policy in an ASP.NET page. LISTING 17.21
DataSlide.aspx
Sub Page_Load Dim strTime As String strTime = Cache( “Time” ) If strTime Is Nothing Then strTime = DateTime.Now.ToString( “T” ) Cache.Insert(“Time”, strTime, Nothing, _ Cache.NoAbsoluteExpiration, TimeSpan.FromMinutes( 1 ) ) End If lblMessage.Text = strTime End Sub DataSlide.aspx Data last cached:
17 CACHING ASP.NET APPLICATIONS
This statement inserts into the cache an item that won’t expire unless it is not accessed for more than one minute. If never more than a minute goes by without the item being accessed, the item will never be deleted from the cache (unless server resources get low).
804
Working with ASP.NET Applications PART IV
In the Page_Load subroutine in Listing 17.21, an item is inserted into the cache with a sliding expiration of 1 minute. If you repeatedly refresh the page, the item is never removed from the cache. Note You cannot add an item to the Cache with both an absolute and sliding expiration policy.
Setting Cache Item Priorities When you insert an item into the cache, you can insert the item with a certain priority. The priority provides the Cache object with a hint about the relative importance of an item. All things being equal, when times are tough and server resources become low, the cache starts dropping lower priority items before higher priority items. You indicate the relative priority of an item by using one of the following values from the CacheItemPriority enumeration: •
AboveNormal—Items
with this priority are less likely to be removed from the
cache than Normal. •
BelowNormal—Items with this priority are more likely to be removed from the cache than Normal.
•
Default—The Default
•
High—Items
•
Low—Items
•
Normal—These
•
NotRemovable—Items
value is Normal.
with this priority are the least likely to be removed from the cache.
with this priority are the most likely to be removed from the cache. items have the Normal priority. with this priority should never be removed from the cache.
You also can supply one of the following values from the CacheItemPriorityDecay enumeration when inserting items into the cache: •
Default—The
•
Fast—Items
•
Medium—Items
default value is Medium.
with this priority decay more quickly from the cache. with this priority decay only after items with a Fast priority decay.
Caching ASP.NET Applications CHAPTER 17
•
Never—Items
•
Slow—Items
805
with this priority of decay should never be removed from the cache.
with this priority decay more slowly from the cache.
To insert an item into the cache with a High priority and a Slow priority decay, for example, you would use the following statement:
This statement provides a hint to the Cache object to preserve the item in the cache for as long as possible.
Creating a Cache Callback Method When you insert an item into the cache, you can associate a callback method with the item. If, for whatever reason, the item is removed from the cache, the callback method automatically executes. A callback method is useful in several applications. The most obvious use for a callback method is for automatically reloading an item into the cache when it expires. You also can create more complicated applications for a callback method. Imagine, for example, that you are displaying banner advertisements at your Web site, and you want the list of banner advertisements to be automatically reloaded from a database table every hour. In that case, you could add an absolute expiration policy to the cached item and create a callback method that reloads the banner advertisements. You also can use the callback method to track the behavior of the Cache object. You can write a callback method that writes to a log file every time an item is dropped from memory. To create a callback method, you need to add a subroutine to an ASP.NET page that accepts three parameters: String, Object, and CacheItemRemovedReason. The callback method should look like this:
17 CACHING ASP.NET APPLICATIONS
Cache.Insert( “myItem”, _ “Hello”, _ Nothing, _ Cache.NoAbsoluteExpiration, _ Cache.NoSlidingExpiration, _ CacheItemPriority.High, _ CacheItemPriorityDecay.Slow, _ Nothing )
806
Working with ASP.NET Applications PART IV Sub ItemRemoved( itemKey As String, _ itemValue As Object, _ removedReason As CacheItemRemovedReason ) End Sub
The first two parameters passed to the callback method represent the name and value of the item removed from the cache. The last parameter represents a value from the CacheItemRemovedReason enumeration. This enumeration, which indicates the reason the item was removed from the cache, can have the following values: •
DependencyChanged—Removed
because of a file or key dependency
•
Expired—Removed
because of an expiration policy
•
Removed—Removed
with an explicit call to either the Remove or Insert method
•
Underused—Removed
because of low server resources
After you create the callback method, you need to create an instance of the CacheItemRemovedCallback class and initialize the class with the callback method. For example, the following statements create an instance of the CacheItemRemovedCallback class named onRemove: Dim Shared onRemove As CacheItemRemovedCallback onRemove = New CacheItemRemovedCallback( AddressOf ItemRemoved )
Finally, you need to refer to the onRemove object as follows when you insert an item into the cache: Cache.Insert( “myItem”, _ “Hello!”, _ Nothing, _ Cache.NoAbsoluteExpiration, _ Cache.NoSlidingExpiration, _ CacheItemPriority.High, _ CacheItemPriorityDecay.Slow, _ onRemove )
Notice that you specify the callback method with the last parameter passed to the Insert method. In this case, you have created an item that does not have any file or key dependencies, or an absolute or sliding expiration date. The page in Listing 17.22 demonstrates how you can use a callback method in an ASP.NET page.
Caching ASP.NET Applications CHAPTER 17
807
FIGURE 17.6 Using a cache callback method.
17 CACHING ASP.NET APPLICATIONS
LISTING 17.22
CacheCallback.aspx
Public Shared onRemove As CacheItemRemovedCallback Sub ItemRemoved( _ strItemKey As String, _ objItemValue As Object, _ objRemovedReason As CacheItemRemovedReason ) Dim strLogEntry As String strLogEntry = “Item with value “ & objItemValue.ToString() strLogEntry &= “ removed at “ & Now.ToString( “T” ) strLogEntry &= “ because of “ & objRemovedReason.ToString() If Application( “CacheLog” ) Is Nothing Then Application( “CacheLog” ) = New ArrayList End If Application( “CacheLog” ).Add( strLogEntry ) Beep End Sub Sub btnAddCache_Click( s As Object, e As EventArgs ) onRemove = New CacheItemRemovedCallback( AddressOf ItemRemoved )
808
Working with ASP.NET Applications PART IV LISTING 17.22
continued
Cache.Insert( “myItem”, _ txtNewValue.Text, _ Nothing, _ Now.AddSeconds( 10 ), _ Cache.NoSlidingExpiration, _ CacheItemPriority.High, _ CacheItemPriorityDecay.Slow, _ onRemove ) End Sub Sub btnRemoveCache_Click( s As Object, e As EventArgs ) Cache.Remove( “myItem” ) End Sub Sub Page_PreRender( s As Object, e As EventArgs ) dgrdCacheLog.DataSource = Application( “CacheLog” ) dgrdCacheLog.Databind() End Sub CacheCallback.aspx Cache Log
1. The method adds an entry to a log that includes the time and reason the item was removed. 2. The method calls the Visual Basic Beep statement to create a beep sound. The page contains a DataGrid that displays the contents of the cache log. The DataGrid displays the reason that each item was removed from the cache. Note It would not be a good idea to use the Beep statement in a production application. I’ve used the statement here so you can detect when an item is expired from the cache.
You see two types of entries in the cache log. If the item expires from the cache, the log entry contains the word Expired. If you explicitly remove an item by clicking the Remove From Cache! button, the log entry contains the word Removed.
Summary In this chapter, you explored three mechanisms for caching the contents of an ASP.NET page. In the first part, you learned how to use page output caching. You learned how to cache the output of a page for a certain duration of time. You also learned how to create different cached versions of a page depending on the query string parameters and headers passed to the page. Next, you learned how to take advantage of page fragment caching and use it with user controls to cache different areas of a page.
17 CACHING ASP.NET APPLICATIONS
The page in Listing 17.22 contains a form that enables you to add and remove items to the cache. When you add an item, the item is added with an absolute expiration policy of 10 seconds and associated with a callback method named itemRemoved. This callback method does two things:
810
Working with ASP.NET Applications PART IV
Finally, the bulk of this chapter was devoted to using the Cache object. You learned how to use the Cache object to store data that can be shared across multiple ASP.NET pages. You learned how to add file and key dependencies to a cached item. You also learned how to associate a cached item with a database trigger. Then you examined how to create a callback method to automatically reload data into the cache.
CHAPTER 18
Application Tracing and Error Handling IN THIS CHAPTER • Responding to Errors
812
• Tracing and Monitoring Your Application 828 • Logging Events
842
• Using the Debugger
853
812
Working with ASP.NET Applications PART IV
In this chapter, you learn how to identify and correct problems in your ASP.NET applications. In the first section, you examine several methods for gracefully handling errors. I discuss methods of capturing and handling errors at both the page and application level. Next, you examine how to trace and monitor the execution of your ASP.NET pages. You learn how to enable tracing, monitor the ASP.NET process, log events to the event log, and use and create performance counters. Finally, you are provided with an overview of the .NET framework debugger. You learn how to attach the debugger to the ASP.NET process and step line-by-line through an executing application.
Responding to Errors Four main types of errors can occur in an ASP.NET application: • Configuration errors—Caused by problems in either the Web.Config or the Machine.Config file • Parser errors—Caused by incorrect syntax in an ASP.NET page • Compilation errors—Raised by the Visual Basic compiler • Runtime errors—Detected when the page is actually executing A configuration error, for example, can be caused by a badly formed Web.Config file like the one in Listing 18.1. LISTING 18.1
ConfigErrors/Web.Config
The problem with the Web.Config file in Listing 18.1 is that it does not include a closing tag that corresponds to the opening tag. If you attempt to open a page from the same directory as the directory that contains this Web.Config file, you receive an error. In this case, a very messy error message, see Figure 18.1. (You might receive a different error message depending on the version of the .NET Framework installed on your machine.) A parser error is raised when the contents of a page cannot be parsed. For example, the ASP.NET page in Listing 18.2 would generate a parser error (see Figure 18.2).
Application Tracing and Error Handling CHAPTER 18
813
FIGURE 18.1 Receiving a Configuration Error.
18 Receiving a Parser Error.
APPLICATION TRACING AND ERROR HANDLING
FIGURE 18.2
814
Working with ASP.NET Applications PART IV LISTING 18.2
Parser.aspx
Sub Page_Load lblMessage.Text = “hello!” End Sub Parser.aspx
The problem with the page in Listing 18.2 is that it does not include a closing tag that corresponds with the opening tag. Parser errors prevent a page from being compiled. A compilation error is raised when the ASP.NET page is in the process of being compiled. The error is raised by the particular language being used to compile the page (for example, Visual Basic). The page in Listing 18.3 generates a compilation error (see Figure 18.3). FIGURE 18.3 Receiving a Compilation Error.
Application Tracing and Error Handling CHAPTER 18 LISTING 18.3
815
Compilation.aspx
Sub Page_Load lblMessage.Value = “hello!” End Sub Compilation.aspx
In the page in Listing 18.3, text is assigned to a Label control’s Value attribute. Because a Label control does not have a Value attribute, a compilation error is raised.
FIGURE 18.4 Receiving a Runtime Error.
APPLICATION TRACING AND ERROR HANDLING
Finally, runtime errors occur after a page is successfully compiled. A runtime error is not detected until the page actually executes. The page in Listing 18.4 generates a runtime error (see Figure 18.4).
18
816
Working with ASP.NET Applications PART IV LISTING 18.4
Runtime.aspx
Sub Page_Load Dim txtTextBox As TextBox txtTextBox.Text = “hello!” End Sub Runtime.aspx
In the Page_Load subroutine in Listing 18.4, a TextBox control is declared, and text is assigned to the TextBox control’s Text property. This assignment generates a Null Reference Exception because the TextBox control wasn’t instantiated before a value was assigned to one of its properties.
Viewing Error Information Two configuration settings affect how error information is displayed: • Custom errors mode—This setting enables or disables custom errors. When custom errors mode is enabled, errors on a page are hidden. This setting has three possible values: On, Off, and RemoteOnly. • Debug mode—When debug mode is enabled, additional information for debugging runtime errors is displayed. You can use custom errors to hide errors from the users of your Web site. When the custom errors mode is assigned the value On, detailed error information is hidden. When the custom errors mode is set to the value Off, all the available error information is displayed. Finally, if you assign the value remoteOnly to custom errors, errors are hidden when requested with a browser on a remote machine, but the errors are displayed when requested with a browser located on the same machine as the Web server.
Application Tracing and Error Handling CHAPTER 18
817
If you need to view detailed error information, you need to disable custom errors or set this mode to the value RemoteOnly. You can configure the custom errors mode in either the Machine.Config file or a Web.Config file located within a particular application. The Web.Config file in Listing 18.5, for example, sets customErrors to the value Off. LISTING 18.5
CustomErrorsOff/Web.Config
When the custom errors mode is set to the value Off, detailed error information is always displayed, even on remote computers. (Always displaying this information can be a security risk.) You might want to disable custom errors while developing an ASP.NET application and before placing the site into production.
Note You can enable debug mode to display additional information for runtime errors. This setting has no effect on the display of parser or compilation errors.
When you enable debug mode, ASP.NET pages are compiled with the Visual Basic compiler’s /debug+ compiler option. The source code for the ASP.NET page and detailed compiler output generated by compiling the page are written to files in the Temporary ASP.NET Files directory. Warning Never enable debug mode for a production Web site. Placing an application in debug mode results in a severe performance penalty.
18 APPLICATION TRACING AND ERROR HANDLING
The second configuration setting, debug mode, controls whether pages are compiled in debug mode. When pages are compiled in this mode, additional debugging information is included with the compiled code. This mode displays more detailed error information when a runtime error occurs.
818
Working with ASP.NET Applications PART IV
You can choose from two methods to enable debug mode for an ASP.NET page. You can enable debug mode for all the pages in a directory in the Web.Config file, or you can enable it for a single page by using a page directive. For example, the page in Listing 18.6 uses a page directive to enable debug mode. LISTING 18.6
Debug.aspx
Sub Page_Load Dim txtTextBox As TextBox txtTextBox.Text = “hello!” End Sub Debug.aspx This page has an error!
When you request the page in Listing 18.6 with debug mode enabled, you see the page in Figure 18.5. When debug mode is disabled, on the other hand, you see the page in Figure 18.6. You can enable or disable debug mode for all the pages in an application by adding the Web.Config file in Listing 18.7 to the root directory of the application. LISTING 18.7
/DebugMode/Web.Config
Application Tracing and Error Handling CHAPTER 18
819
FIGURE 18.5 Page with debug mode enabled.
18 Page with debug mode disabled.
The Web.Config file in Listing 18.7 enables debug mode by setting the debug attribute of the compilation configuration setting to the value true. It also disables custom errors
APPLICATION TRACING AND ERROR HANDLING
FIGURE 18.6
820
Working with ASP.NET Applications PART IV
when pages are requested from the local machine so that error information can be viewed.
Page-Level Error Handling In the following sections, you learn how to capture and handle errors that occur in an ASP.NET page. First, you learn how to use the Visual Basic TRY...CATCH statement to wrap dangerous statements in error handling code. Next, you learn how to capture and respond to unhandled errors that occur in a page by using the Page_Error subroutine.
Catching Exceptions with TRY...CATCH Whenever you need to execute a statement that might fail, it is a good idea to wrap the statement in a Visual Basic TRY...CATCH block. If you place a dangerous statement in a TRY...CATCH block, you can programmatically handle any runtime errors that result from executing the statement. The page in Listing 18.8, for example, uses a TRY...CATCH block when opening a database connection. LISTING 18.8
TryCatch.aspx
Sub Page_Load Dim conNorthwind As SqlConnection Dim cmdSelect As SqlCommand conNorthwind = New SqlConnection( “Server=localhost;UID=sa;pwd=secret; ➥Database=Northwind;Connection TimeOut=15” ) cmdSelect = New SqlCommand( “select * from Products”, conNorthwind ) Try conNorthwind.Open dgrdProducts.DataSource = cmdSelect.ExecuteReader() dgrdProducts.DataBind() conNorthwind.Close Catch Response.Write( “We’re sorry, we are experiencing technical problems...” ) End Try End Sub TryCatch.aspx
Application Tracing and Error Handling CHAPTER 18 LISTING 18.8
821
continued
In the Page_Load subroutine in Listing 18.8, a connection to a SQL Server database named Northwind is opened. The Open method for the SQLConnection object is called within a TRY block. If the connection cannot be opened—for example, the database server is offline—the statement in the CATCH block is executed. This statement simply displays a message reporting technical difficulties. You could, of course, perform other actions in the CATCH block. For example, you could switch to a backup database or automatically send an e-mail to the Webmaster. If you need to retrieve error information within the CATCH block, you can catch the exception that raised the error. Exceptions are represented within the .NET framework with instances of the Exception class. This class includes the following properties:
•
Message—Returns Source—Returns
a string that represents the error message
a string representing the object or application that caused the
error •
StackTrace—Returns
a string that represents the methods called immediately before the error occurred
•
TargetSite—Returns
a MethodBase object that represents the method that caused
the error The page in Listing 18.9, for example, displays the values of these properties when an error occurs. LISTING 18.9
CatchException.aspx
Sub Page_Load Dim conNorthwind As SqlConnection Dim cmdSelect As SqlCommand conNorthwind = New SqlConnection( “Server=localhost;UID=sa;pwd=secret; ➥Database=Northwind;Connection Timeout=15” )
APPLICATION TRACING AND ERROR HANDLING
•
18
822
Working with ASP.NET Applications PART IV LISTING 18.9
continued
cmdSelect = New SqlCommand( “select * from Products”, conNorthwind ) Try conNorthwind.Open dgrdProducts.DataSource = cmdSelect.ExecuteReader dgrdProducts.DataBind() conNorthwind.Close Catch objException As Exception Response.Write( “We’re sorry, we are experiencing technical problems...” ) Response.Write( “” ) Response.Write( “
” ) Response.Write( Server.GetLastError.Message ) Server.ClearError() End Sub Sub Page_Load Dim txtTextBox As TextBox txtTextBox.Text = “Hello World!” End Sub PageError.aspx This page contains an error!
The Page_Error subroutine in Listing 18.11 takes advantage of two methods of the HttpServerUtility class: GetLastError and ClearError. The GetLastError method returns the last exception thrown. In the page in Listing 18.11, the GetLastError method displays the value of the Message property of the last exception thrown.
Application Tracing and Error Handling CHAPTER 18
825
The ClearError method clears the last exception thrown. If you didn’t call the ClearError method in the page in Listing 18.11, the default error page generated by the ASP.NET framework would be displayed, and no one would ever see the polite apology.
Application-Level Error Handling In the following sections, you learn how to handle errors in your ASP.NET pages no matter where they occur within an application. You also learn how to enable and configure custom errors in the Web.Config file and create a global error handler in the Global. asax file.
Using Custom Errors You can use custom errors to hide errors in your ASP.NET application from your users, or to display more user-friendly error messages. After you enable custom errors, you can automatically redirect users to an error page whenever an unhandled error occurs.
LISTING 18.12
CustomErrors/Web.Config
The Web.Config file in Listing 18.11 associates 404—Page Not Found—errors with the NotFound.aspx page. Page errors with the status code 500—Internal Server Errors—are associated with the AppError.aspx page. Finally, the defaultRedirect attribute associates any other error with the AppError.aspx page. If you request a page and a page with that name does not exist on the Web server, you are automatically redirected to the NotFound.aspx page. When you are redirected to a page with custom errors, a query string variable is automatically passed to the error page. The query string variable, named aspxerrorpath, contains the path of the page that was originally requested. For example, the page in
18 APPLICATION TRACING AND ERROR HANDLING
You configure custom errors within the customErrors section of the Web.Config file. This section contains error elements that associate particular errors with error pages. For example, the Web.Config file in Listing 18.12 handles both application errors and Page Not Found errors.
826
Working with ASP.NET Applications PART IV
Listing 18.13 illustrates how you can use the aspxerrorpath query string variable to display a simple error message. LISTING 18.13
NotFound.aspx
Sub Page_Load lblMessage.Text = Request.Params( “aspxerrorpath” ) lblMessage.Text &= “ does not exist!” End Sub NotFound.aspx
If you prefer, you also can enable custom errors on a page-by-page basis. You can specify an error page for a particular page by using either the ErrorPage page directive or the ErrorPage page property. If a runtime error occurs when the page in Listing 18.14 is requested, for example, the user is automatically redirected to the AppError.aspx page (This page can be found in the ErrorPage subdirectory). LISTING 18.14
ErrorPage/ErrorPage.aspx
Sub Page_Load Dim txtTextBox As TextBox txtTextBox.Text = “Hello” End Sub
Application Tracing and Error Handling CHAPTER 18 LISTING 18.14
827
continued
ErrorPage.aspx This page contains an error!
In Listing 18.14, the ErrorPage attribute contained in the page directive points to a page named AppError.aspx. If an unhandled error is thrown in the page, the user is automatically redirected to this page. It’s important to understand that the ErrorPage attribute works only when the custom errors mode has been enabled for the application. The custom errors configuration setting must have the value on or remoteOnly in the Web.Config or Machine.Config file for the ErrorPage attribute to work.
Handling Errors in the Global.asax File
The Global.asax file in Listing 18.15, for example, contains an Application_Error subroutine that automatically e-mails the Webmaster of the Web site whenever an error occurs in a page. LISTING 18.15
GlobalError/Global.asax
Sub Application_Error Dim objMail As New MailMessage objMail.From = “[email protected]” objMail.To = “[email protected]” objMail.Subject = “Error at Web site” objMail.Body = Request.Path objMail.Body &= vbNewLine objMail.Body &= Context.Server.GetLastError.Message SmtpMail.Send( objMail ) Context.Server.ClearError() End Sub
18 APPLICATION TRACING AND ERROR HANDLING
In the Global.asax file, you can create a single subroutine that is triggered by any unhandled exception thrown on any page. To do so, you simply need to add an Application_Error subroutine to the file.
828
Working with ASP.NET Applications PART IV
If you add the file in Listing 18.15 to your application root directory, an e-mail is sent to [email protected] whenever an error occurs in any page within the application. Note To learn more details about sending e-mail in an ASP.NET application, see Chapter 26, “Sending E-mail and Accessing the Network.”
Tracing and Monitoring Your Application In the following sections, you learn how to trace the execution of your ASP.NET application and monitor its performance. First, you learn how to enable tracing for both pages and applications. By taking advantage of tracing, you can make your pages easier to debug. Next, you examine how to monitor the ASP.NET process and automatically restart an ASP.NET application when problems are detected. You learn how to configure the ASP.NET process model. You also learn methods for reading and adding information to the server event log and create custom event logs to monitor the health of your ASP.NET application. Finally, you learn how to work with performance counters. I provide an overview of the performance counters included with the ASP.NET framework that you can use to monitor the performance of your application. You also learn how to create custom performance counters.
Tracing Page Execution You can trace the execution of an ASP.NET page by using the trace attribute of the page directive. To enable tracing for a single ASP.NET page, include the following directive at the top of the page:
When tracing is enabled for a page, trace information is automatically appended to the bottom of the page (see Figure 18.7). The trace information includes the following statistics:
Application Tracing and Error Handling CHAPTER 18
829
FIGURE 18.7 Page with tracing enabled.
18 • Trace Information—Displays information about the steps in page execution. The execution time for each step is displayed in seconds. • Control Tree—Displays the hierarchy of controls in a page. Both the render size and view state size for each control are displayed in bytes. • Session State—Displays each item stored in session state. • Application State—Displays each item stored in application state. • Cookies Collection—Displays the name and value of all cookies stored on the browser. • Headers Collection—Displays all the browser headers sent to the server. • Form Collection—Displays the name and value of each item submitted with an HTML form. •
QueryString
Collection—Displays the name and value of each item submitted in a
query string. • Server Variables—Displays a list of all the server variables (browser headers and environmental variables).
APPLICATION TRACING AND ERROR HANDLING
• Request Details—Displays such information as the user’s unique session ID, time of the request, and status code returned by the server for the request.
830
Working with ASP.NET Applications PART IV
All sections are not necessarily displayed when tracing is enabled. For example, the Form Collection section is displayed only when form data has actually been posted. Notice that you can use the information from the Control Tree section to determine the size of the view state for each control. Because view state must be stored in the hidden __VIEWSTATE form field and passing a large hidden form field can significantly affect the rendering speed of a page, limiting the size of view state is a wise idea. You can use the Control Tree section to identify the controls using the most view state. The Trace Information section provides information about the methods called to render the page. The ASP.NET framework automatically inserts certain messages into this section. For example, information about the page Init and PreRender events is automatically inserted into this section.
Adding Custom Trace Messages You can add your own custom trace messages to the Trace Information section. Adding custom trace messages is valuable for debugging an application. While you’re in the process of debugging a page, you can enable tracing and view all your custom trace messages. For example, you can display the value of variables at different points of page execution. When you are satisfied with the page, you can simply turn off tracing and hide all your messages. ASP Classic Note In Classic Active Server Pages, the only trace tool was the Response.Write statement. Typically, you were forced to isolate errors by adding Response.Write statements throughout the page. If you forgot to comment out a Response. Write statement that you added for debugging purposes, the entire world would see your debugging information.
To add custom trace messages, use either the Trace.Warn or Trace.Write methods. The only difference between these methods is that Trace.Warn displays messages in a red font and Trace.Write displays messages in a black font. The page in Listing 18.16, for example, adds custom trace messages to the Trace Information section (see Figure 18.8).
Application Tracing and Error Handling CHAPTER 18
831
FIGURE 18.8 Page with tracing enabled.
18 Trace.aspx
Sub Page_Load Trace.Warn( “Page Loading” ) End Sub Sub Button_Click( s As Object, e As EventArgs ) Trace.Warn( “The value of favColor is “ & txtFavColor.Text ) lblMessage.Text = “Button Clicked!” End Sub Trace.aspx Enter your Favorite Color Favorite Color:
APPLICATION TRACING AND ERROR HANDLING
LISTING 18.16
832
Working with ASP.NET Applications PART IV LISTING 18.16
continued
In Listing 18.16, trace messages are displayed in both the Page_Load and Button_Click subroutines. For example, the trace message in the Button_Click subroutine displays the value that the user entered into the TextBox control.
Detecting Whether Tracing Is Enabled If you need to execute code only when tracing is enabled, you can use the Trace.IsEnabled property to detect whether tracing is currently enabled or disabled for a page. For example, suppose that you want to display all the items in a collection in a trace message. The page in Listing 18.17 creates a string that contains the values of all the items in a collection only when tracing is enabled. LISTING 18.17
TraceIsEnabled.aspx
Sub Page_Load Dim colArrayList As ArrayList colArrayList = New ArrayList() colArrayList.Add( “red” ) colArrayList.Add( “green” ) colArrayList.Add( “aliceblue” ) If Trace.IsEnabled Then Dim strTraceMessage As String Dim strItem As String
Application Tracing and Error Handling CHAPTER 18 LISTING 18.17
833
continued
For Each strItem in colArrayList strTraceMessage &= strItem & “,” Next Trace.Warn( strTraceMessage ) End If dropColors.DataSource = colArrayList dropColors.DataBind End Sub TraceIsEnabled.aspx Enter your Favorite Color
When tracing is enabled, the page in Listing 18.17 displays all the items in an ArrayList in a trace message. When tracing is disabled—the Trace page directive has the value False—the trace message is not created.
Creating Trace Categories By default, trace messages are displayed in order of execution. For example, a trace message added within the Page_Load subroutine will be displayed before a trace message added in the Page_PreRender subroutine. Instead of displaying trace messages in order of execution, you can create different categories for trace messages and group the messages into the categories. For example, you could create distinct categories for database trace messages and form trace messages. The page in Listing 18.18 illustrates how to group trace messages into different categories.
18 APPLICATION TRACING AND ERROR HANDLING
Favorite Color:
834
Working with ASP.NET Applications PART IV LISTING 18.18
TraceCategories.aspx
Sub Page_Load Dim conPubs As SqlConnection Dim cmdSelect As SqlCommand conPubs = New SqlConnection( “Server=Localhost;UID=sa;PWD=secret; ➥Database=Pubs” ) cmdSelect = New SqlCommand( “SELECT au_lname FROM Authors”, conPubs ) Trace.Warn( “Database”, “Opening Connection” ) conPubs.Open() dropAuthors.DataSource = cmdSelect.ExecuteReader() dropAuthors.DataTextField = “au_lname” Trace.Warn( “Database”, “Binding to DropDownList” ) dropAuthors.DataBind Trace.Warn( “Database”, “Closing Connection” ) conPubs.Close() End Sub Sub Button_Click( s As Object, e As EventArgs ) Trace.Warn( “Form”, “User selected “ & dropAuthors.SelectedItem.Text ) End Sub TraceCategories.aspx Choose an Author Author Last Name:
Notice that the page directive at the top of the page in Listing 18.18 contains a TraceMode attribute. This attribute can have one of two values: SortByTime or
Application Tracing and Error Handling CHAPTER 18
835
SortByCategory. TraceMode
Because you want to group trace messages by category, you set to the value SortByCategory.
When each trace message is created with the Trace.Warn statement, the category is specified. For example, the following Trace.Warn statement creates a trace message in the category Database: Trace.Warn( “Database”, “Opening Connection” )
You can, of course, invent any categories you please. All your custom trace messages appear at the bottom of the Trace Information section.
Displaying Errors in Trace Messages If you use TRY...CATCH statements in your ASP.NET pages to gracefully handle errors (as you should), errors are not displayed in the page when something goes wrong. The lack of error messages can make the process of debugging a page difficult. Fortunately, you can display the errors caught in TRY...CATCH statements within trace messages.
LISTING 18.19
TraceException.aspx
Sub Page_Load Dim txtTextBox As TextBox Try txtTextBox.Text = “Hello!” Catch objException As Exception Trace.Warn( “Page Errors”, “Assigning Value To TextBox”, objException ) End Try End Sub TraceException.aspx This page contains an error!
18 APPLICATION TRACING AND ERROR HANDLING
The page in Listing 18.19, for example, contains an error. The statement that causes the error is wrapped in a TRY...CATCH statement, so the error is not displayed in the page. However, the error is displayed within a trace message.
836
Working with ASP.NET Applications PART IV
In Listing 18.19, you pass three parameters to the Trace.Warn method: the category of the trace message, the text of the trace message, and the exception caught in the TRY...CATCH block. The values of all three parameters are displayed in the Trace Information section when tracing is enabled.
Using Application-Level Tracing You can use a special page, named trace.axd, to view trace information collected from all the pages in your application. By default, the trace.axd page automatically displays a list of the last 10 requests to your application. A View Details link appears next to each item in the list, linking to a page that shows detailed trace information for that request (see Figure 18.9). FIGURE 18.9 Application-level tracing.
Before you can use the trace.axd page, you must enable application-level tracing within either the Machine.Config file or root Web.Config file for your application. The Web.Config file in Listing 18.20 contains the necessary values to enable application-level tracing. (You can find this file in the AppTrace directory on the CD that accompanies this book.) LISTING 18.20
AppTrace/Web.Config
Application Tracing and Error Handling CHAPTER 18 LISTING 18.20
837
continued
The Web.Config file in Listing 18.20 enables application-level tracing on the local machine. It specifies that trace statistics should be stored for the last 50 requests. The trace configuration section contains the following five elements: •
enabled—A
Boolean value that indicates whether tracing is enabled for the application. The default value is true.
•
requestLimit—The
number of requests to list in the trace.axd page. The default
value is 10. pageOutput—A Boolean value that indicates whether trace information is displayed at the bottom of every page in the application. The default value is false.
•
traceMode—A
•
localOnly—A
value that indicates the order in which trace messages are displayed. Possible values are SortByTime and SortByCategory; the default value is SortByTime. Boolean value that indicates whether trace messages should be displayed only on the local computer, not remote computers. The default value is true.
Warning If you want to disable the trace.axd page on a production Web site, you need to remove the reference to the trace page handler in the Machine.Config file. You can remove this entry from the httpHandlers section.
Monitoring the ASP.NET Process ASP.NET applications execute in a separate process from the Web server. The Web server executes in a process named inetinfo.exe, and ASP.NET applications execute in a process named aspnet_wp.exe (the ASP.NET Worker Process).
18 APPLICATION TRACING AND ERROR HANDLING
•
838
Working with ASP.NET Applications PART IV
You can modify several configuration settings that affect the behavior of the ASP.NET process. These configuration settings can be divided into two groups. The first group enables you to automatically restart the ASP.NET process in response to problems such as memory leaks, deadlocks, and access violations: •
memoryLimit—The percentage of system memory that the ASP.NET process is allowed to consume. By default, this setting has the value 80, which stands for 80% of system memory. (memoryLimit refers to the percentage of physical memory, not the percentage of virtual memory.)
•
requestQueueLimit—The maximum number of queued requests. By default, this setting has the value 5000, which stands for 5,000 requests.
•
shutdownTimeout—The amount of time that the ASP.NET process is allowed to gracefully shut down. By default, this setting has the value 00:00:05, which stands for 5 seconds.
When the ASP.NET worker process exceeds the memory or request queue limit, the process is automatically shut down and replaced by a new worker process. All the pending requests to the old process are automatically transferred to the new process. In theory, this should mean that the handoff between processes should be undetectable to the users of the application. You can modify these settings in the section of the Machine.Config file. These setting are read directly by aspnet_isapi.dll when the ASP.NET worker process is started. The second group of configuration settings enables you to automatically restart the ASP.NET process after a certain amount of time or a certain number of requests: •
idleTimeout—The
amount of idle time before the ASP.NET process is automatically shut down. The value is specified in hours, minutes, and seconds represented by a string in the format 99:99:99. By default, this setting has the value infinite, which indicates that the ASP.NET process should never be shut down.
•
requestLimit—The
number of requests before the ASP.NET process is shut down. By default, this setting has the value infinite, which indicates that the ASP.NET process should never be shut down.
•
timeout—The
amount of time before the ASP.NET process is automatically shut down. The value is specified in hours, minutes, and seconds represented by a string in the format 99:99:99. By default, this setting has the value infinite, which indicates that the ASP.NET process should never be shut down.
Application Tracing and Error Handling CHAPTER 18
839
You can use these settings to automatically recycle the ASP.NET process every so often, to reclaim memory lost to memory leaks or dump bad code from memory. For example, the following processModel section automatically shuts down the ASP.NET process once every 24 hours:
You can test processModel configuration settings by using the page in Listing 18.21. Warning The page in Listing 18.21 is designed to crash the ASP.NET process by consuming too much memory.
LISTING 18.21
ProcessModel.aspx
Sub Page_Load Dim objStream As MemoryStream objStream = Application( “Stream” ) If IsNothing( objStream ) Then objStream = New MemoryStream() objStream.WriteByte( 255 ) End If objStream.WriteTo( objStream ) Application( “Stream” ) = objStream lblMemory.Text = objStream.Length lblProcessID.Text = _ ProcessModelInfo.GetCurrentProcessInfo.ProcessID End Sub ProcessModel.aspx Memory Consumed:
18 APPLICATION TRACING AND ERROR HANDLING
840
Working with ASP.NET Applications PART IV LISTING 18.21
continued
bytes
Process ID:
The page in Listing 18.21 doubles the amount of memory that it consumes every time you request it. It also displays the ID of the current process. Notice that when you pass a certain threshold—the percentage of physical memory specified by the memoryLimit setting—a new process automatically replaces the current process.
Retrieving Process Information You can use the ProcessModelInfo class to retrieve information about the ASP.NET process. The GetCurrentProcessInfo method returns information about the ASP.NET process currently executing, and the GetProcessInfoHistory method returns a history of ASP.NET processes. Both methods return instances of the ProcessInfo class, which has the following properties: •
Age—A TimeSpan value that represents the length of time the ASP.NET process has been running
•
PeakMemoryUsed—The
maximum amount of memory used by the ASP.NET
process in bytes •
ProcessID—An
integer that represents the ID of the ASP.NET process
•
RequestCount—An
integer that represents the number of requests handled by the
ASP.NET process •
ShutdownReason—A
value representing the reason the ASP.NET process shut down (for example, MemoryLimitExceeded)
•
StartTime—A DateTime value representing the date and time the ASP.NET process was started
•
Status—The
status of the ASP.NET process (for example, Alive or Shutdown).
The page in Listing 18.22, for example, displays information about the last 10 ASP.NET processes (see Figure 18.10).
Application Tracing and Error Handling CHAPTER 18
841
FIGURE 18.10 Viewing process history.
18 ProcessHistory.aspx
Sub Page_Load dgrdHistory.DataSource = ProcessModelInfo.GetHistory( 10 ) dgrdHistory.Databind() End Sub ProcessHistory.aspx
APPLICATION TRACING AND ERROR HANDLING
LISTING 18.22
842
Working with ASP.NET Applications PART IV LISTING 18.22
continued
Logging Events The Windows 2000 operating system includes a centralized set of system event logs. You can use these centralized logs to record events from your ASP.NET applications to a standard location. You add new entries or retrieve existing entries from your server’s event logs by using the EventLog class. For example, the page in Listing 18.23 contains a form that enables you to insert a new entry into the Application event log. LISTING 18.23
AddEventLog.aspx
Sub Button_Click( s As Object, e As EventArgs ) Dim objLog As EventLog If Not EventLog.SourceExists( “myApp” ) Then EventLog.CreateEventSource( “myApp”, “Application” ) End If
Application Tracing and Error Handling CHAPTER 18 LISTING 18.23
843
continued
objLog = New EventLog objLog.Source = “myApp” objLog.WriteEntry( txtLogEntry.Text, EventLogEntryType.Information) End Sub AddEventLog.aspx
The Button_Click subroutine in Listing 18.23 adds a new event to the Application event log. First, the subroutine creates a new event source named myApp. Next, an instance of the EventLog class is created. The Source property is associated with the name of the event source. Finally, the WriteEntry method is called to add the event to the event log. The first parameter passed to the WriteEntry method represents the message to add to the event log. You can write any message that you please. The second parameter represents the type of the message. The EventLogEntryType enumeration has the following five values: •
Error
•
FailureAudit
•
Information
•
SuccessAudit
•
Warning
You can use the EventLog class with the Application_Error subroutine in the Global. asax file to automatically log all errors in your ASP.NET application to a server event log. The Global.asax file in Listing 18.24 illustrates how to log all application errors to
18 APPLICATION TRACING AND ERROR HANDLING
844
Working with ASP.NET Applications PART IV
a custom event log named CustomLog. (You can find this page in the LogError directory on the CD that accompanies this book.) LISTING 18.24
LogError/Global.asax
Sub Application_Error Dim strErrorText As String Dim objLog As EventLog strErrorText strErrorText strErrorText strErrorText strErrorText
= “ERROR IN “ & Request.Path &= vbNewline & vbNewline & “ERROR:” &= Server.GetLastError.Message &= vbNewline & vbNewline & “STACK TRACE:” &= Server.GetLastError.StackTrace
If Not EventLog.SourceExists( “CustomLog” ) Then EventLog.CreateEventSource( “CustomLog”, “CustomLog” ) End If objLog = New EventLog objLog.Source = “CustomLog” objLog.WriteEntry( strErrorText, EventLogEntryType.Error ) End Sub
If you add the Global.asax file in Listing 18.24 to the root directory of your application, any error generated by any page in your application is recorded to the CustomLog event log. You can view entries in the CustomLog event log by opening Event Viewer on your server. Alternatively, you can use the ASP.NET page in Listing 18.25 to display all the entries in the CustomLog event log. LISTING 18.25
DisplayEventLog.aspx
Sub Page_Load Dim objLog As EventLog objLog = New EventLog objLog.Log = “CustomLog”
Application Tracing and Error Handling CHAPTER 18 LISTING 18.25
845
continued
dgrdLog.DataSource = objLog.Entries dgrdLog.DataBind() End Sub DisplayEventLog.aspx
In Listing 18.25, all the entries from the CustomLog event log are retrieved with the Entries property of the EventLog class. The log entries are displayed in a DataGrid control.
18 You can use the page in Listing 18.25 to view the Application, System, or Security log by changing the Log property to point to the appropriate log file. For example, to view the Application event log, you would set the Log property like this: myLog.Log = “Application”
Using Performance Counters You can use performance counters to monitor the performance of your ASP.NET applications. The ASP.NET framework includes several standard performance counters that you can use. Alternatively, you can create your own custom performance counters.
Using ASP.NET Performance Counters The standard ASP.NET performance counters can be divided into two groups: system performance counters and application performance counters. The system performance counters contain information about every executing ASP.NET application, whereas the application performance counters contain information about a particular ASP.NET application.
APPLICATION TRACING AND ERROR HANDLING
Note
846
Working with ASP.NET Applications PART IV
The system performance counters include the following counters: • Application Restarts—The total number of ASP.NET application restarts since Internet Information Server was last started. Specifically, this counter counts each Application_End event. • Application Running—The total number of executing ASP.NET applications. • Requests Disconnected—The total number of requests that were disconnected due to a communication failure. • Requests Queued—The total number of requests waiting in the queue. • Request Wait Time—The amount of time, in milliseconds, that the last request waited for processing in the queue. • Worker Process Restarts—The total number of times that the ASP.NET process has been restarted. • Worker Process Running—The total number of executing ASP.NET processes. You can use the application counters to monitor the performance of a particular ASP.NET application instance, or an aggregate of all application instances, by using the __Total__ instance. The following is a partial list of the application counters: • Authentication Anonymous Requests—The total number of anonymous requests • Authentication Anonymous Requests/Sec—The number of anonymous requests made per second • Debugging Requests—The number of requests that occurred while debugging was enabled • Errors During Compilation—The number of errors that occurred during compilation • Errors During Execution—The total number of errors that occurred during the execution of an HTTP request • Errors During Preprocessing—The number of errors that occurred during parsing • Errors Unhandled During Execution—The total number of unhandled errors that occurred during the execution of HTTP requests • Errors Unhandled During Execution/Sec—The number of unhandled exceptions per second that occur during the execution of HTTP requests • Total Errors—The total number of errors that occur during the execution of HTTP requests, including any parsing, compilation, or runtime errors
Application Tracing and Error Handling CHAPTER 18
847
• Total Errors/Sec—The number of errors per second that occur during the execution of HTTP requests, including any parsing, compilation, or runtime errors • Request Bytes in Total—The total size of all requests in bytes • Request Bytes out Total—The total size of responses sent to a client in bytes (not including standard HTTP response headers) • Requests Executing—The number of requests currently executing • Requests Failed Total—The total number of failed requests, including requests that timed out, requests that were not authorized (status code 401), or requests not found (404 or 414) • Requests Not Found—The number of requests that failed because resources were not found (status code 404, 414) • Requests Not Authorized—The number of requests that failed due to no authorization (status code 401) • Requests Succeeded—The number of requests that executed successfully and returned with status code 200 • Requests Timed Out— The number of requests that timed out • Requests/Sec—The number of requests executed per second • Sessions Active—The current number of sessions currently active • Sessions Abandoned—The number of sessions that have been explicitly abandoned • Sessions Timed Out—The number of sessions that timed out • Sessions Total—The total number of sessions You can view any of these ASP.NET system or application performance counters by launching Windows Performance Monitor. In Windows 2000, go to Start, Programs, Administrative Tools, Performance. To view a particular counter, click the Add button and select the counter that you want to view from the dialog box. Note You also can launch Performance Monitor by typing perfmon at a command prompt.
18 APPLICATION TRACING AND ERROR HANDLING
• Requests Total—The total number of requests since the service was started
848
Working with ASP.NET Applications PART IV
By monitoring the Requests/Sec performance counter, for example, you can determine how well your Web application scales. By monitoring the Total Errors performance counter, you can detect whether your site is experiencing problems.
Retrieving Performance Counters in an ASP.NET Page Instead of monitoring performance counters through Windows Performance Monitor, you can retrieve counter values directly through an ASP.NET page. You can use the classes from the System.Diagnostics namespace to work with performance counters within your application. To retrieve an object that represents a particular performance counter, you need to supply the performance counter category name, performance counter name, and performance counter instance name. For example, the following statements create a counter that represents the Total Requests performance counter: Dim objCounter As PerformanceCounter objCounter = New PerformanceCounter( “ASP.NET Applications”, “Requests Total”, ➥ “__Total__” )
The first parameter, ASP.NET Applications, represents the performance counter category name. The second parameter, Requests Total, represents a particular performance counter. Finally, the last parameter, __Total__, represents the performance counter instance name (the instance name __Total__ is preceded and followed by two underscore characters). After you create an instance of a performance counter, you can read its values by using one of the following properties or methods: •
RawValue—Returns
the raw value of the performance counter at the time the
counter is read •
NextValue—Returns
the calculated value of the counter
•
NextSample—Returns
a sample of the value of the counter that you can use when calculating statistics for the counter with the Calculate method of the CounterSample class
The page in Listing 18.26, for example, uses the NextValue method to return the value of a particular counter. The page enables you to view the value of any ASP.NET Application counter for any application instance (see Figure 18.11).
Application Tracing and Error Handling CHAPTER 18
849
FIGURE 18.11 Displaying performance counters.
18 ReadCounters.aspx
Sub Page_Load If Not IsPostBack Then Dim objCategory As PerformanceCounterCategory objCategory = New PerformanceCounterCategory( “ASP.NET Applications” ) lstInstances.Datasource = objCategory.GetInstanceNames() DataBind() End If End Sub Sub SelectInstance( s As Object, e As EventArgs ) Dim objCategory As PerformanceCounterCategory objCategory = New PerformanceCounterCategory( “ASP.NET Applications” ) lstCounters.Visible = True lstCounters.Datasource = objCategory.GetCounters( ➥lstInstances.SelectedItem.Text ) lstCounters.DataTextField = “CounterName” DataBind() End Sub
APPLICATION TRACING AND ERROR HANDLING
LISTING 18.26
850
Working with ASP.NET Applications PART IV LISTING 18.26
continued
Sub SelectCounter( s As Object, e As EventArgs ) Dim objCounter As PerformanceCounter objCounter = New PerformanceCounter( “ASP.NET Applications”, _ lstCounters.SelectedItem.Text, lstInstances.SelectedItem.Text ) lblValue.Text = objCounter.NextValue() End Sub ReadCounters.aspx ASP.NET Application Performance Counters
Instances
Counters
Counter Value:
In the Page_Load subroutine in Listing 18.26, a list of instance names is returned from the PerformanceCounterCategory class and assigned to a ListBox control. If you pick a performance counter instance from the ListBox control, the SelectInstance subroutine executes.
Application Tracing and Error Handling CHAPTER 18
851
The SelectInstance subroutine retrieves a list of performance counters for a particular instance with the GetCounters method of the PerformanceCounterCategory class. The list of counters is assigned to a ListBox control. If you pick a counter in the ListBox control, the SelectCounter subroutine is executed. The SelectCounter subroutine uses the NextValue method to display the value of the selected performance counter. The value is displayed in a Label control. You should be aware of two issues when reading performance counters in an ASP.NET page. First, the page in Listing 18.26 uses the NextValue method to retrieve the value of a counter. Several performance counters, such as Requests/Sec, require you to read the counter value more than once to retrieve a value other than 0. To retrieve a useful value from this type of counter, you need to call the NextValue method multiple times or use the NextSample method. You also should be aware that, in the case of performance counters, to be is to be perceived. The values of all the performance counters are reset to 0 when the last component that references the counter is disposed. This means, unless you have an application that is continuously reading the performance counter, you won’t get very accurate information from the performance counter.
Creating Custom Performance Counters Imagine that you want to track the total number of orders, the number of orders per second, or the amount of money per second that your company Web site is producing. You can create custom performance counters to track these values. The easiest way to create a new counter is to use the Create method of the PerformanceCounterCategory class. This statement creates both a new counter category and a single performance counter in that category. For example, the following statement creates a new performance counter category named MyCounters and a new performance counter named Total Orders: PerformanceCounterCategory.Create( _ “MyCounters”, _ “My Custom Counters”, _ “Total Orders”, _ “Displays total product orders” )
APPLICATION TRACING AND ERROR HANDLING
You can get around this limitation in two ways: Keep Windows Performance Monitor running or start the Performance Counter Windows Service. The Performance Counter Service was installed on your server when you installed the .NET framework. To start the service, go to Start, Programs, Administrative Tools, Services. Running either Performance Monitor or the Performance Counter Service prevents your counters from being reset.
18
852
Working with ASP.NET Applications PART IV
In this statement, four parameters are passed to the Create method: • Category name—The name of the new performance counter category • Category help—A message describing the counters in this category • Counter name—The name of the new performance counter • Counter help—A message describing the new performance counter After you execute the Create method, a new performance counter category and performance counter are created on your server. You need to stop and restart Windows Performance Monitor before you can view it. You can modify the value of a custom performance counter in an ASP.NET page by using either the Increment or IncrementBy method. The Increment method increments the current value of the counter by one, and the IncrementBy method increments the current value of the counter by a certain integer amount. If you need to decrement the value of a counter, pass a negative number to the IncrementBy method. The ASP.NET page in Listing 18.27 illustrates how you can create and use a custom performance counter. LISTING 18.27
CreateCounter.aspx
Sub Button_Click( s As Object, e As EventArgs ) Dim objCounter As PerformanceCounter If Not PerformanceCounterCategory.Exists( “MyAppCounters” ) Then PerformanceCounterCategory.Create( _ “MyAppCounters”, _ “My Custom Counters”, _ “Total Orders”, _ “Displays total product orders” ) End If objCounter = New PerformanceCounter( “MyAppCounters”, “Total Orders”, False ) objCounter.Increment End Sub CreateCounter.aspx
Application Tracing and Error Handling CHAPTER 18 LISTING 18.27
853
continued
The page in Listing 18.27 contains a form with one button labeled Place Order! If you click the Place Order! button, the Button_Click subroutine is executed. This subroutine checks whether a performance counter category named MyAppCounters already exists. If the category doesn’t exist, the category is created with a single performance counter named Total Orders. Next, the subroutine creates an instance of the Total Orders counter. The current value of this counter is incremented by one by calling the Increment method.
Using the Debugger The .NET framework includes a visual debugger that enables you to step line-by-line through the statements of an executing ASP.NET page. You can use the debugger to create breakpoints and watches, to view the values of variables at any point during page execution. In the following sections, you learn how to set up the debugger and create breakpoints and watches. Note The debugger included with the .NET framework is the same as the debugger included with Visual Studio .Net, except that the .NET framework debugger is missing some features. For example, the .NET framework debugger does not support remote debugging.
18 APPLICATION TRACING AND ERROR HANDLING
To test this page, launch Windows Performance Monitor and add the Total Orders counter (if you already have Performance Monitor open, you might have to close and reopen it). Every time you click the Place Order! button in the ASP.NET page, the value of the Total Orders counter should increment in Windows Performance Monitor.
854
Working with ASP.NET Applications PART IV
Attaching the Debugger Before you can use the debugger, you must place your ASP.NET application in debug mode. The debugger requires the extra symbol information generated in debug mode to associate executing statements with the appropriate source code. To place your application in debug mode, include the Web.Config file in Listing 18.28 in the root directory of your application. LISTING 18.28
DebugMode/Web.Config
To start debugging an ASP.NET page, follow these steps: 1. Request the page that you want to debug with your Web browser. Requesting the page ensures that the correct ASP.NET application is executing. 2. Launch the debugger named DbgClr.exe. You can find it in the GuiDebug subdirectory in the Framework SDK install directory. 3. Click the Open File button to open the ASP.NET page that you want to debug. 4. Select Debug, Processes to open the Processes dialog box. 5. Select Show system processes, select the aspnet_wp.exe process, and click Attach… 6. Close the Processes dialog box. Warning Never use the debugger on a production Web site. When you attach the debugger to an ASP.NET application, it freezes requests to the application.
Creating Breakpoints Before you can debug an ASP.NET page, you must first specify one or more breakpoints in page execution. When an executing page reaches a breakpoint, the debugger enters break mode. You can view the values of variables only when the debugger is in break mode.
Application Tracing and Error Handling CHAPTER 18
855
To set a break point within the debugger, click the margin on the left of the statement where you want the breakpoint to occur. Clicking the margin should cause a solid red dot to appear to signify the breakpoint (see Figure 18.12). You can add as many breakpoints to a page as you want. FIGURE 18.12 Setting a breakpoint.
18
After you create at least one breakpoint, request the page that you are debugging within a Web browser. The debugger should gain window focus when the breakpoint is reached. The debugger automatically enters break mode.
Creating Watches When the debugger is in break mode after reaching a breakpoint, you can view the current values of the variables in a page in several ways. The easiest way to view the value of any variable in the debugger is to hover the mouse pointer over the name of the variable. A ToolTip displays the current value of the variable. Alternatively, you can open the Quick Watch dialog box by selecting Debug, Quick Watch. You also can automatically add a variable to the Quick Watch dialog box by right-clicking the name of the variable and selecting Quick Watch.
APPLICATION TRACING AND ERROR HANDLING
If you hover your mouse pointer over a breakpoint, a ToolTip displays the line number of the ASP.NET application that is associated with the breakpoint.
856
Working with ASP.NET Applications PART IV
Within the Quick Watch dialog box, you can view the value of any variable by entering its name in the Expression text box and clicking Recalculate. You can also assign a new value to any variable within the Quick Watch dialog box by clicking its current value and entering a new value. Finally, you can add a watch to the debugger. Within the Quick Watch dialog box, click the Add Watch button. Outside Quick Watch, right-click the name of a variable and select Add Watch. Either method adds the variable to the Watch window. You can edit the value of any variable in the Watch window by clicking its current value and entering a new value. After you add a watch for a variable to the Watch window, the Watch window continues to display the current value of the variable as you step through each line in the ASP.NET page.
Stepping Through an ASP.NET Page After the debugger hits a breakpoint, you can continue execution in several ways. If you want the page to continue executing until the next breakpoint is reached, you can click the Continue button (the VCR play button). You also can continue page execution by selecting any of the following options from the Debug menu: • Step Into—Executes the next unit of code. Step Into executes each unit of code in a function or subroutine individually. • Step Over—Executes the next unit of code. Step Over executes an entire function or subroutine without executing each unit of code individually. • Step Out—Executes the remainder of a function or subroutine and enters break mode when executed within the function or subroutine. You can use Step Into, Step Over, and Step Out with different units of execution. You can step by statement, line, or instruction. To select the unit of execution, choose Debug, Step By.
Summary In this chapter, you learned how to debug, trace, and monitor your ASP.NET pages and applications. In the first section, you learned how to debug ASP.NET pages at both the page and application level. You learned how to gracefully handle errors at the page level
Application Tracing and Error Handling CHAPTER 18
857
by using TRY...CATCH blocks. You also examined methods for handling errors at the application level by using the Application_OnError subroutine and configuring custom errors. Next, you learned how to trace, log, and monitor your ASP.NET pages and applications. You learned how to enable tracing for both individual pages and entire ASP.NET applications and use the process model to automatically shut down misbehaving ASP.NET applications. You also examined how to log events in an ASP.NET page to the standard server event logs and monitor the performance of an ASP.NET application by using performance counters. In the last section, you looked at the visual debugger included with the .NET framework. You learned how to use the debugger to create breakpoints and watches and step through an executing ASP.NET page line-by-line.
18 APPLICATION TRACING AND ERROR HANDLING
Securing ASP.NET Applications
PART
V IN THIS PART 19 Using Forms-Based Authentication
861
20 Using Windows-Based Authentication 21 Encrypting Data over the Network
911
927
CHAPTER 19
Using FormsBased Authentication IN THIS CHAPTER • Working with Forms Authentication 862 • Working with Passport Authentication 899
862
Securing ASP.NET Applications PART V
In this chapter, you examine two methods of password-protecting pages in a Web application. Both methods use cookie authentication with HTML forms. In the first part of this chapter, you learn how to use ASP.NET Forms authentication. When you use Forms authentication, you can store usernames and passwords in a custom data store, such as the Web.Config file, an XML file, or a database table. Next, you learn how to configure an ASP.NET application to take advantage of Microsoft Passport authentication. Microsoft Passport is a centralized authentication service that enables users to use the same username and password across multiple Web sites.
Working with Forms Authentication If you want to set up a custom user registration system for your Web site, you can use Forms authentication. The advantage of this type of authentication is that it enables you to store usernames and passwords in whatever storage mechanism you desire. For example, you can store usernames and passwords in the Web.Config file, an XML file, or a database table. Forms authentication relies on browser cookies to determine the identity of a user. After you enable Forms authentication for a directory, pages in the directory cannot be accessed unless the user has the proper Authentication Ticket stored in a cookie. If a user requests a page without the proper Authentication Ticket, he or she can be automatically redirected to a login page. If the user enters a valid username and password combination, you can automatically redirect him or her back to the original page. When using Forms authentication, you can easily set up an automatic user registration system. For example, you can create a database table that contains usernames and passwords. In that case, adding a new registered user is as simple as adding a new username and password to the database table. The .NET classes for Forms authentication are located in the System.Web.Security namespace. The following list contains the most important of these classes: •
FormsAuthentication—This class contains several shared methods for working with Forms authentication.
•
FormsAuthenticationTicket—This
class represents the Authentication Ticket used in the cookie for Forms authentication.
Using Forms-Based Authentication CHAPTER 19
•
FormsIdentity—This class represents the identity of the user authenticated with Forms authentication.
•
FormsAuthenticationModule—This
863
class is the actual module used for forms
authentication. You learn how to take advantage of these classes in the following sections.
Enabling Forms Authentication To enable basic Forms authentication for an application, you must complete the following three steps: • Set the authentication mode for the application by modifying the authentication section in the application root Web.Config file. • Deny access to anonymous users in one or more directories in the application by modifying the authorization section in the Web.Config files in the appropriate directories. • Create a login page containing a form that enables users to enter their usernames and passwords. The first step is to enable Forms authentication for an application. To do so, you must modify an application’s root directory Web.Config file. If the Web.Config file doesn’t already exist, you can create it. Warning
However, enabling Forms authentication does not force you to password-protect every page. You can enable anonymous access for particular directories within an application even when Forms authentication is enabled for the application.
Note All the files discussed in this section are located in the FormSimple directory on the CD that accompanies this book. To use these files, copy the FormSimple directory and all its subdirectories to your Web server. Next, create a new virtual directory that points to the FormSimple directory.
19 USING FORMSBASED AUTHENTICATION
You must enable Forms authentication for an ASP.NET application as a whole. You can set the authentication mode only in the Web.Config file located in an application’s root directory.
864
Securing ASP.NET Applications PART V
The Web.Config file in Listing 19.1 contains the minimal amount of information necessary to enable Forms authentication for an application. LISTING 19.1
FormsSimple/Web.Config
In Listing 19.1, the authentication mode is set to Forms. Creating this file enables Forms authentication for the entire application. Warning The names of the elements and attributes in the Web.Config file are case sensitive.
The next step is to password-protect individual directories. You can require users to log in to access any ASP.NET page at your Web site by modifying the root directory Web.Config file. Alternatively, you can add Web.Config files to particular directories to password-protect only certain pages. To password-protect a particular directory and its subdirectories, add the Web.Config file in Listing 19.2 to the directory. (This file can be found in the FormsSimple\Secret subdirectory.) LISTING 19.2
FormsSimple\Secret\Web.Config
The Web.Config file in Listing 19.2 denies access to the ASP.NET pages contained in the directory to anonymous users. The ? symbol represents all anonymous users.
Using Forms-Based Authentication CHAPTER 19
865
Warning By default, Forms authentication applies only to ASP.NET pages. You cannot use it to password-protect other types of files, such as image files, Microsoft Word documents, text files, or Classic ASP files. If you want to use Forms authentication with other types of files, then you must explicitly map the file type to the aspnet_isapi.dll extension in the Internet Services Manager.
After you add the Web.Config file in Listing 19.2 to a directory, anonymous users are denied access to ASP.NET pages in that directory and all subdirectories. If you want to enable users to access files in a particular subdirectory, you can add the Web.Config file contained in Listing 19.3. (This file can be found in the FormsSimple\Anon subdirectory.) LISTING 19.3
FormsSimple\Anon\Web.Config
The Web.Config file in Listing 19.3 allows all anonymous users to access any page contained in a directory and all the subdirectories of that directory.
The simple Login.aspx page contained in Listing 19.4 displays a form with a field for a username and password (see Figure 19.1). The page requires you to enter the username Sam and password Secret. (The form is case-sensitive.)
USING FORMSBASED AUTHENTICATION
The final step required for enabling Forms authentication is to create the Login.aspx page. If you attempt to access an ASP.NET page in a password-protected directory and you don’t have the proper Authentication Ticket cookie, you are automatically redirected to this page.
19
866
Securing ASP.NET Applications PART V
FIGURE 19.1 A simple login page.
LISTING 19.4
FormsSimple\Login.aspx
Sub Button_Click( s As Object, e As EventArgs ) If IsValid Then If txtUsername.Text = “Sam” And txtPassword.Text = “Secret” Then FormsAuthentication.RedirectFromLoginPage( txtUsername.Text, ➥chkRemember.Checked ) Else lblMessage.Text = “Bad username/password!” End If End If End Sub Login.aspx Please Login:
Username:
Password:
Remember me with a cookie?
In Listing 19.4, the important work happens in the Button_Click subroutine, which first checks the IsValid property to test whether both a username and password were entered into the form. If the page is valid, the values of the username and password form fields are matched against the values Sam and Secret.
19 USING FORMSBASED AUTHENTICATION
868
Securing ASP.NET Applications PART V
If you enter the correct username and password, the RedirectFromLoginPage method is called. Two parameters are passed to this method: the username and a Boolean value indicating whether a persistent cookie should be created. Forms authentication supports both session and persistent cookies. When you call the RedirectFromLoginPage, you can indicate whether a persistent cookie should be created. If the RedirectFromLoginPage creates a persistent cookie, the cookie continues to exist even if the user shuts down his or her computer and returns to your Web site many days in the future. Warning Normally, you should allow users to decide whether to create a persistent cookie. For example, if you were temporarily using a computer at the library or a friend’s house, you would not want to create a persistent cookie on that computer.
Calling the RedirectFromLoginPage method performs two actions. First, it creates a cookie on the user’s browser that contains an Authentication Ticket. After this cookie is set, the user can access pages in directories that require Forms authentication. The RedirectFromLoginPage method also automatically redirects the user back to the page that sent him or her to the Login.aspx page in the first place by using a browser redirect. Note The RedirectFromLoginPage method redirects the user back to the page indicated by the ReturnUrl query string variable. If the user links directly to the Login.aspx page, the ReturnUrl query string variable doesn’t have a value. In that case, the RedirectFromLoginPage redirects the user to the Default.aspx page.
Configuring Forms Authentication In the preceding section, you learned how to modify the Web.Config file to enable Forms authentication for an application. In this section, you examine the options for configuring Forms authentication in more detail.
Using Forms-Based Authentication CHAPTER 19
869
The authentication section in the Web.Config file can contain an optional forms element, which supports the following attributes: •
loginUrl—The page where the user is automatically redirected when authentication is required. By default, users are redirected to the Login.aspx page in the application root directory. However, you can change this attribute to point to any page that you please.
•
name—The
name of the browser cookie that contains the Authentication Ticket. By default, the cookie is named .ASPXAUTH. However, if you are configuring multiple applications on the same server, you should provide a unique cookie name for each application.
•
timeout—The amount of time in minutes before a cookie expires. By default, this attribute has the value 30 minutes. This attribute does not apply to persistent cookies.
•
path—The
•
protection—The
path used for the cookie. By default, this attribute has the value /.
way the cookie data is protected. Possible values are All, None, Encryption, and Validation; the default value is All.
The protection attribute requires some explanation. By default, cookies are encrypted by using either DES or TripleDES encryption (depending on the capabilities of the server). Furthermore, the contents of the cookie are validated with a Message Authentication Code to protect against tampering. You can disable encryption or validation or both features by changing the value of the attribute. For example, setting protection to Encryption causes the cookie to be encrypted but not validated. You can get better performance from your application by disabling encryption and validation. However, disabling these features also results in a less secure site. protection
LISTING 19.5
FormsAttributes\Web.Config
USING FORMSBASED AUTHENTICATION
The Web.Config file in Listing 19.5 illustrates how you can set the forms attributes.
19
870
Securing ASP.NET Applications PART V
Configuring Forms Authorization The authorization section of the Web.Config file determines which users can access ASP.NET pages within a directory. In the simplest case, you want to use the authorization section to deny all anonymous users access to the pages in a directory by using a Web.Config file like the one in Listing 19.6. LISTING 19.6
Web.Config
The authorization section can contain either elements, which deny access for particular users, or elements, which enable access for particular users. You can also use the special symbol ?, which stands for all anonymous users, or the symbol *, which stands for all users (both anonymous and authenticated). You can use the authorization section to make fine-grained distinctions among users and among different actions the users might perform. For example, suppose that you want to deny access to all anonymous users. Furthermore, you want to deny access to James and Mark even after they log in. You can do so by using the Web.Config file in Listing 19.7. LISTING 19.7
Web.Config
In Listing 19.7, the element element>> denies access to James and Mark. So, even if these users log in through the Login.aspx page, they cannot access ASP.NET pages located in the current directory.
Using Forms-Based Authentication CHAPTER 19
871
Finally, you can use the verbs attribute to control whether users can use HTTP Post or HTTP Get. For example, the Web.Config file in Listing 19.8 enables anyone to get an ASP.NET page in the directory but prevents everyone except for James or Mark from posting an HTML form. LISTING 19.8
Web.Config
Retrieving User Information The identity of a user authenticated with Forms authentication is represented by the FormsIdentity class. You can use the following properties of this class to retrieve information about an authenticated user: •
AuthenticationType—Always
•
IsAuthenticated—Indicates
•
Name—Indicates
•
returns the value Forms
whether the user was authenticated
the name of an authenticated user
Ticket—Specifies
the cookie Authentication Ticket associated with the current
user
You cannot retrieve one important bit of information—the password associated with the user. There’s a good reason for this limitation. The password is not actually stored as part of the cookie. If you want the password, you must look it up yourself.
You can use the IsAuthenticated property to test whether this user has already been authenticated. If a user requests a page from a directory that requires authentication and then requests a page from a directory that does not require authentication, the IsAuthenticated property continues to return the value True.
USING FORMSBASED AUTHENTICATION
Note
19
872
Securing ASP.NET Applications PART V
The Name property returns the name associated with the current user. Again, after a user is authenticated once, the Name property continues to hold the username. Finally, the Ticket property represents the Authentication Ticket. The FormsAuthenticationTicket class has the following properties: •
CookiePath—The
path of the Authentication Ticket cookie.
•
Expiration—The
date the Authentication Ticket cookie expires.
•
Expired—A
Boolean value indicating whether the current Authentication Ticket
has expired. •
IsPersistent—A
value that indicates whether the Authentication Ticket is contained in a persistent cookie.
•
IssueDate—The
date and time the cookie containing the Authentication Ticket
was created. •
Name—The
username associated with the Authentication Ticket.
•
UserData—Custom
•
Version—An
data that you can include in the Authentication Ticket.
integer representing the version number of the Authentication Ticket. Currently, by default, this property always returns the value 1.
The ASP.NET page in Listing 19.9 demonstrates how you can retrieve and display all these properties within an ASP.NET page. (This page shows something interesting only if the user has already been authenticated before requesting the page, see Figure 19.2.) FIGURE 19.2 Displaying user information.
Using Forms-Based Authentication CHAPTER 19 LISTING 19.9
873
FormsIdentity\FormsIdentity.aspx
Sub Page_Load Dim objUserIdentity As FormsIdentity Dim objTicket As FormsAuthenticationTicket If User.Identity.IsAuthenticated Then objUserIdentity = User.Identity objTicket = objUserIdentity.Ticket lblName.Text = objUserIdentity.Name lblExpiration.Text = objTicket.Expiration lblExpired.Text = objTicket.Expired lblIsPersistent.Text = objTicket.IsPersistent lblIssueDate.Text = objTicket.IssueDate lblUserData.Text = objTicket.UserData lblVersion.Text = objTicket.Version Else lblName.Text = “Who Are You?” End If End Sub FormsIdentity.aspx
19 USING FORMSBASED AUTHENTICATION
Expiration:
Expired:
IsPersistent:
874
Securing ASP.NET Applications PART V LISTING 19.9
continued
IssueDate:
UserData:
Version:
Creating a Sign Out Page If you want to create a page that enables a user to sign out and return to anonymity, you can use the SignOut method of the FormsAuthentication class. Calling the SignOut method removes either a session or persistent cookie. A simple Sign Out page is contained in Listing 19.10. LISTING 19.10
SignOut.aspx
Sub Page_Load FormsAuthentication.SignOut() End Sub SignOut.aspx Goodbye!
Using Forms-Based Authentication CHAPTER 19
875
Authenticating Users with the Web.Config File The Forms authentication classes are flexible enough to enable you to store usernames and passwords in many different storage mechanisms. If you need to store a small number of usernames and passwords, you can store them directly in the Web.Config file. Note All the files discussed in this section are located in the FormConfig directory on the CD that accompanies this book. To use these files, copy the FormConfig directory and all its subdirectories to your server. Next, you need to create a new virtual directory that has the FormConfig directory as its root directory.
To store the usernames and passwords, you need to add a credentials element to the authentication section in the Web.Config file. The file in Listing 19.11 illustrates how to add two username and password entries. (This page is included under the FormConfig directory on the CD-ROM.) LISTING 19.11
FormConfig\Web.Config
The Web.Config file in Listing 19.11 contains both authentication and authorization sections. The authentication section contains a credentials section that contains two usernames and passwords. The authorization section prevents unauthenticated users from accessing any of the ASP.NET pages in the current directory.
19 USING FORMSBASED AUTHENTICATION
876
Securing ASP.NET Applications PART V
Note You must place the Web.Config file contained in Listing 19.11 in the root directory of your application.
To check whether a username and password combination exists in the Web.Config file, you can use the Authenticate method of the FormsAuthentication class. This method returns the value True if the combination exists, and False otherwise. The Login.aspx page contained in Listing 19.12, for example, uses the Authenticate method to validate the username and password entered into a login form. LISTING 19.12
FormConfig\Login.aspx
Sub Button_Click( s As Object, e As EventArgs ) If IsValid Then If FormsAuthentication.Authenticate( txtUsername.Text, ➥txtPassword.Text ) Then FormsAuthentication.RedirectFromLoginPage( txtUsername.Text, False ) Else lblMessage.Text = “Bad username/password!” End If End If End Sub Login.aspx Please Login:
Username:
Password:
In Listing 19.12, the Authenticate method is used in the Button_Click subroutine. If you neglect to check the Authenticate method before calling the RedirectFromLoginPage method, every user is authenticated regardless of the credential information in the Web.Config file.
In general, storing user passwords in clear text in the Web.Config file is not a wise idea. Any person who happens to be working on the Web server can open the Web.Config file and read everyone’s secret passwords. Instead of storing the passwords in clear text, you can encrypt the passwords by using either the SHA1 or MD5 hash algorithms. For example, storing the user credentials like this can be problematic:
USING FORMSBASED AUTHENTICATION
Encrypting Passwords in the Web.Config File
19
878
Securing ASP.NET Applications PART V
Instead, you can store the credentials like this:
In this example, you set the value of passwordFormat to SHA1 and enter the SHA1 hash values of the passwords. Don’t worry, you don’t need to read a book on cryptography to compute the proper hash value for a password. The FormsAuthentication class includes the method (with the very long name) HashPasswordForStoringInConfigFile, which accepts two parameters: a plain text password to encode and the name of a hash algorithm (SHA1 or MD5). The method returns the hashed password. The page in Listing 19.13 uses the HashPasswordForStoringInConfigFile method to compute the hash value for any clear text password. LISTING 19.13
ComputeHash.aspx
Sub Button_Click( s As Object, e As EventArgs ) Dim strHashValue As String strHashValue = FormsAuthentication.HashPasswordForStoringInConfigFile ➥( txtPassword.Text, lstAlgorithm.SelectedItem.Text ) lblHash.Text = strHashValue End Sub ComputeHash.Aspx Compute Hash Value: Password:
Algorithm:
Using Forms-Based Authentication CHAPTER 19 LISTING 19.13
879
continued
Hash Value:
Authenticating Users with an XML File Instead of using the Web.Config file to store usernames and passwords, you can store the usernames and passwords in an XML file. In this section, you learn how to store credentials in an XML file named Passwords.xml.
All the files discussed in this section are located in the FormsXml directory on the CD that accompanies this book. To use these files, copy the FormsXml directory and all its subdirectories to your server. Next, create a new virtual directory that has the FormsXml directory as its root directory.
For this example, you need to create four files: •
Web.Config—This
file contains configuration information for authentication and
authorization. •
Passwords.xml—This
file stores usernames and passwords.
USING FORMSBASED AUTHENTICATION
Note
19
880
Securing ASP.NET Applications PART V
•
Login.aspx—This
page validates a username and password combination against the Password.xml file.
•
Register.aspx—This page enables a user to register at the Web site and add a new username and password to the Passwords.xml file.
The first file should be familiar; it’s the standard Web.Config file for Forms authentication. This file is included in Listing 19.14. LISTING 19.14
FormsXml\Web.Config
Next, you need to create the Passwords.xml file in which you can store all the usernames and passwords. The Passwords.xml file is contained in Listing 19.15. LISTING 19.15
FormsXml\Passwords.xml
Sam Secret Fred Secret
When you validate a username and password, the username and password combination is checked against the Passwords.xml file. When a new user registers, the new user’s username and password are added to this file. The Login.aspx page is contained in Listing 19.16. LISTING 19.16
FormsXml\Login.aspx
Using Forms-Based Authentication CHAPTER 19 LISTING 19.16
881
continued
Sub Page_Load Dim strLinkPath As String If Not IsPostBack Then strLinkPath = String.Format( “Register/Register.aspx?ReturnUrl={0}”, _ Request.Params( “ReturnUrl” ) ) lnkRegister.NavigateUrl = String.Format( strLinkPath ) End If End Sub Sub Button_Click( s As Object, e As EventArgs ) If IsValid Then If XmlAuthenticate( txtUsername.Text, txtPassword.Text ) Then FormsAuthentication.RedirectFromLoginPage( txtUsername.Text, False ) End If End If End Sub Function XmlAuthenticate( strUsername As String, strPassword As String ) As Boolean Dim dstPasswords As DataSet Dim dtblPasswords As DataTable Dim arrUsers() As DataRow
Login.aspx Please Login:
19 USING FORMSBASED AUTHENTICATION
dstPasswords = New DataSet() dstPasswords.ReadXml( MapPath( “Passwords.xml” ) ) dtblPasswords = dstPasswords.Tables( 0 ) arrUsers = dtblPasswords.Select( “name=’” & strUsername & “‘“ ) If arrUsers.Length > 0 Then If arrUsers( 0 )( “password” ) = strPassword Then Return True Else lblMessage.Text = “Incorrect Password!” End If Else lblMessage.Text = “Username Not Found!” End If Return False End Function
882
Securing ASP.NET Applications PART V LISTING 19.16
continued
Username:
Password:
The page in Listing 19.16 contains a function named XmlAuthenticate. This function returns the value True if the username and password entered into the HTML form exist in the Passwords.xml file; otherwise, the function returns the value False. The function loads the Passwords.xml file into a DataSet with the ReadXml method. Next, it uses the DataSet’s Select method to retrieve any entries in the Passwords.xml
Using Forms-Based Authentication CHAPTER 19
883
file in which the name element matches the name entered into the form. If the username and password exist, the function returns the value True. If XmlAuthenticate returns the value True, the RedirectFromLoginPage method is called. This method issues a new cookie containing an Authentication Ticket and automatically redirects the user’s browser back to the page that was originally requested. Notice that the Login.aspx page includes a link to the Register.aspx page. The user can click this link to register a new username and password for the Web site. A query string variable named ReturnUrl is added to the link to Register.aspx in the Page_Load subroutine. This query string variable contains the path to the original page that the user requested before being redirected to the Login.aspx page. The Register.aspx page is contained in a separate directory from the Login.aspx page. Storing pages separately is necessary because all the pages, except the Login.aspx page, require authentication to access. The Register.aspx page, shown in Listing 19.17, is contained in a directory that includes a Web.Config file that enables access for all users. LISTING 19.17
FormsXml\Register\Register.aspx
Sub Button_Click( s As Object, e As EventArgs ) Dim dstPasswords As DataSet Dim dtblPasswords As DataTable Dim arrUsers() As DataRow Dim drowNew As DataRow
19 USING FORMSBASED AUTHENTICATION
If IsValid Then dstPasswords = New DataSet() dstPasswords.ReadXml( MapPath( “../Passwords.xml” ) ) dtblPasswords = dstPasswords.Tables( 0 ) arrUsers = dtblPasswords.Select( “Name=’” & txtUsername.Text & “‘“ ) If arrUsers.Length > 0 Then lblMessage.Text = “Username Already Registered!” Else drowNew = dtblPasswords.NewRow() drowNew( “Name” ) = txtUsername.Text drowNew( “Password” ) = txtPassword.Text dtblPasswords.Rows.Add( drowNew ) dstPasswords.WriteXml( MapPath( “../Passwords.xml” ) ) FormsAuthentication.RedirectFromLoginPage( txtUsername.Text, False ) End If End If End Sub
884
Securing ASP.NET Applications PART V LISTING 19.17
continued
Register.aspx Please Register
Username:
Password:
The Register.aspx page, like the Login.aspx page, contains a form with fields for a username and password. When the form is submitted, the Button_Click subroutine is executed.
Using Forms-Based Authentication CHAPTER 19
885
Before doing anything else, the Button_Click subroutine checks whether the username entered into the form already exists in the Passwords.xml file. You don’t want the same username registered by more than one user. If the username has already been registered, an error is displayed. If the username has not been registered, the new username and password are added to the Passwords.xml file. Next, the RedirectFromLoginPage method is called to send the user back to the original page he or she requested.
Authenticating Users with a Database Table The most common method of creating a custom user registration system is to use a database table to hold usernames and passwords. For large Web sites, this solution is the best because it is the most scalable. Note All the files discussed in this section are located in the FormsDB directory on the CD that accompanies this book. To use these files, you need to copy the FormsDB directory and all its subdirectories to your server. Next, create each of the database objects. Finally, you need to create a new virtual directory that points to FormsDB.
In this section, you are going to create a user registration system with Forms authentication that works with Microsoft SQL Server. To create this system, you need to create the following objects: Web.Config—This
configuration file configures authentication and authorization for Forms authentication.
•
UserList database table—This database table contains the username, password, and unique ID value for each user.
•
Login.aspx—This ASP.NET
•
DBAuthenticate
•
Register.aspx—This ASP.NET
page enables new users to register usernames and passwords by adding them to the UserList table.
•
DBRegister
page validates usernames and passwords by checking them against the contents of the UserList database table.
stored procedure—This SQL Server stored procedure is used by the Login.aspx page to check for a username and password in the UserList table.
stored procedure—This SQL Server stored procedure is used by the page to add a new username and password to the UserList table.
Register.aspx
19 USING FORMSBASED AUTHENTICATION
•
886
Securing ASP.NET Applications PART V
You use SQL Server stored procedures to validate usernames and passwords and to register new users for performance reasons. The first file that you need to create is Web.Config. This file, included in Listing 19.18, should be familiar to you because it contains the standard sections to enable Forms authentication. LISTING 19.18
FormsDB\Web.Config
You store usernames and passwords in a SQL Server database table. You can create this table by using either the SQL Enterprise Manager or SQL Query Analyzer. The SQL command used to create the UserList table is contained in Listing 19.19. LISTING 19.19
FormsDB\UserList.sql
CREATE TABLE UserList ( u_id INT NOT NULL IDENTITY, u_username VARCHAR( 100 ), u_password VARCHAR( 100 ) )
Next, you need to create the Login.aspx page. This page, contained in Listing 19.20, has a form that enables users to log in with their usernames and passwords. LISTING 19.20
FormsDB\Login.aspx
Sub Page_Load Dim strLinkPath As String If Not IsPostBack Then strLinkPath = String.Format( “Register/Register.aspx?ReturnUrl={0}”, _
Using Forms-Based Authentication CHAPTER 19 LISTING 19.20
887
continued
Request.Params( “ReturnUrl” ) ) lnkRegister.NavigateUrl = String.Format( strLinkPath ) End If End Sub Sub Button_Click( s As Object, e As EventArgs ) If IsValid Then If DBAuthenticate( txtUsername.Text, txtPassword.Text ) > 0 Then FormsAuthentication.RedirectFromLoginPage( txtUsername.Text, False ) End If End If End Sub Function DBAuthenticate( strUsername As String, ➥strPassword As String ) As Integer Dim conMyData As SqlConnection Dim cmdSelect As SqlCommand Dim parmReturnValue As SqlParameter Dim intResult As Integer
Login.aspx
19 USING FORMSBASED AUTHENTICATION
conMyData = New SqlConnection( “Server=localhost;UID=sa;PWD=secret; ➥Database=myData” ) cmdSelect = New SqlCommand( “DBAuthenticate”, conMyData ) cmdSelect.CommandType = CommandType.StoredProcedure parmReturnValue = cmdSelect.Parameters.Add( “RETURN_VALUE”, SqlDbType.Int ) parmReturnValue.Direction = ParameterDirection.ReturnValue cmdSelect.Parameters.Add( “@username”, strUsername ) cmdSelect.Parameters.Add( “@password”, strPassword ) conMyData.Open() cmdSelect.ExecuteNonQuery() intResult = cmdSelect.Parameters( “RETURN_VALUE” ).Value conMyData.Close() If intResult < 0 Then If intResult = -1 Then lblMessage.Text = “Username Not Registered!” Else lblMessage.Text = “Invalid Password!” End If End If Return intResult End Function
888
Securing ASP.NET Applications PART V LISTING 19.20
continued
Please Login:
Username:
Password:
The Login.aspx page contains a function named DBAuthenticate. This function matches the username and password entered into the login form against the UserList database table with the DBAuthenticate stored procedure. If the user can be authenticated, the RedirectFromLoginPage method is called. This method issues a new Authentication Ticket cookie and redirects the user back to the original page he or she requested. If the user cannot be found, an error message is displayed.
Using Forms-Based Authentication CHAPTER 19
889
The Login.aspx page uses a stored procedure named DBAuthenticate. This stored procedure is contained in Listing 19.21. LISTING 19.21
FormsDB\DBAuthenticate.sql
CREATE PROCEDURE DBAuthenticate ( @username Varchar( 100 ), @password Varchar( 100 ) ) As DECLARE @ID INT DECLARE @actualPassword Varchar( 100 ) SELECT @ID = IdentityCol, @actualPassword = u_password FROM UserList WHERE u_username = @username IF @ID IS NOT NULL IF @password = @actualPassword RETURN @ID ELSE RETURN - 2 ELSE RETURN - 1
The Login.aspx page contains a link to the Register.aspx page, which enables new users to register at your Web site. The code for the Register.aspx page is contained in Listing 19.22. Note The Register.aspx page is contained in its own directory with its own Web. Config file. The Web.Config file enables anonymous access to files in the directory. Enabling access this way is necessary to enable unauthenticated users to register. (You could also enable anonymous access to the Register.aspx page with the location element in the root Web.Config file.)
19 USING FORMSBASED AUTHENTICATION
The DBAuthenticate stored procedure returns one of three values. If the username and password passed to the procedure exist in the UserList table, the stored procedure returns the ID of the user. If the username cannot be found, the procedure returns the value -1. If the username is found but the password is invalid, the procedure returns -2.
890
Securing ASP.NET Applications PART V LISTING 19.22
FormsDB\Register\Register.aspx
Sub Button_Click( s As Object, e As EventArgs ) Dim conMyData As SqlConnection Dim cmdSelect As SqlCommand Dim parmReturnValue As SqlParameter Dim intResult As Integer If IsValid Then conMyData = New SqlConnection( “Server=localhost;UID=sa;PWD=secret; ➥Database=myData” ) cmdSelect = New SqlCommand( “DBRegister”, conMyData ) cmdSelect.CommandType = CommandType.StoredProcedure parmReturnValue = cmdSelect.Parameters.Add( “RETURN_VALUE”, SqlDbType.Int ) parmReturnValue.Direction = ParameterDirection.ReturnValue cmdSelect.Parameters.Add( “@username”, txtUsername.Text ) cmdSelect.Parameters.Add( “@password”, txtPassword.Text ) conMyData.Open() cmdSelect.ExecuteNonQuery() intResult = cmdSelect.Parameters( “RETURN_VALUE” ).Value conMyData.Close() If intResult = - 1 Then lblMessage.Text = “Username Already Registered!” Else FormsAuthentication.RedirectFromLoginPage( txtUsername.Text, False ) End If End If End Sub Register.aspx Please Register
Username:
Using Forms-Based Authentication CHAPTER 19 LISTING 19.22
891
continued
Password:
LISTING 19.23
FormsDB\DBRegister.sql
CREATE PROCEDURE DBRegister ( @username Varchar( 100 ), @password Varchar( 100 ) ) AS IF EXISTS( SELECT u_id FROM UserList WHERE u_username=@username ) RETURN - 1
19 USING FORMSBASED AUTHENTICATION
The Register.aspx page contained in Listing 19.22 has one main subroutine named Button_Click. This subroutine adds a new username and password to the UserList table if the username does not already exist. The Button_Click subroutine uses a SQL stored procedure named DBRegister, contained in Listing 19.23, to add the new user information.
892
Securing ASP.NET Applications PART V LISTING 19.23
continued
ELSE INSERT UserList ( u_username, u_password ) VALUES ( @username, @password ) RETURN @@IDENTITY
The DBRegister stored procedure contained in Listing 19.23 returns one of two values. If the username passed to the stored procedure already exists in the UserList table, the procedure returns the value -1. Otherwise, the stored procedure adds the new username and password and returns the ID for the user.
Implementing Roles-based Authentication In certain situations, it makes sense to divide the users of your Web site into different roles. Dividing users into roles is useful when you want to grant or deny different permissions to different groups of users. For example, you might want to permit users in the supervisors role to edit certain data that users in the sales role cannot. Note All the files discussed in this section are located in the FormsRoles directory on the CD that accompanies this book. To use these files, you need to copy the FormsRoles directory to your server. Next, create a new virtual directory that points to the FormsRoles directory.
You can assign authenticated users to different roles within the Application_AuthenticateRequest subroutine in the Global.asax file. To assign a set of roles to a user, you use statements like the following: Dim arrRoles As String() = { “Supervisors”, “Engineers” } Context.User = New GenericPrincipal( Context.User.Identity, arrRoles )
The first statement creates a new string array of role names. The second statement associates the current authenticated user with the list of roles. Imagine that you have created an XML file that contains a list of roles for certain users of your Web site. This XML file is contained in Listing 19.24.
Using Forms-Based Authentication CHAPTER 19 LISTING 19.24
893
FormsRoles\Roles.xml
The XML file in Listing 19.24 associates Bob with the Sales role and Jane with the Supervisor role and Sales roles. After you create the Roles.xml file, you can use the file to assign users to different roles with the Global.asax file in Listing 19.25. LISTING 19.25
FormsRoles\Global.asax
Sub Application_AuthenticateRequest( s As Object, e As EventArgs ) Dim strUserName As String Dim objRoles As XmlDocument Dim arrRoles As String() Dim objNode As XmlNode Dim strXPath As String
Function GetRoles() As XmlDocument Dim objRoles As XmlDocument objRoles = Context.Cache( “Roles” ) If objRoles Is Nothing Then objRoles = New XmlDocument
19 USING FORMSBASED AUTHENTICATION
objRoles = GetRoles() If Context.Request.IsAuthenticated Then strUserName = Context.User.Identity.Name strXPath = String.Format( “user[@name=’{0}’]”, strUserName ) objNode = objRoles.DocumentElement.SelectSingleNode( strXPath ) If Not IsNothing( objNode ) Then arrRoles = objNode.Attributes( “roles” ).Value.Split Context.User = New GenericPrincipal( Context.User.Identity, arrRoles ) End If End If End Sub
894
Securing ASP.NET Applications PART V LISTING 19.25
continued
objRoles.Load( Server.MapPath( “Roles.xml” ) ) Context.Cache.Insert( _ “Roles”, _ objRoles, _ New CacheDependency( Server.MapPath( “Roles.xml” ) ) ) End If Return objRoles End Function
The Global.asax file in Listing 19.25 contains one function and one subroutine. The function, named GetRoles(), retrieves an XmlDocument that represents the Roles.xml file. This file is either retrieved from the cache or from the file system. The Application_AuthenticateRequest subroutine is fired whenever a user requests a page. This subroutine retrieves the list of roles associated with the current users from the list of roles returned by the GetRoles() function. Next, the subroutine assigns the list of roles to the current user. You can test the files in Listings 19.24 and 19.25 with the page in Listing 19.26. LISTING 19.26
FormsRoles\Default.aspx
Default.aspx Welcome
The page in Listing 19.26 uses the IsInRole method to display different messages depending on whether the current user is a member of the Supervisor role or not.
Using Forms-Based Authentication CHAPTER 19
895
Creating a Custom Authentication Ticket Forms authentication uses an Authentication Ticket stored in a cookie to authenticate users each time they request a page. The Authentication Ticket is automatically generated and added to a user’s browser by the RedirectFromLoginPage method. In most cases, the Authentication Ticket automatically generated by the RedirectFromLoginPage method contains all the information you need. You can use this ticket to retrieve such information as the username and date the cookie was issued. However, you also have the option of creating the Authentication Ticket yourself. Why would you want to create the Authentication Ticket manually? If you create it yourself, you can add additional information to it by using the UserData property. For example, you can use the UserData property to store a unique ID or the e-mail address for each user. After the information is stored in the Authentication Ticket, you can retrieve this information from the ticket on each page the user requests. Warning An Authentication Ticket is stored in a browser cookie, and browser cookies have size limitations. For example, the original Netscape cookie specification places a 4KB limit on the size of any cookie (the size refers to the combined size of the cookie’s name and value).
The Authentication Ticket is represented by the FormsAuthenticationTicket class. If you want to create an Authentication Ticket that contains custom data, you need to explicitly create an object of this class and add it to the browser’s cookie collection.
•
version—The
version of the Authentication Ticket. Typically, you supply the
value 1. •
name—The username associated with the Authentication Ticket (a maximum of 32 bytes).
•
issueDate—The
•
expiration—The
date the cookie expires.
•
isPersistent—A
Boolean value indicating whether the cookie is a session or
original date the cookie was issued.
persistent cookie. •
userData—A
string that can contain any data you please.
USING FORMSBASED AUTHENTICATION
You can explicitly create a new Authentication Ticket by passing the following parameters to the FormsAuthenticationTicket constructor:
19
896
Securing ASP.NET Applications PART V
To illustrate the process of creating a custom Authentication Ticket, you are going to create three files: •
Web.Config—This
standard configuration file enables Forms authentication.
•
Login.aspx—This
page holds custom data added to an Authentication Ticket.
•
Default.aspx—This
page retrieves and displays the custom data from the Authentication Ticket.
Note All the files discussed in this section are located in the AuthenticationTicket directory on the CD that accompanies this book. To use these files, copy the AuthenticationTicket directory to your server and create a new virtual directory that points to it.
The standard Web.Config file for this example is contained in Listing 19.27. LISTING 19.27
AuthenticationTicket\Web.Config
If an unauthenticated user attempts to access a page in a directory that contains the Web.Config file in Listing 19.27, the user is automatically redirected to the Login.aspx page. This page is contained in Listing 19.28. LISTING 19.28
AuthenticationTicket\Login.aspx
Sub Button_Click( s As Object, e As EventArgs ) Dim objTicket As FormsAuthenticationTicket Dim objCookie As HttpCookie Dim strReturnURL As String objTicket = New FormsAuthenticationTicket( _ 1, _ txtUsername.Text, _
Using Forms-Based Authentication CHAPTER 19 LISTING 19.28
897
continued
NOW(), _ #12/25/2002#, _ False, _ txtUserdata.Text ) objCookie = New HTTPCookie( “.ASPXAUTH” ) objCookie.Value = FormsAuthentication.Encrypt( objTicket ) Response.Cookies.Add( objCookie ) strReturnURL = Request.Params( “ReturnURL” ) If strReturnURL Nothing Then Response.Redirect( strReturnURL ) Else Response.Redirect( “Default.aspx” ) End If End Sub Login.aspx
The Login.aspx page in Listing 19.28 contains a form for entering a username and custom user data. When the form is submitted, the Button_Click subroutine is executed.
19 USING FORMSBASED AUTHENTICATION
Username:
User Data:
898
Securing ASP.NET Applications PART V
The Button_Click subroutine creates a new Authentication Ticket named objTicket. The username and userdata form fields supply the values of the name and userData properties of the new ticket. Next, the custom Authentication Ticket is added to the browser’s cookie collection with the Add method of the HttpCookiesCollection object. Finally, the user is redirected back to the page indicated by the ReturnURL query string variable. If this query string variable doesn’t exist, the user is redirected to the Default.aspx page. To test whether the custom user data was actually added to the Authentication Ticket, you can use the Default.aspx page contained in Listing 19.29. LISTING 19.29
AuthenticationTicket\Default.aspx
Sub Page_Load Dim objFormsID As FormsIdentity objFormsID = User.Identity lblName.Text = objFormsID.Ticket.Name lblUserData.Text = objFormsID.Ticket.UserData End Sub Default.aspx Name:
User Data:
In the Page_Load subroutine in Listing 19.29, the identity of the current user is retrieved from the User property of the Page class. The identity of the current user is represented by an instance of the FormsIdentity class, which includes the Authentication Ticket.
Using Forms-Based Authentication CHAPTER 19
899
Both the name and custom user data associated with the current user are retrieved from the instance of the FormsIdentity class. Both bits of information are displayed in Label controls.
Using Forms Authentication and Web Farms By default, you cannot share the same Authentication Ticket cookie across multiple servers. This means that, without some extra work, you cannot use Forms Authentication with servers clustered into a Web farm. If you want to use Forms Authentication across multiple servers, you need to configure all the servers to share the same validation and decryption keys. By default, each server automatically generates its own keys. To force multiple servers to use common keys, modify the following machineKey section in either an application root Web.Config file or the Machine.Config file:
You need to replace autogenerate with a valid key. The key must be between 40 and 128 characters (the recommendation is 128 characters). You can pick any random string of characters for the validationKey and decryptionKey values.
Working with Passport Authentication
More than 130 million users are already signed up with Microsoft Passport. For example, both HotMail and MSN use Passport to authenticate users. Passport authentication has several significant advantages over Forms authentication. First, and most important, users benefit by using Passport authentication because they can use the same username and password that they currently use at other Web sites. Therefore, users are less likely to forget their passwords. Also, registration information can be kept more current.
19 USING FORMSBASED AUTHENTICATION
Passport authentication is a centralized authentication service offered by Microsoft. When a user logs in to a Web site that has Passport authentication enabled, he or she is automatically redirected to the Passport Web site. After entering a username and password, the user is automatically redirected back to the original site.
900
Securing ASP.NET Applications PART V
Second, when using Passport authentication, you don’t need to set up and maintain a database to store user registration information. Microsoft does all the work for you. Passport authentication also provides you with options to customize the appearance of the Registration and Sign In pages by supplying templates. You can even supply your own Cascading Style Sheets to format the appearance of these pages. To use the Microsoft Passport service, you must pay a subscription fee. As I write this, the exact fee has yet to be determined. However, it is expected to be based on the average number of unique visitors to your Web site in a month.
Enabling Passport Authentication Before you can use Passport authentication, you must first download and install the Microsoft Passport Software Development Kit (SDK) from www.passport.com/ business. The Passport SDK contains the Passport Manager, the Passport Manager Administrator utility, and a sample site that implements Passport authentication. (Currently, the sample site is written with Classic ASP, not ASP.NET.) After you download and install the SDK, you can create a site in test mode. Certain features of Passport do not work in test mode, however. For example, you cannot read all the information from a user’s profile, nor can you customize the appearance of the Sign In or Registration pages. The sign out process doesn’t work either. (You need to shut down all the instances of your browser to sign out.) To use any of these features, you must enter production mode. To enter production mode, you must register to become a Passport participant site. After you register and sign the necessary agreements, you are issued a Site ID and an encryption key. After you download and install Microsoft Passport, you can enable Passport authentication for an application by creating a Web.Config file in its root directory. The Web. Config file in Listing 19.30 contains the necessary authentication section to enable Passport authentication. Note The files discussed in this section are located in the Passport directory on the CD that accompanies this book. To use these files, copy the Passport directory to your server and create a new virtual directory that points to it.
Using Forms-Based Authentication CHAPTER 19 LISTING 19.30
901
Passport\Web.Config
The Web.Config file in Listing 19.30 sets the authentication mode for the application to use Passport authentication. It also sets the value of the redirectUrl attribute. If a user is not authenticated and requests a page in a directory that requires authentication, he or she is redirected to the page specified by the redirectUrl attribute. After you enable Passport authentication, you can protect the pages in any directory from unauthenticated access by including the file in Listing 19.31 in the directory. LISTING 19.31
Passport\Secret\Web.Config
Warning Passport authentication applies to ASP.NET files. Enabling Passport authentication does not prevent access to other types of files, such as image, text, or Microsoft Word files.
19 USING FORMSBASED AUTHENTICATION
The Web.Config file in Listing 19.31 prevents anonymous users (represented by the ? symbol) from accessing any ASP.NET page in the current directory. This Web.Config file applies to the current directory and all subdirectories that do not have a Web.Config file that overrides it. If an anonymous user attempts to access an ASP.NET page, that user is automatically redirected to the page indicated by the redirectUrl attribute in Listing 19.30.
902
Securing ASP.NET Applications PART V
Enabling Users to Sign In and Sign Out Many Web sites that use Passport authentication, such as MSN, do not have a distinct sign in page. Instead, they display a sign in button on the top of each page. After a user has signed in, customized content can be displayed for him or her. To create a sign in button, you can use the LogoTag2() method of the PassportIdentity class. This method automatically displays a sign in button for unauthenticated users and a sign out button for authenticated users. For example, the page in Listing 19.32 uses the LogoTag2() method to display a sign in button at the top of the page (see Figure 19.3). FIGURE 19.3 Passport sign in button.
LISTING 19.32
Passport\Default.aspx
Sub Page_Load Dim objPassportID As PassportIdentity objPassportID = User.Identity If objPassportID.GetFromNetworkServer Then Response.Redirect( Request.Path ) End If plhPassport.Controls.Add( New LiteralControl( objPassportID.LogoTag2() ) ) End Sub
Using Forms-Based Authentication CHAPTER 19 LISTING 19.32
903
continued
Default.aspx Welcome to our Web site!
In the Page_Load subroutine in Listing 19.32, an instance of the PassportIdentity class is created. Next, the LogoTag2() method is called to retrieve the necessary HTML tag for a sign in button, which is added to the controls collection of an ASP.NET PlaceHolder control. This results in the sign in button being displayed at the top-left corner of the page. Warning The Page_Load subroutine in Listing 19.32 contains the following code: If objPassportID.GetFromNetworkServer Then Response.Redirect( Request.Path ) End If
The LogoTag2() method displays a sign in button until you sign in. After you sign in, the method displays a sign out button. Warning If you are using Passport authentication in test mode, you cannot sign out by clicking the sign out button. To sign out, you must close all instances of the browser (clearing the session cookie used for Passport authentication).
19 USING FORMSBASED AUTHENTICATION
This code clears the query string from the browser’s address bar after the user is authenticated by the Passport service. If you neglect to add this code, the Authentication ticket added by the Passport service will be visible in the browser address bar, presenting a security risk.
904
Securing ASP.NET Applications PART V
If you want to create a distinct sign in page, you can use the LogoTag2() method to create the sign in button on the page. For example, the page contained in Listing 19.33 asks the user to log in. LISTING 19.33
Passport\Login.aspx
Sub Page_Load Dim objPassportID As PassportIdentity objPassportID = User.Identity If objPassportID.IsAuthenticated Then Response.Redirect( “Default.aspx” ) End If plhPassport.Controls.Add( New LiteralControl( objPassportID.LogoTag2 ) ) End Sub Login.aspx Please Login: You have requested a restricted resource. To continue, please login by clicking the following button:
In the Page_Load subroutine in Listing 19.33, the IsAuthenticated property is checked to see whether the user has already been authenticated. If the user hasn’t been authenticated, he or she is invited to click the sign in button. After clicking the sign in button, the user is authenticated by the Passport service and returned to the Default.aspx page. You can use the IsAuthenticated property to detect whether a user has signed in and to display customized content. For example, the page in Listing 19.34, named News.aspx, displays customized news only for authenticated users.
Using Forms-Based Authentication CHAPTER 19 LISTING 19.34
905
Passport\News.aspx
Sub Page_Load( s As Object, e As EventArgs ) Dim objPassportID As PassportIdentity objPassportID = User.Identity If objPassportID.GetFromNetworkServer Then Response.Redirect( Request.Path ) End If plhPassport.Controls.Add( New LiteralControl( objPassportID.LogoTag2() ) ) If objPassportID.IsAuthenticated Then pnlAuth.Visible = True Else pnlAnon.Visible = True End If End Sub News.aspx
Anonymous News News for anonymous users...
19 USING FORMSBASED AUTHENTICATION
Customized News News customized only for you...
906
Securing ASP.NET Applications PART V
In the Page_Load subroutine in Listing 19.34, the IsAuthenticated property checks whether the current user has been authenticated with Passport authentication. If IsAuthenticated returns the value True, the first panel on the page is displayed. Otherwise, the second panel is displayed.
Retrieving User Information Information about authenticated Passport users is stored in Passport user profiles. You can retrieve a Passport user profile from the PassportIdentity class. The Passport profile is guaranteed to contain certain information about each user. For each user, you can read his or her unique Passport User ID, country, region or state, postal code, and language preference. A Passport profile also contains optional information. The user can enter such additional information as e-mail address, nickname, age, or sex. If the user has not already entered this information, you can create a form at your Web site that enables the user to enter and update it at the central Passport database. You can retrieve the unique user ID for a Passport user by reading the Name property of the PassportIdentity class. The Passport User ID (PUID) is a 64-bit number that you can use to uniquely match a user to rows in a database table. Other profile information can be read from the Item property of the PassportIdentity class. For example, you can retrieve a user’s country by using the following code: strUserCountry = objPassportIdentity.Item( “Country” )
Because the Item property represents the default collection for the PassportIdentity class, you also can retrieve profile information like this: strUserCountry = objPassportIdentity( “Country” )
The following attributes are initially defined in a user’s core profile: •
Accessibility—Returns 1
when accessibility features should be enabled for the current user, and 0 otherwise. The default value is 0.
•
BDay_precision—Specifies Birthdate
an integer that indicates the precision of the
attribute:
0
= Birthdate deleted or not supplied
1
= User is 18 or older, but no birth month or day supplied
2
= User is 18 or older; both birth month and day supplied
3
= User is under 18
Using Forms-Based Authentication CHAPTER 19 4
= User is under 13
5
= User is minor over 13
•
Birthdate—Returns
•
City—Indicates
•
Country—Contains
•
Flags—Contains
•
Gender—Specifies
•
Lang_Preference—Indicates
•
MemberIDHigh—Specifies
•
MemberIDLow—Specifies
the lower portion of the 64-bit Passport Unique ID (PUID) associated with each user.
•
Nickname—Indicates a user-supplied friendly name. The nickname is not guaranteed to be unique and must be fewer than 30 bytes.
•
PreferredEmail—Indicates
•
PostalCode—Specifies
•
ProfileVersion—Returns
•
Region—Indicates
•
Wallet—Returns 1
907
either the user’s birth year or full birth date in the format MM/DD/YYYY. The default value is 0. a GeoID value that represents the user’s city. a two-character ISO 3166 country code.
an integer bitmask. If the first bit is set (00000000 00000000 00000000 00000001), the e-mail address of the user has been verified. the value M (Male), F (Female), or U (Unknown). the LCID value for the user’s preferred language.
the upper portion of the 64-bit Passport Unique ID (PUID) associated with each user.
the user’s preferred e-mail address.
the postal code (both for the U.S. and other countries). the value 1 currently.
a GeoID that represents the user’s geographical region. when the user has created an online wallet and 0 otherwise.
The GeoID mentioned in the preceding list is a unique number that maps to a user’s geographical location. The Microsoft Passport SDK includes comma-delimited data files that translate codes, such as GeoIDs or LCIDs, into human-readable strings. For example, the GeoIDs.dat file maps GeoIDs into friendly names for each region. For more information, see the Microsoft Passport SDK documentation.
The page in Listing 19.35, for example, displays several of the profile attributes for the current user.
19 USING FORMSBASED AUTHENTICATION
Note
908
Securing ASP.NET Applications PART V LISTING 19.35
Passport\UserInfo.aspx
Sub Page_Load Dim objID As PassportIdentity objID = User.Identity If objID.IsAuthenticated Then lblInfo.Text &= “
Please enter your credit card information, your social security number, your yearly income, and your favorite color.
In the Page_Load subroutine in Listing 21.1, the IsSecureConnection property of the Request object detects whether the page was requested using SSL. If the page was requested using SSL, several properties of the secure connection are reported, such as the name of the issuer of the certificate and the encryption key size.
Using .NET Encryption Classes The .NET framework includes a rich set of classes for performing cryptographic operations. You can access any of these classes within an ASP.NET page. In several situations, you need to use these classes in an ASP.NET application. For example you can use these classes to encrypt sensitive information, such as credit card numbers and legal documents, before you store the information in a database table or the file
Encrypting Data Over the Network CHAPTER 21
In the following sections, you learn how to use the cryptography classes to generate hash codes, encrypt data by using both symmetric and asymmetric cryptographic algorithms, and create digital signatures. Note Many of the cryptographic classes in the .NET framework are managed wrappers for the Windows Cryptographic Service Providers (CSPs). To use a particular cryptographic class, the proper CSP must be installed.
Using Hash Algorithms A hash algorithm generates a fixed-length hash value that uniquely represents the contents of an arbitrary length string. Identical strings generate the same hash value. Strings that differ in the smallest respect generate different hash values. Imagine that you have a file that contains the complete text of the Declaration of Independence. You can use a hash algorithm to generate a string of 16 characters that represent the contents of this file. If you change a single letter in the file, the hash algorithm would generate a different hash value. Hash values are useful for detecting changes in a file. They enable you to take a snapshot of the state of a file at any point of time. If you want to know whether a file has been changed on the hard drive, you can compare the file’s current hash value to its previous hash value. Hash values can be used to protect data integrity. For example, the Forms authentication module uses hash values to detect changes in the cookie Authentication Tickets that it issues. The classes in the .NET framework support the following hash algorithms: •
MD5—This
hash algorithm generates a 128-bit hash value. (To learn more about it, see RFC 1321 at www.w3.org.) This hash algorithm is implemented by the MD5CryptoServiceProvider class.
•
SHA1—The original Secure Hash Algorithm generates a 160-bit hash value. It is implemented by the SHA1CryptoServiceProvider and SHA1Managed classes.
•
SHA256—The
Secure Hash Algorithm 256 generates a 256-bit hash value. It is implemented by the SHA256CryptoServiceProvider and SHA256Managed classes.
21 ENCRYPTING DATA OVER THE NETWORK
system. You also can use these classes to automatically add digital signatures to documents.
937
938
Securing ASP.NET Applications PART V
•
SHA384—The Secure Hash Algorithm 384 generates a 384-bit hash value. It is implemented by the SHA384CryptoServiceProvider and SHA384Managed classes.
•
SHA512—The Secure Hash Algorithm 512 generates a 512-bit hash value. It is implemented by the SHA512CryptoServiceProvider and SHA512Managed classes.
The MD5 hash algorithm is the fastest but the least secure. Each SHA algorithm is progressively more secure but requires more computation. Note The System.Security.Cryptography namespace includes two classes that support keyed hashed algorithms: HMACSHA1 and MACTripleDES. These classes are particularly useful for generating message authentication codes (MACs).
You can use any of these classes to compute a hash value by calling the ComputeHash method. This method accepts a byte array that represents the data to be hashed and returns a byte array that represents the hash value. For example, the ASP.NET page in Listing 21.2 returns a hash value by using the MD5 hash algorithm (see Figure 21.4). FIGURE 21.4 Computing a hash value.
Encrypting Data Over the Network CHAPTER 21 LISTING 21.2
Hash.aspx
Function Convert2ByteArray( strInput As String ) As Byte() Dim intCounter As Integer Dim arrChar As Char() arrChar = strInput.ToCharArray() Dim arrByte( arrChar.Length - 1 ) As Byte For intCounter = 0 To arrByte.Length - 1 arrByte( intCounter ) = Convert.ToByte( arrChar( intCounter ) ) Next Return arrByte End Function Sub Button_Click( s AS Object, e As EventArgs ) Dim arrHashInput As Byte() Dim arrHashOutput As Byte() Dim objMD5 As MD5CryptoServiceProvider objMD5 = New MD5CryptoServiceProvider arrHashInput = Convert2ByteArray( txtInput.Text ) arrHashOutput = objMD5.ComputeHash( arrHashInput ) lblOutput.Text = BitConverter.ToString( arrHashOutput ) End Sub Hash.aspx Generate MD5 Hash Enter text to hash:
Hash value:
The page in Listing 21.2 displays a multiline TextBox control. If you enter text into it and click the submit button, the hash value for the text is displayed. Notice that even a single character difference generates a different hash value. You cannot pass a string directly to the ComputeHash method of any of the Hash classes. In Listing 21.2, the string is converted to a byte array with the Convert2ByteArray function before being passed to the ComputeHash method. The byte array returned by ComputeHash is converted back to a string with the BitConverter class.
Using Symmetric Encryption Algorithms When you think of encryption, you typically think of symmetric encryption algorithms. When you encrypt a message with a symmetric encryption algorithm, both the sender and recipient of the message must share the same decryption key. The classes in the .NET framework support the following symmetric encryption algorithms: • DES—The United States Data Encryption Standard. This algorithm is implemented by the DESCryptoServiceProvider class. • Triple DES—DES encryption applied three times in a row with different session encryption keys. This algorithm is implemented by the TripleDESCryptoServiceProvider class. • RC2—The RC2 Block Cipher. This algorithm is implemented by the RC2CryptoServiceProvider class. •
Rijndael—The algorithm used for the Advanced Encryption Standard (AES). This algorithm is implemented by the RijndaelManaged class.
Encrypting Data Over the Network CHAPTER 21
For this next example, you use the DESCryptoServiceProvider class to encrypt form data and store the encrypted data in a file. You need to create two ASP.NET pages: SymmetricWrite.aspx and SymmetricRead.aspx. The SymmetricWrite.aspx page, contained in Listing 21.3, will encrypt and write the data to a file, and the SymmetricRead.aspx page will read and decrypt the data from the file. LISTING 21.3
SymmetricWrite.aspx
CONST DESKey As String = “ABCDEFGH” CONST DESIV As String = “HGFEDCBA” Function Convert2ByteArray( strInput As String ) As Byte() Dim intCounter As Integer Dim arrChar As Char() arrChar = strInput.ToCharArray() Dim arrByte( arrChar.Length - 1 ) As Byte For intCounter = 0 To arrByte.Length - 1 arrByte( intCounter ) = Convert.ToByte( arrChar( intCounter ) ) Next Return arrByte End Function Sub Button_Click( s As Object, e As EventArgs ) Dim arrDESKey As Byte() Dim arrDESIV AS Byte() Dim arrInput As Byte() Dim objFileStream As FileStream Dim objDES As DESCryptoServiceProvider Dim objEncryptor As ICryptoTransform Dim objCryptoStream As CryptoStream arrDESKey = Convert2ByteArray( DESKey ) arrDESIV = Convert2ByteArray( DESIV ) arrInput = Convert2ByteArray( txtInput.Text ) objDES = New DESCryptoServiceProvider() objEncryptor = objDES.CreateEncryptor( arrDESKey, arrDESIV ) objFileStream = New FileStream( _
21 ENCRYPTING DATA OVER THE NETWORK
You can use any of these encryption algorithms in your ASP.NET pages. For example, you can encrypt information that users enter into an HTML form and send the information in an e-mail, store the information on the file system, or add the information to a database table.
941
942
Securing ASP.NET Applications PART V LISTING 21.3
continued
MapPath( “secret.txt” ), _ FileMode.Create, _ FileAccess.Write ) objCryptoStream = New CryptoStream( _ objFileStream, _ objEncryptor, _ CryptoStreamMode.Write ) objCryptoStream.Write( arrInput, 0, arrInput.Length ) objCryptoStream.Close() End Sub SymmetricWrite.aspx DES Encryption Enter text to encrypt:
The page in Listing 21.3 displays a form with a single TextBox control (see Figure 21.5). If you enter text into it and click the submit button, the form data is encrypted using the DES encryption algorithm and saved to a file named secret.txt. Notice the two constants named DESKey and DESIV defined at the top of the file. When you encrypt data with DESCryptoServiceProvider, you must supply both a secret key and an initialization vector. You need to use these two constants to decrypt the file after you create it.
Encrypting Data Over the Network CHAPTER 21
FIGURE 21.5
21 ENCRYPTING DATA OVER THE NETWORK
Encrypting a file.
In Listing 21.3, the secret key is assigned the value ABCDEFGH, and the initialization vector is assigned the value HGFEDCBA. You should think of these values as the secret passwords to the file. You can assign any eight-character string to these constants. You can decrypt the contents of the secret.txt file by using the ASP.NET page in Listing 21.4 (see Figure 21.6). LISTING 21.4
943
SymmetricRead.aspx
CONST DESKey As String = “ABCDEFGH” CONST DESIV As String = “HGFEDCBA” Sub Page_load DESDecrypt End Sub Function Convert2ByteArray( strInput As String ) As Byte() Dim intCounter As Integer Dim arrChar As Char()
944
Securing ASP.NET Applications PART V LISTING 21.4
continued
arrChar = strInput.ToCh arArray() Dim arrByte( arrChar.Length - 1 ) As Byte For intCounter = 0 To arrChar.Length - 1 arrByte( intCounter ) = Convert.ToByte( arrChar( intCounter ) ) Next Return arrByte End Function Sub DESDecrypt Dim arrDESKey As Byte() Dim arrDESIV As Byte() Dim objFileStream As FileStream Dim objDES As DESCryptoServiceProvider Dim objDecryptor As ICryptoTransform Dim objCryptoStream As CryptoStream arrDESKey = Convert2ByteArray( DESKey ) arrDESIV = Convert2ByteArray( DESIV ) objDES = New DESCryptoServiceProvider objDecryptor = objDES.CreateDecryptor( arrDESKey, arrDESIV ) objFileStream = New FileStream( _ MapPath( “secret.txt” ), _ FileMode.Open, _ FileAccess.Read ) objCryptoStream = New CryptoStream( _ objFileStream, _ objDecryptor, _ CryptoStreamMode.Read ) lblMessage.Text = New StreamReader( objCryptoStream ).ReadToEnd() objFileStream.Close End Sub SymmetricRead.aspx DES Decryption Decrypted Text:
Encrypting Data Over the Network CHAPTER 21
FIGURE 21.6
The page in Listing 21.4 reads, decrypts, and displays the contents of the secret.txt file in the DESDecrypt subroutine. Notice that the DESKey and DESIV constants are defined at the top of the page. The values of these constants in this page must match the values of these constants in the SymmetricWrite.aspx page.
Using Asymmetric Encryption When you use a symmetric encryption algorithm, both the sender and recipient of a message must share a common decryption key. You use the same secret key to encrypt a message as the key that you use to decrypt the message. When you use an asymmetric encryption algorithm, on the other hand, the key used to encrypt a message is distinct from the key used to decrypt the message. Asymmetric encryption algorithms use a key pair. If you encrypt a message with one key, you need the other key to decrypt the message. Asymmetric encryption algorithms often are used to create digital signatures. Typically, one key is made public, and the other key is kept private. If you encrypt a message with your private key, it can be decrypted only with your public key. If you send another user a message encrypted with your private key, that user can verify that you sent the message by decrypting it with your public key.
21 ENCRYPTING DATA OVER THE NETWORK
Decrypting a file.
945
946
Securing ASP.NET Applications PART V
Note Asymmetric algorithms are often referred to as public key algorithms.
When used with a hash algorithm, an asymmetric algorithm can detect whether a message has been altered. For example, if George needs to send Jane a message, and George and Jane need to ensure that the message has not been altered in transit, the two of them can do the following: 1. George computes the hash value of the message. 2. George encrypts the hash value with his private key. 3. George sends the message and encrypted hash value to Jane. 4. Jane decrypts the hash value received from George. 5. Jane computes the hash value of the message herself. 6. If the two hash values match, Jane knows that the message has not been modified. The classes in the .NET framework support the following two asymmetric algorithms: • DSA—The Digital Signature Algorithm (the digital authentication standard of the United States government). This algorithm is implemented by the DSACryptoServiceProvider class. • RSA—This algorithm is implemented by the RSACryptoServiceProvider class. Both algorithms implement the AsymmetricAlgorithm class, so they share many of the same methods and properties. If you create an instance of either class, the private and public keys are automatically generated. You can access the public or private key by using either the ToXmlString or ExportParameters method. The ToXmlString method represents the public and private keys with an XML string. The following sample statements create an instance of the DSACryptoServiceProvider class and display the private and public keys: Dim objDSA As New DSACryptoServiceProvider() Response.Write( Response.Write( Response.Write( Response.Write(
“Private Key:” ) Server.HTMLEncode( objDSA.ToXmlString( True ) ) ) “Public Key:” ) Server.HTMLEncode( objDSA.ToXmlString( False ) ) )
Encrypting Data Over the Network CHAPTER 21
The preceding statements generate output similar to the following: Private Key:
1AYChM6ITZ1sFQDtDd8rfDNAoeWdIttEpgY53xArTjl1RrZ7LD887IQFRmY CqCjgA0EoToR8elnIbAsr+IGN9h4dRnbKaCk/LupXj1bxJqQbMlqXyfLehxeshRCgajFqAOmMW6 SSlNNdJU8DY8gzPUzSar57UhpbDur1p2MHwlc=
5RdZjO/s6pJMQEOnzThKX0/EJp8=sQNgfhRtZ5GGufpdPgb5zYAP0go4egcNhGibQbIV4OBgqP2jiBLURjnxSHNs5GKbvUGT2t 5Sr2DP8+704Tv+osjNtOsG324Txgdx3Li0wsqguyBu2stlIPI2FxPuTcasLqPmFuWhBHexFT2lg 4w4eVCVH1jlaCZ7lmrI1ux2w2c=FlAgexkYEnPIM06dnGmE5s/mj/7M9V1ohQY+ZH80d LTOWCobBt7T16ToR0dUpcIHDrn7kha5Bj4YnT6pjAr55VtclBu3td8m6M3Ymm8MqLQXTVj9aWem nrHVX+/hMNXOnBGsaf/Lrvjw37TERKocTzJcV9Jv2tsaklC9nQeSx3I=7O1xQHFufGn5 X592ZJ90kuRA/RzxZltsiULtfuNaBu95uNaAYHKOajkBJQP8/U4jzoYvPOTYw9Z37r8BLt8Vt8Q p64IkmlqHMwlFVZhYU/XNwuXfrSdUPxwl6s+eZHe4GKr4H1vz49wTaGvqb3M+ieba mm7xgDEZmUJ2QNB1elg=ARM=iJMgNkrX+Wj4pV eSc1VPV2fOBPg=1AYChM6ITZ1sFQDtDd8rfDNAoeWdIttEpgY53xArTjl1RrZ7LD887IQFRmY CqCjgA0EoToR8elnIbAsr+IGN9h4dRnbKaCk/LupXj1bxJqQbMlqXyfLehxeshRCgajFqAOmMW6 SSlNNdJU8DY8gzPUzSar57UhpbDur1p2MHwlc=
5RdZjO/s6pJMQEOnzThKX0/EJp8=sQNgfhRtZ5GGufpdPgb5zYAP0go4egcNhGibQbIV4OBgqP2jiBLURjnxSHNs5GKbvUGT2t 5Sr2DP8+704Tv+osjNtOsG324Txgdx3Li0wsqguyBu2stlIPI2FxPuTcasLqPmFuWhBHexFT2lg 4w4eVCVH1jlaCZ7lmrI1ux2w2c=FlAgexkYEnPIM06dnGmE5s/mj/7M9V1ohQY+ZH80d LTOWCobBt7T16ToR0dUpcIHDrn7kha5Bj4YnT6pjAr55VtclBu3td8m6M3Ymm8MqLQXTVj9aWem nrHVX+/hMNXOnBGsaf/Lrvjw37TERKocTzJcV9Jv2tsaklC9nQeSx3I=7O1xQHFufGn5 X592ZJ90kuRA/RzxZltsiULtfuNaBu95uNaAYHKOajkBJQP8/U4jzoYvPOTYw9Z37r8BLt8Vt8Q p64IkmlqHMwlFVZhYU/XNwuXfrSdUPxwl6s+eZHe4GKr4H1vz49wTaGvqb3M+ieba mm7xgDEZmUJ2QNB1elg=ARM=
Digital Signature:
Public Key:
21 ENCRYPTING DATA OVER THE NETWORK
arrInput = Convert2ByteArray( txtInput.Text ) objDSA = New DSACryptoServiceProvider() strPublicKey = objDSA.ToXMLString( False ) arrDigitalSignature = objDSA.SignData( arrInput ) txtSignature.Text = BitConverter.ToString( arrDigitalSignature ) txtPublicKey.Text = strPublicKey End Sub
949
950
Securing ASP.NET Applications PART V LISTING 21.5
continued
When you enter text into the TextBox control in Listing 21.5 and submit the form, the Button_Click subroutine generates a digital signature by using RSACryptoServiceProvider. The Button_Click subroutine displays the digital signature and the public key associated with the digital signature in two TextBox controls. The page in Listing 21.6 performs the opposite operation. This page uses the VerifyData method to verify that a message matches a digital signature associated with a particular public key (see Figure 21.8). FIGURE 21.8 Verifying a digital signature.
LISTING 21.6
AsymmetricRead.aspx
Function Convert2ByteArray( strInput As String ) As Byte() Dim intCounter As Integer Dim arrChar As Char()
Encrypting Data Over the Network CHAPTER 21 LISTING 21.6
continued
Function ConvertHexArray( strInput As String ) As Byte() Dim intCounter As Integer Dim arrString As String() arrString = strInput.Split( New Char() { “-” } ) Dim arrByte( arrString.Length - 1 ) As Byte For intCounter = 0 To arrByte.Length - 1 arrByte( intCounter ) = Byte.Parse( arrString( intCounter ), NumberStyles.HexNumber ) Next Return arrByte End Function Sub Button_Click( s As Object, e As EventArgs ) Dim arrInput As Byte() Dim objDSA As DSACryptoServiceProvider Dim strPublicKey As String Dim arrDigitalSignature As Byte() arrInput = Convert2ByteArray( txtInput.Text ) arrDigitalSignature = ConvertHexArray( txtSignature.Text ) objDSA = New DSACryptoServiceProvider() objDSA.FromXMLString( txtPublicKey.Text ) lblVerify.Text = objDSA.VerifyData( arrInput, arrDigitalSignature ) End Sub AsymmetricRead.aspx Verify Digital Signature Message:
Digital Signature:
Public Key:
Verified?
The page in Listing 21.6 displays a form with three TextBox controls: one for a message, one for a digital signature, and one for a public key. If you enter this information and click the verify button, the page returns the value True if the message can be verified and the value False otherwise.
Summary In this chapter, you learned how to securely transmit data across an unsecure network such as the Internet. In the first section, I discussed how you can enable SSL encryption
Encrypting Data Over the Network CHAPTER 21
Next, you examined the cryptography classes included with the .NET framework. You learned how to use the cryptography classes to generate a hash value that uniquely represents the contents of a document and encrypt messages by using symmetric encryption algorithms such as DES and Triple DES. Finally, you learned how to use an asymmetric algorithm to generate a digital signature for a message.
21 ENCRYPTING DATA OVER THE NETWORK
for your Web site and provided an overview of the features of SSL. You also learned how to install SSL on your Web server.
953
Building ASP.NET Web Services
PART
VI IN THIS PART 22 Creating an XML Web Service 23 Advanced XML Web Services
957 995
CHAPTER 22
Creating an XML Web Service
IN THIS CHAPTER • Overview of XML Web Services
958
• Creating a Simple XML Web Service 961 • Testing an XML Web Service from a Browser 965 • Accessing an XML Web Service Through a Proxy Class 969 • Transmitting Complex Data in an XML Web Service 975 • Examining XML Web Service and Web Site Interaction 987
958
Building ASP.NET Web Services PART VI
In this chapter, you learn how to take advantage of an exciting new technology included with ASP.NET: You learn how to build and access XML Web services. First, you are provided with an overview of XML Web services. You learn what they are and why you might want to use them. Next, you build a simple XML Web service that converts temperatures between degrees Fahrenheit and degrees Celsius. After you create an XML Web service, you learn how you can test it from a Web browser. You also learn how to retrieve information about all the methods and properties of an XML Web service and invoke a Web service method from a browser. Next, you learn how to create a proxy class that enables you to interact with an XML Web service across the network. You learn how to automatically generate the proxy class and integrate the class into an ASP.NET application. Finally, you explore the issue of XML Web service and Web site interaction and learn how to integrate application and session state into a Web service.
Overview of XML Web Services ASP.NET XML Web services enable you to remotely access the properties and methods of classes across a network. They are an important part of the Microsoft vision of the programmable Web. The Web, as it currently exists, is designed around humans browsing Web pages through a Web browser. Typically, the only way to present information through the Internet is to build a Web page designed to be consumed by human eyes. However, in many situations, exposing information to an application makes more sense than exposing it to a human. Consider, for example, the process of buying a book online. Currently, to buy a book, you must complete each of the following steps: 1. Find a Web site that sells the book you want for the cheapest price. 2. Log in to the Web site with your username and password. 3. Select a shipping method for the book. 4. Pay for the book by entering your credit card number. XML Web services have the potential to alter the manner in which each step is performed. Let’s start with the last step and work backward. When you enter a credit card number to pay for a book, the Web site must communicate with a credit card processor to authorize your credit card purchase. The credit card
Creating an XML Web Service CHAPTER 22
959
number must be transmitted over the Web, and the purchase must be authorized or declined. All these transactions must be accomplished in real-time. There is no standard way of completing this process. To communicate with each major credit card processor, you must implement different proprietary software. XML Web services have the potential to standardize the process of authorizing credit cards. Imagine a credit card authorization Web service. The Web service would have an authorization method to accept a credit card number, an expiration date, and a purchase amount. When the method is invoked, it would return either approved or declined.
Now imagine a shipping Web service. You would transmit an origin and destination address to the service, and it would return a list of shipping options with different prices. Before you can purchase a book, you must first log in to the Web site by entering your username and password. Typically, you must enter new registration information for each site that you use. Again, imagine a Web service simplifying this process. In this case, it would be a user registration Web service. Whenever you visit a Web site, it could communicate with the user registration Web service to verify your identity. You would pass a username and password to the Web service, and it would return a value indicating whether the username and password combination is valid. Finally, before you purchase the book, you have to visit several Web sites to find the best price. XML Web services can even help here. Now imagine a book Web service. You would transmit the title of a book to the service, and it would return the name of the Web site that currently sells the book for the lowest price. Or, better yet, the Web service would automatically communicate with the credit card authorization Web service and automatically buy the book for you at the lowest price.
XML Web Services Facilitate Communication XML Web services facilitate communication among different Web sites and different applications. Because Web services are built on open standards—such as HTTP, XML, and SOAP—they enable communication in a platform-independent way.
22 CREATING AN XML WEB SERVICE
When purchasing a book, you must select a shipping method. To get a list of shipping options and prices for each option, the Web site must communicate with one or more shipping services. Again, there is no standard way of completing this process. The method for communicating with Federal Express is different than the method for communicating with United Parcel Service.
960
Building ASP.NET Web Services PART VI
This last point is important. Because Web services are based on open standards, an ASP.NET Web site can use Web services to communicate with a Web site created on a different platform, such as Java. Note You can download the XML and Web services Development Environment from the IBM Web site. This programming environment enables you to build Web services with Java.
Another important application of Web services is communication with applications running on legacy systems. Large companies have a regrettable tendency to employ multiple platforms and applications written in multiple programming languages. Because Web services are built on open standards, you can use them as a common language to bridge the different platforms in an organization. In the next chapter, “Advanced XML Web Services,” for example, you’ll learn how to take advantage of a Web services feature called HTML pattern matching, which enables you to perform screen-scraping operations against legacy systems. You can use HTML pattern matching to read information from an application even if the application does not directly support Web services. Web services not only enable Web site–to–Web site communication, they also enable application–to–Web site and application-to-application communication. For example, you can write a desktop application that communicates with a banking Web service to retrieve your current checking account balance. Or you can use Web services to enable a desktop application running on a computer in Australia to communicate with a desktop application running on a computer in Mexico.
XML Web Services Enable Aggregation Another important benefit of XML Web services is that they enable you to aggregate content from multiple Web sites and even multiple Web services. We already discussed the example of aggregating information on the price of books from different Web sites. Another example of aggregation is a meta-search engine. You could query a search Web service, and it could automatically query multiple search engines such as Yahoo and AltaVista and return the combined search results. You could build this search Web service even if Yahoo and AltaVista do not implement Web services themselves. By taking advantage of a feature of Web services called
Creating an XML Web Service CHAPTER 22
961
HTML pattern matching, you can retrieve information from any Web site and expose it in your Web service. Someday soon, all wholesalers might expose their catalog of products as Web services. Retail Web sites could then combine and prune the different catalogs of products to offer a custom selection.
Creating a Simple XML Web Service
LISTING 22.1
TemperatureService.asmx
Imports System Imports System.Web.Services Public Class TemperatureService : Inherits WebService Public Function ToCelsius( TF As Double ) As Double Return ( 5/9 ) * ( TF - 32 ) End Function Public Function ToFahrenheit( TC As Double ) As Double Return ( 9/5 ) * Tc + 32 End Function End Class
The first thing you should notice about Listing 22.1 is that very few lines of code are required to build a Web service. Here, you create a public class, named TemperatureService, that inherits from the .NET WebService class. Note Strictly speaking, you don’t even need to inherit a Web service from the .NET WebService class. Doing so simply makes it easier to access intrinsic objects, such as the Application, Session, and Cache objects.
CREATING AN XML WEB SERVICE
In this section, you build a simple XML Web service that converts back and forth between temperatures in degrees Fahrenheit and degrees Celsius. The Web service has two methods: ToCelsius and ToFahrenheit. The complete code for this Web service is included in Listing 22.1.
22
962
Building ASP.NET Web Services PART VI
Next, you declare two methods for the class by using Visual Basic functions. They are the two methods exposed by the Web service. Notice the strange expression that appears in each of the two function declarations. For example, the ToCelsius function is declared like this: Public Function ToCelsius( TF As Double ) As Double
The expression applies a custom attribute, named WebMethod, to the ToCelsius function. The WebMethod custom attribute exposes the function to the world so that it can be accessed through the Web service. Note You can use the WebMethod attribute with both methods and properties.
You need to understand the difference between a function declared with the Public modifier and the WebMethod custom attribute. Functions declared with the Public access modifier can be accessed by all procedures in all classes in an application. However, they are not automatically exposed as methods of the Web service. To be exposed as a Web method, the function must be Public and be flagged with the WebMethod attribute. One last thing that you should notice about Listing 22.1 is that the name of the file ends with the extension .asmx. This extension identifies Web services. When you create a Web service, you must save the file that defines the Web service with the extension .asmx in a directory that your Web server can access. Note Be careful where you save your Web service file. When you announce your Web service to the world, you cannot easily change its location.
Setting WebMethod Attributes The WebMethod custom attribute has the following optional properties: •
By default, Web service methods and properties buffer responses in chunks. If you want to stream a response back to a client, you can set this property to the value False.
BufferResponse
Creating an XML Web Service CHAPTER 22
•
CacheDuration By default, Web service methods and properties do not cache responses. However, you can set this property to an integer value to cache a response for a certain number of seconds.
•
Description This property contains the description for methods and properties used when Web service description documents are generated.
•
By default, Web service methods and properties do not participate in session state. If you set this property to True, the method or property can use session state.
•
963
EnableSession
MessageName
This property can be used to create an alias for a Web method
22
name. TransactionOption This property enables you to specify how the Web service participates in transactions. By default, transactions are disabled.
If you want to provide a description for the ToCelsius method, for example, you would declare the method like this: Public Function ➥toCelsius( TF As Double ) As Double Return ( 5/9 ) * ( TF - 32 ) End Function
This description is used, for example, in the Web Service Help page. I discuss this file in the section titled “Testing an XML Web Service from a Browser” later in this chapter.
Setting WebService Attributes In the same way in which you can specify attribute properties for individual methods of a WebService, you can specify attribute properties for a Web service as a whole. To do this, you use the properties of the WebService attribute class. This class has the following three properties: •
Description Provides a description for the Web service. This description appears in the Web Service Help page.
•
Name
•
Namespace
Specifies a name for the Web service (by default, the class name is used).
Specifies the XML namespace for the Web service (not to be confused with the .NET namespace).
The last property, Namespace, is particularly important. By default, the XML namespace is set to http://tempuri.org/. You should specify a new URL for the Namespace property, such as http://www.yourdomain.com/services. The URL that you specify does not need to point to an actual Web page. The URL is simply functioning as a unique
CREATING AN XML WEB SERVICE
•
964
Building ASP.NET Web Services PART VI
identifier so that Web services developed by your company can be distinguished from Web services developed by other companies. Here’s how you would set the Description and Namespace properties when declaring the TemperatureService Web service:
_
Public Class TemperatureService
Precompiling an XML Web Service You don’t need to compile the class file for a Web service before you can use the Web service. Like an ASP.NET page, a Web service is automatically compiled when it is requested. However, if you are selling a Web service class file or you have other reasons for hiding the source code, you have the option of precompiling the service. Note There is absolutely no performance difference between a compiled and uncompiled Web service.
To compile the TemperatureService in Listing 22.1, you would need to strip away the first line so that the file contains only a class declaration. The file in Listing 22.2 contains the pure class definition in a VB file (I’ve renamed the class to CompiledTemperatureService). LISTING 22.2
CompiledTemperatureService.vb
Imports System Imports System.Web.Services Public Class CompiledTemperatureService : Inherits WebService Public Function ToCelsius( TF As Double ) As Double Return ( 5/9 ) * ( TF - 32 ) End Function Public Function ToFahrenheit( TC As Double ) As Double Return ( 9/5 ) * Tc + 32 End Function End Class
Creating an XML Web Service CHAPTER 22
965
Next, you must compile the CompiledTemperatureService.vb file. To do so, execute the following command from a command line prompt: vbc /t:library /r:System.dll,System.Web.Services.dll CompiledTemperatureService.vb
This command executes the Visual Basic compiler. The CompiledTemperatureService.vb file is compiled into a DLL file that references both the System.dll and System.Web.Services.dll assemblies. If the CompiledTemperatureService.vb file is successfully compiled, a file named CompiledTemperatureService.dll is produced.
The final step is to create the .asmx file that references the compiled class. The new file contains only a single line. The complete code for the file is shown in Listing 22.3.
.asmx
LISTING 22.3
CompiledTemperatureService.asmx
The page in Listing 22.3 contains a single directive. The Class attribute refers to the compiled CompiledTemperatureService class file in the /bin directory.
Testing an XML Web Service from a Browser After you create a Web service, you can test it in a browser that supports XML, such as Internet Explorer version 5.0 or higher. Just enter the address of the Web service in your Web browser. For example, if you saved TemperatureService.asmx in a subdirectory of your Web site called Services, you can enter the following address: http://localhost/Services/TemperatureService.asmx
When you access a Web service directly from a Web browser, the Web Service Help page is displayed (see Figure 22.1). This page lists all the properties and methods of the Web service.
CREATING AN XML WEB SERVICE
When you have a compiled class file, you need to copy the file to the /bin directory of your ASP.NET application. If the /bin directory doesn’t exist, you can create it.
22
966
Building ASP.NET Web Services PART VI
FIGURE 22.1 The Web Service Help Page.
The Help page for TemperatureService, for example, lists the ToCelsius and ToFahrenheit methods as hypertext links. If you click either link, you are transferred to another page that contains more information about the method. Note You can modify the appearance of the Web Service Help page by modifying the ASP.NET page at the following location: \WINNT\Microsoft.NET\Framework\[version]\CONFIG\DefaultWsdlHelpGenerator. aspx
If you navigate to the description page for the ToCelsius method, you see sample requests and responses using three separate protocols: SOAP, HTTP-Get, and HTTPPost. They are the three standard wire formats for transmitting Web service requests and responses. All three wire formats are based on standard and open Internet protocols, such as HTTP and XML. This means that all three protocols work well across firewalls and proxy servers and can be easily used on a variety of different platforms.
Creating an XML Web Service CHAPTER 22
967
Invoking an XML Web Service with HTTP-Get HTTP-Get is the standard HTTP protocol for transmitting requests for URLs or posting a form with METHOD=”Get”. You could invoke the ToCelsius method of the TemperatureService Web service by adding the following hypertext link in an HTML document: Convert
Alternatively, you could type the following URL directly into the address bar of your Web browser:
22
http://localhost/Services/TemperatureService.asmx/ToCelsius?TF=32
0
Because 0 degrees Celsius is equivalent to 32 degrees Fahrenheit, the ToCelsius method returns the value 0. Warning Web service method names are case sensitive. So, attempting to invoke the TOCELSIUS method would generate an error.
You can test a Web service using HTTP-Get directly from the Web Service Help page, which includes a form for each Web service method that enables you to enter a value and invoke the Web service. Invoking a method from the form opens a new browser window that displays the return value from the method (see Figure 22.2). Note You cannot use the Web Service Help page when testing Web services that accept complex types, such as DataSets, as input parameters or when testing Web services that require special headers.
CREATING AN XML WEB SERVICE
In either case, the ToCelsius Web method would be invoked by passing the TF parameter with the value 32. The Web service would return the following XML document representing the results of converting the value 32 into degrees Celsius:
968
Building ASP.NET Web Services PART VI
FIGURE 22.2 Invoking a Web service method.
Invoking an XML Web Service with HTTP-Post HTTP-Post is the standard HTTP protocol for transmitting form data submitted with METHOD=”Post”. For example, you could invoke the ToCelsius method of TemperatureService by using the HTML page in Listing 22.4. LISTING 22.4
PostInvoke.html
Posting the form in Listing 22.4 would invoke the ToCelsius method of the TemperatureService Web service. The following XML document would be returned: 0
Invoking an XML Web Service with SOAP The Simple Object Access Protocol (SOAP) enables you to transmit more complex types of messages across a network. You can transmit data types with SOAP that you cannot
Creating an XML Web Service CHAPTER 22
969
transmit by using either HTTP-Get or HTTP-Post. For example, you can use SOAP to transmit DataSets, custom classes, and binary files. SOAP is an XML-based protocol backed by a number of large companies including Microsoft, IBM, and Ariba. A SOAP message contains an envelope section that contains the data to be transported. A SOAP request that invokes the ToCelsius method of TemperatureService, for example, looks like this:
This SOAP request invokes the ToCelsius method by passing a TF parameter with the value 32. The Web service would return the following SOAP response: 0
Unlike HTTP-Get and HTTP-Post, the SOAP protocol is not tied to the HTTP protocol. Although you use SOAP over the HTTP protocol in all the examples in this book, by posting SOAP messages, you also can use SOAP over other protocols such as SMTP.
Accessing an XML Web Service Through a Proxy Class You can access a Web service from within an application by creating a Web service proxy class. This type of proxy class is a local representation of the properties and methods of a remote Web service class.
22 CREATING AN XML WEB SERVICE
32
970
Building ASP.NET Web Services PART VI
After you create a proxy class, you can treat the class exactly like any other .NET framework class. For example, imagine that the TemperatureService Web service is hosted at an Internet service provider located in central Borneo. After you create a proxy class, you can invoke the methods of the remote Web service class within your application as though the class were located on your computer.
Generating an XML Web Service Proxy Class You can generate a proxy class by using the Wsdl.exe (Web Services Description Language tool) command-line tool included with the .NET Framework Software Development Kit. To generate a proxy class, you need to complete the following steps: 1. Use the Wsdl.exe tool to generate the source code file for the proxy class. 2. Compile the source code file for the proxy class. 3. Copy the compiled proxy class into the ASP.NET application’s /bin directory. To compile a proxy class for the TemperatureService Web service, for example, you would execute the Wsdl.exe tool from a command line prompt like this: wsdl.exe /l:vb http://www.YourSite.com/Services/TemperatureService.asmx?wsdl
The /l switch indicates the language used for generating the source code file for the proxy class. Because you want to generate a Visual Basic class, you supply the argument vb. The Web address supplied to the tool is the address of the Web service Description Language (WSDL) file for the Web service. You can always retrieve the WSDL file for a Web service by appending the query string ?wsdl to its address. A WSDL file is an XML file that describes all the methods and properties of a Web service. This file is generated automatically for you. If you’re curious, you can view the WSDL file for a Web service by opening the Web Service Help page and clicking the Web Service Description Language link. Warning You can use localhost to refer to the current server when generating a proxy class with the Wsdl.exe tool. Typically, however, using this server is not a good idea. If you use localhost, the proxy class works only on the same computer as the Web service.
Creating an XML Web Service CHAPTER 22
971
Executing the Wsdl.exe tool generates a file named TemperatureService.vb that contains the source code for a Visual Basic class file. You can open this file in Notepad if you want to see its contents. Before you can use the TemperatureService.vb class, you first need to compile it by executing the Visual Basic command-line compiler like this: vbc /t:library /r:System.dll,System.Web.Services.dll,System.Xml.dll ➥ TemperatureService.vb
This statement references all the needed assemblies to compile the TemperatureService.vb file into a DLL file.
Using an XML Web Service Proxy Class After you create the Web service proxy class and copy it to your /bin directory, you can start using the class in your applications. For example, the page in Listing 22.5 invokes the TemperatureService Web service from within an ASP.NET page (see Figure 22.3). LISTING 25.5
ConvertTemperature.aspx
Sub Button_Click( s As Object, e As EventArgs ) Dim objTemp As New TemperatureService lblCelsius.Text = objTemp.ToCelsius( txtFahrenheit.Text ) End Sub ConvertTemperature.aspx Fahrenheit:
FIGURE 22.3 The Convert Temperature.aspx
page.
The page in Listing 25.5 displays a Web form that contains a TextBox and Button control. When you enter a number into the TextBox control and submit the form, the Button_Click subroutine is executed. The Button_Click subroutine creates an instance of the TemperatureService Web service proxy class. Next, it calls the ToCelsius method of the proxy class to convert the temperature entered into the form from degrees Fahrenheit to degrees Celsius. Finally, the converted number is displayed in a Label control named lblCelsius.
Creating an XML Web Service CHAPTER 22
973
From the perspective of the ASP.NET page in Listing 25.5, the proxy class is just a normal .NET class. However, when you call one of the methods of the proxy class, a lot of work happens behind the scenes. A SOAP message must be sent across the network to the TemperatureService Web service, and a response must be returned. Even if the TemperatureService Web service were located on the other side of the world, the page would still work.
Working with the Web Services Description Language Tool
You can use any of the following options with Wsdl.exe: •
/nologo
•
or /l: By default, classes are generated using the C# language. You can specify CS for C#, VB for Visual Basic, or JS for JScript.
•
Prevents the banner text for the Wsdl.exe tool from being displayed.
/language:
/server:
Generates an abstract class for the Web service instead of the default
proxy class. •
/namespace:
or /n: Specifies the namespace to use for the proxy class. By default, the proxy class uses the global namespace.
•
/out:
•
/protocol:
•
or /u: Specifies the username to use when connecting to a server that requires authentication.
•
/password: or /p: Indicates the password to use when connecting to a server that requires authentication.
•
/domain: or /d: Specifies the domain to use when connecting to a server that requires authentication.
•
/proxy:
•
/proxyusername:
or /pu:
Specifies the username to use with the proxy server.
•
/proxypassword:
or /pp:
Specifies the password to use with the proxy server.
•
/appsettingurlkey:
Indicates the filename for the proxy class.
Indicates the protocol to use with the proxy class. By default, a proxy class uses the SOAP protocol. You can specify SOAP, HttpGet, or HttpPost.
/username:
Specifies the URL of a proxy server to use for HTTP requests (defaults to Internet Explorer settings).
or /urlkey: Specifies the Url property for the Web service proxy class from a key in the section of the web.config file.
22 CREATING AN XML WEB SERVICE
In the preceding section, you used the Web Services Description Language tool (Wsdl.exe) to generate the source code for the Web service proxy class. This section goes into the details of how this tool works.
974
Building ASP.NET Web Services PART VI
•
or /baseurl: Specifies the base URL for the Web service proxy class from a key in the section of the web.config file.
/appsettingbaseurl:
The protocol option is particularly important. You can use it to create proxy classes that use HTTP-Post or HTTP-Get instead of the default SOAP protocol. Using a protocol other than SOAP can result in better performance because less data needs to be sent over the wire. However, the HTTP-Get and HTTP-Post protocols support fewer data types than SOAP. The /appsettingurlkey option is also interesting. It enables you to avoid hardwiring the location of the Web service into the proxy class. If you specify a value for this option, the proxy class will first attempt to retrieve the URL for the Web service from the web.config file. Otherwise, the proxy class will use the URL from the WSDL file. You can supply three types of documents to the Wsdl.exe tool: WSDL contract files, XSD schemas, and .discomap discovery files. The tool generates a proxy class from any of these file types. Note You can use the Web service Discovery Tool (Disco.exe) to generate .discomap files.
To specify the location of a WSDL contract file or an XSD schema, you can provide either a URL or the path to a file on your hard drive. (You are required to specify a file path in the case of a .discomap discovery file.) For example, you can save the WSDL contract file generated by the Web Service Help Page to your hard drive and generate a proxy class based on the saved file.
Setting Proxy Class Properties A Web service proxy class derives from the SoapHttpClientProtocol class. Many of the properties of this class are useful for controlling the behavior of your Web service. Here is a list of some of the more useful of these properties: •
AllowAutoRedirect A Boolean value that specifies whether a request to a Web service can be automatically redirected to another URL (default value is False).
•
ClientCertificates
•
CookieContainer
the proxy class.
X509CertificateCollection of client-side certificates.
A CookieContainer that contains the cookies associated with
Creating an XML Web Service CHAPTER 22
•
Credentials
•
PreAuthenticate A Boolean value indicating whether a WWW-authenticate header is sent with each request (default value is False).
•
Proxy
•
RequestEncoding
•
Timeout
•
UserAgent
A class implementing ICredentials (such as NetworkCredential) that can be used for Basic, Digest, NTLM, or Kerberos authentication.
A class implementing IWebProxy that contains information for making requests through a firewall. The character encoding used for proxy requests.
An integer value in milliseconds that specifies when a proxy request times out (default is -1 which represents infinity). A string that specifies the value of the User-Agent header. The default
is MS
Web Services Client Protocol.
Url
The Url used by the proxy when making requests.
For example, if you want to ignore slow responses from Web sites, you might want to change the default Timeout value to a shorter value. We’ll use the CookieContainer property in the final section of this chapter (in the section entitled XML Web services and Session State) to enable session state in a Web service.
Transmitting Complex Data in an XML Web Service To this point, you have worked with only a simple Web service. The TemperatureService Web service returns only a single integer value. However, Web services can return more complex data types. In the following sections, you explore how to use a Web service with arrays, classes, DataSets, and binary files.
XML Web Services and Arrays Imagine that you want to build a Web service that enables you to broadcast news items to Web sites. For example, you might want to build an ASP.NET news service that carries the latest news on programming with ASP.NET. One way to implement the Web service would be to have the service return an array of news items. This way, you could transmit multiple news items at a time. The Web sites that receive your news broadcast could format individual news items in any way that they please. The Web service in Listing 22.6 contains a single method named GetNews that returns an array.
22 CREATING AN XML WEB SERVICE
•
975
976
Building ASP.NET Web Services PART VI LISTING 22.6
NewsService.asmx
Imports System Imports System.Web.Services _ Public Class NewsService : Inherits WebService Dim arrNews( 2 ) As String
Public Function GetNews() As String()
arrNews( 0 ) = “Visit superexpert.com for ASP.NET News!” arrNews( 1 ) = “Visit AspWorkshops.com for ASP.NET Training!” arrNews( 2 ) = “Visit superexpertControls.com for ASP.NET Controls!” Return arrNews End Function End Class
The Web service in Listing 22.6 returns a list of news items as a one-dimensional array of strings. Every time the GetNews method is invoked, all three items in the array are returned. Note The CacheDuration property is used with the GetNews method in Listing 22.6. This property is set to cache the results of the method in memory for 30 seconds. Making liberal use of the CacheDuration property is a good idea when the data returned from a Web service changes infrequently.
After you create the Web service in Listing 22.6, any Web sites that want to subscribe to your service can build proxy classes for it. To create the proxy class, use the Wsdl.exe tool to create the source for the class file like this: wsdl /l:vb http://www.YourSite.com/newsService.asmx?WSDL
Then compile the source for the proxy class as follows: vbc /t:library /r:System.dll,System.Web.Services.dll,System.XML.dll ➥ NewsService.vb
Creating an XML Web Service CHAPTER 22
977
Finally, you must copy the compiled proxy class, NewsService.dll, to the application’s /bin directory. After you create a proxy class for the NewsService Web service, you can use the class in an ASP.NET page. The page in Listing 22.7 illustrates how you can use the NewsService class to format and display news items. LISTING 22.7
DisplayNews.aspx
objNewsService = New NewsService() arrNews = objNewsService.GetNews() rptNews.DataSource = arrNews rptNews.DataBind End Sub DisplayNews.aspx The lastest news:
This page has been requested by you times!
22
Remember that session state relies on a cookie to identify requests within the same session. A special cookie named ASP.NET_SessionId must be passed to the server with every request to identify the user. Since, by default, this cookie won’t be passed by the proxy class, session state won’t work. If we want our proxy class to be able to take advantage of session state, we have to do some extra work. We need to explicitly pass the same ASP.NET_SessionId cookie to the Web service proxy with each request. This is illustrated in Listing 22.19. LISTING 22.19
SessionCounterGood.aspx
Sub Page_Load Dim objSessionService As SessionService objSessionService = New SessionService objSessionService.CookieContainer = New CookieContainer If Session( “CookieContainer” ) Is Nothing Then Session( “CookieContainer” ) = objSessionService.CookieContainer Else objSessionService.CookieContainer = Session( “CookieContainer” ) End If lblSessionCounter.Text = objSessionService.GetSessionCounter() lblSessionID.Text = objSessionService.GetSessionID() End Sub
CREATING AN XML WEB SERVICE
Each time you request the page in Listing 22.18, a new session ID is displayed. The session counter will always have the value 1. The problem is the Web service will interpret each call by the proxy class as the start of a new session.
992
Building ASP.NET Web Services PART VI LISTING 22.19
continued
SessionCounterGood.aspx This page works! Your session ID is
This page has been requested by you times!
In Listing 22.19, you explicitly create an instance of the CookieContainer class. This class contains a collection of all the cookies associated with each Web service request. One of these cookies is the ASP.NET_SessionId cookie which is used to maintain session state. The CookieContainer class is saved in session state between requests. By saving the CookieContainer in session state, you can ensure that the same value for the ASP.NET_SessionId cookie is passed with the Web method request each time the same user requests the page. If you request the SessionCounterGood.aspx page, the session counter should increment as expected. You should also notice that the session ID displayed by the page remains constant (see Figure 22.8).
Creating an XML Web Service CHAPTER 22
993
FIGURE 22.8 The SessionCounter Good.aspx
page.
22 CREATING AN XML WEB SERVICE
Summary This chapter started with an overview of XML Web services. You learned how Web services have the potential to facilitate Web site communication and enable content aggregation. Next, you learned how to build a simple XML Web service and test it from a Web browser. You also learned how to use the Web service Help page to get an overview of the methods and properties included with a particular Web service. I also discussed how you could use different data types with a Web service. You learned how to use arrays, classes, DataSets, and binary files with a Web service. Finally, you examined how to use session and application state within the context of an XML Web service. You created Web services that can preserve their state between requests by taking advantage of session and application state.
CHAPTER 23
Advanced XML Web Services
IN THIS CHAPTER • Using the WebService Behavior
996
• Securing an XML Web Service
1012
• Using HTML Pattern Matching
1025
996
Buiding ASP.NET Web Services PART VI
In the preceding how to create basic XML Web services. This chapter goes beyond the basics and discusses several advanced features of XML Web services. First, the chapter discusses the WebService behavior. You learn how to use the WebService behavior to directly access an XML Web service through Internet Explorer. Next, you explore one method of securing a Web service. The chapter discusses how to pass custom headers to a Web service so that you can identify the same user across multiple requests. Finally, the chapter discusses an interesting feature of XML Web services called HMTL pattern matching. You can use HTML pattern matching to expose the content of any Web site as a Web service.
Using the WebService Behavior If you’re using Internet Explorer (version 5.0 or higher), you can access a Web service directly from the browser. You can access a Web service directly through client script by using the WebService behavior. A behavior is a component that you can associate with an element within an HTML page that extends the element’s default functionality. You already saw some examples of behaviors earlier in this book. In Chapter 8, “Using Third-Party Controls,” you learned about the Internet Explorer WebControls, such as the TreeView and Toolbar controls. The client-side functionality of these controls is implemented through Internet Explorer behaviors. The main advantage of using a behavior to access a Web service is that you can update the content of a page without reloading the whole page. For example, later in this chapter, you learn how to use the WebService behavior to display a continuously updated featured product section in an HTML page. The contents of the featured product section automatically change every five seconds without reloading any other content of the page. Before you can use the WebService behavior, you need to download the webservice.htc file from the Microsoft MSDN library (msdn.microsoft.com). This file is currently located at the following address: http://msdn.microsoft.com/workshop/author/webservice/webservice.htc
Examining Limitations of the WebService Behavior Before you start building pages with the WebService behavior, you should be aware of some of the behavior’s limitations.
Advanced XML Web Services CHAPTER 23
997
The first limitation concerns the XML Web services that you can access through the behavior. You can use the WebService behavior to access only those Web services located in the same domain as the Web page that contains the behavior. (This limitation is a direct result of the security restrictions built into DHTML.) If you want to access a remote Web service, you must access it through a proxy Web service located in the same domain. The second limitation concerns the data types that you can access through the behavior. The WebService behavior supports all the base .NET types, such as Strings, Integers, and DateTimes. The WebService behavior also supports arrays of the base .NET types. For example, you can pass an array of integers through the WebService behavior. WebService
Unfortunately, however, the behavior has no direct support for more complex types, such as DataSets, DataTables, Collections, or custom objects. For example, you can’t pass a DataSet through the WebService behavior and bind it to a drop-down list box on the client. Later in this chapter, in the section titled, “Using the WebService Behavior to Retrieve Database Data,” you explore one method of getting around this limitation.
In the preceding chapter, you created a TemperatureService XML Web service that enabled you to convert between degrees Celsius and degrees Fahrenheit. You learned how to build a proxy class to access this Web service through an ASP.NET page. In this section, you learn how to access the TemperatureService Web service directly through the WebService behavior and bypass any need for a proxy class. The code for the TemperatureService is contained in Listing 23.1. LISTING 23.1
TemperatureService.asmx
Imports System Imports System.Web.Services _ Public Class TemperatureService : Inherits WebService Public Function ToCelsius( TF As Double ) As Double Return ( 5/9 ) * ( TF - 32 ) End Function
ADVANCED XML WEB SERVICES
Creating a Simple Page with a WebService Behavior
23
998
Buiding ASP.NET Web Services PART VI LISTING 23.1
continued
Public Function ToFahrenheit( TC As Double ) As Double Return ( 9/5 ) * TC + 32 End Function End Class
To access the TemperatureService Web service with the WebService behavior, you need to complete the following steps: 1. Attach the WebService behavior to an element within the HTML page. 2. Provide the location of the XML Web service by invoking the WebService behavior’s useService method. 3. Call a method of the XML Web service by invoking the WebService behavior’s callService method. The page in Listing 23.2 illustrates how to add the WebService behavior to a page and invoke the behavior’s methods. Note Because this is a book on ASP.NET, I named all the pages in this section with the extension .aspx. However, the WebService behavior works equally well in a plain, old HTML page.
LISTING 23.2
BehaviorSimple.aspx
var intCallID = 0; function Init() { Service.useService(“/webservices/TemperatureService.asmx?WSDL”, ➥”TemperatureService”); }
function Service_Result() { lblCelsius.innerText = event.result.value; }
Advanced XML Web Services CHAPTER 23 LISTING 23.2
999
continued
function Button_Click() { intCallID = Service.TemperatureService.callService( “ToCelsius”, ➥ txtFahrenheit.value ); } BehaviorSimple.aspx Fahrenheit:
The page in Listing 23.2 enables you to enter a temperature in degrees Fahrenheit and, by clicking a button, convert the temperature to degrees Celsius (see Figure 23.1). When you click the button, the ToCelsius() method of the TemperatureService Web service is called to perform the conversion. The WebService behavior is attached to the page with the following tag:
The behavior is attached to an HTML tag and given the name Service. You can, of course, specify any name that you want for the behavior by supplying another value for the id attribute.
23 ADVANCED XML WEB SERVICES
1000
Buiding ASP.NET Web Services PART VI
FIGURE 23.1 Using the WebService
behavior with the Temperature Service.
The style attribute actually attaches the behavior by providing the path to the webservice.htc file that implements the WebService behavior. After the WebService behavior is attached to the page, you can access the behavior’s methods and properties. The location of the TemperatureService Web service is provided within the JavaScript function (this function is called by the page’s load event). The Init() function contains the following statement:
Init()
Service.useService(“/webservices/TemperatureService.asmx?WSDL”, ➥”TemperatureService”);
This statement calls the WebService behavior’s useService() method, which associates a friendly name with the path to a Web service. In this case, the friendly name TemperatureService is used for the TemperatureService XML Web service. The useService() method also downloads the Web services Description Language (WSDL) file for the Web service. The behavior needs this WSDL file to determine the proper format for the parameters to pass back and forth to the Web service. When you click the Convert! button, the JavaScript Button_Click function executes the following statement: intCallID = Service.TemperatureService.callService( “ToCelsius”, ➥ txtFahrenheit.value );
Advanced XML Web Services CHAPTER 23
1001
This statement calls the ToCelsius method of the TemperatureService Web service. The first parameter to callService represents the Web method to call. Additional parameters represent parameters passed to the Web method. This statement calls the ToCelsius method with the value of the txtFahrenheit text box. By default, the WebService behavior calls methods of a Web service asynchronously. The process of calling a Web service method and getting the result is broken into two steps. When you attached the WebService behavior to the tag, you specified a JavaScript function to handle the result event, which is raised when the Web service method returns a value. In Listing 23.2, you handled the result event by using the following function: function Service_Result() { lblCelsius.innerText = event.result.value; }
This function grabs the value returned by the Web service ToCelsius method and assigns it to a tag named lblCelsius.
In the preceding section, you grabbed the value returned by the ToCelsius Web service method within the result event handler. In fact, you can grab the result of a Web method call in two ways: by using an event handler or specifying a callback function. The page in Listing 23.3 does exactly the same thing as the page in Listing 23.2, except that it uses a callback function rather than an event handler. LISTING 23.3
BehaviorCallback.aspx
var intCallID = 0; function Init() { Service.useService(“TemperatureService.asmx?WSDL”,”TemperatureService”); }
function ToCelsius_Result( result ) {
ADVANCED XML WEB SERVICES
Using a WebService Behavior Callback Function
23
1002
Buiding ASP.NET Web Services PART VI LISTING 23.3
continued
lblCelsius.innerText = result.value; }
function Button_Click() { intCallID = Service.TemperatureService.callService( ToCelsius_Result, ➥”ToCelsius”, txtFahrenheit.value ); } BehaviorCallback.aspx Fahrenheit:
In Listing 23.3, you call the ToCelsius method by using the following statement: intCallID = Service.TemperatureService.callService( ToCelsius_Result, ➥”ToCelsius”, txtFahrenheit.value );
The first parameter passed to the callService method represents a callback function. When the results of the ToCelsius method are returned, the ToCelsius_Result method is called. When should you use an event handler, and when should you use a callback function? If you need to call only a single Web service method in a page, using an event handler is more convenient. However, if you need to call multiple Web service methods, using a
Advanced XML Web Services CHAPTER 23
1003
callback function is more convenient because you can create different callback functions that correspond to different Web service method calls.
Catching Errors in a WebService Behavior Thus far, you haven’t added any logic to the pages to handle errors. You’ve been working under the assumption that a Web service will always be available whenever you attempt to access it. This isn’t good programming practice. You can use the error property of the result object to check for an error. If a Web service cannot be accessed, the error property will have the value True. You can use the errorDetail object to retrieve a detailed error message. The errorDetail object has three properties: •
code—An
error code
•
raw—The
•
string—A
raw contents of the Simple Object Access Protocol (SOAP) packet returned by the Web service method call human-readable error message
FIGURE 23.2 Using the error property and errorDetail
object.
23 ADVANCED XML WEB SERVICES
The page in Listing 23.4 uses both the error property and errorDetail object to detect and report any errors that might occur when accessing the Web service (see Figure 23.2). The error property and errorDetail object are used in the Service_Result function.
1004
Buiding ASP.NET Web Services PART VI LISTING 23.4
BehaviorErrors.aspx
var intCallID = 0; function Init() { Service.useService(“TemperatureService.asmx?WSDL”,”TemperatureService”); }
function Service_Result() { if ( event.result.error ) { lblCelsius.innerText = event.result.errorDetail.string; } else { lblCelsius.innerText = event.result.value; } }
function Button_Click() { intCallID = Service.TemperatureService.callService( “ToCelsius”, ➥ txtFahrenheit.value ); } BehaviorErrors.aspx Fahrenheit:
Advanced XML Web Services CHAPTER 23 LISTING 23.4
1005
continued
Using a WebService Behavior to Perform Partial Page Updates The feature that makes WebService behaviors so interesting is that they enable you to dynamically update part of a page without reloading the whole page. You can use a WebService behavior to retrieve fresh content at timed intervals and display the content on a page. This feature of the WebService behavior is illustrated in the page contained in Listing 23.5, which has a featured product section. The contents of the featured product section are automatically updated every five seconds with a call to a Web service. BehaviorRefresh.aspx
var intCallID = 0; function Init() { GetNewFeatured(); setInterval( “GetNewFeatured()”, 5000 ) } function GetNewFeatured() { Service.useService(“FeaturedService.asmx?WSDL”,”FeaturedService”); intCallID = Service.FeaturedService.callService( “GetFeatured” ); } function Service_Result() { if (event.result.error) { divFeatured.innerText = event.result.errorDetail.string;
ADVANCED XML WEB SERVICES
LISTING 23.5
23
1006
Buiding ASP.NET Web Services PART VI LISTING 23.5
continued
} else { divFeatured.innerText = event.result.value; } } BehaviorRefresh.aspx
Welcome to this Web Site! Browse this Web site to get great deals on the latest books! |
The page in Listing 23.7 contains a drop-down list that enables you to pick a product category. This drop-down list is populated from the Categories table from the Northwind database with a call to the GetCategories() XML Web service method. When you pick a category, a call to another Web service method is made. The products that match the selected category are retrieved from the Products table through the GetProducts() Web service method and are displayed in a list box. The ProductsService Web service is contained in Listing 23.8. LISTING 23.8
ProductsService.asmx
System System.Web.Services System.Data System.Data.SqlClient System.Collections
_ Public Class ProductsService : Inherits WebService Public Function GetCategories() As String() Dim strSelect As String Dim conNorthwind As SqlConnection Dim cmdCategories As SqlCommand Dim dtrCategories As SqlDataReader Dim colCategories As ArrayList strSelect = “SELECT CategoryName FROM Categories” conNorthwind = New SqlConnection( “Server=localhost;UID=sa;PWD=secret; ➥Database=Northwind” ) cmdCategories = New SqlCommand( strSelect, conNorthwind ) conNorthwind.Open() dtrCategories = cmdCategories.ExecuteReader() colCategories = New ArrayList While dtrCategories.Read colCategories.Add( dtrCategories( “CategoryName” ) ) End While
ADVANCED XML WEB SERVICES
Imports Imports Imports Imports Imports
23
1012
Buiding ASP.NET Web Services PART VI LISTING 23.8
continued
conNorthwind.Close() Return colCategories.ToArray( GetType( System.String ) ) End Function Public Function GetProducts( CategoryName As String ) As String() Dim strSelect As String Dim conNorthwind As SqlConnection Dim cmdProducts As SqlCommand Dim dtrProducts As SqlDataReader Dim colProducts As ArrayList CategoryName = Server.UrlDecode( CategoryName ) strSelect = “SELECT ProductName, UnitPrice “ & _ “FROM Products, Categories WHERE Products.CategoryID ➥ = Categories.CategoryID “ & _ “AND CategoryName = @CategoryName” conNorthwind = New SqlConnection( “Server=localhost;UID=sa;PWD=secret; ➥Database=Northwind” ) cmdProducts = New SqlCommand( strSelect, conNorthwind ) cmdProducts.Parameters.Add( New SQLParameter( “@CategoryName”, ➥ CategoryName ) ) conNorthwind.Open() dtrProducts = cmdProducts.ExecuteReader() colProducts = New ArrayList While dtrProducts.Read colProducts.Add( _ dtrProducts( “ProductName” ) & _ String.Format( “ - {0:c}”, dtrProducts( “UnitPrice” ) ) ) End While conNorthwind.Close() Return colProducts.ToArray( GetType( System.String ) ) End Function End Class
Both the GetCategories() and GetProducts() Web service methods work in a similar fashion. Both methods create DataReaders that represent a set of database records. Next, the DataReaders are converted into string arrays that can be consumed by the WebService behavior.
Securing an XML Web Service If you want to build a subscription Web service, you need some method of identifying your subscribers. Currently, there is no standard method of authenticating users of a Web
Advanced XML Web Services CHAPTER 23
1013
service. So, to identify them, you have to be creative. This section explores one approach to securing an XML Web service.
Overview of the Secure XML Web Service For this example, you’ll build a simple XML Web service that enables users to retrieve a number. This Web service will have one method named GetLuckyNumber(), which always returns the number 7. The goal is to prevent unauthorized users from successfully calling the GetLuckyNumber() method. One approach to preventing unauthorized access to the GetLuckyNumber() method would be to force every user to pass a username and password parameter whenever the method is called. However, if the Web service has multiple methods, this requirement could quickly become cumbersome. You would need to add the username and password parameters to each and every method. Though your approach will be to use a custom SOAP header to pass authentication information. You can pass a SOAP header as part of the SOAP packet being transmitted to the Web service and verify the authentication information in the SOAP header with every method call.
To protect the integrity of user passwords, you could use the Secure Sockets Layer (SSL) to encrypt every message sent to the Web service. However, using SSL has a significant impact on the performance of a Web server. It takes a lot of work to encrypt and decrypt each message. Instead, you need to add only one method to the Web service that requires SSL. Create a single method named Login() to accept a username and password and return a session key that is valid for 30 minutes. The session key is passed in the SOAP header to every other method call to authenticate the user. The advantage of this approach is that it is reasonably secure and does not have a significant impact on performance. The user needs to use SSL only when calling the Login() method. After the Login() method is called, only the session key, not the user password, is transmitted when calling other Web methods. If someone manages to steal the session key, the session key will be valid for less than 30 minutes. After 30 minutes elapse, the key will be useless.
ADVANCED XML WEB SERVICES
You could simply pass a username and password in the SOAP header. However, doing so would be dangerous. SOAP packets are transmitted as human-readable XML documents across the Internet. If you transmit a password in a SOAP header, the password could be intercepted and read by the wrong person.
23
1014
Buiding ASP.NET Web Services PART VI
Creating the Database Tables This secure XML Web service will use two Microsoft SQL Server database tables named WebServiceUsers and SessionKeys. The WebServiceUsers database table contains a list of valid usernames and passwords. You can create this database table by using the SQL CREATE TABLE statement contained in Listing 23.9. LISTING 23.9
CreateWebServiceUsers.sql
CREATE TABLE WebServiceUsers ( userid int IDENTITY NOT NULL , username varchar (20) NOT NULL , password varchar (20) NOT NULL , role int NOT NULL )
After you create the WebServiceUsers table, you should add at least one username and password to the table (for example, Steve and Secret). When a user logs in, a session key is added to the SessionKeys database table. The SQL CREATE TABLE statement for the SessionKeys table is contained in Listing 23.10. LISTING 23.10
CreateSessionKeys.sql
CREATE TABLE SessionKeys ( session_key uniqueidentifier ROWGUIDCOL session_expiration datetime NOT NULL , session_userID int NOT NULL , session_username varchar(20) NOT NULL , session_role int NOT NULL )
NOT NULL ,
Creating the Login() Method The Web service will have a method named Login() that users must access once to retrieve a valid session key. Because passwords are passed to the Login() method, this method should be accessed only through a secure channel (with https:// rather than http://). The complete code for the Login() method is contained in Listing 23.11. LISTING 23.11
The Login() Method
Public Function Login( username As String, password As String ) ➥ As ServiceTicket Dim conMyData As SqlConnection Dim cmdCheckPassword As SqlCommand
Advanced XML Web Services CHAPTER 23 LISTING 23.11 Dim Dim Dim Dim Dim
1015
continued
parmWork As SqlParameter intUserID As Integer intRole As Integer objServiceTicket As ServiceTicket drowSession As DataRow
‘ Initialize Sql command conMyData = New SqlConnection( “Server=localhost;UID=sa;pwd=secret; ➥database=myData” ) cmdCheckPassword = New SqlCommand( “CheckPassword”, conMyData ) cmdCheckPassword.CommandType = CommandType.StoredProcedure
‘ Execute the command conMyData.Open() cmdCheckPassword.ExecuteNonQuery() objServiceTicket = New ServiceTicket If cmdCheckPassword.Parameters( “@validuser” ).Value = 0 Then objServiceTicket.IsAuthenticated = True objServiceTicket.SessionKey = cmdCheckPassword.Parameters ➥( “@sessionkey” ).Value.ToString() objServiceTicket.Expiration = cmdCheckPassword.Parameters ➥( “@expiration” ).Value intUserID = cmdCheckPassword.Parameters( “@userID” ).Value intRole = cmdCheckPassword.Parameters( “@role” ).Value Else objServiceTicket.IsAuthenticated = False End If conMyData.Close()
23 ADVANCED XML WEB SERVICES
‘ Add parameters parmWork = cmdCheckPassword.Parameters.Add( _ New SqlParameter( “@validuser”, SqlDbType.Int ) ) parmWork.Direction = ParameterDirection.ReturnValue cmdCheckPassword.Parameters.Add( _ New SqlParameter( “@username”, username ) ) cmdCheckPassword.Parameters.Add( _ New SqlParameter( “@password”, password ) ) parmWork = cmdCheckPassword.Parameters.Add( _ New SqlParameter( “@sessionkey”, SqlDbType.UniqueIdentifier ) ) parmWork.Direction = ParameterDirection.Output parmWork = cmdCheckPassword.Parameters.Add( _ New SqlParameter( “@expiration”, SqlDbType.DateTime ) ) parmWork.Direction = ParameterDirection.Output parmWork = cmdCheckPassword.Parameters.Add( _ New SqlParameter( “@userID”, SqlDbType.Int ) ) parmWork.Direction = ParameterDirection.Output parmWork = cmdCheckPassword.Parameters.Add( _ New SqlParameter( “@role”, SqlDbType.Int ) ) parmWork.Direction = ParameterDirection.Output
1016
Buiding ASP.NET Web Services PART VI LISTING 23.11
continued
‘ Add session to cache If objServiceTicket.IsAuthenticated Then If Context.Cache( “SessionKeys” ) Is Nothing Then LoadSessionKeys End If drowSession = Context.Cache( “SessionKeys” ).NewRow() drowSession( “session_key” ) = objServiceTicket.SessionKey drowSession( “session_expiration” ) = objServiceTicket.Expiration drowSession( “session_userID” ) = intUserID drowSession( “session_username” ) = username drowSession( “Session_role” ) = intRole Context.Cache( “SessionKeys” ).Rows.Add( drowSession ) End If ‘ Return ServiceTicket Return objServiceTicket End Function
The Login() method does the following: • Executes a SQL stored procedure named CheckPassword • Adds the current session to the cache • Returns a valid session key The SQL CREATE Listing 23.12. LISTING 23.12
PROCEDURE
statement for the CheckPassword procedure is contained in
CreateCheckPassword.sql
Create Procedure CheckPassword ( @username varchar( 20 ), @password varchar( 20 ), @sessionkey uniqueidentifier Output, @expiration DateTime Output, @userID int Output, @role int Output ) As Select @userID = userid, @role = role From WebServiceUsers Where username = @username And password = @password
Advanced XML Web Services CHAPTER 23 LISTING 23.12
continued
If @userID Is Not Null Begin SET @sessionkey = NEWID() SET @expiration = DateAdd( mi, 30, GetDate() ) Insert SessionKeys ( session_key, session_expiration, session_userID, session_username, session_role ) values ( @sessionkey, @expiration, @userID, @username, @role ) End Else Return -1
The stored procedure also generates the expiration time for the session key. The function returns a time that is 30 minutes in the future.
DataAdd()
Both the GUID and expiration time are added as part of a new record to the SessionKeys database table, which tracks all the active current sessions. After the Login() method calls the CheckPassword stored procedure, the method constructs a new instance of the ServiceTicket class. The declaration for this class is contained in Listing 23.13. The ServiceTicket Class
Public Class ServiceTicket Public IsAuthenticated As Boolean Public SessionKey As String Public Expiration As DateTime End Class
23 ADVANCED XML WEB SERVICES
The CheckPassword stored procedure checks whether the username and password passed to the procedure exist in the WebServiceUsers table. If the username and password combination is valid, a new session key is generated. The SQL NEWID() function then generates a Globally Unique Identifier (GUID) to use as the session key.
LISTING 23.13
1017
1018
Buiding ASP.NET Web Services PART VI
The Login() method returns an instance of the ServiceTicket class, which contains the validated session key, session key expiration time, and a value indicating whether the user was successfully authenticated.
Retrieving the Custom SOAP Header The XML Web service uses a custom SOAP header to retrieve authentication information. The session key is passed in a SOAP header named AuthHeader. You create the actual SOAP header by using the class in Listing 23.14. LISTING 23.14
The SOAP Header
Public Class AuthHeader:Inherits SoapHeader Public SessionKey As String End Class
Notice that this class inherits from the SoapHeader class. It contains a single property named SessionKey that is used to store the session key. Note The SoapHeader class inhabits the System.Web.Services.Protocols namespace. You need to import this namespace before you can use the SoapHeader class.
To use the AuthHeader class in your Web service, you need to add a public property that represents the header. You declare a public property in your Web service class like this: Public AuthenticationHeader As AuthHeader
Finally, if you want to retrieve this header in any particular method, you must add the SoapHeader attribute to the method. The GetLuckyNumber() method in Listing 23.15 illustrates how to use this attribute. LISTING 23.15
The GetLuckyNumber() Method
_ Public Function GetLuckyNumber As Integer If Authenticate( AuthenticationHeader ) Then Return 7 End If End Function
Advanced XML Web Services CHAPTER 23
1019
The GetLuckyNumber() method simply passes AuthenticationHeader to a function named Authenticate(). This function is described in the next section.
Authenticating the Session Key Every time someone calls the GetLuckyNumber() method, you could check the user’s session key against the SessionKeys database table. However, with enough users, checking every session key would be slow. Instead, you can check the session key against a cached copy of the database table. The private Authenticate() function in Listing 23.16 checks the session key retrieved from the authentication header against a cached copy of the SessionKeys database table. LISTING 23.16
The Authenticate() Function
Private Function Authenticate( objAuthenticationHeader ) As Boolean Dim arrSessions As DataRow() Dim strMatch As String
‘ Test for match strMatch = “session_key=’” & objAuthenticationHeader.SessionKey strMatch &= “‘ And session_expiration > #” & DateTime.Now() & “#” arrSessions = Context.Cache( “SessionKeys” ).Select( strMatch ) If arrSessions.Length > 0 Then Return True Else Return False End If End Function End Class
Caching the Session Keys As I mentioned previously, the SessionKeys database table is cached in memory to improve performance. The table is cached with the LoadSessionKeys subroutine contained in Listing 23.17. LISTING 23.17
The LoadSessionKeys Subroutine
Private Sub LoadSessionKeys Dim conMyData As SqlConnection
23 ADVANCED XML WEB SERVICES
‘ Load Session keys If Context.Cache( “SessionKeys” ) Is Nothing Then LoadSessionKeys End If
1020
Buiding ASP.NET Web Services PART VI LISTING 23.17
continued
Dim dadMyData As SqlDataAdapter Dim dstSessionKeys As DataSet conMyData = New SqlConnection( “Server=localhost;UID=sa;PWD=secret; ➥database=myData” ) dadMyData = New SqlDataAdapter( “LoadSessionKeys”, conMyData ) dadMyData.SelectCommand.CommandType = CommandType.StoredProcedure dstSessionKeys = New DataSet dadMyData.Fill( dstSessionKeys, “SessionKeys” ) Context.Cache.Insert( _ “SessionKeys”, _ dstSessionKeys.Tables( “SessionKeys” ), _ Nothing, _ DateTime.Now.AddHours( 3 ), _ TimeSpan.Zero ) End Sub
The SessionKeys table is cached in memory for a maximum of three hours. The table is dumped every three hours to get rid of expired session keys. When the table is automatically reloaded into the cache, only fresh session keys are retrieved. The LoadSessionKeys subroutine uses the LoadSessionKeys SQL stored procedure. This procedure is included on the CD with the name CreateLoadSessionKeys.sql.
Building the Secure XML Web Service The complete code for the XML Web service described in this section is contained in Listing 23.18. You can test the Login() method by opening the SecureService.asmx page in a Web browser. However, you cannot test the GetLuckyNumber() method because it requires the custom SOAP authentication header. LISTING 23.18
SecureService.asmx
Imports Imports Imports Imports Imports
System System.Web.Services System.Web.Services.Protocols System.Data System.Data.SqlClient
_ Public Class SecureService : Inherits WebService Public AuthenticationHeader As AuthHeader
Advanced XML Web Services CHAPTER 23 LISTING 23.18
1021
continued
Public Function Login( username As String, password As String ) ➥ As ServiceTicket Dim Dim Dim Dim Dim Dim Dim
conMyData As SqlConnection cmdCheckPassword As SqlCommand parmWork As SqlParameter intUserID As Integer intRole As Integer objServiceTicket As ServiceTicket drowSession As DataRow
‘ Initialize Sql command conMyData = New SqlConnection( “Server=localhost;UID=sa;pwd=secret; ➥database=myData” ) cmdCheckPassword = New SqlCommand( “CheckPassword”, conMyData ) cmdCheckPassword.CommandType = CommandType.StoredProcedure
‘ Execute the command conMyData.Open() cmdCheckPassword.ExecuteNonQuery() objServiceTicket = New ServiceTicket If cmdCheckPassword.Parameters( “@validuser” ).Value = 0 Then objServiceTicket.IsAuthenticated = True objServiceTicket.SessionKey = cmdCheckPassword.Parameters ➥( “@sessionkey” ).Value.ToString() objServiceTicket.Expiration = cmdCheckPassword.Parameters ➥( “@expiration” ).Value intUserID = cmdCheckPassword.Parameters( “@userID” ).Value
23 ADVANCED XML WEB SERVICES
‘ Add parameters parmWork = cmdCheckPassword.Parameters.Add( _ New SqlParameter( “@validuser”, SqlDbType.Int ) ) parmWork.Direction = ParameterDirection.ReturnValue cmdCheckPassword.Parameters.Add( _ New SqlParameter( “@username”, username ) ) cmdCheckPassword.Parameters.Add( _ New SqlParameter( “@password”, password ) ) parmWork = cmdCheckPassword.Parameters.Add( _ New SqlParameter( “@sessionkey”, SqlDbType.UniqueIdentifier ) ) parmWork.Direction = ParameterDirection.Output parmWork = cmdCheckPassword.Parameters.Add( _ New SqlParameter( “@expiration”, SqlDbType.DateTime ) ) parmWork.Direction = ParameterDirection.Output parmWork = cmdCheckPassword.Parameters.Add( _ New SqlParameter( “@userID”, SqlDbType.Int ) ) parmWork.Direction = ParameterDirection.Output parmWork = cmdCheckPassword.Parameters.Add( _ New SqlParameter( “@role”, SqlDbType.Int ) ) parmWork.Direction = ParameterDirection.Output
1022
Buiding ASP.NET Web Services PART VI LISTING 23.18
continued
intRole = cmdCheckPassword.Parameters( “@role” ).Value Else objServiceTicket.IsAuthenticated = False End If conMyData.Close() ‘ Add session to cache If objServiceTicket.IsAuthenticated Then If Context.Cache( “SessionKeys” ) Is Nothing Then LoadSessionKeys End If drowSession = Context.Cache( “SessionKeys” ).NewRow() drowSession( “session_key” ) = objServiceTicket.SessionKey drowSession( “session_expiration” ) = objServiceTicket.Expiration drowSession( “session_userID” ) = intUserID drowSession( “session_username” ) = username drowSession( “Session_role” ) = intRole Context.Cache( “SessionKeys” ).Rows.Add( drowSession ) End If ‘ Return ServiceTicket Return objServiceTicket End Function _ Public Function GetLuckyNumber As Integer If Authenticate( AuthenticationHeader ) Then Return 7 End If End Function Private Sub LoadSessionKeys Dim conMyData As SqlConnection Dim dadMyData As SqlDataAdapter Dim dstSessionKeys As DataSet conMyData = New SqlConnection( “Server=localhost;UID=sa;PWD=secret; ➥database=myData” ) dadMyData = New SqlDataAdapter( “LoadSessionKeys”, conMyData ) dadMyData.SelectCommand.CommandType = CommandType.StoredProcedure dstSessionKeys = New DataSet dadMyData.Fill( dstSessionKeys, “SessionKeys” ) Context.Cache.Insert( _ “SessionKeys”, _ dstSessionKeys.Tables( “SessionKeys” ), _ Nothing, _ DateTime.Now.AddHours( 3 ), _ TimeSpan.Zero ) End Sub
Advanced XML Web Services CHAPTER 23 LISTING 23.18
1023
continued
Private Function Authenticate( objAuthenticationHeader ) As Boolean Dim arrSessions As DataRow() Dim strMatch As String ‘ Load Session keys If Context.Cache( “SessionKeys” ) Is Nothing Then LoadSessionKeys End If ‘ Test for match strMatch = “session_key=’” & objAuthenticationHeader.SessionKey strMatch &= “‘ And session_expiration > #” & DateTime.Now() & “#” arrSessions = Context.Cache( “SessionKeys” ).Select( strMatch ) If arrSessions.Length > 0 Then Return True Else Return False End If End Function End Class
Public Class ServiceTicket Public IsAuthenticated As Boolean Public SessionKey As String Public Expiration As DateTime End Class
Accessing the Secure Web Service Before you can access the SecureService Web service, you need to create a proxy class by executing the following two statements: wsdl /l:vb /n:services http://localhost/webservices/SecureService.asmx?wsdl vbc /t:library /r:System.dll,System.Web.Services.dll,System.xml.dll ➥ SecureService.vb
After you create the proxy class, remember to copy it into your application /bin directory. You can test the SecureService Web service by using the ASP.NET page in Listing 23.19. You’ll need to change the username and password to match the username and password that you entered into the WebServiceUsers table.
23 ADVANCED XML WEB SERVICES
Public Class AuthHeader:Inherits SoapHeader Public SessionKey As String End Class
1024
Buiding ASP.NET Web Services PART VI LISTING 23.19
TestSecureService.aspx
Const strUsername As String = “Steve” Const strPassword As String = “Secret” Sub Page_Load Dim objSecureService As SecureService Dim objServiceTicket As ServiceTicket Dim objAuthHeader As AuthHeader objSecureService = New SecureService objServiceTicket = Session( “ServiceTicket” ) ‘ Check for ticket existence If objServiceTicket Is Nothing Then objServiceTicket = objSecureService.Login( strUsername, strPassword ) Session( “ServiceTicket” ) = objServiceTicket End If ‘ Check for ticket expiration If objServiceTicket.Expiration < DateTime.Now Then objServiceTicket = objSecureService.Login( strUsername, strPassword ) Session( “ServiceTicket” ) = objServiceTicket End If ‘ Call the web service If objServiceTicket.IsAuthenticated Then objAuthHeader = New AuthHeader objAuthHeader.SessionKey = objServiceTicket.SessionKey objSecureService.AuthHeaderValue = objAuthHeader lblLuckyNumber.Text = objSecureService.GetLuckyNumber() Else lblLuckyNumber.Text = “Invalid username or password!” End If End Sub TestSecureService.aspx
In the Page_Load subroutine in Listing 23.19, an instance of the SecureService proxy class is created. Next, a ServiceTicket is retrieved by calling the Login() Web service method. The ServiceTicket is stored in session state so that it can be used multiple times. The subroutine checks whether the ServiceTicket has expired before trying to use it. If the Expiration property of the ServiceTicket class contains a time that has passed, the Login() method is called to retrieve a new ServiceTicket. The ServiceTicket contains a session key in its SessionKey property. The session key is used when constructing the authentication header. The authentication header is created and the GetLuckyNumber() method is called with the following statements:
The lucky number retrieved from the Web service is assigned to a label named lblLuckyNumber.
Using HTML Pattern Matching Using HTML pattern matching, you can make any Web site act like an XML Web service. HTML pattern matching enables you to extract content from any document by specifying regular expressions that match content in the document. Matched content is exposed as values of Web service properties. This technology has two broad applications. First, you can use it to access data from legacy systems. For example, if you have thousands of old customer invoices stored in an ancient pre-war mainframe in the back office, you can use HTML pattern matching to liberate the data. If a document is accessible over the network—regardless of whether the document is a plain text file, XML document, or HMTL page—you can use HTML pattern matching to expose it.
23 ADVANCED XML WEB SERVICES
objAuthHeader = New AuthHeader objAuthHeader.SessionKey = objServiceTicket.SessionKey objSecureService.AuthHeaderValue = objAuthHeader lblLuckyNumber.Text = objSecureService.GetLuckyNumber()
1026
Buiding ASP.NET Web Services PART VI
HTML pattern matching also can be used for communicating with Web sites that haven’t implemented a Web service. For example, suppose that you need to exchange product information over the Internet with a sister Web site. If the sister Web site displays a normal HTML page with a list of products, you can use HTML pattern matching to scrape the information from the page. To implement HTML pattern matching, you need to write a WSDL document that describes the content you want to expose. After you create the WSDL document, you generate a proxy class for the Web service described by the WSDL file. You can then integrate the proxy class into an ASP.NET application in the same way as you would for any other Web service.
Creating the WSDL Document A Web services Description Language (WSDL) document is an XML document that describes, among other things, all the methods and properties accessible through a Web service. It provides the name and location of each method and property. It also specifies the data types of all the parameters that can be used with the methods and properties. The hardest part of implementing HTML pattern matching is writing the WSDL document. You need to write this document to specify the text patterns that you want to match. Therefore, you need to spend a little time examining the structure of a WSDL document. Note You can find the current version of the WSDL document standard at the following address: http://www.w3.org/TR/wsdl
A WSDL document contains the following five sections: •
Types—Lists
all the data types used when exchanging messages.
•
Messages—Lists the name and all the parts of each message. Input elements are listed separately from output elements. Each message part is associated with a data type.
•
PortTypes—Lists
•
Bindings—Associates
•
Services—Associates
the operations that can be performed with messages.
the operations listed in PortTypes with a particular message format and protocol. a binding with a particular location (URL).
Advanced XML Web Services CHAPTER 23
1027
Each section of a WSDL document builds on the elements of the preceding section. For example, you specify all the messages in the Messages section and combine the messages into operations in the PortTypes section. To use HTML pattern matching, you add a regular expression pattern to the Bindings section. Any text matched by the regular expression is exposed as a property of the Web service. Note Regular expressions are described in Chapter 24, “Working with Collections and Strings.”
The file in Listing 23.20 is a template for a simple WSDL document that you can use for HTML pattern matching. All the parameters that you need to replace are displayed in bold. LISTING 23.20
Template.Wsdl
xmlns:s=”http://www.w3.org/2001/XMLSchema” xmlns:http=”http://schemas.xmlsoap.org/wsdl/http/” xmlns:mime=”http://schemas.xmlsoap.org/wsdl/mime/” xmlns:tm=”http://microsoft.com/wsdl/mime/textMatching/” xmlns:soap=”http://schemas.xmlsoap.org/wsdl/soap/” xmlns:soapenc=”http://schemas.xmlsoap.org/soap/encoding/” xmlns:s0=”http://yourdomain.com/webservices” targetNamespace=”http://yourdomain.com/webservices” xmlns=”http://schemas.xmlsoap.org/wsdl/”>
ADVANCED XML WEB SERVICES
If the BinarySearch method finds a match, it returns the index of the matched item in the ArrayList. Otherwise, it returns a negative number. By default, the BinarySearch method performs a case-sensitive search. If you need to perform a search that is not case sensitive, call the BinarySearch method like this: intItemIndex = colArrayList.BinarySearch( “Smith”, New CaseInsensitiveComparer )
This statement performs a binary search for Smith using an instance of CaseInsensitiveComparer. So, the statement matches both Smith and smith.
Using the HashTable Collection A HashTable, like an ArrayList, can be used to store a collection of items. However, a HashTable, unlike an ArrayList, is used to store key and value pairs.
Working with Collections and Strings CHAPTER 24
1055
The key and value pairs can represent just about anything you desire. For example, you can use the key and value pairs to represent product codes and product names. Or the keys could represent page numbers, and the values could represent the contents of a page. Each key in a HashTable must be unique. If you try to add two items with the same key, you receive an error. There’s a good reason for this unique key requirement. You can look up a key very quickly in a HashTable because of the manner in which it stores its items. Whenever you add an item to a HashTable, the HashTable calculates a hash code for the item’s key. The HashTable internally sorts the hash codes using different buckets. By dividing the hash codes into different buckets, it can retrieve a key very quickly. Note A hash code is a shorthand representation of a value. For example, you can use a hash code to represent text that contains thousands of characters with a relatively small, fixed-length number. Small differences in values result in different hash codes. So, two versions of the Declaration of Independence that differ in a single letter result in different hash codes.
You can create a new HashTable by using the following statement: colHashTable = New HashTable()
This statement creates a new HashTable named colHashTable.
colHashTable = New HashTable( 100 )
If you enter more than 100 items, the HashTable automatically expands to handle the greater number of items. Finally, you can create a HashTable with a particular load factor. The load factor determines the number of buckets to use when sorting the hash codes for a HashTable. (It specifies the maximum ratio of items to buckets.) By default, a HashTable is initialized
WORKING WITH COLLECTIONS AND STRINGS
You also have the option, when creating a HashTable, of specifying its initial capacity. For example, if you know that the HashTable will be used to hold at least 100 items, you can declare the HashTable like this:
24
1056
Leveraging the .Net Framework PART VII
with a load factor of 1.0. However, you can use a lower value, as shown here, if you want to use more buckets and perform faster searches: colHashTable = New HashTable( 100, 0.1 )
This statement initializes the HashTable with a capacity of 100 items and a load factor of 0.1 (the minimum load factor). The advantage of a lower load factor is faster searches. The disadvantage of using a lower load factor is that it requires more memory.
Adding Items to a HashTable You can add an item to a HashTable in two ways. First, you can use the Add method like this: colHashTable.Add( “WA”, “Washington” )
This statement adds a new key with the value “WA” and a new item with the value “Washington” to the HashTable named colHashTable. Alternatively, you can add a value to a HashTable like this: colHashTable( “WA” ) = “Washington”
If the key “WA” doesn’t already exist, it’s added with the value. If the key already exists, it’s assigned the new value. Remember, you cannot add duplicate keys to a HashTable. So, the following two statements would result in an error: colHashTable.Add( “CA”, “California” ) colHashTable.Add( “CA”, “California” )
However, there is nothing wrong with adding two items with the same value. The following two statements would not generate an error: colHashTable.Add( “CA”, “California” ) colHashTable.Add( “CALIF”, “California” )
Removing Items from a HashTable You use the Remove method to remove an item from a HashTable. For example, to remove an item with the key “WA”, you would use the following statement: colHashTable.Remove( “WA” )
Realize that the Remove method is, by default, case sensitive. So, this statement would not remove an item with the key “Wa”.
Working with Collections and Strings CHAPTER 24
1057
If you want to remove all the items in a HashTable, you can call the Clear method like this: colHashTable.Clear()
This statement removes all the items in the HashTable named colHashTable.
Iterating Through the Items in a HashTable Several methods can be used for displaying the items in a HashTable. You can use a For...Each loop like this, for example: Dim objItem As DictionaryEntry For each objItem in colHashTable Response.Write( “
counter = {0:n}”: Dim objStringBuilder As StringBuilder Dim intCounter As Integer objStringBuilder = New StringBuilder() For intCounter = 1 to 10 objStringBuilder.AppendFormat( “
counter = {0:n}”, intCounter ) Next Response.Write( objStringBuilder.ToString() )
This example outputs the following: counter counter counter counter counter counter counter
= = = = = = =
1.00 2.00 3.00 4.00 5.00 6.00 7.00
Working with Collections and Strings CHAPTER 24
1079
counter = 8.00 counter = 9.00 counter = 10.00
Note To learn more details about using format strings, see the section “Formatting Strings” earlier in this chapter.
Inserting and Removing Strings with a StringBuilder The StringBuilder Append method appends strings only at the end of a StringBuilder. If you need to add strings to other parts of a StringBuilder, you should use the Insert method. The following example adds a string to the middle of a StringBuilder: Dim objStringBuilder As StringBuilder objStringBuilder = New StringBuilder( “The slow-moving aardvark” ) objStringBuilder.Insert( 4, “happy turtle and the “ ) Response.Write( objStringBuilder.ToString() )
In this example, the Insert method adds a string at the fourth index position. The StringBuilder displays the string The happy turtle and the slow-moving aardvark.
Dim objStringBuilder As StringBuilder objStringBuilder = New StringBuilder( “A very long sentence” ) objStringBuilder.Insert( 2, “very, “, 100 ) Response.Write( objStringBuilder.ToString() )
If you need to perform the opposite operation and remove characters from a StringBuilder, you can use the Remove method. The following example removes characters from a StringBuilder starting at position 2 and continuing for five characters:
24 WORKING WITH COLLECTIONS AND STRINGS
You also can use the Insert method to add a string multiple times. For example, the following example adds the word very to the StringBuilder 100 times starting at the second index position:
1080
Leveraging the .Net Framework PART VII Dim objStringBuilder As StringBuilder objStringBuilder = New StringBuilder( “A very long sentence” ) objStringBuilder.Remove( 2, 5 ) Response.Write( objStringBuilder.ToString() )
This example outputs the string A
long sentence.
Replacing Strings with a StringBuilder If you need to replace one string with another in a StringBuilder, you can use the Replace method like this: Dim objStringBuilder As StringBuilder objStringBuilder = New StringBuilder( “The slow-moving aardvark went to ➥ the store” ) objStringBuilder.Replace( “aardvark”, “turtle” ) Response.Write( objStringBuilder.ToString() )
This example replaces the string aardvark with the string turtle. The Replace method automatically replaces each and every occurrence of the target string.
Iterating Through the Contents of a StringBuilder You can march through the contents of a StringBuilder, character by character, by using the Chars and Length properties. The Chars property represents a character at a certain index position, whereas the Length property represents the total number of characters in a StringBuilder. The following example uses a For...Next loop to loop through each character in a StringBuilder and report whether the character is a digit: Dim objStringBuilder As StringBuilder Dim chrChar As Char Dim intCounter As Integer objStringBuilder = New StringBuilder( “549-27-7878” ) For intCounter = 0 To objStringBuilder.Length - 1 chrChar = objStringBuilder.Chars( intCounter ) If Char.IsDigit( chrChar ) Then Response.Write( String.Format( “
{0} is a digit”, chrChar ) ) End If Next
Working with Collections and Strings CHAPTER 24
1081
This For...Next loop loops through each character in the StringBuilder. If the character is a digit, this fact is reported with a Response.Write statement. Note The IsDigit method is a member of the .NET Char structure, which includes other methods such as IsWhiteSpace and IsLetter.
You can modify a character represented by the Chars property. The following example converts an uppercase string to a lowercase string: Dim objStringBuilder As StringBuilder Dim chrChar As Char Dim intCounter As Integer objStringBuilder = New StringBuilder( “ALL IN UPPERCASE!” ) For intCounter = 0 to objStringBuilder.Length - 1 chrChar = objStringBuilder.Chars( intCounter ) chrChar = Char.ToLower( chrChar ) objStringBuilder.Chars( intCounter ) = chrChar Next Response.Write( objStringBuilder.ToString() )
Here, each character is retrieved from the StringBuilder and converted to lowercase using the ToLower method of the Char structure.
Converting a StringBuilder to a String
You can use a special variation of the ToString method with the StringBuilder class. Because a StringBuilder can be used to represent gargantuan strings, you might need to display partial strings from a StringBuilder. You can pass a beginning index and length to the ToString method like this: Dim objStringBuilder As StringBuilder objStringBuilder = New StringBuilder() objStringBuilder.Insert( 0, “aardvark,”, 8999 ) Response.Write( objStringBuilder.ToString( 18, 8 ) )
24 WORKING WITH COLLECTIONS AND STRINGS
You can display the contents of a StringBuilder by using the ToString method. Because it is a method of the Object class (the grandfather of all objects), it works with any class in the .NET framework.
1082
Leveraging the .Net Framework PART VII
This example creates a StringBuilder that contains the word aardvark 8,999 times. The ToString method displays eight characters from the StringBuilder, starting at the character with an index of 18.
Working with Regular Expressions You can use regular expressions to perform very complex pattern matching. You’ll quickly discover that you need to make use of regular expressions within a number of different contexts when building ASP.NET applications. In Chapter 3, “Performing Form Validation with Validation Controls,” for example, you learned how to use the Validation controls to validate HTML form data. One of the Validation controls, RegularExpressionValidator, depends on regular expressions. You can use regular expressions with RegularExpressionValidator to validate e-mail addresses, Social Security numbers, and Internet addresses, for example. With a proper understanding of how regular expressions work, you can validate any sequence of characters and numbers with RegularExpressionValidator and the proper regular expressions. You also can use regular expressions to perform complex formatting operations. You can use regular expressions not only to match expressions, but also to replace expressions. For example, imagine that you need to convert a plain text document into an HTML document that you can display at your Web site. You can automatically insert the correct HTML tags into the document by taking advantage of regular expressions. In the following sections, you learn about the implementation of regular expressions in the .NET framework. You also learn how to use regular expressions to both match and replace expressions.
Using Regular Expression Classes You use three main classes in the .NET framework when working with regular expressions: RegEx, Match, and MatchCollection. All three classes can be found in the System.Text.RegularExpressions namespace. The RegEx class represents a regular expression. You initialize it with a regular expression like this: Dim objRegEx As RegEx objRegEx = New RegEx( “b[iao]t” )
This example initializes a new RegEx class named objRegEx with the regular expression “b[iao]t”. This regular expression matches the strings bit, bat, and bot.
Working with Collections and Strings CHAPTER 24
1083
After you initialize an instance of the RegEx class, you can check whether a string matches a regular expression by calling the RegEx IsMatch method: Dim objRegEx As RegEx objRegEx = New RegEx( “b[iao]t” ) If objRegEx.IsMatch( “bat” ) Then Response.Write( “It’s a Match!” ) End If
In this example, the IsMatch method returns the value True because the string bat matches the regular expression b[iao]t. Alternatively, you can use an instance of the RegEx class to match and replace parts of a string. For example, the following instance of the RegEx class replaces all vowels in a string with question marks: Dim objRegEx As RegEx Dim strString As String objRegEx = New RegEx( “[aeiou]” ) strString = “The slow-moving aardvark” Response.Write( objRegEx.Replace( strString, “?” ) )
In this case, the Replace method returns the string Th?
sl?w-m?v?ng ??rdv?rk.
If you need additional information about the expression matched by the RegEx class, you can return an instance of the Match class: Dim objRegEx As RegEx Dim objMatch As Match
Response.Write( “
Success: “ & objMatch.Success ) Response.Write( “
Index: “ & objMatch.Index ) Response.Write( “
Value: “ & objMatch.Value )
In this example, the Match method returns an instance of the Match class. If a match is found, the Success property returns the value True, the Index property returns the position of the match, and the Value property returns the matching expression. A regular expression can match a string in multiple places. If multiple matches are found, you can use the MatchCollection class to represent all the matches: Dim objRegEx As RegEx Dim objMatch As Match Dim objMatchCollection As MatchCollection
24 WORKING WITH COLLECTIONS AND STRINGS
objRegEx = New RegEx( “b[iao]t” ) objMatch = objRegEx.Match( “The bat flew in the window” )
1084
Leveraging the .Net Framework PART VII objRegEx = New RegEx( “[aeiou]” ) objMatchCollection = objRegEx.Matches( “The bat flew in the window” ) For Each objMatch in objMatchCollection Response.Write( “
Success: “ & objMatch.Success ) Response.Write( “
Index: “ & objMatch.Index ) Response.Write( “
Value: “ & objMatch.Value ) Next
In this example, the Matches method returns an instance of the MatchCollection class. A For...Each loop displays the information from each match represented by the MatchCollection class. To make it easier for you to follow the discussion of regular expressions in this section, I’ve included the page in Listing 24.9 so that you can test a regular expression (see Figure 24.2). FIGURE 24.2 Testing regular expressions.
LISTING 24.9
TestRegEx.aspx
Sub Button_Click( s As Object, e As EventArgs ) Dim objRegEx As RegEx objRegEx = New RegEx( txtRegularExpression.Text ) dgrdMatches.DataSource = objRegEx.Matches( txtMatchExpression.Text ) dgrdMatches.DataBind() End Sub
Working with Collections and Strings CHAPTER 24 LISTING 24.9
1085
continued
TestRegEx.aspx
24 WORKING WITH COLLECTIONS AND STRINGS
Regular Expression:
Text:
Value
1086
Leveraging the .Net Framework PART VII LISTING 24.9
continued
The page in Listing 24.9 contains a form with two TextBox controls. You can enter a regular expression in the first TextBox control and some text in the second TextBox control. When you click the Button control, the Button_Click subroutine executes. The Button_Click subroutine evaluates the regular expression and text on the form and binds the results to a DataGrid control, which displays all the properties of each Match class that match the regular expression.
Examining Elements of a Regular Expression A regular expression typically contains both literal text and metacharacters. Literal text, as you might assume, is nothing more than plain, ordinary text. You can place literal text in a regular expression to perform an exact character-by-character match. For example, the regular expression let matches any occurrence of the characters let in a string. The regular expression matches the word let, the first part of the word letter, and the last part of the word hamlet. Regular expressions get interesting when you start using metacharacters. Metacharacters enable you to match much more than simple sequences of characters. For example, you can use metacharacters to match only words, only words that start with the letter P, or words that appear at the beginning or end of a line of text.
Matching Single Characters You can use metacharacters to define character classes. A character class is a set of characters that you can use to match a single character. Imagine that you want to match every instance of the word affect in a string, but you know that people often use the word effect to mean the same thing. You can use the following regular expression to match either word: [ae]ffect
Working with Collections and Strings CHAPTER 24
1087
The brackets define a character class. You can place multiple characters within brackets to represent any one of the multiple characters. The characters do not have to be letters. The following regular expression matches any single punctuation symbol: [!?.,]
You also can specify a range of characters in a character class. For example, the following character class matches any single uppercase character: [A-Z]
You can also use a range with numerals:
This regular expression would match and , but not . A character class matches only a single character at a time. Note To match a hyphen in a character class, list the hyphen as the first character.
You can include multiple character classes in a single regular expression. For example, the following regular expression matches a time of the form 12:59pm: [01][0-9]:[0-5][0-9][ap]m
This regular expression matches 03:59am and 12:22pm but does not match 3:59am.
m[^a]t
This regular expression matches mit and met, but not mat.
Matching Special Characters You can use the following special characters in a regular expression: •
\t—Matches
a tab character
•
\r—Matches
a carriage return character
WORKING WITH COLLECTIONS AND STRINGS
You also can use a caret (^) within a character class to exclude characters. For example, the following regular expression fails to match words that contain the letter a in their second position:
24
1088
Leveraging the .Net Framework PART VII
•
\f—Matches
a form-feed character
•
\n—Matches
a newline character
You can use the following code, for example, to replace newline characters with HTML characters: Dim objRegEx As RegEx Dim strString As String strString = “The slow-moving aardvark.” & vbNewline strString &= “The happy turtle.” objRegEx = New RegEx( “\n” ) Response.Write( objRegEx.Replace( strString, “” ) )
The following list describes metacharacters that represent character classes: •
.—Matches
any character except \n (or any character in singleline mode)
•
\w—Matches
any word character (any letter or numeral)
•
\W—Matches
any nonword character (anything except letters or numerals)
•
\s—Matches any whitespace character (including spaces, newlines, tabs, and so on)
•
\S—Matches any nonwhitespace characters (anything except spaces, newlines, tabs, and so on)
•
\d—Matches
any digit character (the numerals 0 through 9)
•
\D—Matches
any nondigit character (anything except the numerals 0 to 9)
The dot character (.) is particularly useful. You can use it to represent any single character. Consider the following regular expression: 12.25.99
This regular expression matches various data formats, including 12/25/99, 12-25-99, 12 25 99, and even 12.25.99. The following metacharacters do not represent characters, but represent the position of characters in a string: •
^—Matches
the beginning of a string (or beginning of a line in multiline mode)
•
$—Matches
the end of a string or the last character before \n at the end of a string (or the end of a line in multiline mode)
•
\A—Matches
•
\Z—Matches
the beginning of a string (even in multiline mode)
the end of a string or the last character before \n at the end of a string (even in multiline mode)
Working with Collections and Strings CHAPTER 24
•
\z—Matches
the end of a string
•
\G—Matches
the beginning of the current search
•
\b—Matches
a word boundary
•
\B—Matches
nonword boundaries
1089
You can use the \b metacharacter, for example, to match word boundaries. So, if you want to match the word let, but not letter or hamlet, you can use the following: \blet\b
The \A and \z metacharacters are useful for guaranteeing that a string contains a certain expression and nothing else. For example, you might want to check whether a Text control contains the word secret without any extraneous characters, newline characters, or whitespace. You can check for the word secret by using the following regular expression: \Asecret\z
This regular expression matches an expression that starts at the beginning of a string and ends at the end of the string. Note The list of metacharacters in this section is not exhaustive. See the .NET Framework SDK Documentation for a complete list of metacharacters.
\.
The backslash works as an escape character. You can use it with a metacharacter to represent the character’s literal value.
Matching Alternative Sequences of Characters The character classes and metacharacters you have examined thus far enable you to represent alternative characters but not alternative sequences of characters. For example, imagine that you want to match either a
or tag in a string. To match either sequence of characters, you need to use the alternation metacharacter | like this:
|
24 WORKING WITH COLLECTIONS AND STRINGS
One additional metacharacter warrants discussion in this section. Because metacharacters, such as the dot character (.), have a special meaning, you can’t use them to represent a literal character. To represent a period, you need to place a backslash before it like this:
1090
Leveraging the .Net Framework PART VII
When using the alternation character, you often need to group characters with parentheses. For example, the following regular expression matches both color and colour: col(o|ou)r
You also can use parentheses to control the precedence of other metacharacters. If you need to find every occurrence of the name Bill or Ted starting on a word boundary, you can use the following: \b(bill|ted)
Notice that the preceding regular expression means something different than the following one: \bbill|ted
This latter example matches the word malted because the \b metacharacter applies only to bill.
Matching with Quantifiers You can use quantifiers to match a sequence of characters a certain number of times. The following list describes the quantifiers that you can use with regular expressions: •
*—Matches
zero or more times
•
+—Matches
one or more times
•
?—Matches
zero or one time
•
{n}—Matches
•
{n,}—Matches
•
{n,m}—Matches
exactly n times at least n times at least n times and at most m times
Suppose that you want to match both the words aardvark and aardvarks. You can use the ? quantifier to indicate that the s is optional like this: aardvarks?
Or, imagine that you know that all product codes in your company start with the letter p and contain between three and five numerals. In that case, you can match a product code by using the following regular expression: \bp\d{3,5}\b
Working with Collections and Strings CHAPTER 24
1091
In this example, the \b checks whether the product code starts at a word boundary, the \d{3,5} indicates a string of between three and five digits, and the \b represents the end of the word boundary. You also can use quantifiers with parentheses, as follows, so that the quantifiers apply to a whole sequence of letters: The( very)? slow-moving aardvark
This regular expression matches both “The slow-moving aardvark”.
very slow-moving aardvark”
and “The
Identifying Regular Expressions and Greed Some of the quantifiers are greedy. They match the characters that you intend for them to match, but they keep matching additional characters. The * quantifier, for example, matches zero or more characters. Imagine that you want to match any HTML tag in a string. You might be tempted to match HTML tags with the following regular expression:
This regular expression reads: Match any character that appears zero or more times between a < and >. This seems like a perfect regular expression for hunting down HTML tags. Now, imagine that you applied this regular expression to the following string: A quantifier can be greedy.
To handle this problem, you need to use a special nongreedy modifier with the quantifier: So, to fix the regular expression in the preceding example, you would use the following:
?.
24 WORKING WITH COLLECTIONS AND STRINGS
You might expect the regular expression to match the tags , , , and . Unfortunately, the * quantifier gets too ambitious and matches the whole expression quantifier can be greedy. The * quantifier matches the i that follows the first and every other character that follows until it reaches the final > at the end of .
1092
Leveraging the .Net Framework PART VII
The ? forces the * quantifier to be nongreedy. This regular expression correctly matches , , , and . The ? forces the quantifier to match as few characters as possible. You can use the ? modifier with several of the quantifiers: •
*?—Nongreedy ?
quantifier
•
+?—Nongreedy +
quantifier
•
??—Nongreedy ?
quantifier
•
{n}?—Nongreedy {n}
•
{n,}?—Nongreedy {n,}
•
{n,m}?—Nongreedy {n,m}
quantifier quantifier quantifier
Capturing and Backreferences Capture groups work like variables in a regular expression. A capture group enables you to capture a pattern of characters in a regular expression and refer to the pattern by either a number or name later in the regular expression. You indicate a capture group in a regular expression by using parentheses. For example, the following regular expression contains a capture group with the characters aardvark: The slow-moving (aardvark)
After you capture an expression, you can refer to it later in the regular expression by using a backreference. For example, the expression \1 is used as a backreference in the following regular expression: The slow-moving (aardvark) \1
This regular expression matches a string that contains The aardvark.
slow-moving aardvark
Why are capture groups useful? You can use a capture group to match repeating patterns of characters. For example, imagine that you want to match any string that contains the same two letters in a row. You can match repeating characters by using the following regular expression: (\w)\1
The parentheses in this regular expression capture any single word character. Later in the regular expression, the \1 backreference refers to the captured expression.
Working with Collections and Strings CHAPTER 24
1093
If you apply this regular expression to The happy aardvark, the regular expression would match both the pp in happy and aa in aardvark. Note Parentheses can be used for a number of different functions in a regular expression. For example, you use parentheses with alternation to group alternative sequences of characters. By default, whenever you use parentheses, the characters contained within the parentheses are captured. You can disable this default behavior by using the n option (discussed in the section “Setting Regular Expression Options”) or by adding ?: to the parentheses like this: (?:aardvark)|(?:turtle)
This regular expression does not capture the characters aardvark or turtle.
You can capture multiple groups of characters in a regular expression by using multiple sets of parentheses. You can refer to each captured group by using \1, \2, \3, and so on. For example, the following regular expression matches any four letters in a row when the last two letters are reversed copies of the first two letters: (\w)(\w)\2\1
When applied to the following sentence, this regular expression matches the ette in letter: The aardvark sent a letter
(?\w)\k
The construct (?Grocery List size=3>eggs size=3>milk size=3>pancake mix
Setting Regular Expression Options You can set a number of options when working with regular expressions. These options can be found in the RegexOptions enumeration in the System.Text.RegularExpressions namespace. These options are as follows: •
IgnoreCase—Performs
matches that are not case sensitive (uses the option i within a regular expression)
•
Multiline—Indicates multiline mode (uses the option m within a regular expression)
24 WORKING WITH COLLECTIONS AND STRINGS
0 Then intRanIndex = objRandom.Next( Items.Count ) objSelectedItem = CType( Items( intRanIndex ), RotatorItem3 ) objTextWriter.Write( objSelectedItem.Text ) End If End Sub End Class ‘ Declare Rotator Item Public Class RotatorItem3 : Inherits Control Public Text As String End Class End Namespace
In Listing 28.15, you create a new class named SimpleRotator3ControlBuilder. The SimpleRotator3ControlBuilder class inherits from the .NET ControlBuilder class.
Finally, notice that you add a custom attribute to the declaration of the SimpleRotator3 class. You declare the class like this: _ Public Class SimpleRotator3: Inherits Control
The ControlBuilderAttribute indicates that the SimpleRotator3ControlBuilder class should be used as the Control Builder for the SimpleRotator3 control, rather than the default Control Builder. In other words, this attribute associates the SimpleRotator3ControlBuilder class with the SimpleRotator3 control. After you make these modifications to SimpleRotator—and compile and copy the control to the application’s /bin directory—you can use simplified syntax when declaring the control. The ASP.NET page in Listing 28.16 illustrates how you can declare the new control.
DEVELOPING CUSTOM CONTROLS
Within the SimpleRotator3ControlBuilder class, you override the GetChildControlType method and map the tag RotatorItem3 to the type RotatorItem3. Because you map the abbreviated tag to the type, you no longer need to fully declare the RotatorItem controls when using the SimpleRotator control.
28
1234
Building Custom ASP.NET Controls PART VIII LISTING 28.16
DisplaySimpleRotator3.aspx
DisplaySimpleRotator3.aspx
In Listing 28.16, notice that you can declare each RotatorItem without using the myControls tag prefix or the Runat=”Server” attribute. Note The company Superexpert produces a content rotator control that you can download from www.superexpertControls.com. The Superexpert Content Rotator control supports different item priorities, templates, data binding, and XML files.
Examining Custom Controls and Events Every control has the following six standard events: •
DataBinding—This
event is typically raised when the DataBind method is called.
•
Disposed—This event occurs when the control is released from memory. This is a good place to close expensive resources, such as database connections.
•
Init—This
event occurs when the control is first initialized.
•
Load—This
event occurs when the control is loaded into the Page object.
•
PreRender—This
•
Unload—This
event occurs right before the control’s Render method is called.
event occurs when the control is unloaded from memory.
Developing Custom Controls CHAPTER 28
1235
The control in Listing 28.17, for example, illustrates how the Init, Load, and PreRender events can be handled. LISTING 28.17
ControlEvents.vb
Imports System Imports System.Web Imports System.Web.UI Namespace myControls Public Class ControlEvents Inherits Control Protected Overrides Sub OnInit( e As EventArgs ) Context.Response.Write( “
When the BlueTextBoxChanged control is declared in Listing 28.25, the BlueTextBoxChanged_TextChanged subroutine is associated with the TextChanged event. If you submit the form containing the control and the contents of the control have been modified, the TextChanged event triggers the BlueTextBoxChanged_TextChanged subroutine.
Handling Postback Events One last topic not yet examined concerns postbacks. In the preceding section, you learned how to repopulate a text box after a form is submitted. However, I have not discussed a method for actually posting the data from a form. You can use three standard ASP.NET controls to post a form: Button, ImageButton, and LinkButton. All three controls generate client-side JavaScript that is executed when you submit a form. In this section, you learn how to create your own form element that generates the necessary JavaScript to raise a postback event. To handle postback events from a custom control, you must implement the IPostBackEvent interface. This interface contains a definition for a single method: the RaisePostBack event. The control in Listing 28.26 illustrates how you can implement this method in a custom control. LISTING 28.26 Imports Imports Imports Imports
TextBoxColor.vb
System System.Web System.Web.UI System.Collections.Specialized
Namespace myControls Public Class TextBoxColor Inherits Control
Developing Custom Controls CHAPTER 28 LISTING 28.26
1245
continued
Implements IPostBackDataHandler Implements IPostBackEventHandler Public BoxColor As String = “Blue” Public Text As String Public Sub RaisePostBackEvent(EventArgument As String) _ Implements IPostBackEventHandler.RaisePostBackEvent If (eventArgument = “Red” ) Me.BoxColor = “Red” Else Me.BoxColor = “Blue” End If End Sub Public Function LoadPostData(PostDataKey As String, ➥Values As NameValueCollection) As Boolean _ Implements IPostBackDataHandler.LoadPostData Dim strNewValue As String
Public Sub RaisePostDataChangedEvent() _ Implements IPostBackDataHandler.RaisePostDataChangedEvent ‘ Raise Change Event End Sub Protected Overrides Sub Render( objTextWriter As HtmlTextWriter ) objTextWriter.AddAttribute( “Name”, Me.UniqueID ) objTextWriter.AddStyleAttribute( “background-color”, BoxColor ) objTextWriter.AddStyleAttribute( “color”, “Yellow” ) objTextWriter.AddAttribute( “value”, Text ) objTextWriter.RenderBeginTag( “input” ) objTextWriter.RenderEndTag objTextWriter.WriteLine( “
” ) objTextWriter.AddAttribute( “Type”, “Button” ) objTextWriter.AddAttribute( “Value”, “Display Red!” ) objTextWriter.AddAttribute( “OnClick”, “JScript:” & ➥Page.GetPostBackEventReference( Me, “Red” ) ) objTextWriter.RenderBeginTag( “Input” ) objTextWriter.RenderEndTag() objTextWriter.WriteLine( “ ” ) objTextWriter.AddAttribute( “Type”, “Button” ) objTextWriter.AddAttribute( “Value”, “Display Blue!” ) objTextWriter.AddAttribute( “OnClick”, “JScript:” & ➥Page.GetPostBackEventReference( Me, “Blue” ) )
28 DEVELOPING CUSTOM CONTROLS
Text = Values( Me.UniqueID ) Return False End Function
1246
Building Custom ASP.NET Controls PART VIII LISTING 28.26
continued
objTextWriter.RenderBeginTag( “Input” ) objTextWriter.RenderEndTag() End Sub End Class End Namespace
The control in Listing 28.26 displays a text box and two buttons. When you click the button labeled Display Red!, the background color of the text box changes to red. When you click the button labeled Display Blue!, the background color changes to blue. Both buttons are rendered within the control’s Render method. They are created with an attribute that has a JScript function as its value. For example, the OnClick attribute for the Display Red! button is declared like this: OnClick
objTextWriter.AddAttribute( “OnClick”, “JScript:” & ➥Page.GetPostBackEventReference( Me, “Red” ) )
The GetPostBackEventReference method returns a reference to the JavaScript function on the client; this reference causes the form to be posted back to the server. The first parameter passed to the method represents the control that will process the postback event on the server. In this case, you use the Visual Basic keyword Me, which refers to the current control. The second parameter is optional. It’s an argument passed back to the server during postback. In this case, you pass back the argument “Red” so that you can detect that the Display Red! button was clicked. When the Display
Red!
button is rendered, the following code is sent to the browser:
The Display
button triggers the client-side JavaScript function named Notice that the unique ID of the control and parameter are passed by the
Red!
__doPostBack.
function. You can view the source of the __doPostBack function by selecting View Source on your browser. In case you’re curious, the __doPostBack function looks like this: function __doPostBack(eventTarget, eventArgument) { var theform = document.ctrl1 theform.__EVENTTARGET.value = eventTarget theform.__EVENTARGUMENT.value = eventArgument theform.submit() }
Developing Custom Controls CHAPTER 28
1247
The __doPostBack function initializes two arguments and submits the form back to the server. When the form is posted back to the server, the custom control’s RaisePostBack method is executed. This method looks like this: Public Sub RaisePostBackEvent(EventArgument As String) _ Implements IPostBackEventHandler.RaisePostBackEvent If (eventArgument = “Red” ) Me.BoxColor = “Red” Else Me.BoxColor = “Blue” End If End Sub
This method detects whether Display Red! or Display Blue! was clicked. If the Display Red! button was clicked, the BoxColor property is assigned the value Red. Otherwise, the property is assigned the value Blue. When the control renders the text box in the control’s Render method, it uses the value of the BoxColor property when setting the background color of the text box. You can test the TextBoxColor control by using the ASP.NET page in Listing 28.27. DisplayTextBoxColor.aspx
DisplayTextBoxColor.aspx
Creating Composite Controls To this point, you’ve built custom controls from scratch. However, in some cases, creating a new control out of existing controls is much easier. For example, imagine that you want to create a custom control that displays a login form. You want the control to
DEVELOPING CUSTOM CONTROLS
LISTING 28.27
28
1248
Building Custom ASP.NET Controls PART VIII
display both username and password text boxes. This control is a good candidate for a composite control. Listing 28.28 contains the code for a composite Login control. LISTING 28.28 Imports Imports Imports Imports
Login.vb
System System.Web System.Web.UI System.Web.UI.WebControls
Namespace myControls Public Class Login Inherits Control Implements INamingContainer Public Property Username As String Get Me.EnsureChildControls() Return CType( Controls( 2 ), TextBox ).Text End Get Set Me.EnsureChildControls() CType( Controls( 2 ), TextBox ).Text = Value End Set End Property Public Property Password As String Get Me.EnsureChildControls() Return CType( Controls( 5 ), TextBox ).Text End Get Set Me.EnsureChildControls() CType( Controls( 5 ), TextBox ).Text = Value End Set End Property
Protected Overrides Sub CreateChildControls() Me.Controls.Add( New LiteralControl( “” ) ) ‘ Add Username Me.Controls.Add( New LiteralControl( “Username: “ ) ) Me.Controls.Add( New TextBox ) Me.Controls.Add( New LiteralControl( “
” ) )
Developing Custom Controls CHAPTER 28 LISTING 28.28
1249
continued
‘ Add Password Dim txtPass As New TextBox Me.Controls.Add( New LiteralControl( “Password: “ ) ) txtPass.TextMode = TextBoxMode.Password Me.Controls.Add( txtPass ) Me.Controls.Add( New LiteralControl( “
” ) ) ‘ Add Submit Button Dim btnButton As New Button btnButton.Text = “Login!” Me.Controls.Add( btnButton ) Me.Controls.Add( New LiteralControl( “” ) ) End Sub End Class End Namespace
The bulk of Listing 28.28 is contained in the CreateChildControls subroutine. This subroutine adds username TextBox, password TextBox, and Button controls to the Controls collection of the Login control.
The current values of the username and password TextBox controls are exposed through two properties: Username and Password. Because these properties are declared with the Public modifier, you can read and set them from within an ASP.NET page. There’s one final thing you should notice about Listing 28.28. The control implements the INamingContainer interface, which does not define any methods that you must implement. This interface creates a new ID namespace within a page’s control hierarchy; it prevents naming conflicts when the Login control is instantiated more than once. You should always implement the interface when building compositional controls. The page in Listing 28.29 illustrates how you can use the Login control within an ASP.NET page. LISTING 28.29
DisplayLogin.aspx
DEVELOPING CUSTOM CONTROLS
Notice that the Login control does not contain a Render method. It isn’t necessary because all the rendering is taken care of by the child controls. The child controls even automatically handle preserving view state. If you enter a username in the username control, the username is preserved between form posts.
28
1250
Building Custom ASP.NET Controls PART VIII LISTING 28.29
continued
Sub Page_Load If IsPostBack Then lblMessage.Text = “Hi “ & ctrlLogin.Username lblMessage.Text &= “, your password is: “ lblMessage.Text &= ctrlLogin.Password End If End Sub DisplayLogin.aspx
The page in Listing 28.29 displays the values of the Username and Password properties of the Login control. When the form containing the Login control is submitted, the values of these properties are displayed in a Label control (see Figure 28.3).
Handling Events in a Composite Control In the preceding section, you created a control that displays a simple login form. You can use the Login control to submit a username and password combination. In this section, you learn how to extend the Login control to handle events, such as the button Click event that occurs when the login form is submitted. The trick to handling events in a composite control is to use the Visual Basic AddHandler statement, which enables you to associate a subroutine with an event. Listing 28.30 demonstrates how you can use the AddHandler statement with the Login control to associate a subroutine with the button Click event.
Developing Custom Controls CHAPTER 28
1251
FIGURE 28.3 Displaying the Login control.
28 LISTING 28.30
System System.Web System.Web.UI System.Web.UI.WebControls
Namespace myControls Public Class LoginEvent Inherits Control Implements INamingContainer Sub CheckPassword( s As Object, e As EventArgs ) Dim strUsername, strPassword As String Dim lblLabel As Label strUsername = CTYPE( Controls( 2 ), TextBox ).Text strPassword = CTYPE( Controls( 5 ), TextBox ).Text lblLabel = CTYPE( Controls( 9 ), Label ) If strUsername = “joe” and strPassword = “secret” Then lblLabel.Text = “Welcome Joe!” Else lblLabel.Text = “Invalid Password!” End If End Sub
DEVELOPING CUSTOM CONTROLS
Imports Imports Imports Imports
LoginEvent.vb
1252
Building Custom ASP.NET Controls PART VIII LISTING 28.30
continued
Protected Overrides Sub CreateChildControls() Me.Controls.Add( New LiteralControl( “” ) ) ‘ Add Username Me.Controls.Add( New LiteralControl( “Username: “ ) ) Me.Controls.Add( New TextBox ) Me.Controls.Add( New LiteralControl( “
” ) ) ‘ Add Password Dim txtPass As New TextBox Me.Controls.Add( New LiteralControl( “Password: “ ) ) txtPass.TextMode = TextBoxMode.Password Me.Controls.Add( txtPass ) Me.Controls.Add( New LiteralControl( “
” ) ) ‘ Add Submit Button Dim btnButton As New Button btnButton.Text = “Login!” AddHandler btnButton.Click, AddressOf CheckPassword Me.Controls.Add( btnButton ) Me.Controls.Add( New LiteralControl( “” ) ) ‘ Add Label Control Dim lblLabel As New Label lblLabel.EnableViewState = False Me.Controls.Add( lblLabel ) End Sub End Class End Namespace
In Listing 28.30, the AddHandler statement associates the login form’s button Click event with a subroutine named CheckPassword. The CheckPassword subroutine compares the username and password entered into the form to the username joe and password secret. If you enter joe and secret, the string Welcome Joe! is assigned to a Label control. Otherwise, the string Invalid Password! is assigned to the Label control. You can test the new Login control, named LoginEvent, by using the page in Listing 28.31.
Developing Custom Controls CHAPTER 28 LISTING 28.31
1253
DisplayLoginEvent.aspx
DisplayLoginEvent.aspx
Inheriting from Existing Controls All the controls in this chapter are inherited from the base Control class. However, in some situations, inheriting from a different control makes more sense. You can inherit from any of the HTML or Web controls in the ASP.NET framework.
LISTING 28.32 Imports Imports Imports Imports
myWebControl.vb
System System.Web System.Web.UI System.Web.UI.WebControls
Namespace myControls Public Class myWebControl Inherits WebControl Overrides Protected Sub RenderContents( objTextWriter As HtmlTextWriter ) objTextWriter.Write( “Hello World!” ) End Sub End Class End Namespace
DEVELOPING CUSTOM CONTROLS
If you inherit from the WebControl class, for example, you can take advantage of all the formatting properties available in that class. The control in Listing 28.32 illustrates how you can inherit from the WebControl class.
28
1254
Building Custom ASP.NET Controls PART VIII
The control in Listing 28.32 inherits from the WebControl class. Notice that content is rendered within the RenderContents method instead of the standard Render method. By overriding the RenderContents method, you can take advantage of the WebControl class’ built-in support for formatting. The ASP.NET page in Listing 28.33, for example, displays the output of the control in a bold red font with a yellow background.
myWebControl
LISTING 28.33
DisplayWebControl.aspx
DisplayWebControl.aspx
Accessing the Current Context You can access application and session state, server variables, Response and Request objects, and security information through the Context property. For example, the following control displays the values of the UserHostAddress and UserAgent properties retrieved from the Request object. LISTING 28.34
ControlContext.vb
Imports System Imports System.Web Imports System.Web.UI Namespace myControls Public Class ControlContext Inherits Control
Developing Custom Controls CHAPTER 28 LISTING 28.34
1255
continued
Overrides Protected Sub Render( objTextWriter As HtmlTextWriter ) Dim strHostAddress As String Dim strUserAgent As String strHostAddress = Context.Request.UserHostAddress strUserAgent = Context.Request.UserAgent objTextWriter.WriteLine( “
If you click the Reload Page! button, the page is reloaded. However, DataBoundState continues to display all the data from the data source. The values of all the items are preserved in view state.
Binding a Custom Control to a DataSet You can bind all the standard ASP.NET controls to a number of different types of data sources. For example, you can bind a Repeater control to an ArrayList like this: colArrayList = New ArrayList colArrayList.Add( “Hello World!” ) rptRepeater.DataSource = colArrayList rptRepeater.DataBind()
29 ADVANCED CONTROL DEVELOPMENT
The IsPostBack property in the Page_Load subroutine in Listing 29.12 detects whether this is the first time the page has been loaded. The data source is bound to the control only when IsPostBack has the value False.
1280
Building Custom ASP.NET Controls PART VIII
You also can bind a Repeater to a DataReader like this: rptRepeater.DataSource = dtrDataReader rptRepeater.DataBind()
You can bind a Repeater to a DataView like this: rptRepeater.DataSource = dstDataSet.Tables( “Products” ).DefaultView() rptRepeater.DataBind()
You can bind a Repeater directly to a DataSet like this: rptRepeater.DataSource = dstDataSet rptRepeater.DataBind()
If a DataSet contains multiple tables, you can select a particular table to bind to like this: rptRepeater.DataSource = dstDataSet rptRepeater.DataMember = “titles” rptRepeater.DataBind()
To create a custom control that works like the standard ASP.NET controls, you need to support all these very different data-binding options. In this section, you learn how to add a function to a custom control that enables it to bind to any standard data source. The custom control, named DataBoundDataSet, is contained in Listing 29.13. LISTING 29.13 Imports Imports Imports Imports Imports
DataBoundDataSet.aspx
System System.Web System.Web.UI System.Collections System.Data
Namespace myControls _ Public Class DataBoundDataSet Inherits Control Private _dataSource As Object Private _dataMember As String = String.Empty Private _itemTemplate As ITemplate Public Property DataSource As Object Get Return _dataSource End Get Set _dataSource = Value End Set End Property
Advanced Control Development CHAPTER 29 LISTING 29.13
1281
continued
Public Property DataMember As String Get Return _dataMember End Get Set _dataMember = Value End Set End Property Private Function GetDataSource( DataSource, DataMember ) As IEnumerable If TypeOf DataSource Is IEnumerable Then Return DataSource Else If TypeOf DataSource Is DataSet Then If DataMember String.Empty Then Return DataSource.Tables( DataMember ).DefaultView Else Return DataSource.Tables( 0 ).DefaultView End If Else Throw New ArgumentException( “Invalid data source!” ) End If End Function Protected Overrides Sub OnDataBinding( e As EventArgs ) Dim objDataEnum As IEnumerator Dim objItem As DataBoundDataSetItem Dim intCounter As Integer If Not DataSource Is Nothing Controls.Clear() ClearChildViewState() objDataEnum = GetDataSource( _dataSource, _dataMember ).GetEnumerator()
Protected Overrides Sub CreateChildControls() Dim objNumItems As Object Dim intItemCount As Integer
ADVANCED CONTROL DEVELOPMENT
While ( objDataEnum.MoveNext() ) objItem = New DataBoundDataSetItem( objDataEnum.Current ) ItemTemplate.InstantiateIn( objItem ) Controls.Add( objItem ) intCounter += 1 End While ViewState( “NumItems” ) = intCounter ChildControlsCreated = True End If End Sub
29
1282
Building Custom ASP.NET Controls PART VIII LISTING 29.13
continued
Dim intCounter As Integer Dim objItem As DataBoundDataSetItem objNumItems = ViewState( “NumItems” ) If Not objNumItems = Nothing Then Controls.Clear() intItemCount = CInt( objNumItems) For intCounter = 0 To intItemCount - 1 objItem = New DataBoundDataSetItem( Nothing ) ItemTemplate.InstantiateIn( objItem ) Controls.Add( objItem ) Next End If End Sub _ Public Property ItemTemplate As ITemplate Get Return _itemTemplate End Get Set _itemTemplate = value End Set End Property End Class Public Class DataBoundDataSetItem Inherits Control Implements INamingContainer Private _DataItem As Object Public Sub New( DataItem As Object ) MyBase.New() _DataItem = DataItem End Sub Public ReadOnly Property DataItem As Object Get Return _DataItem End Get End Property End Class End Namespace
Advanced Control Development CHAPTER 29
1283
Listing 29.13 contains a private function named GetDataSource that has two parameters: the data source and a data member. The function does the best it can to return an instance of a data source that supports the IEnumerable interface based on these parameters. If you pass an ArrayList or DataView to the function, the function simply returns the data source cast as IEnumerable. If you pass a DataSet to the function, the function does its best to return a DataTable from the DataSet. For example, if you pass a DataSet without providing a data member parameter, the function returns the first DataTable in the DataSet. Note The standard Microsoft data binding controls, such as the Repeater and DataGrid controls, use a private, undocumented function named GetResolvedDataSource. This function is a member of the private DataSourceHelper class. Microsoft’s GetResolvedDataSource function is slightly more sophisticated than our GetDataSource function. It uses methods of the IListSource interface to retrieve an enumerator from a DataSet.
Since the GetDataSource function explicitly refers to the DataSet class, you need to compile the control with the following statement: vbc /t:library /r:System.dll,System.Web.dll,System.Data.dll, ➥System.Xml.dll DataBoundDataSet.vb
You need to add a reference to both the System.Data.dll and System.Xml.dll assemblies when working with DataSets in a control.
ADVANCED CONTROL DEVELOPMENT
The page in Listing 29.14 illustrates how you can bind a number of different types of data sources to a custom control that uses the GetDataSource function (see Figure 29.2).
29
1284
Building Custom ASP.NET Controls PART VIII
FIGURE 29.2 Binding to multiple data sources.
LISTING 29.14
DisplayDataBoundDataSet.aspx
Sub Page_Load If Not IsPostBack Then Dim conPubs As SqlConnection Dim dadAdapter As SqlDataAdapter Dim dstDataSet As DataSet Dim cmdSelect As SqlCommand Dim colArrayList As ArrayList conPubs = New SqlConnection( “Server=LocalHost;UID=sa;PWD=secret;Database=Pubs” ) conPubs.Open() ‘ Create a dataset with 2 DataTables dadAdapter = New SqlDataAdapter( “Select top 3 * From Titles”, conPubs ) dstDataSet = New DataSet() dadAdapter.Fill( dstDataSet, “Titles” ) dadAdapter.SelectCommand = New SqlCommand( “Select top 3 * From Authors”, conPubs ) dadAdapter.Fill( dstDataSet, “Authors” )
Advanced Control Development CHAPTER 29 LISTING 29.14
1285
continued
‘ Bind with DataSet and no DataMember ctrlDataBound1.DataSource = dstDataSet ctrlDataBound1.DataBind() ‘ Bind with DataSet and explicit DataMember ctrlDataBound2.DataSource = dstDataSet ctrlDataBound2.DataMember = “Authors” ctrlDataBound2.DataBind() ‘ Bind to DataReader cmdSelect = New SqlCommand( “Select top 3 * From Titles”, conPubs ) ctrlDataBound3.DataSource = cmdSelect.ExecuteReader() ctrlDataBound3.DataBind() ‘ Bind to ArrayList colArrayList = New ArrayList colArrayList.Add( “Milk” ) colArrayList.Add( “Toast” ) ctrlDataBound4.DataSource = colArrayList ctrlDataBound4.DataBind() conPubs.Close End If End Sub DisplayDataBoundDataSet.aspx
DataSet with DataMember
” ) Next End Sub
1290
Building Custom ASP.NET Controls PART VIII
Note To learn more information about data caching, see Chapter 17, “Caching ASP.NET Applications.”
Within the RenderContents method, the control attempts to retrieve the list of featured products from the cache. If the products are not present in the cache, the list of products is retrieved with the getDataSet function. The getDataSet function calls the Web Service’s getFeatured method and returns the of products retrieved from the Web Service.
DataSet
The remainder of the RenderContents method is devoted to displaying each item in the list of products. A FOR...EACH loop steps through each row in the Products data table. The contents of the ProductName and UnitPrice columns are output with the HtmlTextWriter. To compile the control in Listing 29.16, you need to execute the following statement from the command line (within the same directory as the source for the control): vbc /t:library /r:System.dll,System.Web.dll,System.Data.dll, ➥System.Web.Services.dll,System.XML.dll,FeaturedService.dll ➥ FeaturedControl.vb
Notice that you must include several references to assemblies. When you’re compiling the control, to use the DataSet in it, you must refer to the System.Data assembly. You also must refer to the System.Web.Services, System.XML, and FeaturedService assemblies to use the FeaturedService Web Service.
Displaying the Featured Products Control After you compile and copy the FeaturedControl control to the application’s /bin directory, you can use it in an ASP.NET page. The page in Listing 29.17 uses the control to list the featured products from the Web Service (see Figure 29.3). LISTING 29.17
DisplayFeatured.aspx
DisplayFeatured.aspx
Advanced Control Development CHAPTER 29 LISTING 29.17
1291
continued
Featured Products:
FIGURE 29.3 Output of the FeaturedControl.
29
Summary This chapter tackled three advanced topics in custom control development. In the first section, you learned how to add templates to a control to format its output.
ADVANCED CONTROL DEVELOPMENT
Notice that you can format the output of the control by setting the value of the Font-Name, Font-Size, and ForeColor properties. Because the FeaturedControl control inherits from the WebControl class, you can use all the formatting properties of the WebControl class when displaying the control.
1292
Building Custom ASP.NET Controls PART VIII
Next, you explored the topic of controls and data binding. You learned how to implement data binding in a control to enable it to be bound to such data sources as ArrayLists, DataReaders, DataViews, and DataSets. Finally, you learned how to create a custom control that works with a Web Service. You created a control that retrieves and displays a list of featured products from a Web Service.
Sample ASP.NET Applications
PART
IX IN THIS PART 30 Creating a Job Site
1295
31 Creating an Online Store
1321
CHAPTER 30
Creating a Job Site
IN THIS CHAPTER • Installing the Job Site
1297
• Creating the Home Page
1298
• Authenticating Users
1301
• Creating Vanity URLs
1307
• Listing and Updating Jobs • Creating the Jobs XML Web Service 1312
1310
1296
Sample ASP.NET Applications PART IX
In this chapter, you’ll build a job site from start to finish with ASP.NET. You’ll create the ASP.NET job site; a public Web site for posting and finding ASP.NET-related jobs and resumes. Building this site provides you with an opportunity to see how the different technologies in the ASP.NET Framework work together. The ASP.NET job site illustrates several advanced features of the ASP.NET framework including • Vanity URLs—All registered users of the job site have their own personalized home pages. For example, if Mark Smith adds his resume to the job site, anyone can view his resume by going to /aspnetjobs/marksmith.aspx. Mark Smith, and no one else, can update his resume by going to his home page. • XML Web Services—The job site includes two custom controls, named NewJobs and NewResumes, which display the most recent jobs and resumes added to the site. Both custom controls use an XML Web Service to retrieve current listings from the job site. By providing these custom controls to affiliate Web sites, you can drive traffic to the job site. • Forms Authentication—The job site uses Forms authentication to authenticate users. When a new user registers, the Forms Authentication module automatically adds an Authentication Ticket to the user’s browser to identify the user. • Form Validation—Several of the pages included in the job site use Validation controls to validate form information before it is added to a database table. • Caching—To improve performance, the job site caches data between requests. For example, the list of new jobs and resumes displayed on the home page is cached until a new job or resume is added. The Jobs Web service also uses caching to improve performance. • User Controls—To simplify development, all the pages in the job site are built with user controls. For example, the standard header and footer for each page are created with a header and footer user control. Users of the job site can publicly post resumes, descriptions of their companies, and jobs. If a user is interested in a job, the user can submit an application. If an employer is interested in a resume, the employer can submit an enquiry to the owner of the resume. Employers can visit their company home page or their job pages to view enquiries about their company or view applications for particular jobs. Individuals searching for jobs can visit their home pages to update their resume or view enquires about their resumes.
Creating a Job Site CHAPTER 30
1297
New job and resume listings are displayed in two places. First, they are automatically displayed on the home page of the job site. They are also displayed by any affiliate Web sites that contain the NewJobs or NewResumes custom controls. The complete code for the job site is included on the CD-ROM that accompanies this book. Because of the number of files included in the job site, I won’t be able to discuss each file in detail. In the following sections, you’ll learn how some of the more interesting features of the job site are implemented. Note You can also view the job site online by visiting: http://superexpert.com/aspnetunleashed/aspnetjobs/
Installing the Job Site To install the job site, you need to complete the following two steps: 1. Create a virtual directory named aspnetjobs. 2. Execute the InstallAspNETJobsDatabase.sql script to install all the necessary database objects. First, copy all the files from the CD-ROM to a directory on your computer. Next, launch Internet Services Manager and create a new virtual directory named aspnetjobs that points to the directory where you copied the files. Next, install all the tables and stored procedures used by the job site. An installation script for the database objects is included on the CD-ROM that accompanies this book. Follow these steps: 1. Launch Microsoft SQL Server Query Analyzer. Make sure you login using the sa account. 2. Open the InstallAspNETJobsDatabase.sql script in Query Analyzer. 3. Execute the script (click the green VCR Run button) in Query Analyzer.
30 CREATING A JOB SITE
The script creates a new database named AspNETJobs. It automatically adds all the necessary database tables and stored procedures. Finally, it adds a new login named AspNETJobsUser with the password secret.
1298
Sample ASP.NET Applications PART IX
Caution The InstallAspNETJobsDatabase.sql script assumes that you have your SQL Server database configured to use SQL Server authentication. If you have only Windows authentication enabled, you need to manually add a login that has access to the AspNETJobs database. You’ll also need to modify the Web.Config file to use the proper login.
After you complete these steps, you can request the home page of the job site application by entering the following URL in your browser: http://localhost/aspnetjobs
Note If you encounter problems when executing the InstallAspNETJobsDatabase.sql script, try stopping and starting your database server to clear away any existing connections.
Creating the Home Page You can start exploring the ASP.NET job site with the home page. The main function of this page is to display a list of the newest jobs and resumes posted to the site and to display links for posting new jobs and resumes (see Figure 30.1). The complete code for the home page is included in Listing 30.1. LISTING 30.1
Default.aspx
Creating a Job Site CHAPTER 30 LISTING 30.1 1299 continued |
Login Username: Password:
Automatically remember me | Register Are you a new user?
It’s free and it only takes a minute. |
The page in Listing 30.13 contains a Register page directive to register the NewListings custom controls for the current page. After the controls are registered, they can be used exactly like any of the standard ASP.NET controls. In Listing 30.13, the PageSize property of both the NewJobs and NewResumes controls are set to display a maximum of 10 listings.
Summary In this chapter, you built the ASP.NET job site. This site illustrates many of the advanced features of ASP.NET. For example, the job site illustrates how to use Forms authentication to password-protect pages and identify users, how to implement vanity URLs to create customized home pages, how to cache data between page requests, and how to structure pages with user controls. You also learned how to build an XML Web Service and custom controls to implement an affiliate program. You created the JobService Web Service to expose a list of recent jobs and resumes posted to the site. You also created the NewJobs and NewResumes controls, which consume the JobService Web Service, to distribute to affiliate Web sites.
CHAPTER 31
Creating an Online Store
IN THIS CHAPTER • Overview of the ASP.NET Unleashed Sample Store 1322 • Installing the ASP.NET Unleashed Sample Store 1324 • Building the Navigation System • Caching the Product Data
1325
1328
• Building the Shopping Cart • Dynamically Loading Product Templates 1337
1330
1322
Sample ASP.NET Applications PART IX
In this final chapter, you learn how to build the ASP.NET Unleashed Sample Store. The ASP.NET Unleashed Sample Store illustrates a number of advanced features of ASP.NET: • Dynamically loaded user controls—The store that you build in this chapter takes advantage of user controls to display product information. You’ll use user controls to create product templates so that you can display product information with different page layouts. • A database-driven shopping cart—The shopping cart that you’ll build for your store makes use of several advanced features of ADO.NET. The shopping cart uses a DataSet and a DataAdapter to automatically synchronize its contents with a SQL Server database table. • Complex interaction between ASP.NET objects—Your online store will be built out of a wide variety of objects from the ASP.NET Framework, such as custom business components, user controls, a module, and ASP.NET pages. In the course of this chapter, you’ll learn some tricks to make it easier to exchange information between all these objects. Note You can view the store described in this chapter online by visiting: http://superexpert.com/aspnetunleashed/aspnetstore/
Overview of the ASP.NET Unleashed Sample Store Before getting into the details of building the store, you’ll benefit from a quick tour of the structure of the application. The ASP.NET Unleashed Sample Store contains the following six ASP.NET pages (all these pages can be found in the root directory of the application): •
Default.aspx—The
homepage of the store.
•
Category.aspx—Displays
•
Product.aspx—Displays
•
CheckOut.aspx—A page that confirms whether a user wants to check out of the store and purchase the items in the shopping cart.
links to all the products in a category.
information on a particular product.
Creating an Online Store CHAPTER 31 PlaceOrder.aspx—The final check out page that contains a form for billing and shipping information.
•
ThankYou.aspx—A
page that thanks the user for purchasing items at the Web site.
You navigate between the pages in the store by using a tab control. Each tab on the tab control corresponds to a product category (see Figure 31.1). When you click a tab, you arrive at the Category.aspx page. From the Category.aspx page, you can link directly to different product descriptions. All product descriptions are displayed by the Product. aspx page. FIGURE 31.1 Navigating with the tab control.
The Default.aspx, Category.aspx, and Product.aspx pages all contain a shopping cart within their left panel. The shopping cart includes a link for checking out of the store. When you click this link, you arrive at the CheckOut.aspx page. From the CheckOut. aspx page, you link to the PlaceOrder.aspx page and ultimately to the ThankYou.aspx page. All the ASP.NET pages (except the ThankYou.aspx page) use several SQL Server database tables. The application relies on the following database tables: •
Products—Contains
the information for all the products displayed at the Web site
•
Categories—Contains
•
ShoppingCarts—Contains
a list of the product categories all the user shopping carts
31 CREATING AN ONLINE STORE
•
1323
1324
Sample ASP.NET Applications PART IX
•
OrderDetails—When
a user finalizes a purchase, all the items in the user’s shopping cart are transferred to this table.
•
Orders—Contains the billing and shipping information that the user submits from the PlaceOrder.aspx page.
The ASP.NET Unleashed Sample Store also uses three user controls for common user interface elements. All these controls can be found in the UserControls subdirectory: •
Tabs.ascx—A
simple tab control that enables you to navigate from one page to another at the store.
•
ShoppingCart.ascx—The
•
Address.ascx—A
user interface portion of the shopping cart.
user control used for the shipping and billing address forms within the PlaceOrder.aspx page.
Finally, the store makes use of one assembly named StoreComponents. The source code for this assembly can be found in the Components subdirectory. The StoreComponents assembly contains three components: •
CachedData—This
component represents all the cached product and category information. The component automatically retrieves all the products and product categories from SQL Server and stores this information in the Cache object.
•
ProductTemplate—A code-behind class for the product template user controls. All the product template user controls inherit from this class.
•
ShoppingCart—The
guts of the shopping cart can be found in this class. This class handles adding and removing items from the shopping cart. It also takes care of automatically updating the ShoppingCarts database table when changes are made to the items in the shopping cart.
In the next section, you learn how to install these files on your server so that you can experiment with the ASP.NET Unleashed Sample Store.
Installing the ASP.NET Unleashed Sample Store The complete source code for the sample store is included on the CD that accompanies this book. Three steps are required to install the sample store: 1. Create a new virtual directory named aspnetstore. 2. Execute the CreateSQLObjects.sql installation script to create all the necessary tables and stored procedures to use the store.
Creating an Online Store CHAPTER 31
3. Modify the Web.Config file so that it contains a valid connection string to your database.
The next step is to generate the necessary SQL database tables and stored procedures. Open the CreateSQLObjects.sql script from the CD that accompanies this book and execute the script from the SQL Query Analyzer. The script builds the Categories, Products, OrderDetails, and Orders tables. It also copies the contents of the Categories and Products tables from the Northwind database to the new Categories and Products tables. Caution The ASP.NET Unleashed Sample Store uses the Categories and Products tables from the Northwind database table. If you have uninstalled this database from your server, the CreateSQLObjects.sql script won’t work.
The final installation step is to modify the Web.Config file. The Web.Config file contains the SQL Server connection string used by all the ASP.NET pages and components. You must change the connection string to one that will work for your installation of SQL Server.
Building the Navigation System Users navigate the ASP.NET Unleashed Sample Store with the help of a tabs user control. The tabs user control displays a tab for each product category in the Categories database table. The complete code for the tabs control is contained in Listing 31.1. LISTING 31.1
Tabs.ascx
Sub Page_Load Dim intTabIndex intTabIndex = Context.Items( “Category” ) dlstTabs.SelectedIndex = intTabIndex
31 CREATING AN ONLINE STORE
After you copy the directory that contains all the files for the sample store to your computer, you’ll need to create a virtual directory that points to it. This virtual directory must be named aspnetstore.
1325
1326
Sample ASP.NET Applications PART IX LISTING 31.1
continued
dlstTabs.DataSource = CachedData.GetCategories() dlstTabs.DataBind() End Sub
The bulk of the tabs control consists of a single DataList control. A DataList control is perfect for displaying a tab list because a DataList can automatically display content in a horizontal HTML table. The tabs control is bound to its data source in the Page_Load subroutine. The records for the Categories table are retrieved through the GetCategories() method of the CachedData component. Because the GetCategories() method caches the Categories table in memory, you can use the tabs control without worrying about its impact on the performance of your Web site. When you click a tab, you are always brought to the Category.aspx file. You should notice that a cat query string parameter is also sent. This parameter represents the currently selected tab. The value of the cat query string variable is automatically retrieved at the start of each page request within the Global.asax file. The Global.asax file used for the store is contained in Listing 31.2. LISTING 31.2
Global.asax
Creating an Online Store CHAPTER 31 LISTING 31.2
continued
1327
31
‘ Get current category index If Request.QueryString( “cat” ) “” Then intCategory = Request.QueryString( “cat” ) End If ‘ Store Category in Context Context.Items( “Category” ) = intCategory ‘ Get current product ID If Request.QueryString( “pid” ) “” Then intProductID = Request.QueryString( “pid” ) objProductInfo = CachedData.GetProductRow( intProductID ) Context.Items( “ProductInfo” ) = objProductInfo Context.Items( “ProductName” ) = objProductInfo( “ProductName” ) Context.Items( “UnitPrice” ) = objProductInfo( “UnitPrice” ) End If ‘ Store ProductID in Context Context.Items( “ProductID” ) = intProductID End Sub
The BeginRequest event is raised right before a page request is processed. In the Global.asax file in Listing 31.2, the cat query string variable is retrieved. Next, it is added to the Items collection of the HttpContext object. What’s the purpose of the HttpContext Items collection? You should think of it as a universal portal. You can use the HttpContext Items collection to pass objects from the Global.asax to an Active Server Page or even from the Global.asax file to a user control or business component. The HttpContext Items collection is accessible wherever you go. The current Category is added to the HttpContext Items collection in Listing 31.2 for the benefit of the Category.aspx page. The Category.aspx page retrieves the current Category to display the proper set of products when you click a tab.
CREATING AN ONLINE STORE
Sub Application_BeginRequest( s As Object, e As EventArgs ) Dim intCategory As Integer = -1 Dim intProductID As Integer = -1 Dim objProductInfo As DataRowView
1328
Sample ASP.NET Applications PART IX
Caching the Product Data The tabs user control discussed in the previous section does not retrieve a list of categories from the Categories database table every time it is displayed. Instead, a cached copy of the data is used. The same thing is true in the case of the Products database table. For performance reasons, when product information is displayed in the Products.aspx page, cached information from the Products database table is displayed. All the caching in the ASP.NET store happens in a custom component located within the StoreComponents assembly. The source code for this component is contained in Listing 31.3. LISTING 31.3
CachedData Class
‘’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’ ‘ Contains shared methods for retrieving ‘ cached database data ‘’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’ Public Class CachedData Public Shared Function GetCategories() As DataView If HttpContext.Current.Cache( “Categories” ) Is Nothing Then HttpContext.Current.Cache( “Categories” ) = GetCategoriesFromDB End If Return HttpContext.Current.Cache( “Categories” ) End Function Public Shared Function GetCategoryDescription( ➥intCategory As Integer ) As String If HttpContext.Current.Cache( “Categories” ) Is Nothing Then HttpContext.Current.Cache( “Categories” ) = GetCategoriesFromDB End If Return HttpContext.Current.Cache( “Categories” ).Item( intCategory ) ➥( “Description” ) End Function Private Shared Function GetCategoriesFromDB() As DataView Dim strConString As String Dim conMyData As SqlConnection Dim strSelect As String Dim dadCategories As SqlDataAdapter Dim dstCategories As DataSet
Creating an Online Store CHAPTER 31 LISTING 31.3
continued
Public Shared Function GetProducts( intCategoryIndex As Integer ) As DataView Dim intCategoryID As Integer Dim dvwProducts As DataView dvwProducts = HttpContext.Current.Cache( “Products” ) If dvwProducts Is Nothing Then dvwProducts = GetProductsFromDB HttpContext.Current.Cache( “Products” ) = dvwProducts End If If HttpContext.Current.Cache( “Categories” ) Is Nothing Then HttpContext.Current.Cache( “Categories” ) = GetCategoriesFromDB End If intCategoryID = HttpContext.Current.Cache( “Categories” ).Item( ➥intCategoryIndex )( “CategoryID” ) dvwProducts.RowFilter = “CategoryID=” & intCategoryID Return dvwProducts End Function Public Shared Function GetProductTemplate( intProductID As Integer ) As String Dim intProductIndex As Integer If HttpContext.Current.Cache( “Products” ) Is Nothing Then HttpContext.Current.Cache( “Products” ) = GetProductsFromDB End If HttpContext.Current.Cache( “Products” ).RowFilter = “” intProductIndex = HttpContext.Current.Cache( “Products” ).Find( ➥intProductID ) Return HttpContext.Current.Cache( “Products” ).Item( ➥intProductIndex )( “Template” ) End Function Public Shared Function GetProductRow( intProductID As Integer ) As DataRowView Dim intProductIndex As Integer If HttpContext.Current.Cache( “Products” ) Is Nothing Then HttpContext.Current.Cache( “Products” ) = GetProductsFromDB End If
31 CREATING AN ONLINE STORE
strConString = ConfigurationSettings.AppSettings( “connectionString” ) conMyData = New SqlConnection( strConString ) strSelect = “Select CategoryID,CategoryName,Description From Categories” dadCategories = New SqlDataAdapter( strSelect, conMyData ) dstCategories = New DataSet dadCategories.Fill( dstCategories, “Categories” ) Return dstCategories.Tables( “Categories” ).DefaultView End Function
1329
1330
Sample ASP.NET Applications PART IX LISTING 31.3
continued
HttpContext.Current.Cache( “Products” ).RowFilter = “” intProductIndex = HttpContext.Current.Cache( “Products” ).Find( ➥intProductID ) Return HttpContext.Current.Cache( “Products” ).Item( intProductIndex ) End Function Private Shared Function GetProductsFromDB() As DataView Dim strConString As String Dim conMyData As SqlConnection Dim strSelect As String Dim dadProducts As SqlDataAdapter Dim dstProducts As DataSet strConString = ConfigurationSettings.AppSettings( “connectionString” ) conMyData = New SqlConnection( strConString ) strSelect = “Select * From Products” dadProducts = New SqlDataAdapter( strSelect, conMyData ) dstProducts = New DataSet dadProducts.Fill( dstProducts, “Products” ) dstProducts.Tables( “Products” ).DefaultView.Sort = “ProductID” Return dstProducts.Tables( “Products” ).DefaultView End Function End Class
Most of the methods in the CachedData class work by attempting to retrieve data from the cache first. If that doesn’t work, the information is retrieved from the original database and cached for the next request. For example, if the GetCategories() method can’t retrieve the categories from the the method immediately calls the GetCategoriesFromDb function.
Cache,
Building the Shopping Cart The shopping cart for the store appears within the Default.aspx, Category.aspx, and Product.aspx pages. For example, Figure 31.2 displays what the shopping cart looks like in the Products page. The shopping cart actually has two parts. The user interface portion of the shopping cart is implemented as a user control. The source code for the ShoppingCart.ascx user control is contained in Listing 31.4.
Creating an Online Store CHAPTER 31
31
FIGURE 31.2
CREATING AN ONLINE STORE
The shopping cart.
LISTING 31.4
ShoppingCart.ascx
Dim Dim Dim Dim Dim
1331
intProductID As Integer intCategory As Integer strProductName As String decUnitPrice As Decimal objShoppingCart As ShoppingCart
Sub Page_Load intProductID = Context.Items( “ProductID” ) intCategory = Context.Items( “Category” ) strProductName = Context.Items( “ProductName” ) decUnitPrice = Context.Items( “UnitPrice” ) objShoppingCart = New ShoppingCart If Not IsPostBack Then If intProductID = -1 Then btnAdd.Visible = False End If BindDataGrid End If End Sub
1332
Sample ASP.NET Applications PART IX LISTING 31.4
continued
Sub Page_PreRender If objShoppingCart.Items.Count = 0 Then btnCheckOut.Visible = False Else btnCheckOut.Visible = True btnCheckOut.NavigateUrl = String.Format( _ “/aspnetstore/CheckOut.aspx?cat={0}”, intCategory ) End If End Sub
Sub btnAdd_Click( s As Object, e As EventArgs ) objShoppingCart.Add( intProductID, strProductName, decUnitPrice ) BindDataGrid End Sub Sub LinkButton_Click( s As Object, e As DataGridCommandEventArgs ) objShoppingCart.Remove( e.Item.ItemIndex ) BindDataGrid End Sub Sub BindDataGrid dgrdShoppingCart.DataSource = objShoppingCart.Items dgrdShoppingCart.DataBind() End Sub
Your Shopping Cart |
Total: |
Notice that the product template is a user control file (an .ascx file). Also, notice that it displays the value of a database column that the default product template doesn’t display. This template displays the value of the QuantityPerUnit column.
31 CREATING AN ONLINE STORE
Dynamically Loading Product Templates
1337
1338
Sample ASP.NET Applications PART IX
Now that you have a new template, you need to associate it with all the Seafood products. You can do this by executing the following SQL statement from SQL Query Analyzer: Update Products Set Template=’Seafood’ Where CategoryID=8
This SQL Update statement changes the value of the Template column to Seafood so that your new template will be displayed. If you browse to a Seafood product in the ASP.NET Unleashed Sample Store, the product should be displayed with your new template (see Figure 31.3). FIGURE 31.3 The Seafood template.
Using dynamically loaded controls in this way provides a great deal of flexibility. You can use user controls to free yourself from being locked down to a particular user interface. In the next two sections, you examine how these templates are implemented.
Using the LoadControl Method You can display different templates for the different products in your store because of the way the Product.aspx page was written. The Product.aspx page was written to take advantage of dynamically loaded user controls.
Creating an Online Store CHAPTER 31
31
Note
The code for the Product.aspx page is contained in Listing 31.7. Product.aspx
Sub Page_Load Dim intProductID As Integer Dim strProductTemplate As String Dim objProductTemplate As ProductTemplate intProductID = Context.Items( “ProductID” ) strProductTemplate = CachedData.GetProductTemplate( intProductID ) strProductTemplate = String.Format( “/aspnetstore/ProductTemplates/{0}.ascx”, ➥strProductTemplate ) objProductTemplate = LoadControl( strProductTemplate ) plhProductTemplate.Controls.Add( objProductTemplate ) End Sub