• | Table of Contents
• | Index
ASP.NET Unleashed, Second Edition
By Stephen Walther
Publisher | : Sams Publishing
Pub Date | : July 18, 2003
ISBN | : 0-672-32542-X
Pages | : 1488

The second edition of ASP.NET Unleashed further strengthens a book that is already one of the most highly regarded ASP.NET books available. Stephen covers virtually all relevant features of ASP.NET. In addition, all samples will now be presented in VB .NET and C#.

Throughout the more than 1,400 pages readers are shown how to develop state-of-the-art Web applications using Microsoft's powerful ASP.NET. It progresses through Web Forms basics, advanced page development, ADO.NET and XML, securing Web applications, XML Web Services, leveraging the .NET Framework, building custom controls and powerful sample applications. This is a resource that will be referenced over and over.


• | Table of Contents
• | Index
ASP.NET Unleashed, Second Edition
By Stephen Walther
Publisher | : Sams Publishing
Pub Date | : July 18, 2003
ISBN | : 0-672-32542-X
Pages | : 1488

Copyright
About the Author
Acknowledgments
We Want to Hear from You!
Reader Services
Introduction
 | Who Should Read This Book?
 | What Do You Need to Have Before You Read This Book?
 | What Do You Need to Know Before You Read This Book?
 | How This Book Is Organized
 | Code Naming Conventions Used in This Book
 | Conventions Used in This Book
Part I. Working with ASP.NET Web Forms
 | Chapter 1. Building ASP.NET Pages
 |  | ASP.NET and the .NET Framework
 |  | Introducing ASP.NET Controls
 |  | Adding Application Logic to an ASP.NET Page
 |  | The Structure of an ASP.NET Page
 |  | Summary
 | Chapter 2. Building Forms with Web Server Controls
 |  | Building Smart Forms
 |  | Controlling Page Navigation
 |  | Applying Formatting to Controls
 |  | Summary
 | Chapter 3. Performing Form Validation with Validation Controls
 |  | Using Client-side Validation
 |  | Requiring Fields: The RequiredFieldValidator Control
 |  | Validating Expressions: The RegularExpressionValidator Control
 |  | Comparing Values: The CompareValidator Control
 |  | Checking for a Range of Values: The RangeValidator Control
 |  | Summarizing Errors: The ValidationSummary Control
 |  | Performing Custom Validation: The CustomValidator Control
 |  | Disabling Validation
 |  | Summary
 | Chapter 4. Advanced Control Programming
 |  | Working with View State
 |  | Displaying and Hiding Content
 |  | Using Rich Controls
 |  | Summary
Part II. Advanced ASP.NET Page Development
 | Chapter 5. Creating Custom Controls with User Controls
 |  | Including Standard Content with User Controls
 |  | Exposing Properties and Methods in User Controls
 |  | Exposing Web Controls in User Controls
 |  | Exposing Events in User Controls
 |  | Loading User Controls Programmatically
 |  | Summary
 | Chapter 6. Separating Code from Presentation
 |  | Creating Business Components
 |  | Creating Multitiered Web Applications
 |  | Using Code-Behind
 |  | Summary
 | Chapter 7. Targeting Mobile Devices with Mobile Controls
 |  | Introduction to Mobile Devices
 |  | Using Mobile Device Software Simulators
 |  | Using ASP.NET Mobile Controls
 |  | Creating Cross-Device-Compatible Mobile Pages
 |  | Summary
 | Chapter 8. Additional Samples and Controls
 |  | Using the Microsoft Internet Explorer WebControls
 |  | Using the ASP.NET Starter Kits
 |  | Summary
Part III. Working with ADO.NET
 | Chapter 9. Introduction to ADO.NET
 |  | An Overview of ADO.NET
 |  | Performing Common Database Tasks
 |  | Improving Database Performance
 |  | Advanced Database Topics
 |  | Summary
 | Chapter 10. Binding Data to Web Controls
 |  | Overview of Data Binding
 |  | Binding a Server Control to a Data Source
 |  | Creating Master/Detail Forms
 |  | Data Binding without Data Binding Expressions
 |  | Summary
 | Chapter 11. Using the DataList and DataGrid Controls
 |  | Overview of the DataList and DataGrid Controls
 |  | Using the DataList Control
 |  | Using the DataGrid Control
 |  | Summary
 | Chapter 12. Working with DataSets
 |  | Understanding DataSets
 |  | Understanding DataTables
 |  | Understanding DataViews
 |  | Using DataSets with ASP.NET Pages
 |  | Summary
 | Chapter 13. Working with XML
 |  | Overview of the XML Classes
 |  | Using XML with DataSets
 |  | Transforming XML with XSL Stylesheets
 |  | Using the ASP.NET Xml Control
 |  | Using the XslTransform Class
 |  | Using Strongly Typed DataSets
 |  | Summary
 | Chapter 14. Using ADO.NET to Create a Search Page
 |  | Using SQL Server Full-Text Search
 |  | Using the Microsoft Indexing Service
 |  | Summary
Part IV. Working with ASP.NET Applications
 | Chapter 15. Creating ASP.NET Applications
 |  | Overview of ASP.NET Applications
 |  | Using Application State
 |  | Using the Web.Config File
 |  | Using HTTP Handlers and Modules
 |  | Summary
 | Chapter 16. Tracking User Sessions
 |  | Using Browser Cookies
 |  | Using Session State
 |  | Using Cookieless Sessions
 |  | Summary
 | Chapter 17. Caching ASP.NET Applications
 |  | Using Page Output Caching
 |  | Using Page Fragment Caching
 |  | Using Page Data Caching
 |  | Summary
 | Chapter 18. Application Tracing and Error Handling
 |  | Responding to Errors
 |  | Tracing and Monitoring Your Application
 |  | Logging Events
 |  | Using the Debugger
 |  | Summary
Part V. Securing ASP.NET Applications
 | Chapter 19. Using Forms-Based Authentication
 |  | Working with Forms Authentication
 |  | Working with Passport Authentication
 |  | Summary
 | Chapter 20. Using Windows-Based Authentication
 |  | Working with Windows Authentication
 |  | Impersonating User Accounts
 |  | Setting Security Policies
 |  | Summary
 | Chapter 21. Encrypting Data over the Network
 |  | Using the Secure Sockets Layer
 |  | Configuring Your Server to Use SSL
 |  | Using SSL in ASP.NET Pages
 |  | Using .NET Encryption Classes
 |  | Summary
Part VI. Building ASP.NET Web Services
 | Chapter 22. Creating an XML Web Service
 |  | Overview of XML Web Services
 |  | Creating a Simple XML Web Service
 |  | Testing an XML Web Service from a Browser
 |  | Accessing an XML Web Service Through a Proxy Class
 |  | Transmitting Complex Data in an XML Web Service
 |  | Examining XML Web Service and Web Site Interaction
 |  | Summary
 | Chapter 23. Advanced XML Web Services
 |  | Using the WebService Behavior
 |  | Securing an XML Web Service
 |  | Using HTML Pattern Matching
 |  | Summary
Part VII. Leveraging the .NET Framework
 | Chapter 24. Working with Collections and Strings
 |  | Working with Collections
 |  | Working with Strings
 |  | Working with Regular Expressions
 |  | Summary
 | Chapter 25. Working with the File System
 |  | Security and the File System
 |  | Using Files and Directories
 |  | Using Serialization
 |  | Summary
 | Chapter 26. Sending Email and Accessing the Network
 |  | Sending Email from an ASP.NET Page
 |  | Using Message Queuing
 |  | Accessing Other Web Sites with HTTP
 |  | Summary
 | Chapter 27. Creating Graphics On-the-Fly with GDI+
 |  | Building Simple Images
 |  | Drawing Objects with GDI+
 |  | Drawing a String of Text
 |  | Building Sample GDI+ Applications
 |  | Summary
Part VIII. Building Custom ASP.NET Controls
 | Chapter 28. Developing Custom Controls
 |  | Creating a Simple Control
 |  | Using HtmlTextWriter
 |  | Adding Properties and Methods to Controls
 |  | Parsing Inner Content
 |  | Adding Child Controls to a Control
 |  | Examining Custom Controls and Events
 |  | Participating in Postbacks
 |  | Creating Composite Controls
 |  | Inheriting from Existing Controls
 |  | Accessing the Current Context
 |  | Debugging Controls
 |  | Summary
 | Chapter 29. Advanced Control Development
 |  | Adding Templates to a Control
 |  | Creating DataBound Controls
 |  | Using Custom Controls and Web Services
 |  | Summary
Part IX. Sample ASP.NET Applications
 | Chapter 30. Creating a Job Site
 |  | Installing the Job Site
 |  | Creating the Home Page
 |  | Authenticating Users
 |  | Creating Vanity URLs
 |  | Listing and Updating Jobs
 |  | Creating the Jobs XML Web Service
 |  | Summary
 | Chapter 31. Creating an Online Store
 |  | Overview of the ASP.NET Unleashed Sample Store
 |  | Installing the ASP.NET Unleashed Sample Store
 |  | Building the Navigation System
 |  | Caching the Product Data
 |  | Building the Shopping Cart
 |  | Dynamically Loading Product Templates
 |  | Summary
Part X. Appendixes
 | Appendix A. Migrating from ASP to ASP.NET
 |  | ASP.NET Pages End with the Extension .aspx
 |  | Option Explicit Is On by Default
 |  | No More Variants
 |  | Use Parentheses When Passing Parameters to Subroutines and Methods
 |  | Functions and Subroutines Must Be Declared in <Script> Tags
 |  | Parameters Are Passed by Value
 |  | Forms Should Post Back to the Same Page
 |  | Only One Server-Side Form per Page
 |  | Don't Use Set and Let When Working with Objects
 |  | Apartment-Threaded Components Are Not Supported
 |  | No Nonindexed Default Properties
 |  | ASP.NET Pages Can Contain Only a Single Language
 |  | Differences in Request, Request.Form, and Request.QueryString
 | Appendix B. HTML Control Reference
 | Appendix C. Web Control Reference
What's on the CD-ROM
 | Windows Installation Instructions
 | License Agreement
 | The Community Starter Kit, Microsoft .NET Framework 1.1 Redistributable and Software Development Kit, Microsoft SQL Server 2000 Desktop Engine, and Web Matrix
Article
 | License Agreement
 | The Community Starter Kit, Microsoft .NET Framework 1.1 Redistributable and Software Development Kit, Microsoft SQL Server 2000 Desktop Engine, and Web Matrix
Index


Copyright

Copyright © 2004 by Sams Publishing

All rights reserved. No part of this book shall be reproduced, stored in a retrieval system, or transmitted by any means, electronic, mechanical, photocopying, recording, or otherwise, without written permission from the publisher. No patent liability is assumed with respect to the use of the information contained herein. Although every precaution has been taken in the preparation of this book, the publisher and author assume no responsibility for errors or omissions. Nor is any liability assumed for damages resulting from the use of the information contained herein.

Library of Congress Catalog Card Number: 2003092623

Printed in the United States of America

First Printing: May 2003

06 05 04 03 6 5 4 3

Sams Publishing offers excellent discounts on this book when ordered in quantity for bulk purchases or special sales. For more information, please contact

U.S. Corporate and Government Sales
1-800-382-3419
corpsales@pearsontechgroup.com

For sales outside of the U.S., please contact

International Sales
1-317-581-3793
international@pearsontechgroup.com

Trademarks

All terms mentioned in this book that are known to be trademarks or service marks have been appropriately capitalized. Sams Publishing cannot attest to the accuracy of this information. Use of a term in this book should not be regarded as affecting the validity of any trademark or service mark.

Warning and Disclaimer

Every effort has been made to make this book as complete and as accurate as possible, but no warranty or fitness is implied. The information provided is on an "as is" basis. The authors and the publisher shall have neither liability nor responsibility to any person or entity with respect to any loss or damages arising from the information contained in this book.

Credits

Associate Publisher

Michael Stephens

Acquisitions Editor

Neil Rowe

Development Editor

Mark Renfrow

Managing Editor

Charlotte Clapp

Project Editor

Andy Beaster

Indexer

Larry Sweazy

Proofreader

Paula Lowell

Technical Editor

Doug Holland

Team Coordinator

Cindy Teeters

Interior Designer

Gary Adair

Cover Designer

Aren Howell

Page Layout

Julie Parks

Dedication

This book is dedicated to Ruth Walther.


About the Author

Stephen Walther a well-known ASP.NET expert and trainer, has provided training to companies and organizations throughout the United States including NASA, Verizon, MetLife, and the National Science Foundation through his company Superexpert AspWorkshops (www.AspWorkshops.com). He was the lead developer of the ASP.NET Community Starter Kit, a "best-practices" application released by Microsoft. Stephen was the past Chief Technical Officer of CityAuction (www.cityauction.com), one of the Internet's top person-to-person auction Web sites, and the exclusive auction provider for both Snap! and CitySearch. Stephen was also past Chief Technical Officer of Collegescape, the leading provider of online college applications.


Acknowledgments

I want to thank Rob Howard and Scott Guthrie at Microsoft for taking the time to meet with me and answering my questions about ASP.NET. I'm always amazed by the amount of effort both Rob and Scott make to contribute to the ASP.NET community. Thanks also to Shawn Nandi at Microsoft.

I also want to express my appreciation for the work that Tim Shakarian performed translating all the VB.NET code samples in this book into C#. Considering the number of code samples contained in this book, this was not an easy task! Thanks also to Charles Carroll who provided many valuable suggestions for improving the original version of this book.

Finally, I want to thank Neil Rowe for all his support and encouragement while I was writing this book.


We Want to Hear from You!

As the reader of this book, you are our most important critic and commentator. We value your opinion and want to know what we're doing right, what we could do better, what areas you'd like to see us publish in, and any other words of wisdom you're willing to pass our way.

You can email or write me directly to let me know what you did or didn't like about this book—as well as what we can do to make our books stronger.

Please note that I cannot help you with technical problems related to the topic of this book, and that due to the high volume of mail I receive, I might not be able to reply to every message.

When you write, please be sure to include this book's title and author as well as your name and phone or email address. I will carefully review your comments and share them with the author and editors who worked on the book.

Email: | feedback@samspublishing.com
Mail: | Michael Stephens Associate Publisher Sams Publishing 800 East 96th Street Indianapolis, IN 46240 USA


Reader Services

For more information about this book or others from Sams Publishing, visit our Web site at www.samspublishing.com. Type the ISBN (excluding hyphens) or the title of the book in the Search box to find the book you're looking for.


Introduction

ASP.NET 1.1, the latest version of Active Server Pages, is Microsoft's technology for building dynamic, database-driven Web sites.

ASP.NET is one of the most popular languages for building scalable, interactive Web sites. Several of the highest traffic Web sites on the Internet employ ASP.NET. Prominent examples include The Home Shopping Network, MSN, and the Microsoft site itself.

ASP.NET represents a radical departure from previous versions of Active Server Pages. If you have programmed with earlier versions of Active Server Pages and you have not been exposed to the new features of ASP.NET, prepare to be shocked.

Following are some of the significant new features of ASP.NET:

  • ASP.NET uses compiled code written in Common Language Runtime languages such as Visual Basic and C#. Unlike previous versions of Active Server Pages, this version does not use interpreted scripting languages such as VBScript.

  • ASP.NET pages are built out of server-side controls. Web server controls enable you to represent and program against Hypertext Markup Language (HTML) elements using an intuitive object model.

  • ASP.NET includes a new technology called Web Services. You can use Web Services to access methods and properties and transfer database data across the Internet.

  • ASP.NET is part of Microsoft's .NET Framework. You can access thousands of .NET classes in your code that enable you to perform such wondrously diverse tasks as generating images on-the-fly and saving an array to a file.

  • ASP.NET includes page and data caching mechanisms that enable you to easily and dramatically improve the performance of your Web site.


Who Should Read This Book?

ASP.NET Unleashed is intended for professional programmers who need to create a Web site. This book is a comprehensive reference for building Web sites with ASP.NET. The CD that accompanies this book contains hundreds of code samples written in both VB.NET and C# that you can start using immediately while building your Web site.

If you are new to building Web sites with Active Server Pages, you can use this book to teach yourself everything you need to know to build a Web site with ASP.NET. If you are an experienced Active Server Pages programmer, you can use this book to learn about the new features of ASP.NET. Finally, if you are familiar with ASP.NET 1.0, then you can use this book to learn about the new features of ASP.NET 1.1. The CD that accompanies this book includes a white paper named Moving from ASP.NET 1.0 to 1.1 that focuses on the changes from ASP.NET 1.0 to ASP.NET 1.1.

The final part of this book contains two complete sample applications: a job site and an online store. The complete code for these applications is included on the CD that accompanies this book.


What Do You Need to Have Before You Read This Book?

The CD that accompanies this book includes everything you need to build ASP.NET applications. The CD contains the Microsoft .NET Framework SDK version 1.1 which includes both the Visual Basic and C# programming languages. You can install the .NET Framework SDK from the CD by double-clicking the file named Setup.exe in the Framework folder. This can also be downloaded from the www.ASP.net Web site.

The CD also includes ASP.NET Web Matrix. ASP.NET Web Matrix is the development environment developed by members of the ASP.NET team at Microsoft for building ASP.NET applications. You can install Web Matrix by double-clicking the file named webmatrix.msi located in the WebMatrix folder. The latest version can also be downloaded from the www.ASP.net Web site.

The only additional requirement for building ASP.NET applications is that you have the right operating system. For developing ASP.NET applications, you are required to have Windows 2000 (Professional or Server editions), Windows XP (Home or Professional editions), or Windows 2003 (Standard, Enterprise, Datacenter, or Web editions). While you can develop ASP.NET applications using Windows XP Home edition and Windows 2000 Professional edition by using the Web server included with Web Matrix, you cannot use either of these operating systems for a production Web site.

The database chapters in this book assume that you have Microsoft SQL Server, MSDE, or Microsoft Access. The two sample applications in the last two chapters of this book, the job site and online store, require that you have either Microsoft SQL Server or MSDE installed on your computer. You can download MSDE from the www.ASP.net Web site.

The CD that accompanies this book includes a white paper named "Moving from ASP.NET 1.0 to 1.1" that focuses on the changes from ASP.NET 1.0 to ASP.NET 1.1.


What Do You Need to Know Before You Read This Book?

This book assumes that you know HTML and that you have experience with a programming language, such as Visual Basic, Java, or VBScript. Almost all the code discussed in this book was written with Visual Basic. However, if you are familiar with VBScript, you should have very little trouble understanding the code samples. The CD that accompanies this book contains both VB.NET and C# versions of all the code samples. To get the most from the database chapters, you should have some experience working with a database, such as Microsoft SQL Server, Oracle, or Microsoft Access.


How This Book Is Organized

Although I encourage you to read this book from start to finish, reading chapter by chapter, I realize that not everyone has time to do so. If necessary, you can use this book solely as a reference and jump to a chapter only when the need arises. It might be helpful, therefore, to have an idea of the overall organization of this book.

  • Part I: Working with ASP.NET Web Forms— The chapters in this part provide you with detailed information on building ASP.NET pages with form and validation controls. All the controls used for constructing interactive HTML forms are discussed.

  • Part II: Advanced ASP.NET Page Development— These chapters discuss how to create your own controls with user controls, how to use mobile controls to create Web pages that work with cell phones and personal digital assistants (PDAs), how to divide the code from the design content on a page, and how to use third-party controls in your ASP.NET pages.

  • Part III: Working with ADO.NET— The chapters in this part provide you with a detailed understanding of ADO.NET. You learn how to perform basic database tasks, such as saving form data to a database. You also learn how to use the specialized database Web controls, such as the DataList and DataGrid controls.

  • Part IV: Working with ASP.NET Applications— These chapters discuss features of an ASP.NET application. For example, you learn how to configure your ASP.NET application with the Web.config file and work with both application and session state.

  • Part V: Securing ASP.NET Applications— These chapters provide you with an overview of the security features built into ASP.NET. For example, you learn how to use form-based security to password-protect pages on your site. You also learn how to use the .NET classes for performing encryption.

  • Part VI: Building ASP.NET Web Services— The chapters in this part explain how to use an exciting new technology included with this release of Active Server Pages. In these chapters, you learn how to create Web Services and integrate the Web Services into your Web site.

  • Part VII: Leveraging the .NET Framework— These chapters discuss many of the useful .NET classes that you can use in your pages. For example, you learn how to generate images on-the-fly, work with collections and regular expressions, and send e-mail from an ASP.NET page.

  • Part VIII: Building Custom ASP.NET Controls— These chapters discuss how you can extend the ASP.NET Framework with custom controls. You learn how to create your own ASP.NET controls.

  • Part IX: Sample ASP.NET Applications— The final part of this book contains two complete sample applications. You learn how to build a job site and an online store.


Code Naming Conventions Used in This Book

The topic of variable naming and code conventions is a touchy one. Programmers tend to develop their personal naming conventions and don't like others dictating how they should write their code.

Nevertheless, having a common convention is valuable when code must be read by multiple people in an organization (think code reviews). Having a common convention also makes it easier to read your own code if you return to it at a later date.

The most popular variable naming convention is called Hungarian Notation. It was originally developed by Charles Simonyi at Microsoft and has been used internally at Microsoft for a number of years. According to this convention, you should name all variables starting with a standard three or four letter prefix that represents the variable's data type. For example, an integer variable used to represent a customer's age should be named intCustomerAge.

Microsoft recommends that you do not follow this convention in the case of the .NET Framework and ASP.NET. The motivation for this recommendation is that they expect you to use an advanced editor, such as Microsoft Visual Studio, to write your code. Visual Studio automatically provides you with information about a variable's type.

However, in this book, I will not assume that you are using Visual Studio to write your ASP.NET pages. In practice, developers use a wide variety of editors—such as Web Matrix, TextPad, UltraEdit, and Notepad—to write code. Since I am not assuming that you are using Visual Studio, a variable naming convention is necessary.

Furthermore, in my experience, many companies require their programmers to follow a strict naming convention that is typically some variation on the Hungarian Notation. I'll be using a variation of Hungarian Notation for the naming conventions used in this book. I've created a set of prefixes and naming conventions for the basic variable data types, ASP.NET Web Controls, ADO.NET classes, and event-handling subroutines. These conventions are listed in the following tables.

My goal is not to provide a standard prefix for each and every class in the .NET Framework. That would require over 3,400 standard prefixes! The goal is to provide standard prefixes for the most common types that you would use in ASP.NET.

Table . Variable Naming Conventions

Type

Prefix

Example

Array

arr

arrShoppingList

Boolean

bln

blnIsPostBack

Byte

byt

bytPixelValue

Char

chr

chrDelimiter

DateTime

dtm

dtmStartDate

Decimal

dec

decAverageHeight

Double

dbl

dblSizeOfUniverse

Integer

int

intRowCounter

Long

lng

lngBillGatesIncome

Object

obj

objReturnValue

Short

shr

shrAverage

Single

sng

sngMaximum

String

str

strFirstName

Table . WebControls

Type

Prefix

Example

AdRotator

adrt

adrtTopAd

Button

btn

btnSubmit

Calendar

cal

calMeetingDates

CheckBox

chk

chkBlue

CheckBoxList

chkl

chklFavColors

CompareValidator

valc

valcValidAge

CustomValidator

valx

valxDBCheck

DataGrid

dgrd

dgrdTitles

DataList

dlst

dlstTitles

DropDownList

drop

dropCountries

HyperLink

lnk

lnkDetails

Image

img

imgAuntBetty

ImageButton

ibtn

ibtnSubmit

Label

lbl

lblResults

LinkButton

lbtn

lbtnSubmit

ListBox

lst

lstCountries

Panel

pnl

pnlForm2

PlaceHolder

plh

plhFormContents

RadioButton

rad

radFemale

RadioButtonList

radl

radlGender

RangeValidator

valg

valgAge

RegularExpression

vale

valeEmail_Validator

Repeater

rpt

rptQueryResults

RequiredFieldValidator

valr

valrFirstName

Table

tbl

tblCountryCodes

TableCell

tblc

tblcGermany

TableRow

tblr

tblrCountry

TextBox

txt

txtFirstName

ValidationSummary

vals

valsFormErrors

XML

xmlc

xmlcTransformResults

Table . ADO.NET

Type

Prefix

Example

Connection

con

conNorthwind

Commmand

cmd

cmdReturnProducts

Parameter

parm

parmProductID

DataAdapter

dad

dadProducts

DataReader

dtr

dtrProducts

DataSet

dst

dstNorthwind

DataTable

dtbl

dtblProduct

DataRow

drow

drowRow98

DataColumn

dcol

dcolProductID

DataRelation

drel

drelMasterDetail

DataView

dvw

dvwFilteredProducts

Event-Handling Subroutines

The name of an event-handling subroutine will consist of the ID of the control that raised the event followed by the type of event being handled. For example, a subroutine named btnSubmit_Click handles the Click event of a Button control named btnSubmit.

When a control that raises an event is not assigned an ID, the type of the control is used instead of the ID. For example, the subroutine named Button_Click handles the Click event of a Button control without an ID.


Conventions Used in This Book

The following typographic conventions are used in this book:

  • Code lines, commands, statements, variables, and any text you type or see onscreen appears in a mono typeface. Bold mono typeface is often used to represent the user's input.

  • Placeholders in syntax descriptions appear in an italic mono typeface. Replace the placeholder with the actual filename, parameter, or whatever element it represents.

  • Italics highlight technical terms when they're being defined.

  • The [image] icon is used before a line of code that is really a continuation of the preceding line. Sometimes a line of code is too long to fit as a single line on the page. If you see [image] before a line of code, remember that it's part of the line immediately above it.

  • The book also contains Notes, Tips, and Cautions to help you spot important or useful information more quickly.

  • The notes include comments for experienced ASP users who are upgrading to ASP.NET. These notes are labeled "ASP Classic Note."


Part I: Working with ASP.NET Web Forms

IN THIS PART

CHAPTER 1 Building ASP.NET Pages

CHAPTER 2 Building Forms with Web Server Controls

CHAPTER 3 Performing Form Validation with Validation Controls

CHAPTER 4 Advanced Control Programming


Chapter 1. Building ASP.NET Pages

IN THIS CHAPTER

  • ASP.NET and the .NET Framework

  • Introducing ASP.NET Controls

  • Adding Application Logic to an ASP.NET Page

  • The Structure of an ASP.NET Page

In this chapter, you learn how to build basic ASP.NET Web Forms Pages. Don't let the Forms part of the name mislead you. Web Forms Pages can do much more than display standard HTML forms. Most, if not all, of the pages of your ASP.NET application will be Web Forms Pages, which enable you to create pages with interactive, dynamic, or database-driven content.

Web Forms Pages are pieced together out of two building blocks. First, you assemble the dynamic portion of the user interface by using ASP.NET controls. ASP.NET controls enable you to display "smart" HTML forms, for example, and present interactive grids of database data. The first part of this chapter provides an overview of all the ASP.NET controls.

The second building block of a Web Forms Page is the application logic, which includes the code that executes when you click a form button, or the code that retrieves the database data displayed within a control. In the second part of this chapter, you learn how to add application logic to your Web Forms Pages to handle both control and page events.

Finally, in the last part of this chapter, you are formally introduced to the structure of an ASP.NET Web Forms Page. You learn about the different sections of a Web Forms Page and the type of content appropriate to each section.

After reading this chapter, you can start creating dynamic Web sites by building interactive, dynamic pages using ASP.NET controls and handling control and page events with application logic.


ASP.NET and the .NET Framework

ASP.NET is part of Microsoft's overall .NET framework, which contains a vast set of programming classes designed to satisfy any conceivable programming need. In the following two sections, you learn how ASP.NET fits within the .NET framework, and you learn about the languages you can use in your ASP.NET pages.

The .NET Framework Class Library

Imagine that you are Microsoft. Imagine that you have to support multiple programming languages—such as Visual Basic, JScript, and C++. A great deal of the functionality of these programming languages overlaps. For example, for each language, you would have to include methods for accessing the file system, working with databases, and manipulating strings.

Furthermore, these languages contain similar programming constructs. Every language, for example, can represent loops and conditionals. Even though the syntax of a conditional written in Visual Basic differs from the syntax of a conditional written in C++, the programming function is the same.

Finally, most programming languages have similar variable data types. In most languages, you have some means of representing strings and integers, for example. The maximum and minimum size of an integer might depend on the language, but the basic data type is the same.

Maintaining all this functionality for multiple languages requires a lot of work. Why keep reinventing the wheel? Wouldn't it be easier to create all this functionality once and use it for every language?

The .NET Framework Class Library does exactly that. It consists of a vast set of classes designed to satisfy any conceivable programming need. For example, the .NET framework contains classes for handling database access, working with the file system, manipulating text, and generating graphics. In addition, it contains more specialized classes for performing tasks such as working with regular expressions and handling network protocols.

The .NET framework, furthermore, contains classes that represent all the basic variable data types such as strings, integers, bytes, characters, and arrays.

Most importantly, for purposes of this book, the .NET Framework Class Library contains classes for building ASP.NET pages. You need to understand, however, that you can access any of the .NET framework classes when you are building your ASP.NET pages.

Understanding Namespaces

As you might guess, the .NET framework is huge. It contains thousands of classes (over 3,400). Fortunately, the classes are not simply jumbled together. The classes of the .NET framework are organized into a hierarchy of namespaces.

ASP Classic Note

In previous versions of Active Server Pages, you had access to only five standard classes (the Response, Request, Session, Application, and Server objects). ASP.NET, in contrast, provides you with access to over 3,400 classes!

A namespace is a logical grouping of classes. For example, all the classes that relate to working with the file system are gathered together into the System.IO namespace.

The namespaces are organized into a hierarchy (a logical tree). At the root of the tree is the System namespace. This namespace contains all the classes for the base data types, such as strings and arrays. It also contains classes for working with random numbers and dates and times.

You can uniquely identify any class in the .NET framework by using the full namespace of the class. For example, to uniquely refer to the class that represents a file system file (the File class), you would use the following:

System.IO.File

System.IO refers to the namespace, and File refers to the particular class.

NOTE

You can view all the namespaces of the standard classes in the .NET Framework Class Library by viewing the Reference Documentation for the .NET Framework.

Standard ASP.NET Namespaces

The classes contained in a select number of namespaces are available in your ASP.NET pages by default. (You must explicitly import other namespaces.) These default namespaces contain classes that you use most often in your ASP.NET applications:

  • System— Contains all the base data types and other useful classes such as those related to generating random numbers and working with dates and times.

  • System.Collections— Contains classes for working with standard collection types such as hash tables, and array lists.

  • System.Collections.Specialized— Contains classes that represent specialized collections such as linked lists and string collections.

  • System.Configuration— Contains classes for working with configuration files (Web.config files).

  • System.Text— Contains classes for encoding, decoding, and manipulating the contents of strings.

  • System.Text.RegularExpressions— Contains classes for performing regular expression match and replace operations.

  • System.Web— Contains the basic classes for working with the World Wide Web, including classes for representing browser requests and server responses.

  • System.Web.Caching— Contains classes used for caching the content of pages and classes for performing custom caching operations.

  • System.Web.Security— Contains classes for implementing authentication and authorization such as Forms and Passport authentication.

  • System.Web.SessionState— Contains classes for implementing session state.

  • System.Web.UI— Contains the basic classes used in building the user interface of ASP.NET pages.

  • System.Web.UI.HTMLControls— Contains the classes for the HTML controls.

  • System.Web.UI.WebControls— Contains the classes for the Web controls.

.NET Framework-Compatible Languages

For purposes of this book, you will write the application logic for your ASP.NET pages using Visual Basic as your programming language. It is the default language for ASP.NET pages (and the most popular programming language in the world). Although you stick to Visual Basic in this book, you also need to understand that you can create ASP.NET pages by using any language that supports the .NET Common Language Runtime. Out of the box, this includes C# (pronounced See Sharp), JScript.NET (the .NET version of JavaScript), and the Managed Extensions to C++.

NOTE

The CD included with this book contains C# versions of all the code samples.

Dozens of other languages created by companies other than Microsoft have been developed to work with the .NET framework. Some examples of these other languages include Python, SmallTalk, Eiffel, and COBOL. This means that you could, if you really wanted to, write ASP.NET pages using COBOL.

Regardless of the language that you use to develop your ASP.NET pages, you need to understand that ASP.NET pages are compiled before they are executed. This means that ASP.NET pages can execute very quickly.

The first time you request an ASP.NET page, the page is compiled into a .NET class, and the resulting class file is saved beneath a special directory on your server named Temporary ASP.NET Files. For each and every ASP.NET page, a corresponding class file appears in the Temporary ASP.NET Files directory. Whenever you request the same ASP.NET page in the future, the corresponding class file is executed.

When an ASP.NET page is compiled, it is not compiled directly into machine code. Instead, it is compiled into an intermediate-level language called Microsoft Intermediate Language (MSIL). All .NET-compatible languages are compiled into this intermediate language.

An ASP.NET page isn't compiled into native machine code until it is actually requested by a browser. At that point, the class file contained in the Temporary ASP.NET Files directory is compiled with the .NET framework Just in Time (JIT) compiler and executed.

The magical aspect of this whole process is that it happens automatically in the background. All you have to do is create a text file with the source code for your ASP.NET page, and the .NET framework handles all the hard work of converting it into compiled code for you.

ASP CLASSIC NOTE

What about VBScript? Before ASP.NET, VBScript was the most popular language for developing Active Server Pages.

ASP.NET does not support VBScript, and this is good news. Visual Basic is a superset of VBScript, which means that Visual Basic has all the functionality of VBScript and more. So, you have a richer set of functions and statements with Visual Basic.

Furthermore, unlike VBScript, Visual Basic is a compiled language. This means that if you use Visual Basic to rewrite the same code that you wrote with VBScript, you can get better performance.

If you have worked only with VBScript and not Visual Basic in the past, don't worry. Since VBScript is so closely related to Visual Basic, you'll find it easy to make the transition between the two languages.

NOTE

Microsoft includes an interesting tool named the IL Disassembler (ILDASM) with the .NET framework. You can use this tool to view the disassembled code for any of the ASP.NET classes in the Temporary ASP.NET Files directory. It lists all the methods and properties of the class and enables you to view the intermediate-level code.

This tool also works with all the ASP.NET controls discussed in this chapter. For example, you can use the IL Disassembler to view the intermediate-level code for the TextBox control (located in a file named System.Web.dll).


Introducing ASP.NET Controls

ASP.NET controls provide the dynamic and interactive portions of the user interface for your Web application. The controls render the content that the users of your Web site actually see and interact with. For example, you can use controls to create HTML form elements, interactive calendars, and rotating banner advertisements.

ASP.NET controls coexist peacefully with HTML content. Typically, you create the static areas of your Web pages with normal HTML content and create the dynamic or interactive portions with ASP.NET controls.

The best way to understand how ASP.NET controls work in an HTML page is to look at a simple Web Forms Page.

A Simple ASP.NET Page

Let's start by looking at a normal, everyday HTML page. You'll convert this page into an ASP.NET page in a moment. First, open either Web Matrix or Notepad on your computer and enter the HTML page contained in Listing 1.1. After you finish entering the HTML page, save it with the name SimpleHTML.aspx in your inetpub/wwwroot directory.

NOTE

When you create ASP.NET pages, be sure to save them in a directory that your Web server can access. You need to save your pages in the inetpub/wwwroot directory, a subdirectory of the inetpub/wwwroot directory, or within a virtual directory of your Web server.

After you save a page, do not open the page directly in your Web browser by typing the file path of the page. If your Web server is located on the same computer as your Web browser, open the page by entering the page's address in the Address Bar of your browser. For example, if the page is named simpleHTML.aspx, open the page by typing the following text into your browser's Address Bar:

http://localhost/simpleHTML.aspx

For purposes of this book, I'll assume that you are using either Web Matrix or Notepad to create your Web pages. Web Matrix is included on the CD that accompanies this book. However, there is nothing wrong with using other text editors or Web development environments such as Microsoft Visual Studio .NET, Microsoft Visual Interdev, Allaire's Homesite, or TextPad. Any program that can save a file as plain text will work fine.

TIP

When you're saving a file with Notepad, remember to pick All Types as the value in the Save As Type box in the Save As dialog box. If you don't choose this option, Notepad automatically adds the extension .txt to the name of your file. An alternative method of preventing .txt from being appended to your filename is to place the filename in quotation marks when saving the file.

Listing 1.1 SimpleHTML.aspx
<html>
<head><title>Simple HTML Page</title></head>
<body>

<form method="post" action="SimpleHTML.aspx">
<b>Username:</b>
<br><input name="username" type="text" size="30">

<p>

<b>Comments:</b>
<br><textarea name="comments" cols=60 rows=10></textarea>

<p>

<input type="submit" value="Add Comment">

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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

Listing 1.2 SimpleASPX.aspx
<html>
<head><title>Simple ASPX Page</title></head>
<body>

<form Runat="Server">
<b>Username:</b>
<br><input id="username" type="text" size="30" Runat="Server">

<p>

<b>Comments:</b>
<br><textarea id="comments" cols=60 rows=10 Runat="Server"></textarea>

<p>

<input type="submit" value="Add Comment" Runat="Server">

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Notice the four modifications to this page. First, you rename the page from SimpleHTML.aspx 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.

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_isapi.dll. So, both classic ASP files and ASP.NET files can happily live and work together on the same Web server.

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

<input name="username" type="text" size="30">

to create the username form field, you use the following tag instead:

<input id="username" type="text" size="30" Runat="Server">

The Runat="Server" attribute converts these standard HTML tags into "smart" server-side 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

<form Runat="Server">

rather than the standard

<form method="post" action="SimpleASPX.aspx">

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.

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.

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 <form> tag from <form method="post" action="SimpleASPX.aspx"> to the simpler <form Runat="Server">.

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 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 <b>bold</b> 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.

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 want. Or you can write code that reads the Value property and saves it to a file or database.

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
<Script Runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  field2.Value = field1.Value
End Sub

</Script>

<html>
<head><title>CopyField.aspx</title></head>
<body>

<form Runat="Server">

<b>Field 1:</b>
<br><input id="field1" size="30" Runat="Server">

<p>

<b>Field 2:</b>
<br><input id="field2" size="30" Runat="Server">

<p>

<input type="submit"
  OnServerClick="Button_Click"
  Runat="Server">

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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 server-side 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 <span> tag and make it into an ASP.NET control like this:

<span id="myLabel" Runat="Server">

In this line, you magically convert the <span> tag into an ASP.NET control by adding an ID attribute and a Runat="Server" attribute. Now that the <span> 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
<Script Runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  entries.innerHtml = "<hr>" & username.Value & "<p>" & comments.Value &
entries.innerHTML
End Sub

</Script>

<html>
<head><title>Guestbook.aspx</title></head>
<body>

<form Runat="Server">
<b>Username:</b>
<br><input id="username" type="text" size="30" Runat="Server">

<p>

<b>Comments:</b>
<br><textarea id="comments" cols=60 rows=10 Runat="Server"></textarea>

<p>

<input type="submit" value="Add Comment"
OnServerClick="Button_Click" Runat="Server">

<span id="entries" Runat="Server" />

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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 <span> tag's view state. (The <span> tag with the entries ID.) Every time the form is posted, the contents of the Username and Comments fields are added to the <span> tag's view state.

Figure 1.1. Preserving view state.

The contents of the Username and Comments fields are added to the text displayed by the <span> tag by modifying the <span> tag's innerHTML property. Every time a new guest book entry is added, the following code is executed:

entries.innerHTML = "<hr>" & username.Value & "<p>" & comments.Value & entries.innerHTML

How is the view state of the <span> 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:

[View full width]

<input type="hidden" name="__VIEWSTATE" value="YTB6LTU2MjIwODU1X2Ewel9oejV6MX
   hfYTB6X2h6NXo3eF9hMHpoejBfPGhyPkJvYlxxRnJlZDxwPkhlcmUgaXMgbXkgY29tbWV
   udCANCmFuZCA8aHI+Qm9iPHA+SGVyZSBpcyBteSBjb21tZW50PGhyPlN0ZXZlPHA+SGVy
   ZSBpcyBteSBjb21tZW50PGhyPlN0ZXZlPHA+SGVyZSBpcyBteSBjb21tZW50PGhyPlN0ZX
   ZlPHA+SGVyZSBpcyBteSBjb21tZW50eF9feF81ejN4X2Ewemh6MF9IZXJlIGlzIG15IGNv
   bW1lbnQgDQphbmQgeF9feHhfeHhfeF9feFxuMF9pbm5lcmh0bWw=a6f9326a" />

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.

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.

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.

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 <input> tag, for example, is the HTMLInputText control:

<input id="username" type="text" size="30" Runat="Server">

And corresponding to the standard HTML <img> tag is the HTMLImage control:

<img ID="myImage" src="smile.gif" Runat="Server">

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

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 <MyOwnTag> tag. There is nothing wrong with creating a server-side tag like this:

<MyOwnTag Runat="Server">

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:

<input id="username" type="text" myAttribute="39" Runat="Server">

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:

<a href="somepage.aspx" Runat="Server">Click Here</a>
HtmlButton

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 <button> tag, as shown here:

<button Runat="Server">Click Here</button>
HtmlForm

HtmlForm creates a server-side HTML <form> tag. You must use this control, as shown here, if you want to include other ASP.NET controls in the Web Forms Page:

<form Runat="Server">
HtmlGenericControl

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 <span>, <body>, or <marquee> tag with the Runat="Server" attribute, as shown here, an instance of the HTMLGenericControl is created:

<span Runat="Server">Hello World!</span>
HtmlImage

HtmlImage creates a server-side <img> tag, as in this example:

<img ID="myImage" src="smile.gif" Runat="Server">
HtmlInputButton

HtmlInputButton creates a server-side <input type="submit">, <input type="reset">, or <input type="button"> tag, as in this example:

<input type="Submit" value="Click Here!" Runat="Server">
HtmlInputCheckBox

HtmlInputCheckBox creates a server-side <input type="checkbox"> tag, as in this example:

<input type="checkbox" Runat="Server"> Do you like this Web Site?
HtmlInputFile

HtmlInputFile creates a server-side <input type="file"> tag used for accepting file uploads, as in this example:

<input type="file" Runat="Server">
HtmlInputHidden

HtmlInputHidden creates a server-side <input type="hidden"> tag, as in this example:

<input type="hidden" Runat="Server">
HtmlInputImage

HtmlInputImage creates a server-side <input type="image"> tag, as in this example:

<input type="image" Runat="Server">
HtmlInputRadioButton

HtmlInputRadioButton creates a server-side form radio button, as in this example:

Gender?
<br> <input type="radio" Runat="Server"> Male
<br> <input type="radio" Runat="Server"> Female
HtmlInputText

HtmlInputText creates a server-side <input type="text"> or _<input type="password"> tag, as in this example:

Please Login:
Username:
<input id="username" type="text" Runat="Server">
<p>
Password:
<input id="password" type="password" Runat="Server">
HtmlSelect

HtmlSelect creates a server-side version of the HTML <select> tag for creating drop-down lists or list boxes, as in this example:

Select your favorite movie:
<Select Runat="Server">
  <option>Star Wars</option>
  <option>Citizen Kane</option>
  <option>The Princess Bride</option>
</Select>
HtmlTable

HtmlTable creates a server-side version of the HTML <table> tag for creating HTML tables, as in this example:

<table border=1 cellspacing=0 cellpadding=4 Runat="Server">
HtmlTableCell

HtmlTableCell creates a server-side version of the HTML table <td> tag for creating table cells, as in this example:

<td Runat="Server"> Table Data </td>
HtmlTableRow

HtmlTableRow creates a server-side version of the HTML table <tr> tag for creating table rows, as in this example:

<tr Runat="Server">
  <td Runat="Server"> Table Data 1 </td>
  <td Runat="Server"> Table Data 2 </td>
</tr>
HtmlTextArea

HtmlTextArea creates a server-side version of the form <textArea> tag for creating multiline text input fields, as in this example:

<textarea cols="60" rows="10" Runat="Server"></textarea>
Web Controls

When you create an ASP.NET page with ASP.NET controls, you have a choice. Either you can use the HTML controls discussed in the previous sections to build the page, or you can use the Web controls. All the most common HTML tags can be represented equally well with either an HTML control or a Web control.

NOTE

The HTML controls are all contained in the System.Web.UI.HTMLControls namespace, and the Web controls are contained in the System.Web.UI.WebControls namespace.

Why are you given this choice? The HTML controls were designed to be server-side duplicates of the standard HTML tags so that you can quickly convert an existing HTML document into an ASP.NET page. The HTML controls mirror the standard HTML tags almost exactly. This is not true in the case of the Web controls.

Web controls are a more rationalized version of the standard HTML tags. Remember that the HTML standard has evolved slowly over time. Because the attributes of the standard HTML tags were cobbled together over time, they don't always make the most sense.

Consider the standard HTML <select> tag. You can use it to create a single-select drop-down list like this:

<select name="favColor">
  <option> Red </option>
  <option> Blue </option>
  <option> Green </option>
</select>

Alternatively, you can use the same tag to create a multiselect list box like this:

<select name="favColor" Size="3" MULTIPLE>
  <option> Red </option>
  <option> Blue </option>
  <option> Green </option>
</select>

Being able to use the tags this way really doesn't make a lot of sense. When a browser renders the same tag with slightly different attributes, very different user interface elements are created (see Figure 1.2).

Figure 1.2. Difference between DropDownList and ListBox.

With the ASP.NET controls, Microsoft basically reinvented HTML starting from scratch. For example, it created two different Web controls that correspond to the two different versions of the <select> tag: a DropDownList control and ListBox control.

Because the Web controls do not directly correspond to standard HTML tags, you cannot create a Web control by simply sticking the Runat="Server" attribute at the end of a standard HTML tag. The Web controls do not look like standard HTML tags. For example, the Web control that corresponds to the standard HTML text <input> tag

<INPUT NAME="username" TYPE="text" SIZE="30">

looks like

<asp:TextBox id="username" Columns="30" Runat="Server"/>

However, if you place the TextBox Web control in an ASP.NET page, the standard HTML text <input> tag is rendered.

Microsoft fixed up standard HTML with the Web controls in one other place. Notice that the TextBox control is closed with a trailing forward slash (/). When you add a Web control to a page, you must be careful to always explicitly close the tag. You can close the TextBox control like this:

<asp:TextBox id="username" Columns="30" Runat="Server"></asp:TextBox>

Or this approach requires less typing you can close the TextBox control like this:

<asp:TextBox id="username" Columns="30" Runat="Server"/>

There are more Web controls than there are HTML controls. Several of the Web controls do not correspond to existing HTML tags at all. The Web controls can be divided into five different groups:

  • Basic Web controls

  • Validation Web controls

  • Data controls

  • Rich Web controls

  • Mobile controls

The first group consists of the Web controls that (roughly) correspond to existing HTML tags. The Web controls in this group include the TextBox, DropDownList, and ListBox controls. You can use these controls instead of the HTML controls to create server-side versions of standard HTML tags. Each of these controls is described in detail in the next chapter. The Web controls are as follows:

  • Button

  • CheckBox

  • CheckBoxList

  • DropDownList

  • HyperLink

  • Image

  • ImageButton

  • Label

  • ListBox

  • LinkButton

  • Panel

  • PlaceHolder

  • RadioButton

  • RadioButtonList

  • Table

  • TableCell

  • TableRow

  • TextBox

The second group of Web controls contains controls used for form validation. For example, you can add Web controls to an HTML form to make certain fields in the form required. The controls in this group do not correspond to any existing HTML tags. Each validation control is discussed in detail in Chapter 3. The following list contains all the validation Web controls.

  • CompareValidator

  • CustomValidator

  • RangeValidator

  • RegularExpressionValidator

  • RequiredFieldValidator

  • ValidationSummary

The third group of controls displays data. For example, you can use the DataGrid control to easily display data from a database table. The controls in this group are discussed in detail in the third part of this book, "Working with ADO.NET." The following is a complete list of the data-related Web controls.

  • DataGrid

  • DataList

  • Repeater

Microsoft included a couple of more complicated controls with the ASP.NET Web controls. They include a control that can render an interactive calendar, the Calendar Web control, and a control for displaying banner advertisements, the AdRotator control. You see how these controls work in Chapter 4, "Advanced Control Programming."

Finally, Microsoft developed several controls for working with mobile devices such as cell phones and personal digital assistants (PDAs) such as the Microsoft Pocket PC. You can target these devices in your ASP.NET pages by using the Mobile controls. (There are too many to list here.) These controls are described in detail in Chapter 7, "Targeting Mobile Devices with Mobile Controls."


Adding Application Logic to an ASP.NET Page

The second building block of an ASP.NET page is the application logic, which is the actual programming code in the page. You add application logic to a page to handle both control and page events.

If a user clicks a Button control within an HTML form, for example, the Button control raises an event (the Click event). Typically, you want to add code to the page that does something in response to this event. For example, when someone clicks the Button control, you might want to save the form data to a file or database.

Controls are not the only things that can raise events. An ASP.NET page itself raises several events every time it is requested. For example, whenever you request a page, the page's Load event is triggered. You can add application logic to the page that executes whenever the Load event occurs.

You learn how to handle both types of events in the following two sections.

NOTE

The distinction that I am making in this section between control and page events is slightly misleading. In fact, an ASP.NET page itself is a control within the ASP.NET Framework.

Handling Control Events

You handle an event raised by an ASP.NET control by associating a subroutine with the event. To see how these events work, start with a simple page that contains application logic for a Button control. The page in Listing 1.5 displays a message in a Label Web control when you click the button labeled Click Here!

Listing 1.5 DisplayMessage.aspx
<Script Runat="Server">

Sub btnSubmit_Click( s As Object, e As EventArgs )
  lblMessage.Text = "Hello World!"
End Sub

</Script>

<html>
<head><title>DisplayMessage.aspx</title></head>
<body>

Click on the button to view the message:

<form Runat="Server">

<asp:Button
  id="btnSubmit"
  Text="Click Here!"
  OnClick="btnSubmit_Click"
  Runat="Server" />

<p>

<asp:Label id="lblMessage" Runat="Server" />

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

When you click the form button, the subroutine named btnSubmit_Click is executed. This subroutine assigns the text "Hello World!" to the Label control with the ID lblMessage. When the page is redisplayed, the message "Hello World!" appears on the page (see Figure 1.3).

Figure 1.3. Output of the DisplayMessage.aspx page.

The btnSubmit_Click subroutine is associated with the Button control through the control's OnClick method. You can name the subroutine anything you want; there's nothing magic about the name btnSubmit_Click.

NOTE

In this book, I'll stick to the convention of naming event-handling subroutines by using the name of the control that raises the event and the type of event being raised. See the Introduction for more naming conventions.

The btnSubmit_Click subroutine is declared within the <Script Runat="Server"> tags at the top of the page. Notice that the subroutine accepts two parameters of type Object and EventArgs. I'll explain the significance of these parameters in a moment.

The "Hello World!" text is assigned to the Label control named lblMessage with the following line of code:

lblMessage.Text = "Hello World!"

This line of code assigns the text "Hello World!" to the Label control's Text property. The text is displayed within the label when the page is displayed (the Label control renders the text within a standard HTML <span> tag).

You'll quickly become accustomed to declaring subroutines within your ASP.NET pages that look like this:

Sub btnSubmit_Click( s As Object, e As EventArgs )
End Sub

Whenever you need to wire code to respond to a button click, you use a similar subroutine. The first argument passed to the subroutine represents the object that raised the event. In Listing 1.5, the button control with the ID btnSubmit triggered the event.

You can use the Object parameter to determine the particular object that triggered the subroutine and respond accordingly. For example, the page in Listing 1.6 contains two Button controls that are wired to the same subroutine. Even though both buttons are associated with the same subroutine, clicking the different buttons has different effects.

Listing 1.6 HelloGoodbye.aspx
<Script Runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  If s.id = "btnHello" Then
    lblMessage.Text = "Hello!"
  Else
    lblMessage.Text = "Goodbye!"
  End If
End Sub

</Script>

<html>
<head><title>HelloGoodbye.aspx</title></head>
<body>

Click on the button to view the message:

<form Runat="Server">

<asp:button
  id="btnHello"
  Text="Say Hello!"
  OnClick="Button_Click"
  Runat="Server" />

<asp:button
  id="btnGoodbye"
  Text="Say Goodbye!"
  OnClick="Button_Click"
  Runat="Server" />

<p>

<asp:Label id="lblMessage" Runat="Server" />

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

The subroutine in Listing 1.6 detects which button was clicked by checking the ID of the object that raised the subroutine. If the object has an ID of btnHello, the text "Hello!" is displayed. Otherwise, the text "Goodbye!" is displayed.

The second parameter passed to the Button_Click subroutine is the EventArgs parameter, which contains information specific to the event that was raised. In the case of the Button control, the EventArgs parameter does not have any properties.

An example of a control that does pass a meaningful EventArgs parameter is the ImageButton control. When you click an ImageButton control, the EventArgs parameter includes information about the precise pixel coordinates on the button where you clicked. In Listing 1.7, an ImageButton control displays a simple image (a picture of a dartboard). When you click the image, the precise x,y coordinate of your click is displayed (see Figure 1.4).

Listing 1.7 Dartboard.aspx
<Script Runat="Server">

Sub ImageButton_Click( s As Object, e As ImageClickEventArgs )
  lblXLocation.Text = e.X
  lblYLocation.Text = e.Y
  If e.X = 100 and e.Y = 100 Then
    lblMessage.Text = "!!! You Win !!!"
  Else
    lblMessage.Text = "You Missed!"
  End If
End Sub

</Script>

<html>
<head><title>Dartboard.aspx</title></head>
<body>

Hit the dartboard:

<form Runat="Server">

<asp:ImageButton
  Src="target.gif"
  OnClick="ImageButton_Click"
  Runat="Server" />

<p>

X Coordinate:
<asp:Label
  id="lblXLocation"
  Runat="Server" />

<p>

Y Coordinate:
<asp:Label
  id="lblYLocation"
  Runat="Server" />

<p>

<asp:Label id="lblMessage" Runat="Server" />

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 1.4. Output of the Dartboard.aspx page.

In Listing 1.7, the X property of the ImageClickEventArgs parameter contains the x coordinate of the click, and the Y property contains the y coordinate.

Handling Page Events

An ASP.NET page itself raises several events whenever it is requested. You can add application logic to a page that is executed whenever each of these events occurs. The sequence of events raised by a page is illustrated in Listing 1.8.

Listing 1.8 PageEvents.aspx
<Script Runat="Server">

Sub Page_Init
  lblMessage.Text &= "<li> Init Event"
End Sub

Sub Page_Load
  lblMessage.Text &= "<li> Load Event"
End Sub

Sub Page_PreRender
  lblMessage.Text &= "<li> PreRender Event"
End Sub

Sub Page_Unload
  lblMessage.Text &= "<li> Unload Event"
End Sub

</Script>

<html>
<head><title>PageEvents.aspx</title></head>
<body>

<form Runat="Server">

<asp:Label id="lblMessage" Runat="Server" />

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

The page in Listing 1.8 adds a message to the Label control named lblMessage when each of the four page events occurs. The four events handled on the page are the Init, Load, PreRender, and Unload events.

NOTE

The message added by the Page_Unload subroutine is never displayed because the Unload event occurs after any content has been rendered to the browser.

The most useful of the page events is the Load event. You can use this event to initialize any of the variables and the state of any of the controls that you use in a page. For example, the page in Listing 1.9 displays a random number in a Label control; the random number is assigned to the label in the Page_Load subroutine.

Listing 1.9 PageLoad.aspx
<Script Runat="Server">

Sub Page_Load
  Dim RanNum As New Random
  lblMessage.Text = RanNum.Next( 1, 11 )
End Sub

</Script>

<html>
<head><title>PageLoad.aspx</title></head>
<body>

The random number is:
<asp:label id="lblMessage" Runat="Server"/>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Every time the page in Listing 1.9 is opened in a browser, a new random number is displayed on the page. The Page_Load subroutine contains two statements. The first statement declares and initializes a variable named RanNum that represents the random number. In the second statement, the Next() method of the Random class returns a random number between 1 and 10 and assigns the number to the Label control.

NOTE

The Random class is part of the System namespace. You can learn about the other methods and properties of the Random class by looking at the Reference Documentation for the Microsoft .NET Framework SDK.

Advanced Note

In most cases, the only page event that you'll use is the Load event. However, there are certain rare circumstances in which you'll need to use the PreRender event. The crucial difference between the Load event and the PreRender event is when each event occurs in relation to control events.

The Load event is raised before any control events are raised, and the PreRender event is raised after any control events are raised. For example, if you click a form button, any subroutine associated with the click event of the form button is raised after the Page_Load subroutine executes, but before the Page_PreRender subroutine executes.

The IsPostBack Property

You can use the Page.IsPostBack property with the Load event to execute code only when a page is first loaded. If you have a Web form on a page, you might want to initialize the variables and controls on a page only the first time the page is loaded, not every time the page is submitted.

Listing 1.10 illustrates how you can use the Page.IsPostBack property. Two random numbers are displayed in the page. The first random number is generated every time the page is loaded; the second random number is generated only once.

Listing 1.10 PageIsPostBack.aspx
<Script Runat="Server">

Sub Page_Load
  Dim RanNum As New Random
  lblFirstRanNum.Text = RanNum.Next( 1, 11 )
  If Not Page.IsPostBack Then
    lblSecondRanNum.Text = RanNum.Next( 1, 11 )
  End If
End Sub

</Script>

<html>
<head><title>IsPostBack.aspx</title></head>
<body>

<form Runat="Server">
The first random number is:
<asp:label id="lblFirstRanNum" Runat="Server"/>
<p>
The second random number is:
<asp:label id="lblSecondRanNum" Runat="Server"/>
<p>
<asp:button Text="Click Here!" Runat="Server"/>

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Clicking the button changes the value of the first number every time. However, the second number retains its value.

NOTE

Because the Page control is the default, using Page.IsPostBack is no different than using just IsPostBack.


The Structure of an ASP.NET Page

You are now in a position to examine the formal structure of an ASP.NET page. The following is a list of the important elements of an ASP.NET page:

  • Directives

  • Code declaration blocks

  • ASP.NET controls

  • Code render blocks

  • Server-side comments

  • Server-side include directives

  • Literal text and HTML tags

Each element is discussed in the following sections.

Directives

A directive controls how an ASP.NET page is compiled. The beginning of a directive is marked with the characters <%@ and the end of a directive is marked with the characters %>. A directive can appear anywhere within a page. By convention, however, a directive typically appears at the top of an ASP.NET page.

There are several types of directives that you can add to an ASP.NET page. Two of the most useful types are page and import directives.

Page Directives

You can use a page directive to specify the default programming language for a page. Page directives can also be used to enable tracing and debugging for a page.

To change the default programming language of an ASP.NET page from Visual Basic to C#, for example, you would use the following page directive:

<%@ Page Language="C#" %>

TIP

The keyword Page in a page directive is optional. The following two directives are equivalent:

<%@ Page Language="C#" %>

<%@ Language="C#" %>

Another extremely useful page directive is the Trace directive. If you enable tracing for a page, additional information about the execution of the page is displayed along with the content of the page (see Figure 1.5). You can enable tracing for a page by including the following directive:

<%@ Page Trace="True" %>
Figure 1.5. Enabling page tracing.

After you enable tracing for a page, you can display trace messages by using two methods of the Trace class: Write() and Warn(). You can use either method to display a custom message in the trace information displayed at the bottom of the page. The only difference between the two methods is that the former method displays messages in black text, and the latter method displays messages in red text, which is easier to see.

The ASP.NET page in Listing 1.11 uses these methods to display various trace messages.

Listing 1.11 Trace.aspx
<%@ Page Trace="True" %>
<Script Runat="Server">

Sub Page_Load
  Dim strTraceMessage As String

  Trace.Warn( "Page_Load event executing!" )
  strTraceMessage = "Hello World!"
  Trace.Write( "The value of strTraceMessage is " & strTraceMessage )
End Sub

</Script>

<html>
<head><title>Trace.aspx</title></head>
<body>

<h2>Testing Page Trace</h2>

<% Trace.Warn( "Rendering page content!" ) %>

</body>
</html>

The C# version of this code can be found on the CD-ROM

To enable runtime error messages to be displayed on a page, you need to use the Debug directive. To display errors in your ASP.NET page, include the following directive:

<%@ Page Debug="True" %>

When you include this directive, if an error is encountered when processing the page, the error is displayed. In most cases, you also can view the source code for the exact statement that generated the error.

To enable both tracing and debugging for a page, combine the directives like this (the order of Debug and Trace is not important):

<%@ Page Debug="True" Trace="True" %>

NOTE

For more information on tracing and debugging ASP.NET pages see Chapter 18, "Application Tracing and Error Handling."

Import Directives

By default, only certain namespaces are automatically imported into an ASP.NET page. If you want to refer to a class that isn't a member of one of the default namespaces, then you must explicitly import the namespace of the class or you must use the fully qualified name of the class.

For example, suppose that you want to send an e-mail from an ASP.NET page by using the Send method of the SmtpMail class. The SmtpMail class is contained in the System.Web.Mail namespace. This is not one of the default namespaces imported into an ASP.NET page.

The easiest way to use the SmtpMail class is to add an import directive to your ASP.NET page to import the necessary namespace. The page in Listing 1.12 illustrates how to import the System.Web.Mail namespace and send an e-mail message.

Listing 1.12 ImportNamespace.aspx
<%@ Import Namespace="System.Web.Mail" %>

<Script Runat="Server">

Sub Page_Load
  Dim mailMessage As SmtpMail

  mailMessage.Send( _
    "bob@somewhere.com", _
    "alice@somewhere.com", _
    "Sending Mail!", _
    "Hello!" )
End Sub

</Script>

<html>
<head><title>ImportNamespace.aspx</title></head>
<body>

<h2>Email Sent!</h2>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

The first line in Listing 1.12 contains an import directive. Without the import directive, the page would generate an error because it would not be able to resolve the SmtpMail class.

Instead of importing the System.Web.Mail namespace with the import directive, you could alternatively use its fully qualified name. In that case, you would declare an instance of the class like this:

Dim mailMessage As System.Web.Mail.SmtpMail

Code Declaration Blocks

A code declaration block contains all the application logic for your ASP.NET page and all the global variable declarations, subroutines, and functions. It must appear within a <Script Runat="Server"> tag.

Note that you can declare subroutines and functions only within a code declaration block. You receive an error if you attempt to define a function or subroutine in any other section of an ASP.NET page.

ASP Classic Note

In previous versions of ASP, you could declare a subroutine or function like this:

<%
Sub mySub
   ... subroutine code
End Sub
%>

Don't try this declaration with ASP.NET because it generates an error. You must use the <Script> tag with ASP.NET like this:

<Script runat="Server">
Sub mySub
  ...subroutine code
End Sub
</Script>

The <Script Runat="Server"> tag accepts two optional attributes. First, you can specify the programming language to use within the <Script> tag by including a language attribute like this:

<Script Language="C#" Runat="Server">

If you don't specify a language, the language defaults to the one defined by the <%@ Page Language %> directive mentioned in the previous section. If no language is specified in a page, the language defaults to Visual Basic.

ASP Classic Note

In previous versions of Active Server Pages, you could include multiple languages in the same page by using the language attribute with the <Script> tag. This technique doesn't work with ASP.NET. You get an error if you attempt to use more than one language in the same page.

The second optional attribute of the <Script Runat="Server"> tag is SRC. You can use it to specify an external file that contains the contents of the code block. This is illustrated in Listings 1.13 and 1.14.

Listing 1.13 ExternalFile.aspx
<Script Runat="Server" SRC="ApplicationLogic.aspx"/>

<html>
<head><title>ExternalFile.aspx</title></head>
<body>

<form Runat="Server">

<asp:label id="lblMessage" Runat="Server"/>

<asp:Button
  Text="Click Here!"
  OnClick="Button_Click"
  Runat="Server"/>

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Listing 1.14 ApplicationLogic.aspx
Sub Button_Click( s As Object, e As EventArgs )
  lblMessage.Text = "Hello World!"
End Sub

The C# version of this code can be found on the CD-ROM.

The page in Listing 1.13, ExternalFile.aspx, uses the SRC attribute to include the code in Listing 1.14, ApplicationLogic.aspx.

ASP.NET Controls

ASP.NET controls can be freely interspersed with the text and HTML content of a page. The only requirement is that the controls should appear within a <form Runat= "Server"> tag. And, for certain tags such as <span Runat="Server"> and <ASP:Label Runat="Server"/>, this requirement can be ignored without any dire consequences.

One significant limitation of ASP.NET pages is that they can contain only one <form Runat="Server"> tag. This means that you cannot group ASP.NET into multiple forms on a page. If you try, you get an error.

Code Render Blocks

If you need to execute code within the HTML or text content of your ASP.NET page, you can do so within code render blocks. The two types of code render blocks are inline code and inline expressions. Inline code executes a statement or series of statements. This type of code begins with the characters <% and ends with the characters %>.

Inline expressions, on the other hand, display the value of a variable or method (this type of code is shorthand for Response.Write). Inline expressions begin with the characters <%= and end with the characters %>.

The ASP.NET page in Listing 1.15 illustrates how to use both inline code and inline expressions in an ASP.NET page.

Listing 1.15 CodeRender.aspx
<Script Runat="Server">
  Dim strSomeText As String

  Sub Page_Load
    strSomeText = "Hello!"
  End Sub
</Script>

<html>
<head><title>CodeRender.aspx</title></head>
<body>

<form Runat="Server">

The value of strSomeText is:
<%=strSomeText%>

<p>

<% strSomeText = "Goodbye!" %>
The value of strSomeText is:
<%=strSomeText%>

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Notice that you can use variables declared in the code declaration block within the code render block. However, the variable has to be declared with page scope. The variable could not, for example, be declared within the Page_Load subroutine.

Server-side Comments

You can add comments to your ASP.NET pages by using server-side comment blocks. The beginning of a server-side comment is marked with the characters <%-- and the end of the comment is marked with the characters --%>.

Server-side comments can be added to a page for the purposes of documentation. Note that you cannot see the contents of server-side comment tags, unlike normal HTML comment tags, by using the View Source command on your Web browser.

Server-side comments can also be useful when you're debugging an ASP.NET page. You can temporarily remove both ASP.NET controls and code render blocks from a page by surrounding these elements with server-side comments. The page in Listing 1.16 illustrates this use.

Listing 1.16 ServerComments.aspx
<Script Runat="Server">
  Dim strSomeText As String
  Sub Page_Load
    strSomeText = "Hello!"
  End Sub
</Script>

<html>
<head><title>ServerComments.aspx</title></head>
<body>

<form Runat="Server">

<%--
This is inside the comments
<asp:Label Text="hello!" Runat="Server" />
<%= strSomeText %>
--%>

This is outside the comments

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Server-side Include Directives

You can include a file in an ASP.NET page by using one of the two forms of the server-side include directive. If you want to include a file that is located in the same directory or in a subdirectory of the page including the file, you would use the following directive:

<!-- #INCLUDE file="includefile.aspx" -->

Alternatively, you can include a file by supplying the full virtual path. For example, if you have a subdirectory named myDirectory under the wwwroot directory, you can include a file from that directory like this:

<!-- #INCLUDE virtual="/myDirectory/includefile.aspx" -->

The include directive is executed before any of the code in a page. One implication is that you cannot use variables to specify the path to the file that you want to include. For example, the following directive would generate an error:

<!-- #INCLUDE file="<%=myVar%>" -->

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.

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
<Script Runat="Server">

  Sub Page_Load
    Dim litControl As LiteralControl

    For each litControl in Page.Controls
      litControl.Text = strReverse( litControl.Text )
    Next
  End Sub

</Script>

<html>
<head><title>Literal.aspx</title></head>
<body>

<b>This text is reversed</b>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 1.6. Reversing the contents of a page.

    [ Team LiB ]


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.


Chapter 2. Building Forms with Web Server Controls

IN THIS CHAPTER

  • Building Smart Forms

  • Controlling Page Navigation

  • Applying Formatting to Controls

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

None

Events

Description

None

The Label control has one important property: Text. You can assign any text or HTML content that you want 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:

<asp:Label
  ID="lblMessage"
  Text="Here is some text!"
  Runat="Server" />

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.aspx
<Script Runat="Server">

Sub Page_Load
  If TimeOfDay < #12:00pm# Then
    lblMessage.Text = "Good Morning!"
  Else
    lblMessage.Text = "Good Day!"
  End If
End Sub

</Script>

<html>
<head><title>Label.aspx</title></head>
<body>

<form Runat="Server">

<asp:Label
  ID="lblMessage"
  Runat="Server" />

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

In Listing 2.1, the Text property is assigned the value "Good Morning!" or the value "Good Day!" depending on the time of day.

The contents of a Label control are rendered within a <span> 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 <title> tag of your page would not be a good idea because the literal text <span> also would appear in the title.

NOTE

The LiteralControl is very similar to the Label control except for the fact that it does not render <span> tags. You can use the LiteralControl within the <title> tag of a page.

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

ReadOnly

Indicates whether the text box is read-only 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.

Listing 2.2 textbox.aspx
<html>
<head><title>TextBox.aspx</title></head>
<body>

<form Runat="Server">

Username:
<asp:TextBox
  id="txtUsername"
  Columns="30"
  Runat="Server" />

<p>
Password:
<asp:TextBox
  id="txtPassword"
  TextMode="password"
  Columns="30"
  Runat="Server" />

<p>
Comments:
<br>
<asp:TextBox
  id="txtComments"
  TextMode="multiline"
  Columns="30"
  Rows="10"
  Runat="Server" />

<p>
<asp:Button
  Text="Click Here!"
  Runat="Server" />

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

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.

NOTE

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 <input> 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 single-line 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.)

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:

<asp:TextBox
  ID="txtTextBox"
  Text="This is a default value!"
  Runat="Server" />

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
<Script Runat="Server">

Sub Page_Load( s As Object, e As EventArgs )
  If Not IsPostBack Then
    txtCountry.Text = "United States"
  End If
End Sub

</Script>

<html>
<head><title>TextBoxText.aspx</title></head>
<body>

<form Runat="Server">

Please enter the name of your country:
<br>
<asp:TextBox
  id="txtCountry"
  Runat="Server"/>

<asp:Button
  Text="Submit!"
  Runat="Server"/>

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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:

Comments.Text = "He said ""Good Morning!"" "

When the quotation marks are actually rendered on the page, the quotation marks appear as &quot; 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
<Script Runat="Server">

Sub Page_Load
  lblMessage.Text = ""
End Sub

Sub txtSomeText_TextChanged( s As Object, e As EventArgs )
  lblMessage.Text = "You changed the value!"
End Sub

</Script>

<html>
<head><title>TextBoxChanged.aspx</title></head>
<body>

<form Runat="Server">

<asp:TextBox
  ID="txtSomeText"
  Text="Apple"
  OnTextChanged="txtSomeText_TextChanged"
  Runat="Server"/>

<asp:Button
  Text="Submit!"
  Runat="Server"/>

<p>
<asp:Label
  id="lblMessage"
  Runat="Server" />

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

Listing 2.5 TextBoxAutoPostBack.aspx
<Script Runat="Server">

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

Sub CheckWin
  If txtBox1.Text = "Apple" _
    And txtBox2.Text="Apple" _
      And txtBox3.Text="Apple" Then
        lblMessage.Text = "You Win!"
  End If
End Sub

</Script>

<html>
<head><title>TextboxAutoPostBack.aspx</title></head>
<body>

<form Runat="Server">

Enter the word Apple into all three TextBoxes:

<p>
<asp:TextBox
  ID="txtBox1"
  AutoPostBack="True"
  onTextChanged="ChangeBox3"
  Runat="Server"/>

<p>
<asp:TextBox
  ID="txtBox2"
  AutoPostBack="True"
  OnTextChanged="ChangeBox1"
  Runat="Server"/>

<p>
<asp:TextBox
  ID="txtBox3"
  AutoPostBack="True"
  OnTextChanged="ChangeBox3"
  Runat="Server"/>

<p>
<asp:Label
  ID="lblMessage"
  Runat="Server" />

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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 Win!" is displayed in the Label control named lblMessage (see Figure 2.2).

Figure 2.2. The TextBox control's AutoPostBack property.

Limiting TextBox Input with Request Validation

ASP.NET 1.1 includes a new feature named Request Validation. When Request Validation is enabled (which it is by default), you cannot enter certain content into a TextBox control without receiving an error. For example, you cannot add HTML tags such as a <b> tag into a TextBox and you cannot enter potentially dangerous strings such as the string Expression( into a TextBox.

Request Validation was added to the ASP.NET Framework to handle security worries about cross-site scripting attacks. If users are able to enter arbitrary text into a TextBox, and that information is redisplayed on your Web site, then a user could execute a JavaScript script on your Web server that performs a malicious or obscene task. For example, if you are hosting a message board, then a user could post a message that automatically redirects anyone who views the message to the PlayBoy.com Web site.

NOTE

Request Validation is implemented behind the scenes by a private class named the CrossSiteScriptingValidation class (this class is located in the System.Web namespace). If you are curious about exactly which strings are illegal, then you can use the ILDASM tool to view the methods of this class.

There are certain situations, however, in which you'll need to disable Request Validation. If you want users to be able to enter HTML content into a TextBox then you can disable Request Validation at either a page or application level. To disable Request Validation for a page, enter the following directive at the top of the page:

<%@ ValidateRequest="False" %>

To disable Request Validation for an entire application, add the following <compilation> element to the <system.web> section of the application's root Web.Config file (the Web.Config file is described in Chapter 15, "Creating ASP.NET Applications"):

<pages validateRequest="false" />

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 an image form button

  • LinkButton— Displays 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.

The Button Control

By default, the Button control renders the same form submit button as rendered by the normal HTML <input type="Submit"> 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.

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 Button control's Click event. This subroutine adds one to the value of the Button control's Text property.

Listing 2.6 Button.aspx
<Script Runat="Server">

Sub btnCounter_Click( s As Object, e As EventArgs )
  btnCounter.Text += 1
End Sub

</Script>

<html>
<head><title>Button.aspx</title></head>
<body>

<form Runat="Server">

<asp:Button
  id="btnCounter"
  text="1"
  OnClick="btnCounter_Click"
  Runat="Server"/>

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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
<Script Runat="Server">

Sub Button_Command( s As Object, e As CommandEventArgs )
  Dim intAmount As Integer

  intAmount = e.CommandArgument
  If e.CommandName = "Add" Then
    lblCounter.Text += intAmount
  Else
    lblCounter.Text -= intAmount
  End If
End Sub

</Script>

<html>
<head><title>ButtonCommand.aspx</title></head>
<body>

<form Runat="Server">

<asp:Button
  Text="Add 5"
  CommandName="Add"
  CommandArgument="5"
  OnCommand="Button_Command"
  Runat="Server"/>

<asp:Button
  Text="Subtract 10"
  CommandName="Subtract"
  CommandArgument="10"
  OnCommand="Button_Command"
  Runat="Server"/>

<p>

<asp:Label
  id="lblCounter"
  text="1"
  Runat="Server"/>

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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 (the second parameter passed to 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.

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 to this event.

For example, Listing 2.8 displays a simple Click Here image button (see Figure 2.3).

Listing 2.8 ImageButton.aspx
<Script Runat="Server">

Sub ImageButton_Click( s As Object, e As ImageClickEventArgs )
  Dim RanNum As New Random
  Select Case RanNum.Next( 3 )
  Case 0
    lblMessage.Text = "Boom!"
  Case 1
    lblMessage.Text = "Zap!"
  Case 2
    lblMessage.Text = "Yikes!"
  End Select
End Sub

</Script>

<html>
<head><title>ImageButton.aspx</title></head>
<body>

<form Runat="Server">

<asp:ImageButton
  OnClick="ImageButton_Click"
  ImageUrl="clickhere.gif"
  Runat="Server"/>

<p>

<asp:Label
  id="lblMessage"
  Runat="Server"/>

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 2.3. The ImageButton control.

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.

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.

Listing 2.9 ImageButtonEventArgs.aspx
<Script Runat="Server">

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

</Script>

<html>
<head><title>ImageButtonEventArgs.aspx</title></head>
<body>

<form Runat="Server">

<asp:ImageButton
  OnClick="ImageButton_Click"
  ImageUrl="menu.gif"
  Runat="Server"/>

<p>

<asp:TextBox
  ID="txtSomeText"
  TextMode="MultiLine"
  Columns="50"
  Rows="10"
  Text="Type Some Text Here!"
  Runat="Server"/>

<hr>
<asp:Label
  id="lblMessage"
  Runat="Server"/>

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 2.4. The ImageButton control's X and Y properties.

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.

Methods

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
<Script Runat="Server">

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

</Script>

<html>
<head><title>LinkButton.aspx</title></head>
<body>

<form Runat="Server">

Enter your name to receive your fortune:
<p>
<asp:TextBox
  ID="txtUsername"
  Runat="Server"/>

<p>

<asp:LinkButton
  Text="Tell Me!"
  OnClick="LinkButton_Click"
  Runat="Server"/>

<hr>

<asp:Label
  ID="lblFortune"
  Runat="Server" />

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

Table 2.6. RadioButton Properties, Methods, and Events

Properties

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
<Script Runat="Server">

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

</Script>

<html>
<head><title>RadioButton.aspx</title></head>
<body>

<form Runat="Server">

Enter the name of your favorite city, please
select only one:

<p>
<asp:RadioButton
  ID="radSeattle"
  GroupName="city"
  Text="Seattle"
  Runat="Server" />

<br>
<asp:RadioButton
  ID="radSanFran"
  GroupName="city"
  Text="San Francisco"
  Runat="Server" />

<br>
<asp:RadioButton
  ID="radBoston"
  Checked="True"
  GroupName="city"
  Text="Boston"
  Runat="Server" />

<p>
<asp:Button
  Text="Pick City"
  OnClick="Button_Click"
  Runat="Server"/>

<p>
<asp:Label
  ID="lblCity"
  Runat="Server" />

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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
<Script Runat="Server">

Sub City_Changed( s As Object, e As EventArgs )
  lblCity.Text = s.Text
End Sub

</Script>

<html>
<head><title>RadioButtonChanged.aspx</title></head>
<body>

<form Runat="Server">

Enter the name of your favorite city, please
select only one:

<p>
<asp:RadioButton
  ID="radSeattle"
  GroupName="city"
  OnCheckedChanged="City_Changed"
  Text="Seattle"
  Runat="Server" />

<br>
<asp:RadioButton
  ID="radSanFran"
  GroupName="city"
  OnCheckedChanged="City_Changed"
  Text="San Francisco"
  Runat="Server" />

<br>
<asp:RadioButton
  ID="radBoston"
  GroupName="city"
  OnCheckedChanged="City_Changed"
  Text="Boston"
  Runat="Server" />

<p>
<asp:Button
  Text="Pick City"
  Runat="Server"/>

<p>
<asp:Label
  ID="lblCity"
  Runat="Server" />

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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

CAUTION

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.

CAUTION

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 AutoPostBack property has the value True for each RadioButton control, the page is automatically posted whenever a new radio button is selected.

Listing 2.13 RadioButtonAutoPostBack.aspx
<Script Runat="Server">

Dim strBackGroundColor As String = "white"

Sub BackColor_Changed( s As Object, e As EventArgs )
  strBackGroundColor = s.Text
End Sub

</Script>

<html>
<head><title>RadioButtonAutoPostBack.aspx</title></head>
<body bgcolor="<%= strBackGroundColor %>">

<form Runat="Server">

Select a background color:

<p>
<asp:RadioButton
  ID="radRed"
  AutoPostBack="True"
  GroupName="backcolor"
  OnCheckedChanged="BackColor_Changed"
  Text="Red"
  Runat="Server" />

<br>
<asp:RadioButton
  ID="radBlue"
  AutoPostBack="True"
  GroupName="backcolor"
  OnCheckedChanged="BackColor_Changed"
  Text="Blue"
  Runat="Server" />

<br>
<asp:RadioButton
  ID="radGreen"
  AutoPostBack="True"
  GroupName="backcolor"
  OnCheckedChanged="BackColor_Changed"
  Text="Green"
  Runat="Server" />

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

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.

SelectedValue

Gets or sets 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:

<asp:RadioButtonList
  ID="radlFavoriteColor"
  Runat="Server">
  <asp:ListItem Text="Red" />
  <asp:ListItem Text="Blue" Selected="True"/>
  <asp:ListItem Text="Green"/>
</asp:RadioButtonList>

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 RadioButtonList. For example, in Listing 2.14, three colors are added to a RadioButtonList in the Page_Load subroutine.

Listing 2.14 RadioButtonList.aspx
<Script Runat="Server">

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

</Script>

<html>
<head><title>RadioButtonlist.aspx</title></head>
<body>

<form Runat="Server">

<asp:RadioButtonList
  ID="radlFavoriteColor"
  Runat="Server"/>

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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
<Script Runat="Server">

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

</Script>

<html>
<head><title>RadioButtonListDataBind.aspx</title></head>
<body>

<form Runat="Server">

<asp:RadioButtonList
  ID="radlFavoriteColor"
  Runat="Server"/>

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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:

<asp:RadioButtonList
  ID="radlFavoriteColor"
  DataTextFormatString="The color is {0}"
  Runat="Server">
</asp:RadioButtonList>

To learn more about format strings see Chapter 24.

Detecting the Selected Item in a RadioButtonList

You can detect which radio button is checked in a RadioButtonList in three ways. You can use the SelectedIndex, SelectedItem, or SelectedValue 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 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 radio button.

The SelectedValue property can also be used to return the Text property for the selected radio button. Unlike the SelectedItem property, however, you can also use the SelectedValue property to set which radio button in the RadioButtonList is currently checked.

NOTE

The SelectedValue property is new with ASP.NET 1.1. 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
<Script Runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  lblSelectedIndex.Text = radlFavoriteColor.SelectedIndex
  lblSelectedItem.Text = radlFavoriteColor.SelectedItem.Text
End Sub

</Script>

<html>
<head><title>RadioButtonListSelected.aspx</title></head>
<body>

<form Runat="Server">

<asp:RadioButtonList
  ID="radlFavoriteColor"
  Runat="Server">
  <asp:ListItem Text="Red" />
  <asp:ListItem Text="Blue" Selected="True"/>
  <asp:ListItem Text="Green"/>
</asp:RadioButtonList>

<asp:Button
  Text="Pick A Color!"
  OnClick="Button_Click"
  Runat="Server" />

<hr>

Selected Index:
<asp:Label
  ID="lblSelectedIndex"
  Runat="Server" />

<p>

Selected Item:
<asp:Label
  ID="lblSelectedItem"
  Runat="Server" />

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

You can assign the Value property to list items when you create a RadioButtonList like this:

<asp:RadioButtonList
  ID="radlStateOfResidence"
  Runat="Server">
  <asp:ListItem
    Text="California"
    Value="CA" />
  <asp:ListItem
    Text="Massachusetts"
    Value="MA"
    Selected="True"/>
  <asp:ListItem
    Text="Ohio"
    Value="OH" />
</asp:RadioButtonList>

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
<Script Runat="Server">

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

</Script>

<html>
<head><title>RadioButtonListValue.aspx</title></head>
<body>

<form Runat="Server">

<asp:RadioButtonList
  ID="radlStateOfResidence"
  Runat="Server"/>

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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
<%@ Import namespace="System.Data" %>
<Script runat="Server">

Sub Page_Load
  If Not IsPostBack Then
    Dim dtblStates As New DataTable
    Dim drowNewState As DataRow

    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

</script>

<html>
<head><title>RadioButtonListDataValueField.aspx</title></head>
<body>

<form Runat="Server">

<asp:RadioButtonList
  ID="radlStateOfResidence"
  Runat="Server"/>

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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
<Script Runat="Server">

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

</Script>

<html>
<head><title>RadioButtonListValueSelected.aspx</title></head>
<body>

<form Runat="Server">

<asp:RadioButtonList
  ID="radlStateOfResidence"
  Runat="Server">
  <asp:ListItem
    Text="California"
    Value="CA" />
  <asp:ListItem
    Text="Massachusetts"
    Value="MA"
    Selected="True"/>
  <asp:ListItem
    Text="Ohio"
    Value="OH" />
</asp:RadioButtonList>

<p>

<asp:Button
  Text="Select State!"
  OnClick="Button_Click"
  Runat="Server" />

<hr>

Selected Index:
<asp:Label
  ID="lblSelectedIndex"
  Runat="Server" />

<p>
Selected Text:

<asp:Label
  ID="lblSelectedText"
  Runat="Server" />

<p>
Selected Value:
<asp:Label
  ID="lblSelectedValue"
  Runat="Server" />

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Selecting a Default Radio Button in a RadioButtonList

You can check a particular radio button in a RadioButtonList by taking advantage of either the SelectedIndex or SelectedValue property. Use the SelectedIndex property to select a radio button by its index and use the SelectedValue property to select a radio button by its value.

For example, the following line of code causes the second radio button in a RadioButtonList named radlColors to be checked (the ListItemCollection is zero based):

radlColors.SelectedIndex = 1

The following line of code causes the radio button labeled Green to be checked:

radlColors.SelectedValue = "green"

Some things to be aware of when using the SelectedValue property: First, the SelectedValue property is case-sensitive. So, assigning the value green is different than assigning the value GREEN. Second, if more than one list item has the same value, then the SelectedValue property will select only the first list item.

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

Figure 2.6. Displaying a RadioButtonList with multiple columns.

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.

Listing 2.20 RadioButtonListColumns.aspx
<Script Runat="Server">

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

</Script>

<html>
<head><title>RadioButtonListColumns.aspx</title></head>
<body>

<form Runat="Server">

<asp:RadioButtonList
  ID="radlRadioButtons"
  RepeatColumns="3"
  RepeatDirection="Vertical"
  Runat="Server"/>

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

The RadioButtonList Control's AutoPostBack Property

If you enable the AutoPostBack property of the RadioButtonList control, the form containing the RadioButtonList is submitted automatically whenever a new radio button is checked.

CAUTION

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
<Script Runat="Server">

Dim strBackGroundColor As String = "white"

Sub radlColors_SelectedIndexChanged( s As Object, e As EventArgs )
  strBackGroundColor = radlColors.SelectedItem.Text
End Sub

</Script>

<html>
<head><title>RadioButtonListAutoPostBack.aspx</title></head>
<body bgcolor="<%=strBackGroundColor%>">

<form Runat="Server">

<asp:RadioButtonList
  ID="radlColors"
  AutoPostBack="True"
  OnSelectedIndexChanged="radlColors_SelectedIndexChanged"
  Runat="Server" >
  <asp:ListItem text="Red" />
  <asp:ListItem text="Green" />
  <asp:ListItem text="Blue" />
</asp:RadioButtonList>

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

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.

Listing 2.22 CheckBox.aspx
<Script Runat="Server">

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

</Script>

<html>
<head><title>CheckBox.aspx</title></head>
<body>

<form Runat="Server">

<asp:CheckBox
  ID="chkLikeSite"
  Text="Do you like this Web site?"
  Runat="Server" />

<p>

<asp:Button
  Text="Submit Answer!"
  OnClick="Button_Click"
  Runat="Server" />

<hr>

<asp:Label
  ID="lblResponse"
  Runat="Server" />

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

CAUTION

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
<Script Runat="Server">

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

</Script>

<html>
<head><title>CheckBoxAutoPostBack.aspx</title></head>
<body>

Select among the following formatting options:
<p>

<form Runat="Server">

<asp:CheckBox
  ID="chkFormatBold"
  Text="Bold"
  AutoPostBack="True"
  Runat="Server" />

<asp:CheckBox
  ID="chkFormatItalic"
  Text="Italic"
  AutoPostBack="True"
  Runat="Server" />

<hr>

<asp:Label
  ID="lblMessage"
  Text="When in the course of human events it becomes..."
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

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.

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

SelectedValue

Gets or sets 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.

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:

<asp:CheckBoxList
  ID="chklFavoriteFoods"
  Runat="Server">
  <asp:ListItem Text="Pizza" />
  <asp:ListItem Text="Hamburgers" Selected="True"/>
  <asp:ListItem Text="Pasta"/>
</asp:CheckBoxList>

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
<Script Runat="Server">

Sub Page_Load
  If Not IsPostBack Then
    chklFavoriteFoods.Items.Add( "Pizza" )
    chklFavoriteFoods.Items.Add( "Hamburgers" )
    chklFavoriteFoods.Items.Add( "Pasta" )
  End If
End Sub

</Script>

<html>
<head><title>CheckBoxList.aspx</title></head>
<body>
<form Runat="Server">

<asp:CheckBoxList
  ID="chklFavoriteFoods"
  Runat="Server"/>

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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
<Script Runat="Server">

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

</Script>

<html>
<head><title>CheckBoxListDataBind.aspx</title></head>
<body>

<form Runat="Server">

<asp:CheckBoxList
  ID="chklFavoriteFoods"
  Runat="Server"/>

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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 DataBind() method is called. After this method is called, all the items in the ArrayList are loaded into the ListItem collection of the CheckBoxList.

NOTE

An ArrayList is a collection. Think of it as an array without any fixed size. Collections are discussed in detail in Chapter 24.

Detecting the Selected Item in a CheckBoxList

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.

You can also use the SelectedValue property to determine which check box is currently selected. Unlike the SelectedItem property, the SelectedValue property also enables you to set the selected check box.

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, the SelectedItem property returns the first item selected, and the SelectedValue property returns the value of 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
<Script Runat="Server">

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 &= "<li>" & itmFood.Text
    End If
  Next
  lblSelectedFoods.Text = strList
End Sub

</Script>

<html>
<head><title>CheckBoxListMultiSelect.aspx</title></head>
<body>

<form Runat="Server">

<asp:CheckBoxList
  ID="chklFavoriteFoods"
  Runat="Server">
  <asp:ListItem Text="Pizza" />
  <asp:ListItem Text="Hamburgers" Selected="True"/>
  <asp:ListItem Text="Pasta"/>
</asp:CheckBoxList>

<p>

<asp:Button
  Text="Submit!"
  OnClick="Button_Click"
  Runat="Server" />

<hr>

You picked:
<asp:Label
  ID="lblSelectedFoods"
  Runat="Server" />

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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:

<asp:CheckBoxList
  ID="chklProducts"
  Runat="Server">
  <asp:ListItem
    Text="Hair Dryer"
    Value="A8999" />
  <asp:ListItem
    Text="Shaving Cream"
    Value="S7777"
    Selected="True"/>
  <asp:ListItem
    Text="Electric Comb"
    Value="E23234" />
</asp:CheckBoxList>

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.

Listing 2.27 CheckBoxListValue.aspx
<Script Runat="Server">

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

</Script>

<html>
<head><title>CheckBoxListValue.aspx</title></head>
<body>

<form Runat="Server">

<asp:CheckBoxList
  ID="chklProducts"
  Runat="Server"/>

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

Listing 2.28 CheckBoxListDataValue.aspx
<Script runat="server">

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

</script>

<html>
<head><title>CheckBoxListDataValue.aspx</title></head>
<body>

<form Runat="Server">

<asp:CheckBoxList
  ID="chklProducts"
  Runat="Server"/>

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

Listing 2.29 CheckBoxListValueSelected.aspx
<Script Runat="Server">

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 &= "<li>" & itmProduct.Text
    strValueList &= "<li>" & itmProduct.Value
  End If
  Next
  lblSelectedText.Text = strTextList
  lblSelectedValue.Text = strValueList
End Sub

</Script>

<html>
<head><title>CheckBoxListValueSelected.aspx</title></head>
<body>

<form Runat="Server">

<asp:CheckBoxList
  id="chklProducts"
  Runat="Server">
  <asp:ListItem
    Text="Hair Dryer"
    Value="A8999" />
  <asp:ListItem
    Text="Shaving Cream"
    Value="S7777"
    Selected="True"/>
  <asp:ListItem
    Text="Electric Comb"
    Value="E23234" />
</asp:CheckBoxList>

<p>

<asp:Button
  Text="Select Products!"
  OnClick="Button_Click"
  Runat="Server" />

<hr>

Selected Text:

<asp:Label
  id="lblSelectedText"
  Runat="Server" />

<p>
Selected Value:
<asp:Label
  ID="lblSelectedValue"
  Runat="Server" />

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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
<Script Runat="Server">

Sub Page_Load
  Dim intCounter As Integer
  Dim strListItem As String

  If Not IsPostBack Then
    For intCounter = 1 To 50
    strListItem = "Checkbox " & intCounter
    chklCheckBoxList.Items.Add( strListItem )
    Next
  End If
End Sub

</Script>

<html>
<head><title>CheckBoxListColumns.aspx</title></head>
<body>

<form Runat="Server">

<asp:CheckBoxList
  ID="chklCheckBoxList"
  RepeatColumns="3"
  RepeatDirection="Vertical"
  Runat="Server"/>

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

CAUTION

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.

Listing 2.31 CheckBoxListAutoPostBack.aspx
<Script Runat="Server">

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 &= "<li>" & itmFood.Text
    End If
  Next
  lblFoodList.Text = strList
End Sub

</Script>

<html>
<head><title>CheckBoxListAutoPostBack.aspx</title></head>
<body>

<form Runat="Server">

<asp:CheckBoxList
  ID="chklFavoriteFoods"
  AutoPostBack="True"
  OnSelectedIndexChanged="chklFavoriteFoods_SelectedIndexChanged"
  Runat="Server">
  <asp:ListItem Text="Pizza" />
  <asp:ListItem Text="Hamburgers" Selected="True"/>
  <asp:ListItem Text="Pasta"/>
</asp:CheckBoxList>

<hr>

You like to eat:
<asp:Label
  ID="lblFoodList"
  Runat="Server" />

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Using the DropDownList Control

A DropDownList control is similar to a RadioButtonList control except for the fact that it occupies less screen real estate. Like a RadioButtonList, a DropDownList can represent a set of mutually exclusive options. Unlike a RadioButtonList, a DropDownList's options are displayed in a single-line picklist widget. All the properties, methods, and events of this control are listed in Table 2.10.

Table 2.10. DropDownList Properties, Methods, and Events

Properties

Description

AutoPostBack

When True, automatically posts the form containing the DropDownList 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 DropDownList.

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

SelectedIndex

Specifies the index number of the selected option.

SelectedItem

Represents the selected option.

SelectedValue

Gets or sets the selected option.

Method

Description

DataBind

Binds the DropDownList 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 DropDownList.

A DropDownList control contains a ListItemCollection collection that represents the options displayed in the DropDownList. Each list item in the collection represents one option.

You can add options to a DropDownList in three ways: You can list the options when you declare the DropDownList control, you can add items directly to the ListItemCollection collection of the DropDownList control, or you can bind a DropDownList to a data source.

First, you can simply list the options as list items when you declare the control like this:

<asp:DropDownList
  ID="dropCategory"
  Runat="Server">
  <asp:ListItem Text="Country Music" />
  <asp:ListItem Text="Rock Music" Selected="True"/>
  <asp:ListItem Text="Classical Music"/>
</asp:DropDownList>

This DropDownList contains three options: one labeled Country Music, one labeled Rock Music, and one labeled Classical Music. The Rock Music option is selected by default.

Second, you can add list items directly to the ListItemCollection collection of a DropDownList. For example, in Listing 2.32, three options are added to a DropDownList in the Page_Load subroutine.

Listing 2.32 DropDownList.aspx
<Script Runat="Server">

Sub Page_Load
  If Not IsPostBack Then
    dropCategory.Items.Add( "Country Music" )
    dropCategory.Items.Add( "Rock Music" )
    dropCategory.Items.Add( "Classical Music" )
  End If
End Sub

</Script>

<html>
<head><title>DropDownList.aspx</title></head>
<body>
<form Runat="Server">

<asp:DropDownList
  ID="dropCategory"
  Runat="Server"/>

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Finally, you can bind the DropDownList 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 DropDownList in Listing 2.33.

Listing 2.33 DropDownListDataBind.aspx
<Script Runat="Server">

Sub Page_Load
  Dim colArrayList As New ArrayList

  If Not IsPostBack Then
    colArrayList.Add( "Country Music" )
    colArrayList.Add( "Rock Music" )
    colArrayList.Add( "Classical Music" )

    dropCategory.DataSource = colArrayList
    dropCategory.DataBind()
  End If
End Sub

</Script>

<html>
<head><title>DropDownListDataBind.aspx</title></head>
<body>

<form Runat="Server">

<asp:DropDownList
  ID="dropCategory"
  Runat="Server"/>

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

In the Page_Load subroutine in Listing 2.33, an ArrayList named colArrayList is created. The values Country Music, Rock Music, and Classical Music are added to the ArrayList. Next, the ArrayList is assigned to the DropDownList 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 DropDownList.

NOTE

An ArrayList is a collection. Think of it as an array without any fixed size. Collections are discussed in detail in Chapter 24.

Detecting the Selected Item in a DropDownList

You can detect which option is selected in a DropDownList in three ways. You can use the SelectedIndex, SelectedItem, or SelectedValue property.

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.

Finally, the SelectedValue property returns the value of the currently selected option. Unlike the SelectedItem property, you can use the SelectedValue property to set the currently selected option in addtition to reading it.

When you select an option in Listing 2.34, for example, the Button_Click subroutine is executed, and the SelectedIndex and SelectedItem properties for the selected option are displayed.

Listing 2.34 DropDownListSelected.aspx
<Script Runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  lblSelectedIndex.Text = dropCategory.SelectedIndex
  lblSelectedItem.Text = dropCategory.SelectedItem.Text
End Sub

</Script>

<html>
<head><title>DropDownListSelected.aspx</title></head>
<body>

<form Runat="Server">

<asp:DropDownList
  ID="dropCategory"
  Runat="Server">
  <asp:ListItem Text="Country Music" />
  <asp:ListItem Text="Rock Music" Selected="True"/>
  <asp:ListItem Text="Classical Music"/>
</asp:DropDownList>

<asp:Button
  Text="Pick A Category!"
  OnClick="Button_Click"
  Runat="Server" />

<hr>

Selected Index:
<asp:Label
  ID="lblSelectedIndex"
  Runat="Server" />

<p>

Selected Item:
<asp:Label
  ID="lblSelectedItem"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Assigning Different Text and Values to a DropDownList

Imagine that you are using a DropDownList to display a list of categories. For example, you might display a list of music category options, such as Country Music, Rock Music, and Classical Music. Now, imagine that you want the DropDownList to return a category ID rather than the full category name when an option 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 DropDownList like this:

<asp:DropDownList
  ID="dropCategory"
  Runat="Server">
  <asp:ListItem
    Text="Country Music"
    Value="country" />
  <asp:ListItem
    Text="Rock Music"
    Value="rock"
    Selected="True"/>
  <asp:ListItem
    Text="Classical Music"
    Value="classic" />
</asp:DropDownList>

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
<Script Runat="Server">

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

</Script>

<html>
<head><title>DropDownListValue.aspx</title></head>
<body>

<form Runat="Server">

<asp:DropDownList
  ID="dropCategory"
  Runat="Server"/>

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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
<%@ Import namespace="System.Data" %>
<Script runat="Server">

Sub Page_Load
  If Not IsPostBack Then
    Dim dtbMusic As New DataTable
    Dim drowNewRows As DataRow

    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 )

    dropCategory.DataSource = dtbMusic
    dropCategory.DataTextField = "CategoryName"
    dropCategory.DataValueField = "CategoryID"
    dropCategory.DataBind
  End If
End Sub

</script>

<html>
<head><title>DropDownListDataValue.aspx</title></head>
<body>

<form Runat="Server">

<asp:DropDownList
  ID="dropCategory"
  Runat="Server"/>

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

Listing 2.37 DropDownListValueSelected.aspx
<Script Runat="Server">

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

</Script>

<html>
<head><title>DropDownListValueSelected.aspx</title></head>
<body>

<form Runat="Server">

<asp:DropDownList
  ID="dropCategory"
  Runat="Server">
  <asp:ListItem
    Text="Country Music"
    Value="country" />
  <asp:ListItem
    Text="Rock Music"
    Value="rock"
    Selected="True"/>
  <asp:ListItem
    Text="Classical Music"
    Value="classic" />
</asp:DropDownList>

<asp:Button
  Text="Select Category!"
  OnClick="Button_Click"
  Runat="Server" />

<hr>

Selected Index:
<asp:Label
  ID="lblSelectedIndex"
  Runat="Server" />

<p>
Selected Text:

<asp:Label
  ID="lblSelectedText"
  Runat="Server" />

<p>
Selected Value:
<asp:Label
  ID="lblSelectedValue"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Selecting a Default Option in a DropDownList

You can select a particular" option in a DropDownList by taking advantage of either the SelectedIndex or SelectedValue property. Use the SelectedIndex property to select an option by its index and use the SelectedValue property to select an option by its value.

For example, the following line of code causes the second option in a DropDownList named dropCategory to be selected (the ListItemCollection is zero based):

dropCategory.SelectedIndex = 1

The following line of code causes the option labeled Rock Music to be selected:

dropCategory.SelectedValue = "Rock Music"

Some things to be aware of when using the SelectedValue property: First, the SelectedValue property is case-sensitive. So, assigning the value rock music is different than assigning the value ROCK MUSIC. Second, if more than one list item has the same value, then the SelectedValue property will select only the first list item.

The DropDownList Control's AutoPostBack Property

If you enable the" AutoPostBack property of the DropDownList control, the form containing the DropDownList is submitted automatically whenever a new option is selected.

CAUTION

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
<Script Runat="Server">

Dim strBackGroundColor As String = "white"

Sub dropColors_SelectedIndexChanged( s As Object, e As EventArgs )
  strBackGroundColor = dropColors.SelectedItem.Text
End Sub

</Script>

<html>
<head><title>DropDownListAutoPostBack.aspx</title></head>
<body bgcolor="<%=strBackGroundColor%>">

<form Runat="Server">

<asp:DropDownList
  ID="dropColors"
  AutoPostBack="True"
  OnSelectedIndexChanged="dropColors_SelectedIndexChanged"
  Runat="Server" >
  <asp:ListItem text="Red" />
  <asp:ListItem text="Green" />
  <asp:ListItem text="Blue" />
</asp:DropDownList>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

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.

SelectedValue

Gets or sets the selected list item.

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:

<asp:ListBox
  ID="lstProducts"
  Runat="Server">
  <asp:ListItem Text="Hair Dryer" />
  <asp:ListItem Text="Shaving Cream" Selected="True"/>
  <asp:ListItem Text="Electric Comb"/>
</asp:ListBox>

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.

Listing 2.39 ListBox.aspx
<Script Runat="Server">

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

</Script>

<html>
<head><title>ListBox.aspx</title></head>
<body>
<form Runat="Server">

<asp:ListBox
  ID="lstProducts"
  Runat="Server"/>

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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
<Script Runat="Server">

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

</Script>

<html>
<head><title>ListBoxDataBind.aspx</title></head>
<body>

<form Runat="Server">

<asp:ListBox
  ID="lstProducts"
  Runat="Server"/>

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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
<html>
<head><title>ListBoxMultiSelect.aspx</title></head>
<body>

<form Runat="Server">

<asp:ListBox
  id="lstProducts"
  SelectionMode="Multiple"
  Runat="Server">
  <asp:ListItem Text="Hair Dryer" />
  <asp:ListItem Text="Shaving Cream" Selected="True"/>
  <asp:ListItem Text="Electric Comb"/>
</asp:ListBox>

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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 the SelectedIndex, SelectedItem, or SelectedValue 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.

Finally, the SelectedValue property returns the value of the selected item. Unlike the SelectedItem property, you can use the SelectedValue property to set a list item as the selected item.

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
<Script Runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  lblSelectedIndex.Text = lstProducts.SelectedIndex
  lblSelectedItem.Text = lstProducts.SelectedItem.Text
End Sub

</Script>

<html>
<head><title>ListBoxSelected.aspx</title></head>
<body>

<form Runat="Server">

<asp:ListBox
  ID="lstProducts"
  Runat="Server">
  <asp:ListItem Text="Hair Dryer" />
  <asp:ListItem Text="Shaving Cream" Selected="True"/>
  <asp:ListItem Text="Electric Comb"/>
</asp:ListBox>

<asp:Button
  Text="Pick A Product!"
  OnClick="Button_Click"
  Runat="Server" />

<hr>

Selected Index:
<asp:Label
  ID="lblSelectedIndex"
  Runat="Server" />

<p>

Selected Item:
<asp:Label
  ID="lblSelectedItem"
  Runat="Server" />

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

Listing 2.43 ListBoxSelectedMultiple.aspx
<Script Runat="Server">

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 &= "<li>" & itmProduct.Text
    End If
  Next
  lblList.Text = strList
End Sub

</Script>

<html>
<head><title>ListBoxSelectedMultiple.aspx</title></head>
<body>

<form Runat="Server">

<asp:ListBox
  ID="lstProducts"
  SelectionMode="Multiple"
  Runat="Server">
  <asp:ListItem Text="Hair Dryer" />
  <asp:ListItem Text="Shaving Cream" Selected="True"/>
  <asp:ListItem Text="Electric Comb"/>
</asp:ListBox>

<asp:Button
  Text="Pick Multiple Products!"
  OnClick="Button_Click"
  Runat="Server" />

<hr>

Selected Item(s):
<asp:Label
  ID="lblList"
  Runat="Server" />

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Assigning Different Text and Values to a ListBox

Imagine that you are using a list box to display a list of states. For example, you might want to provide a user with the choice of whether to list a resume in California, Massachusetts, and/or Ohio. Now, imagine that you want the list box to return the state abbreviation rather than the full state name when an option 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 an option in a list box like this:

<asp:ListBox
  ID="lstStateOfResidence"
  Runat="Server">
  <asp:ListItem
    Text="California"
    Value="CA" />
  <asp:ListItem
    Text="Massachusetts"
    Value="MA"
    Selected="True"/>
  <asp:ListItem
    Text="Ohio"
    Value="OH" />
</asp:ListBox>

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

Listing 2.44 ListBoxValue.aspx
<Script Runat="Server">

Sub Page_Load
  If Not IsPostBack Then
    lstStateOfResidence.Items.Add( New ListItem( "California", "CA" ) )
    lstStateOfResidence.Items.Add( New ListItem( "Massachusetts", "MA" ) )
    lstStateOfResidence.Items.Add( New ListItem( "Ohio", "OH" ) )
  End If
End Sub

</Script>

<html>
<head><title>ListBoxValue.aspx</title></head>
<body>

<form Runat="Server">

asp:ListBox
  ID="lstStateOfResidence"
  Runat="Server"/>

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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 before you bind to the data source. The page in Listing 2.45 demonstrates how you can use these properties when binding to a DataTable.

Listing 2.45 ListBoxDataValue.aspx
<%@ Import namespace="System.Data" %>
<Script Runat="server">

Sub Page_Load
  If Not IsPostBack Then
    Dim dtblStates As New DataTable
    Dim drowNewRow As DataRow

    dtblStates.Columns.Add(New DataColumn( "StateName", GetType( String ) ) )
    dtblStates.Columns.Add(New DataColumn( "StateAbbr", GetType( String ) ) )
    drowNewRow = dtblStates.NewRow()
    drowNewRow( "StateName" ) = "California"
    drowNewRow( "StateAbbr" ) = "CA"
    dtblStates.Rows.Add( drowNewRow )
    drowNewRow = dtblStates.NewRow()
    drowNewRow( "StateName" ) = "Massachusetts"
    drowNewRow( "StateAbbr" ) = "MA"
    dtblStates.Rows.Add( drowNewRow )
    drowNewRow = dtblStates.NewRow()
    drowNewRow( "StateName" ) = "Ohio"
    drowNewRow( "StateAbbr" ) = "OH"
    dtblStates.Rows.Add( drowNewRow )

    lstStateOfResidence.DataSource = dtblStates
    lstStateOfResidence.DataTextField = "StateName"
    lstStateOfResidence.DataValueField = "StateAbbr"
    lstStateOfResidence.DataBind
  End If
End Sub

</script>

<html>
<head><title>ListBoxDataValue.aspx</title></head>
<body>

<form Runat="Server">

<asp:ListBox
  ID="lstStateOfResidence"
  Runat="Server"/>

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

After you specify a distinct Value property, you can use it in your code. When you click a radio button in Listing 2.46, the list item's Index, Text, and Value properties are displayed.

Listing 2.46 ListBoxValueSelected.aspx
<Script Runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
    lblSelectedIndex.Text = lstStateOfResidence.SelectedIndex
    lblSelectedText.Text = lstStateOfResidence.SelectedItem.Text
    lblSelectedValue.Text = lstStateOfResidence.SelectedItem.Value
  End Sub

</Script>

<html>
<head><title>ListBoxValueSelected.aspx</title></head>
<body>

<form Runat="Server">

<asp:ListBox
  ID="lstStateOfResidence"
  Runat="Server">
  <asp:ListItem
    Text="California"
    Value="CA" />
  <asp:ListItem
    Text="Massachusetts"
    Value="MA"
    Selected="True"/>
  <asp:ListItem
    Text="Ohio"
    Value="OH" />
</asp:ListBox>

<p>

<asp:Button
  Text="Select State!"
  OnClick="Button_Click"
  Runat="Server" />

<hr>

Selected Index:
<asp:Label
  ID="lblSelectedIndex"
  Runat="Server" />

<p>
Selected Text:
<asp:Label
  ID="lblSelectedText"
  Runat="Server" />

<p>
Selected Value:
<asp:Label
  ID="lblSelectedValue"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

CAUTION

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.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
<Script Runat="Server">

Dim strBackGroundColor As String = "white"

Sub lstColors_SelectedIndexChanged( s As Object, e As EventArgs )
  strBackGroundColor = lstColors.SelectedItem.Text
End Sub

</Script>

<html>
<head><title>ListboxAutoPostBack.aspx</title></head>
<body bgcolor="<%=strBackGroundColor%>">

<form Runat="Server">

<asp:ListBox
  ID="lstColors"
  AutoPostBack="True"
  OnSelectedIndexChanged="lstColors_SelectedIndexChanged"
  Runat="Server" >
  <asp:ListItem text="Red" />
  <asp:ListItem text="Green" />
  <asp:ListItem text="Blue" />
</asp:ListBox>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.


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.

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 <form> tag, you cannot post a form to a different page (the server-side form tag does not have an Action property).

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
<html>
<head><title>Form.aspx</title></head>
<body>

<form Method="Post" Action="Results.aspx">

Username:
<br>
<input name="username">
<p>
Comments:
<br>
<TextArea name="comments"
  cols="50" rows="10"></textarea>
<p>
<input type="submit">

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

In Listing 2.48, the Action attribute of the <form> 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.

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.

Listing 2.49 Results.aspx
<Script Runat="Server">

Sub Page_Load
  lblUsername.Text = Request.Params( "Username" )
  lblComments.Text = Request.Params( "Comments" )
End Sub

</Script>

<html>
<head><title>Results.aspx</title></head>
<body>

<asp:Label
  ID="lblUsername"
  Runat="Server" />

wrote

<asp:Label
  ID="lblComments"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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?

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
<Script Runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  ' Save Form Data to Database Table
  Response.Redirect( "ThankYou.aspx" )
End Sub

</Script>

<html>
<head><title>Redirect.aspx</title></head>
<body>

<form Runat="Server">

Username:
<br>
<asp:TextBox
  ID="txtUsername"
  Runat="Server" />
<p>
Comments:
<br>
<asp:TextBox
  ID="txtComments"
  TextMode="MultiLine"
  Runat="Server" />
<p>
<asp:Button
  Text="Submit!"
  OnClick="Button_Click"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

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

Properties

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
<Script Runat="Server">

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

</Script>

<html>
<head><title>HyperLink.aspx</title></head>
<body>

<form Runat="Server">

Click on the link below to receive
a list of contact phone numbers for help
information:

<p>

<asp:HyperLink
  id="lnkHelpLink"
  Runat="Server" />

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.


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.

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

Listing 2.52 BaseWebControl.aspx
<html>
<head><title>BaseWebControl.aspx</title></head>
<body>

<form Runat="Server">

Field1:
<br>
<asp:TextBox
  BackColor="yellow"
  ForeColor="Blue"
  AccessKey="1"
  BorderWidth="4"
  BorderStyle="Dashed"
  Runat="Server" />

<p>

Field 2
<br>
<asp:TextBox
  BackColor="yellow"
  ForeColor="Blue"
  AccessKey="2"
  ToolTip="Enter some data"
  Runat="Server" />

<p>

Field 3
<br>
<asp:TextBox
  TextMode="MultiLine"
  BackColor="yellow"
  ForeColor="Blue"
  Font-Name="Script"
  Font-Size="24pt"
  Width="100%"
  Height="100px"
  AccessKey="3"
  Runat="Server" />

<p>

<asp:Button
  Text="Click Here!"
  BackColor="LightGreen"
  ForeColor="Red"
  Font-Name="System"
  Font-Bold="True"
  Font-Size="10pt"
  BorderStyle="Double"
  Width="100"
  Height="100"
  Runat="Server" />

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 2.7. Using base Web control properties.

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.

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
<%@ Import Namespace="System.Drawing" %>
<Script Runat="Server">

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

</Script>

<html>
<head><title>BaseWebControlDynamic.aspx</title></head>
<body>

<form Runat="Server">

<asp:TextBox
  ID="txtTextBox"
  TextMode="MultiLine"
  Text="Here is some text"
  Runat="Server" />

<p>
BackColor:
<asp:DropDownList
  ID="dropBackColor"
  AutoPostBack="True"
  OnSelectedIndexChanged="dropBackColor_SelectedIndexChanged"
  Runat="Server">
  <asp:ListItem Text="Red" />
  <asp:ListItem Text="Blue" />
  <asp:ListItem Text="Green" />
</asp:DropDownList>

<p>
Font Size:
<asp:DropDownList
  ID="dropFontSize"
  AutoPostBack="True"
  OnSelectedIndexChanged="dropFontSize_SelectedIndexChanged"
  Runat="Server">
  <asp:ListItem Text="12pt" />
  <asp:ListItem Text="14pt" />
  <asp:ListItem Text="24pt" />
</asp:DropDownList>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

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:

<asp:Label
  ID="lblLabel"
  Style="Text-Transform:Capitalize"
  Text="this is some text"
  Runat="Server"/>

When this Label control is rendered, a <span> tag that includes the Style attribute is generated. The following tag is displayed:

<span id="lblLabel" style="Text-Transform:Capitalize">this is some text</span>

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.

Listing 2.54 Style.aspx
<html>
<head>

<Style>

.myClass
{
  text-align: justify;
  font: 14pt Script;
}

</Style>

<title>Style.aspx</title>
</head>
<body>

<form Runat="Server">

<asp:Label
  ID="lblLabel"
  Width="10"
  cssClass="myClass"
  Text="this is some text"
  Runat="Server"/>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

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
<Script Runat="Server">

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"
End Sub

</Script>

<html>
<head><title>StyleDynamic.aspx</title></head>
<body>

<form Runat="Server">

<asp:Label
  ID="lblLabel"
  Text="This is some text!"
  Runat="Server" />

<hr>

<asp:LinkButton
  id="lbtnCapitalize"
  Text="Capitalize!"
  OnClick="lbtnCapitalize_Click"
  Runat="Server" />

<asp:LinkButton
  id="lbtnUppercase"
  Text="Uppercase!"
  OnClick="lbtnUppercase_Click"
  Runat="Server" />

<asp:LinkButton
  id="lbtnLowercase"
  Text="Lowercase!"
  OnClick="lbtnLowercase_Click"
  Runat="Server" />

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 2.8. Applying different Style classes.

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.

Listing 2.56 CssClassDynamic.aspx
<Script Runat="Server">

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

</Script>

<html>
<head>

<STYLE>

.myClass1
{
  font: 18pt script;
  color: blue;
}

.myClass2
{
  font: 24pt verdana;
  color: red;
}

</STYLE>

<title>CssClassDynamic.aspx</title>
</head>
<body>

<form Runat="Server">

<asp:Label
  ID="myLabel"
  Text="Here is some text!"
  Runat="Server" />

<hr>

<asp:LinkButton
  id="lbtnScript"
  Text="Show Script!"
  OnClick="lbtnScript_Click"
  Runat="Server" />

<asp:LinkButton
  id="lbtnVerdana"
  Text="Show Verdana!"
  OnClick="lbtnVerdana_Click"
  Runat="Server" />

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

Listing 2.57 StyleClass.aspx
<%@ Import Namespace="System.Drawing" %>
<Script Runat="Server">

Sub Page_Load( s As Object, e As EventArgs )
  Dim myStyle As New Style

  myStyle.BackColor = Color.Yellow
  myStyle.ForeColor = Color.Green
  myStyle.BorderStyle = BorderStyle.Dashed
  myStyle.BorderWidth = New Unit(4)

  txtTextBox1.ApplyStyle( myStyle )
  txtTextBox2.ApplyStyle( myStyle )
  txtTextBox3.MergeStyle( myStyle )
End Sub

</Script>

<html>
<head><title>StyleClass.aspx</title></head>
<body>

<form Runat="Server">

<asp:TextBox
  id="txtTextBox1"
  BackColor="red"
  Text="Here is some text!"
  Runat="Server"/>

<p>

<asp:TextBox
  id="txtTextBox2"
  BackColor="red"
  Text="Here is some text!"
  Runat="Server"/>

<p>

<asp:TextBox
  id="txtTextBox3"
  BackColor="red"
  Text="Here is some text!"
  Runat="Server"/>

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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. Using the Style class.

    [ Team LiB ]


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.


Chapter 3. Performing Form Validation with Validation Controls

IN THIS CHAPTER

  • Using Client-side Validation

  • Requiring Fields: The RequiredFieldValidator Control

  • Validating Expressions: The RegularExpressionValidator Control

  • Comparing Values: The CompareValidator Control

  • Checking for a Range of Values: The RangeValidator Control

  • Performing Custom Validation: The CustomValidator Control

  • Disabling Validation

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

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

Figure 3.1. Error from missing validation file.

NOTE

The exact location of the WebUIValidation.js file is determined by your machine.config file (in the <webControls clientScriptsLocation> 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:

<%@ Page ClientTarget="downlevel" %>

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 <clientTarget> 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. Because all validation 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.

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.

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
<Script Runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  If IsValid Then
    Response.Redirect( "ThankYou.aspx" )
  End If
End Sub

</Script>

<html>
<head><title>RequiredFieldValidator.aspx</title></head>
<body>

<form Runat="Server">

Username:
<br><asp:TextBox
  ID="txtUsername"
  Runat="Server" />

<asp:RequiredFieldValidator
  ControlToValidate="txtUsername"
  Text="You must enter a username!"
  Runat="Server" />

<p>
Comments:
<br>
<asp:TextBox
  id="txtComments"
  TextMode="MultiLine"
  Runat="Server"/>

<asp:RequiredFieldValidator
  ControlToValidate="txtComments"
  Text="You must enter some comments!"
  Runat="Server" />

<p>

<asp:Button
  Text="Submit"
  OnClick="Button_Click"
  Runat="Server"/>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

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.

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:

<asp:RequiredFieldValidator
  ForeColor="Blue"
  Font-Name="Script"
  ControlToValidate="txtUsername"
  Text="You must enter a username!"
  Runat="Server" />

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
<html>
<head><title>RequiredFieldValidatorDisplay.aspx</title></head>
<body>

<form Runat="Server">

Field 1:
<br><asp:TextBox
  id="txtField1"
  Runat="Server" />

<asp:RequiredFieldValidator
  ControlToValidate="txtField1"
  Text="You must enter a value for field1!"
  Runat="Server" />

Here is Some Text

<p>

Field 2:
<br>
<asp:TextBox
  id="txtField2"
  Runat="Server"/>

<asp:RequiredFieldValidator
  ControlToValidate="txtField2"
  Text="You must enter a value for field2!"
  Display="Dynamic"
  Runat="Server" />

Here is Some Text

<p>

<asp:Button
  Text="Submit"
  Runat="Server"/>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

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.

Listing 3.3 RequiredFieldValidatorInitialValue.aspx
<Script Runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  If IsValid Then
    Response.Redirect( "ThankYou.aspx" )
  End If
End Sub

</Script>

<html>
<head><title>RequiredFieldValidatorInitialValue.aspx</title></head>
<body>

<form Runat="Server">

Comments:
<br>
<asp:TextBox
  id="txtComments"
  TextMode="MultiLine"
  Text="Enter Some Text"
  Runat="Server"/>

<asp:RequiredFieldValidator
  ControlToValidate="txtComments"
  Text="You must enter some comments!"
  InitialValue="Enter Some Text"
  Runat="Server" />

<p>

<asp:Button
  Text="Submit"
  OnClick="Button_Click"
  Runat="Server"/>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.


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

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

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.

CAUTION

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
<Script Runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  If IsValid Then
    Response.Redirect( "ThankYou.aspx" )
  End If
End Sub

</Script>

<html>
<head><title>RegularExpressionValidator.aspx</title></head>
<body>

<form Runat="Server">

Product Code:
<br>
<asp:TextBox
  id="txtProductCode"
  Runat="Server"/>

<asp:RegularExpressionValidator
  ControlToValidate="txtProductCode"
  Text="Invalid Product Code!"
  ValidationExpression="P[0-9]{4}"
  Runat="Server" />
<p>
<asp:Button
  Text="Submit"
  OnClick="Button_Click"
  Runat="Server"/>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

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

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:

steve@somewhere.com

steve@host.somewhere.com

steve_smith@somewhere.com

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
<Script Runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  If IsValid Then
    Response.Redirect( "ThankYou.aspx" )
  End If
End Sub

</Script>

<html>
<head><title>RegularExpressionvalidatorEmail.aspx</title></head>
<body>

<form Runat="Server">

Email Address:
<br>
<asp:TextBox
  id="txtEmail"
  Columns="50"
  Runat="Server"/>

<asp:RegularExpressionValidator
  ControlToValidate="txtEmail"
  Text="Invalid Email Address!"
  ValidationExpression="\S+@\S+\.\S{2,3}"
  Runat="Server" />
<p>
<asp:Button
  Text="Submit"
  OnClick="Button_Click"
  Runat="Server"/>

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Validating Usernames and Passwords

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.

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
<Script Runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  If IsValid Then
    Response.Redirect( "thankyou.aspx" )
  End If
End Sub

</Script>

<html>
<head><title>RegularExpressionValidatorPassowrd.aspx</title></head>
<body>

<form Runat="Server">

Password:
<br>
<asp:TextBox
  id="txtPassword"
  Columns="30"
  Runat="Server"/>

<asp:RequiredFieldValidator
  ControlToValidate="txtPassword"
  Display="Dynamic"
  Text="You must enter a password!"
  Runat="Server" />

<asp:RegularExpressionValidator
  ControlToValidate="txtPassword"
  Display="Dynamic"
  Text="Your password must contain between 3 and 20 characters!"
  ValidationExpression="\w{3,20}"
  Runat="Server" />

<asp:RegularExpressionValidator
  ControlToValidate="txtPassword"
  Display="Dynamic"
  Text="Your password must contain at least one number and letter!"
  ValidationExpression="[a-zA-Z]+\w*\d+\w*"
  Runat="Server" />
<p>
<asp:Button
  Text="Submit"
  OnClick="Button_Click"
  Runat="Server"/>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

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:

(555) 555-5555
555.555.5555
555 555-555

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
<Script Runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  If IsValid Then
    Response.Redirect( "ThankYou.aspx" )
  End If
End Sub

</Script>

<html>
<head><title>RegularExpressionValidatorPhone.aspx</title></head>
<body>

<form Runat="Server">

Phone Number:
<br>
<asp:TextBox
  id="txtPhone"
  Columns="30"
  Runat="Server"/>

<asp:RegularExpressionValidator
  ControlToValidate="txtPhone"
  Display="Dynamic"
  Text="Invalid Phone Number!"
  ValidationExpression="\(?\s*\d{3}\s*[\)\.\-]?\s*\d{3}\s*[\-\.]?\s*\d{4}"
  Runat="Server" />
<p>

<asp:Button
  Text="Submit"
  OnClick="Button_Click"
  Runat="Server"/>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

Listing 3.8 RegularExpressionValidatorWeb.aspx
<Script Runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  If IsValid Then
    Response.Redirect( "thankyou.aspx" )
  End If
End Sub

</Script>

<html>
<head><title>RegularExpressionValidatorWeb.aspx</title></head>
<body>

<form Runat="Server">

Enter the address
of your homepage:
<br>
<asp:TextBox
  id="txtHomepage"
  Columns="50"
  Runat="Server"/>

<asp:RegularExpressionValidator
  ControlToValidate="txtHomepage"
  Display="Dynamic"
  Text="Invalid URL!"
  ValidationExpression="http://\S+\.\S+"
  Runat="Server" />

<p>

<asp:Button
  Text="Submit"
  OnClick="Button_Click"
  Runat="Server"/>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

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

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 case-insensitive 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
<Script Runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  If IsValid Then
    Response.Redirect( "thankyou.aspx" )
  End If
End Sub

</Script>

<html>
<head><title>RegularExpressionValidatorIgnoreCase.aspx</title></head>
<body>

<form Runat="Server">

Enter the address
of your homepage:
<br>
<asp:TextBox
  id="txtHomepage"
  Columns="50"
  Runat="Server"/>

<asp:RegularExpressionValidator
  ControlToValidate="txtHomepage"
  Display="Dynamic"
  Text="Invalid URL!"
  EnableClientScript="False"
  ValidationExpression="(?i:http://\S+\.\S+)"
  Runat="Server" />

<p>

<asp:Button
  Text="Submit"
  OnClick="Button_Click"
  Runat="Server"/>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Checking for Entry Length

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.

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
<Script Runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  If IsValid Then
    Response.Redirect( "ThankYou.aspx" )
  End If
End Sub

</Script>

<html>
<head><title>RegularExpressionValidatorLength.aspx</title></head>
<body>

<form Runat="Server">

Enter your last name:
<br>(no more than 10 characters)
<br>
<asp:TextBox
  id="txtLastname"
  Columns="50"
  Runat="Server"/>

<asp:RegularExpressionValidator
  ControlToValidate="txtLastname"
  Display="Dynamic"
  Text="Your last name can contain
        a maximum of 10 characters and no spaces!"
  ValidationExpression="\S{0,10}"
  Runat="Server" />
<p>

<asp:Button
  Text="Submit"
  OnClick="Button_Click"
  Runat="Server"/>

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

Listing 3.11 RegularExpressionValidatorZip.aspx
<Script Runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  If IsValid Then
    Response.Redirect( "ThankYou.aspx" )
  End If
End Sub

</Script>

<html>
<head><title>RegularExpressionValidatorZip.aspx</title></head>
<body>

<form Runat="Server">

ZIP Code:
<asp:TextBox
  id="txtZipCode"
  Columns="8"
  Runat="Server"/>

<asp:RegularExpressionValidator
  ControlToValidate="txtZipCode"
  Display="Dynamic"
  Text="Invalid ZIP Code!"
  ValidationExpression="\d{5}"
  Runat="Server" />

<p>

<asp:Button
  Text="Submit"
  OnClick="Button_Click"
  Runat="Server"/>

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.


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 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 entered into another control, or check whether a currency amount is less than a certain specified amount.

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

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.

Listing 3.12 CompareValidator.aspx
<Script Runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  If IsValid Then
    Response.Redirect( "ThankYou.aspx" )
  End If
End Sub

</Script>

<html>
<head><title>CompareValidator.aspx</title></head>
<body>

<form Runat="Server">

Start Date:
<asp:TextBox
  id="txtStartDate"
  Columns="8"
  Runat="Server"/>

End Date:
<asp:TextBox
  id="txtEndDate"
  Columns="8"
  Runat="Server"/>

<br>
<asp:CompareValidator
  ControlToValidate="txtEndDate"
  ControlToCompare="txtStartDate"
  Display="Dynamic"
  Text="End date must be greater than start date!"
  Operator="GreaterThan"
  Type="Date"
  Runat="Server" />

<p>

<asp:Button
  Text="Submit!"
  OnClick="Button_Click"
  Runat="Server"/>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

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

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.

Listing 3.13 CompareValidatorValue.aspx
<Script Runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  If IsValid Then
    Response.Redirect( "ThankYou.aspx" )
  End If
End Sub

</Script>

<html>
<head><title>CompareValidatorValue.aspx</title></head>
<body>

<form Runat="Server">

Minimum Bid: $2,344.89
<br>
Enter your bid:
<asp:TextBox
  id="txtBidAmount"
  Columns="8"
  Runat="Server"/>

<asp:CompareValidator
  ControlToValidate="txtBidAmount"
  ValueToCompare="2,344.89"
  Display="Dynamic"
  Text="Your bid must be greater than the minimum bid!"
  Operator="GreaterThan"
  Type="Currency"
  Runat="Server" />

<p>

<asp:Button
  Text="Submit"
  OnClick="Button_Click"
  Runat="Server"/>

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

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
<Script Runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  If IsValid Then
    Response.Redirect( "ThankYou.aspx" )
  End If
End Sub

</Script>

<html>
<head><title>CompareValidatorDataTypeCheck.aspx</title></head>
<body>

<form Runat="Server">

Enter your birth date:
<asp:TextBox
  id="txtBirthDate"
  Columns="10"
  Runat="Server"/>

<asp:CompareValidator
  ControlToValidate="txtBirthDate"
  Display="Dynamic"
  Text="Invalid birth date!"
  Operator="DataTypeCheck"
  Type="Date"
  Runat="Server" />

<p>

<asp:Button
  Text="Submit"
  OnClick="Button_Click"
  Runat="Server"/>

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

CAUTION

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

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.

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
<Script Runat="Server">

Sub Page_Load
  valgMeetingDate.MinimumValue = Now.Date
  valgMeetingDate.MaximumValue = Now.Date.AddMonths( 3 )
End Sub

Sub Button_Click( s As Object, e As EventArgs )
  If IsValid Then
    Response.Redirect( "ThankYou.aspx" )
  End If
End Sub

</Script>

<html>
<head><title>RangeValidator.aspx</title></head>
<body>

<form Runat="Server">

Choose a meeting date in the next three months:
<br>
<asp:TextBox
  id="txtMeetingDate"
  Columns="10"
  Runat="Server"/>

<asp:RangeValidator
  ID="valgMeetingDate"
  ControlToValidate="txtMeetingDate"
  Display="Dynamic"
  Text="Date must be in the next 3 months!"
  Type="Date"
  Runat="Server" />

<p>

<asp:Button
  Text="Submit"
  OnClick="Button_Click"
  Runat="Server"/>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

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 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
<Script Runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  If IsValid Then
    Response.Redirect( "Thankyou.aspx" )
  End If
End Sub

</Script>

<html>
<head><title>CompareValidatorRange.aspx</title></head>
<body>

<form Runat="Server">

Minimum Value:
<asp:TextBox
  id="txtMinNumber"
  Runat="Server"/>

<p>

Maximum Value:
<asp:TextBox
  id="txtMaxNumber"
  Runat="Server"/>

<p>

Value:
<asp:TextBox
  id="txtRangeNumber"
  Runat="Server"/>

<asp:CompareValidator
  ControlToValidate="txtRangeNumber"
  ControlToCompare="txtMinNumber"
  Display="Dynamic"
  Text="Number must be greater than minimum value!"
  Operator="GreaterThan"
  Type="Integer"
  Runat="Server" />

<asp:CompareValidator
  ControlToValidate="txtRangeNumber"
  ControlToCompare="txtMaxNumber"
  Display="Dynamic"
  Text="Number must be less than maximum value!"
  Operator="LessThan"
  Type="Integer"
  Runat="Server" />

<p>

<asp:Button
  Text="Submit"
  OnClick="Button_Click"
  Runat="Server"/>

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.


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

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

Listing 3.17 ValidationSummary.aspx
<Script Runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  If IsValid Then
    Response.Redirect( "ThankYou.aspx" )
  End If
End Sub

</Script>

<html>
<head><title>ValidationSummary.aspx</title></head>
<body>

<form Runat="Server">

<asp:ValidationSummary
  HeaderText="There are problems with the following
    form fields:"
  Runat="Server" />

<p>

First Name:
<br>
<asp:TextBox
  ID="txtFirstname"
  Runat="Server" />

<asp:RequiredFieldValidator
  ID="reqVal1"
  ControlToValidate="txtFirstname"
  Text="You must enter a first name!"
  ErrorMessage="First Name"
  Runat="Server" />

<p>

Last Name:
<br>
<asp:TextBox
  ID="txtLastname"
  Runat="Server" />

<asp:RequiredFieldValidator
  ControlToValidate="txtLastname"
  Text="You must enter a last name!"
  ErrorMessage="Last Name"
  Runat="Server" />

<p>

Occupation:
<br>
<asp:TextBox
  ID="txtOccupation"
  Runat="Server" />

<asp:RequiredFieldValidator
  Text="You must enter an occupation!"
  ControlToValidate="txtOccupation"
  ErrorMessage="Occupation"
  Runat="Server" />

<p>

<asp:Button
  Text="Submit"
  OnClick="Button_Click"
  Runat="Server"/>

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 3.3. Summarizing errors with the ValidationSummary control.

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.

Figure 3.4. Different ValidationSummary display modes.

Displaying Pop-Up Error Messages

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

Figure 3.5. Enabling the ValidationSummary control's ShowMessageBox property.

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.

Listing 3.18 ValidationSummaryPopUp.aspx
<Script Runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  If IsValid Then
    Response.Redirect( "ThankYou.aspx" )
  End If
End Sub

</Script>

<html>
<head><title>ValidationSummaryPopUp.aspx</title></head>
<body>

<form Runat="Server">

<asp:ValidationSummary
  ShowMessageBox="True"
  HeaderText="There are problems with the following
    form fields:"
  Runat="Server" />

<p>

First Name:
<br>
<asp:TextBox
  ID="txtFirstname"
  Runat="Server" />

<asp:RequiredFieldValidator
  ControlToValidate="txtFirstname"
  Text="You must enter a first name!"
  ErrorMessage="First Name"
  Runat="Server" />

<p>

Last Name:
<br>
<asp:TextBox
  ID="txtLastname"
  Runat="Server" />

<asp:RequiredFieldValidator
  ControlToValidate="txtLastname"
  Text="You must enter a last name!"
  ErrorMessage="Last Name"
  Runat="Server" />

<p>

Occupation:
<br>
<asp:TextBox
  ID="txtOccupation"
  Runat="Server" />

<asp:RequiredFieldValidator
  Text="You must enter an occupation!"
  ControlToValidate="txtOccupation"
  ErrorMessage="Occupation"
  Runat="Server" />

<p>

<asp:Button
  Text="Submit"
  OnClick="Button_Click"
  Runat="Server"/>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.


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.

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.

CAUTION

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

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:

<Script Language="VBScript">
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
</Script>

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
<Script Runat="Server">

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
  Else
    e.IsValid = False
  End If
End Sub

</script>

<html>
<head>

<Script Language="VBScript">
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
</Script>

<title>CustomValidator.aspx</title>
</head>
<body>

<form Runat="Server">

Enter the name of your favorite book:
<br>
<asp:CustomValidator
  ControlToValidate="txtFavBook"
  ClientValidationFunction="CustomValidator_ClientValidate"
  OnServerValidate="CustomValidator_ServerValidate"
  Display="Dynamic"
  Text="You must type ASP.NET Unleashed!"
  Runat="Server" />

<br>
<asp:TextBox
  ID="txtFavBook"
  TextMode="Multiline"
  Columns="50"
  Rows="3"
  Runat="Server" />

<p>
<asp:Button
  Text="Submit!"
  OnClick="Button_Click"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

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 to 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 CustomValidator. If you enter an invalid credit card number, the validation check fails and an error message is displayed.

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
<Script Runat="Server">

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

 ' Double every other digit
 For intCounter = Len( strCCNumber ) To 1 Step -1
 If blnIsEven Then
   strDigits = strDigits & cINT( MID( strCCNumber, intCounter, 1 ) ) * 2
 Else
   strDigits = strDigits & cINT( MID( strCCNumber, intCounter, 1 ) )
 End If
 blnIsEven = ( NOT blnIsEven )
 Next

 ' 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

</script>

<html>
<head><title>CustomValidatorLuhn.aspx</title></head>
<body>

<form Runat="Server">

Enter your credit card number:
<br>
<asp:TextBox
  ID="txtCCNumber"
  Columns="20"
  MaxLength="20"
  Runat="Server" />

<asp:CustomValidator
  ControlToValidate="txtCCNumber"
  OnServerValidate="ValidateCCNumber"
  Display="Dynamic"
  Text="Invalid Credit Card Number!"
  Runat="Server" />
<p>
<asp:Button
  Text="Submit!"
  OnClick="Button_Click"
  Runat="Server" />
</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.


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
<Script runat="Server">

Sub btnSubmit_Click( s As Object, e As EventArgs )
  If IsValid Then
    Response.Redirect( "ThankYou.aspx" )
  End If
End Sub

Sub btnCancel_Click( s As Object, e As EventArgs )
  Response.Redirect( "Cancel.aspx" )
End Sub

</Script>

<html>
<head><title>CausesValidation.aspx</title></head>
<body>

<form runat="Server">

Enter your first name:
<br>
<asp:TextBox
  id="txtFirstName"
  Runat="Server" />

<asp:RequiredFieldValidator
  ControlToValidate="txtFirstName"
  Text="Required!"
  Runat="Server" />

<p>

<asp:Button
  id="btnSubmit"
  Text="Submit"
  OnClick="btnSubmit_Click"
  Runat="Server" />

<asp:Button
  id="btnCancel"
  Text="Cancel"
  OnClick="btnCancel_Click"
  CausesValidation="False"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.p


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

  • Displaying and Hiding Content

  • Using Rich Controls

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.

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.

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.

Listing 4.1 ViewState.aspx
<Script Runat="Server">

Sub Page_Load
  If Not IsPostBack Then
    lblMessage.Text = "Hello!"
  End If
End Sub

</Script>

<html>
<head><title>ViewState.aspx</title></head>
<body>

<form Runat="Server">

<asp:Label
  ID="lblMessage"
  Runat="Server" />

<p>

<asp:Button
  Text="Just Submit"
  Runat="Server" />

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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
<Script Runat="Server">

Sub Page_Load
  If Not IsPostBack Then
    lblMessage.Text = "Hello!"
  End If
End Sub

</Script>

<html>
<head><title>EnableViewState.aspx</title></head>
<body>
<form Runat="Server">

<asp:Label
  ID="lblMessage"
  EnableViewState="False"
  Runat="Server" />

<p>

<asp:Button
  Text="Just Submit"
  Runat="Server" />

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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
<%@ Page EnableViewState="False" %>

<Script Runat="Server">

Sub Page_Load
  If Not IsPostBack Then
    lblMessage.Text = "Hello!"
  End If
End Sub

</Script>

<html>
<head><title>PageViewState.aspx</title></head>
<body>

<form Runat="Server">

<asp:Label
  ID="lblMessage"
  Runat="Server" />

<p>

<asp:Button
  Text="Just Submit"
  Runat="Server" />

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

CAUTION

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
<Script Runat="Server">

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

</Script>

<html>
<head><title>StateBag.aspx</title></head>
<body>

<form Runat="Server">

<asp:Button
  Text="Click Here"
  OnClick="Button_Click"
  Runat="Server" />

<p>
<asp:Label
  ID="lblCount"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.


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.

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.

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 pre-rendering or rendering.

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.

Listing 4.5 Display.aspx
<Script Runat="Server">

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

</Script>

<html>
<head><title>Display.aspx</title></head>
<body>

<form Runat="Server">

TextBox:
<br>
<asp:TextBox
  ID="txtTextBox"
  Text="Some Text"
  Runat="Server" />

<p>
HyperLink:
<br>
<asp:HyperLink
  ID="lnkHyperLink"
  NavigateURL="somepage.aspx"
  Text="Click Here!"
  Runat="Server" />

<p>
LinkButton:
<br>
<asp:LinkButton
  ID="lbtnLinkButton"
  Text="Click Here!"
  Runat="Server" />

<p>
Button:
<br>
<asp:Button
  ID="btnButton"
  Text="Submit!"
  Runat="Server" />

<hr>

<asp:LinkButton
  ID="lbtnVisible"
  Text="Make Invisible!"
  OnClick="lbtnVisible_Click"
  Runat="Server" />

<asp:LinkButton
  ID="lbtnEnabled"
  Text="Make Disabled!"
  OnClick="lbtnEnabled_Click"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 4.1. The Visible and Enabled properties.

The page in Listing 4.5 contains two LinkButton controls initially labeled Make Invisible! 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.

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.

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

Properties

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

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

Listing 4.6 Panel.aspx
<Script Runat="Server">

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

</Script>

<html>
<head><title>Panel.aspx</title></head>
<body>

<form Runat="Server">

Select your favorite ASP Web site:
<br>
<asp:RadioButtonList
  ID="dropFavSite"
  Runat="Server">
  <asp:ListItem Text="Superexpert" />
  <asp:ListItem Text="LearnASP" />
  <asp:ListItem Text="DotNetJunkies" />
  <asp:ListItem Text="Other Site" />
</asp:RadioButtonList>

<asp:Panel
  ID="pnlOtherSite"
  Visible="False"
  Runat="Server">

  <p>
  Other Site:
  <br>
  <asp:TextBox
    ID="txtOtherSite"
    Runat="Server" />

</asp:Panel>
<p>

<asp:Button
  Text="Submit!"
  OnClick="Button_Click"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 4.2. Hiding and displaying content with the Panel control.

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.

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.

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
<Script Runat="Server">

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" )
  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 = "<h2>You entered:</h2>"
  lblSummary.Text &= "<li> First Name=" & txtFirstname.Text
  lblSummary.Text &= "<li> Last Name=" & txtLastname.Text
  lblSummary.Text &= "<li> Color=" & txtFavColor.Text
  lblSummary.Text &= "<li> Philosopher=" & radlFavPhilosopher.SelectedItem.Text
End Sub

</Script>

<html>
<head><title>PanelMultiPage.aspx</title></head>
<body>

<form Runat="Server">

<asp:Panel ID="pnlForm1" Runat="Server">

<h3>Page <%=ViewState( "CurrentPage" )%> of 3</h3>
<hr>
First Name:
<asp:TextBox
  ID="txtFirstname"
  Runat="Server" />
<p>
Last Name:
<asp:TextBox
  ID="txtLastname"
  Runat="Server" />
<hr>
<asp:Button
  Text="Next Page >>"
  OnClick="btnNextPage_Click"
  Runat="Server" />

</asp:Panel>
<asp:Panel ID="pnlForm2" Runat="Server" Visible="False">

<h3>Page <%=ViewState( "CurrentPage" )%> of 3</h3>
<hr>
Favorite Color:
<asp:TextBox
  ID="txtfavColor"
  Runat="Server" />
<hr>
<asp:Button
  Text="<< Prev Page"
  OnClick="btnPrevPage_Click"
  Runat="Server" />
<asp:Button
  Text="Next Page >>"
  OnClick="btnNextPage_Click"
  Runat="Server" />

</asp:Panel>
<asp:Panel ID="pnlForm3" Runat="Server" Visible="False">

<h3>Page <%=ViewState( "CurrentPage" )%> of 3</h3>
<hr>
Favorite Philosopher:
<asp:RadioButtonList
  ID="radlFavPhilosopher"
  Runat="Server">
  <asp:ListItem Text="Frege" Selected="True"/>
  <asp:ListItem Text="Russell" />
  <asp:ListItem Text="Carnap" />
</asp:RadioButtonList>
<hr>
<asp:Button
  Text="<< Prev Page"
  OnClick="btnPrevPage_Click"
  Runat="Server" />
<asp:Button
  Text="Finish"
  OnClick="btnFinish_Click"
  Runat="Server" />

</asp:Panel>
<asp:Panel ID="pnlForm4" Runat="Server" Visible="False">

<asp:Label
  ID="lblSummary"
  Runat="Server"/>

</asp:Panel>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

The page in Listing 4.7 contains four Panel controls (see Figure 4.3). Each of the first three Panel controls contains a group of form questions. The final Panel control displays a summary of the information that the user entered.

Figure 4.3. Simulating multipage forms with the Panel control.

When the Next Page button is clicked, the btnNextPage_Click subroutine is executed. This subroutine hides the previous panel and displays the contents of the next panel.

The Previous Page button causes the opposite operation to take place. When the Previous Page button is clicked, the current panel is hidden and the contents of the previous panel are displayed.

Finally, if you click the Finish button, all the Panel controls are hidden except for the fourth panel, which displays a summary of all the values the user entered into the form fields in the previous panels.

Adding Controls Programmatically

An ASP.NET page is built from controls. Every element of a page, including the static text and HTML content, is represented by a control.

You can retrieve a list of the controls contained in a page by using the Controls property of the Page class. For example, the following code loops through the Controls collection and displays each of its members:

Dim ctlControl As Control
For Each ctlControl in Page.Controls
  Response.Write( ctlControl.ToString() )
Next

The preceding code does not display a list of all the controls contained in a page. It displays only the immediate child controls of the Page class; it does not display any child controls of these controls.

The HTMLForm control, for example, contains child controls. It contains all the form controls on a page. The Panel control is another example of a control that contains child controls.

Every control has a Controls property that represents its child controls. If you want to display all the controls in a page, you can move recursively through the Controls collection of each control, as in Listing 4.8.

Listing 4.8 Controls.aspx
<Script Runat="Server">

Dim strControlList As String

Sub Page_Load
  strControlList = "<ul>"
  DisplayControls( Page )
  strcontrolList &= "</ul>"
  lblOutput.Text = strControlList
End Sub

Sub DisplayControls( s As Object )
  Dim ctlControl As Control

  For Each ctlControl in s.Controls
    DisplayControlInfo( ctlControl )
    strControlList &= "<ul>"
    DisplayControls( ctlControl )
    strControlList &= "</ul>"
  Next
End Sub

Sub DisplayControlInfo( s As Object )
  strControlList &= "<li> Client ID: " & s.ClientID
  strControlList &= "<br> Type: " & s.GetType().ToString()
  strControlList &= "<br> ID: " & s.ID
  strControlList &= "<br> Naming Container: " & s.NamingContainer.ToString()
  strControlList &= "<br> Page: " & s.Page.ToString()
  strControlList &= "<br> Parent: " & s.Parent.ToString()
  strControlList &= "<br> Unique ID: " & s.UniqueID
  strControlList &= "<p>"
End Sub

</Script>

<html>
<head><title>Controls.aspx</title></head>
<body>

<form Runat="Server">

<asp:Panel
  ID="pnlPanel"
  Runat="Server">

  <asp:Label
    id="lblNewLabel"
    Runat="Server"/>

</asp:Panel>

Username:
<br>
<asp:TextBox
  ID="txtUsername"
  Runat="Server" />
<p>
<asp:Button
  ID="btnButton"
  Text="Submit Name!"
  Runat="Server" />

<hr>
<asp:Label
  ID="lblOutput"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

The Page_Load subroutine in Listing 4.8 calls a subroutine named DisplayControls. This subroutine recursively displays all the controls on the page (see Figure 4.4).

Figure 4.4. Recursively displaying all the controls on a page.

The values of all the properties listed in Table 4.2 is displayed for each control:

  • ClientID— The ID generated automatically for each control by the ASP.NET framework. It is not the same as the ID that you assign to a control when declaring it.

  • ID— The ID that you assign to the control when declaring it.

  • NamingContainer— The first parent control that contains the current control that implements the INamingContainer interface.

  • Page— The Page object that contains the current control.

  • Parent— The immediate parent of the control in the control hierarchy.

  • UniqueID— The fully qualified unique ID of the control. Controls can have the same ID if they are contained in different naming containers, but no two controls can share the same UniqueID.

Adding Controls to a Page

You can add new controls to a page by adding the controls to a page's Controls collection. You can add a control at the end of the Controls collection by using the Add() method. Alternatively, you can add a control at a specific index location in the Controls collection by using the AddAt() method.

The page in Listing 4.9, for example, constructs all its own controls. It contains a single Page_Load subroutine, which adds 30 LiteralControl controls to the page. If you open this page in your Web browser, you see the word Hello! repeated 30 times (see Figure 4.5).

Listing 4.9 ControlsAdd.aspx
<Script Runat="Server">
Sub Page_Load
  Dim intCounter As Integer
  For intCounter = 1 To 30
    Controls.Add( New LiteralControl( "Hello!" ) )
  Next
End Sub
</Script>

The C# version of this code can be found on the CD-ROM.

Figure 4.5. Self-building page.

You can remove controls from the Controls collection by using the Remove(), RemoveAt(), or Clear() method. The Remove() method removes the control that you pass to it, the RemoveAt() method removes a control at a specific index location, and the Clear() method removes all the controls from the Controls collection.

The page in Listing 4.10, for example, adds three controls and then uses the Remove() method to remove the first control.

Listing 4.10 ControlsRemove.aspx
<Script Runat="Server">
Sub Page_Load
  Dim intCounter As Integer

  For intCounter = 1 To 3
    Controls.Add( new LiteralControl( "Control" & intCounter  ) )
  Next
  Controls.Remove( Controls.Item( 0 ) )
End Sub
</Script>

The C# version of this code can be found on the CD-ROM.

The PlaceHolder Control

Typically, you don't add controls directly to a page's Controls collection. In most cases, you will want to add a control to a particular location on a page.

You can add a control to the Controls collection of any other control. For example, there is nothing wrong with adding a new control to the Controls collection of either an HTMLForm or Panel control.

However, there is a special control included in the ASP.NET Framework, called the PlaceHolder control that has the singular purpose of acting as a container for other controls.

Listing 4.11 illustrates how you can use the PlaceHolder control to dynamically add TextBox controls to a form.

Listing 4.11 PlaceHolder.aspx
<Script Runat="Server">

Sub Page_Load
  Dim intCounter As Integer
  Dim btnNewButton As Button

  For intCounter = 1 to 10
    plhPlaceHolder.Controls.Add( _
      New LiteralControl( "<p>Field " & intCounter & ": " ) )
    plhPlaceHolder.Controls.Add( _
      New TextBox )
  Next

  btnNewButton = New Button
  btnNewButton.Text = "Click Here!"
  plhPlaceHolder.Controls.Add( btnNewButton )
End Sub

</Script>

<html>
<head><title>PlaceHolder.aspx</title></html>
<body>

<form Runat="Server">

<asp:PlaceHolder
  id="plhPlaceHolder"
  Runat="Server" />

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

In the Page_Load subroutine in Listing 4.11, ten TextBox controls and one Button control are added to the Controls collection of a PlaceHolder control. When the page is displayed in a browser, the new TextBox controls are rendered.

Dynamically Generating Forms

Imagine that you need to create a form that has a variable number of input fields. For example, you can imagine a simple order entry form that enables you to enter a variable number of product orders. The same form enables you to submit a single order or 25 orders at a time.

You can use the methods discussed in the previous section to dynamically add form fields to a form. Listing 4.12 contains a simple order entry form. When the page first loads, it contains a single form field for the customer name and a single form field for the product.

The page also contains a button labeled Add Product Field. When you click this button, a new product form field is added to the form. You can click this button as many times as you wish to add any number of new product form fields (see Figure 4.6).

Figure 4.6. Dynamically generated form.

When you click the Add Product Field button, the AddProductFieldCount subroutine is executed. This subroutine increments the count of product fields (stored in the state bag) by one. It also adds a new text box to the page by calling the AddProductField subroutine.

Notice that the Page_Load subroutine rebuilds all the product fields every time the page is loaded. When you dynamically add a new control to a page, the control is not automatically retained between form posts. The control itself is not preserved in the page's view state. However, any text you enter into a dynamically generated form field is automatically preserved in view state.

Listing 4.12 DynamicForm.aspx
<Script Runat="Server">

Sub Page_Load
  Dim intCounter As Integer

  If Not IsPostBack Then
    ViewState( "ProductFieldCount" ) = 1
  ElseIf ViewState( "ProductFieldCount" ) > 1 Then
    For intCounter = 2 To ViewState( "ProductFieldCount" )
      AddProductField( intCounter )
    Next
  End If
End Sub

Sub AddProductFieldCount( s As Object, e As EventArgs )
  ViewState( "ProductFieldCount" ) += 1
  AddProductField( ViewState( "ProductFieldCount" ) )
End Sub

Sub AddProductField( strFieldNum As String )
  Dim litLabel As LiteralControl
  Dim txtTextBox As TextBox

  ' Add Literal Control
  litLabel = New LiteralControl
  litLabel.Text = "<p><b>Product " & strFieldNum & ":</b> "
  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

</Script>

<html>
<head><title>DynamicForm.aspx</title></html>
<body>

<form Runat="Server">

<b>Customer Name:</b>
<asp:TextBox
  ID="txtCustomer"
  Runat="Server" />

<p>
<div style="background-color: yellow;padding:10px">

<b>Product 1:</b>
<asp:TextBox
  ID="txtProduct1"
  Runat="Server" />

<asp:PlaceHolder
  id="plhProductFields"
  Runat="Server" />

</div>

<p>
<asp:Button
  Text="Add Product Field"
  OnClick="AddProductFieldCount"
  Runat="Server" />

<asp:Button
  id="btnSubmit"
  Text="Submit Complete Order"
  OnClick="btnSubmit_Click"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

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

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.

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.

Listing 4.13 ListItemCollection.aspx
<Script Runat="Server">

Sub lstProducts_SelectedIndexChanged( s As Object, e As EventArgs )
  lstCart.SelectedIndex = -1
  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

</Script>

<html>
<head><title>ListItemCollection.aspx</title></head>
<body>

<form Runat="Server">

<h2>Products:</h2>
<asp:ListBox
  id="lstProducts"
  Width="200"
  AutoPostBack="True"
  OnSelectedIndexChanged="lstProducts_SelectedIndexChanged"
  Runat="Server">
  <asp:ListItem Text="Hair Dryer"/>
  <asp:ListItem Text="Shaving Cream"/>
  <asp:ListItem Text="Electric Comb"/>
  <asp:ListItem Text="Nail Polish"/>
  <asp:ListItem Text="French Toast"/>
</asp:ListBox>

<p>

<h2>Shopping Cart:</h2>
<asp:ListBox
  ID="lstCart"
  Width="200"
  Runat="Server"/>
<br>
<asp:Button
  id="btnRemove"
  Text="Remove Item"
  OnClick="btnRemove_Click"
  Runat="Server"/>

<asp:Button
  id="btnRemoveAll"
  Text="Remove All"
  OnClick="btnRemoveAll_Click"
  Runat="Server"/>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.


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

Properties

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.

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 the DayRender event.

OnSelectionChanged

Raises the SelectionChanged event.

OnVisibleMonthChanged

Raises the VisibleMonthChanged event.

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

Listing 4.14 Calendar.aspx
<html>
<head><title>calendar.aspx</title></head>
<body>

<form Runat="Server">

<asp:Calendar
  Runat="Server" />

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 4.8. Calendar control with default properties.

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
<html>
<head><title>CalendarBaseFormatting.aspx</title></head>
<body>

<form Runat="Server">

<asp:Calendar
  Width="100%"
  BackColor="blue"
  ForeColor="Yellow"
  Font-Name="Script"
  Font-Size="24pt"
  Font-Bold="True"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 4.9. A calendar with different base Web control formatting properties.

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.

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:

<asp:Calendar
  ID="myCal"
  PrevMonthText="<img src='prev.gif'>"
  NextMonthText="<img src='next.gif'>"
  Runat="Server" />

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.

Listing 4.16 CalendarStyle.aspx
<html>
<head><title>CalendarStyle.aspx</title></head>
<body>

<form Runat="Server">

<asp:Calendar
  DayStyle-BackColor="Blue"
  WeekendDayStyle-BackColor="Green"
  TodayDayStyle-BackColor="Yellow"
  SelectedDayStyle-BackColor="Orange"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

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:

<asp:Calendar
  ID="myCal"
  SelectionMode="DayWeekMonth"
  SelectWeekText="<img src='selectWeek.gif'>"
  SelectMonthText="<img src='selectMonth.gif'>"
  Runat="Server" />

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.

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

Listing 4.17 CalendarSelected.aspx
<Script Runat="Server">

Sub Calendar_SelectionChanged( s As Object, e As EventArgs )
  Dim dtmDate As DateTime

  lblDates.Text = "<h2>You selected the following date(s):</h2>"
  For Each dtmDate in calCalendar.SelectedDates
    lblDates.Text &= "<li>" & dtmDate.ToString( "D" )
  Next
End Sub

</Script>

<html>
<head><title>CalendarSelected.aspx</title></head>
<body>

<form Runat="Server">

<asp:Calendar
  id="calCalendar"
  SelectionMode="DayWeekMonth"
  SelectWeekText="Select Week"
  SelectMonthText="Select Month"
  OnSelectionChanged="Calendar_SelectionChanged"
  Runat="Server" />

<asp:Label
  ID="lblDates"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 4.10. Selecting calendar dates.

When a new day, week, or month is selected, the SelectionChanged event is raised. This event is handled in Listing 4.17 by a subroutine named Calendar_SelectionChanged. This subroutine loops through the collection of dates represented by the Calendar control's SelectedDates property. Each item in this collection is a DateTime.

Rendering Calendar Days

Whenever the Calendar control renders a particular day, the control raises the DayRender event. You can write a subroutine that handles this event and display custom content on each calendar day.

To capture the DayRender event, create a subroutine with these parameters:

Sub Calendar_DayRender( s As Object, e As DayRenderEventArgs )
End Sub

The second parameter passed to the subroutine, DayRenderEventArgs, has a number of properties that you can use in your code. These properties are listed in Table 4.5.

Table 4.5. DayRenderEventArgs Properties

Properties

Description

Cell

The table cell containing the day being rendered.

Cell.ColumnSpan

An integer value indicating the number of table columns the cell should span.

Cell.HorizontalAlign

The cell's horizontal alignment. Possible values are Center, Justify, Left, NotSet, and Right.

Cell.RowSpan

An integer value indicating the number of table rows the cell should span.

Cell.Text

The text that appears in the cell.

Cell.VerticalAlign

The cell's vertical alignment. Possible values are Bottom, Middle, NotSet, and Top.

Cell.Wrap

If True, the contents of the cell word-wrap.

Day

A CalendarDay object that represents the day being rendered.

Day.Date

A DateTime value that represents the day being rendered.

Day.DayNumberText

A string that represents the number of the day being rendered.

Day.IsOtherMonth

If True, the day being rendered is not a member of the current month.

Day.IsSelectable

If True, this day can be selected.

Day.IsSelected

If True, this day is selected.

Day.IsToday

If True, this day is the current day.

Day.IsWeekend

If True, this day is a Saturday or Sunday.

Listing 4.18 illustrates how you can use the DayRender event to display a daily horoscope in a Calendar control.

Listing 4.18 CalendarHoroscope.aspx
<%@ Import Namespace="System.Drawing" %>
<Script Runat="Server">

  Dim RanNum As New Random( 23 )

  Sub Calendar_RenderDay( s As Object, e As DayRenderEventArgs )
    Dim ctlCell As TableCell
    Dim strHoroscope As String

    ctlCell = e.Cell

    Select Case RanNum.Next( 4 )
    Case 0
      strHoroscope = "Good things will happen today!"
      ctlCell.BackColor = Color.FromName( "Green" )
    Case 1
      strHoroscope = "You're doomed! Hide in bed!"
      ctlCell.BackColor = Color.FromName( "Red" )
    Case 2
      strHoroscope = "Something unexpected will happen"
    Case 3
      strHoroscope = "Look before you leap!"
    End Select

    ctlCell.Controls.Add( New LiteralControl( "<p>" ) )
    ctlCell.Controls.Add( New LiteralControl( strHoroscope ) )
  End Sub

</Script>

<html>
<head><title>CalendarHoroscope.aspx</title></head>
<body>

<h1>Horoscope:</h1>

<form Runat="Server">

<asp:Calendar
  Width="100%"
  CellPadding="10"
  ShowGridLines="True"
  SelectionMode="None"
  OnDayRender="Calendar_RenderDay"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

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

Figure 4.11. Handling the DayRender event.

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.

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.

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.

NOTE

Since the page in Listing 4.19 writes to the file system, you'll need to grant write permissions to the ASPNET user account to write to the directory containing the schedule.bin file. For more information on the ASPNET user account see Chapter 20, "Using Windows-Based Authentication."

Listing 4.19 CalendarSchedule.aspx
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.Runtime.Serialization" %>
<%@ Import Namespace="System.Runtime.Serialization.Formatters.Binary" %>
<%@ Import Namespace="System.Drawing" %>

<Script Runat="Server">

Dim arrCalendar( 13, 32 ) As String

' Get schedule from cache or disk
Sub Page_Load
  Dim strmFileStream As FileStream
  Dim fmtrBinaryFormatter As BinaryFormatter

  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

' 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()
  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

</Script>

<html>
<head><title>CalendarSchedule.aspx</title></head>
<body>

<form Runat="Server">

<asp:Calendar
  id="calSchedule"
  Width="100%"
  ShowGridLines="True"
  OnSelectionChanged="Calendar_SelectionChanged"
  OnDayRender="Calendar_RenderDay"
  Runat="Server" />

<p>
<asp:TextBox
  id="txtNotes"
  TextMode="MultiLIne"
  Columns="50"
  Rows="10"
  Runat="Server" />

<p>
<asp:Button
  id="btnSave"
  Text="Save Changes"
  OnClick="btnSave_Click"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

Methods

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:

<Advertisements>
   <Ad>
      <ImageUrl>URL of image to display</ImageUrl>
      <TargetUrl> URL of page to link to </TargetUrl>
      <AlternateText>
         Text to show as ToolTip
       </AlternateText>
      <Keyword>keyword used to filter</Keyword>
      <Impressions>relative weighting of ad</Impressions>
   </Ad>
</Advertisements>

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 page you go to when you click an advertisement.

  • 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 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
<html>
<head><title>AdRotator.aspx</title></head>
<body>

<form Runat="Server">

<asp:AdRotator
  AdvertisementFile="myAds.xml"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Listing 4.21 myAds.xml
<Advertisements>
   <Ad>
      <ImageUrl>AspWorkshopsBanner.gif</ImageUrl>
      <NavigateUrl>http://www.AspWorkshops.com</NavigateUrl>
      <AlternateText>
         Need ASP.NET Training?
       </AlternateText>
      <Impressions>2</Impressions>
   </Ad>
   <Ad>
      <ImageUrl>SuperexpertBanner.gif</ImageUrl>
      <NavigateUrl>http://www.superexpert.com</NavigateUrl>
      <AlternateText>
         Click here to visit Superexpert.com!
       </AlternateText>
      <Impressions>1</Impressions>
   </Ad>
</Advertisements>

The C# version of this code can be found on the CD-ROM.

Tracking AdRotator Impressions

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.

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

Listing 4.22 AdRotatorImpressions.aspx
<%@ Import Namespace="System.IO" %>
<Script runat="Server">

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 )
    strmStreamReader.Close
  End If

  ' Write current impressions
  strmStreamWriter = File.CreateText( MapPath( strFileName ) )
  strmStreamWriter.Write( intImpressions + 1 )
  strmStreamWriter.Close
End Sub

</Script>

<html>
<head><title>AdRotatorImpressions.aspx</title></head>
<body>

<form Runat="Server">

<asp:AdRotator
  AdvertisementFile="AdCampaigns.xml"
  OnAdCreated="AdRotator_AdCreated"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Listing 4.23 AdCampaigns.xml
<Advertisements>
   <Ad>
      <Campaign>AspWorkshops</Campaign>
      <ImageUrl>AspWorkshopsBanner.gif</ImageUrl>
      <NavigateUrl>http://www.AspWorkshops.com</NavigateUrl>
      <AlternateText>
         Need ASP.NET Training?
       </AlternateText>
      <Impressions>2</Impressions>
   </Ad>
   <Ad>
      <Campaign>Superexpert</Campaign>
      <ImageUrl>SuperexpertBanner.gif</ImageUrl>
      <NavigateUrl>http://www.superexpert.com</NavigateUrl>
      <AlternateText>
         Click here to visit Superexpert.com!
       </AlternateText>
      <Impressions>1</Impressions>
   </Ad>
</Advertisements>

The C# version of this code can be found on the CD-ROM.

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
<Script Runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  inpFileUp.PostedFile.SaveAs( "c:\Uploads\NewFile.gif" )
End Sub

</Script>

<html>
<head><title>HtmlInputFile.aspx</title></head>
<body>

<form EncType="multipart/form-data" Runat="Server">

<input id="inpFileUp" Type="File" Runat="Server">
<p>
<asp:Button
  Text="Upload File!"
  OnClick="Button_Click"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

The HTMLInputFile is an HTML control (not a Web control). The control is declared on the page with the following tag:

<input id="fileUp" Type="File" Runat="Server">

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. Creating a file upload form with the HTMLInputFile control.

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:

<form EncType="multipart/form-data" Runat="Server">

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.


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.


Part II: Advanced ASP.NET Page Development

IN THIS PART

CHAPTER 5 Creating Custom Controls with User Controls

CHAPTER 6 Separating Code from Presentation

CHAPTER 7 Targeting Mobile Devices with Mobile Controls

CHAPTER 8 Additional Samples and Controls


Chapter 5. Creating Custom Controls with User Controls

IN THIS CHAPTER

  • Including Standard Content with User Controls

  • Exposing Properties and Methods in User Controls

  • Exposing Web Controls in User Controls

  • Exposing Events in User Controls

  • Loading User Controls Programmatically

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.

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

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
<html>
<head><title>Global Super Company</title></head>
<body>

<h2>Global Super Company</h2>
<i>We mean business!</i>
<hr>

The C# version of this code can be found on the CD-ROM.

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
<%@ Register TagPrefix="SuperCompany" TagName="Header"
  Src="SimpleHeader.ascx" %>

<Supercompany:Header
  ID="ctlHeader"
  Runat="Server" />

Welcome to our home page!

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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 alias to associate with the user control's namespace

  • TagName— The alias to associate with the user control's class

  • Src— The 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.

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:

<Supercompany:Header
  ID="ctlHeader"
  Runat="Server" />

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 <Form Runat="Server"> 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 <table> tag.

Listing 5.3 Divider.ascx
<p>
<table width="90%"
cellpadding="2" cellspacing="0"
border="1" bordercolor="red">
<tr>
  <td bgcolor="orange" Height="3">
  </td>
</tr>
</table>
<p>

The C# version of this code can be found on the CD-ROM.

This divider is displayed multiple times in the page contained in Listing 5.4 (see Figure 5.2 for the output of this page).

Listing 5.4 UserControlsDivider.aspx
<%@ Register TagPrefix="SuperCompany" TagName="Divider"
  Src="divider.ascx" %>

<html>
<head><title>UserControlsDivider.aspx</title>
<body>

Here is some text...

<SuperCompany:Divider
  id="ctlDivider1"
  Runat="Server" />

Here is some more text...

<SuperCompany:Divider
  id="ctlDivider2"
  Runat="Server" />

Yet some more text...

<SuperCompany:Divider
  id="ctlDivider3"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 5.2. Displaying a single user control multiple times.

    [ Team LiB ]


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
<Script Runat="Server">

Public PageTitle As String = "Global Super Company"

</Script>

<html>
<head><title><%=PageTitle%></title></head>
<body>

<h2>Global Super Company</h2>
<i>We mean business!</i>
<hr>

The C# version of this code can be found on the CD-ROM.

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

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.

Listing 5.6 HomePageTitle.aspx
<%@ Register TagPrefix="SuperCompany" TagName="Header"
  Src="HeaderTitle.ascx" %>

<Supercompany:Header
  ID="ctlHeader"
  PageTitle="The Home Page"
  Runat="Server" />

Welcome to our home page!

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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
<%@ Register TagPrefix="SuperCompany" TagName="Header"
  Src="HeaderTitle.ascx" %>

<Script Runat="Server">

Sub Page_Load
  ctlHeader.PageTitle = "Date: " & Now.ToString( "D" )
End Sub

</Script>

<Supercompany:Header
  ID="ctlHeader"
  PageTitle="The Home Page"
  Runat="Server" />

Welcome to our home page!

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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. 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
<Script Runat="Server">

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

</Script>

The C# version of this code can be found on the CD-ROM.

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
<%@ Register TagPrefix="SuperCompany" TagName="WordRepeater"
  Src="WordRepeater.ascx" %>

<Script Runat="Server">

Sub Page_Load
  myWordRepeater.Repeat( 50 )
End Sub

</Script>

<html>
<head><title>DisplayWordRepeater.aspx</title>
<body>

<SuperCompany:WordRepeater
  ID="myWordRepeater"
  Word="Hello"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM


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
<p>Street Address:
<br>
<asp:TextBox
  ID="txtStreet"
  Runat="Server" />

<p>City:
<br>
<asp:TextBox
  ID="txtCity"
  Runat="Server" />

<p>State:
<br>
<asp:TextBox
  ID="txtState"
  Runat="Server" />

<p>ZIP:
<br>
<asp:TextBox
  ID="txtZIP"
  Runat="Server" />

The C# version of this code can be found on the CD-ROM.

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
<%@ Register TagPrefix="SuperCompany" TagName="Address"
  Src="Address.ascx" %>

<html>
<head><title>DisplayAddress.aspx</title>
<body>

<form Runat="Server">

<h2>Billing Address</h2>

<SuperCompany:Address
  ID="ctlBillingAddress"
  Runat="Server" />

<h2>Shipping Address</h2>

<SuperCompany:Address
  ID="ctlShippingAddress"
  Runat="Server" />

<p>
<asp:Button
  Text="Submit"
  Runat="Server" />

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 5.3. Displaying multiple address forms with a single user control.

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.

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
<Script Runat="Server">

Public Property Street As String
  Get
    Return txtStreet.Text
  End Get
  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

</Script>

<p>Street Address:
<br>
<asp:TextBox
  ID="txtStreet"
  Runat="Server" />

<p>City:
<br>
<asp:TextBox
  ID="txtCity"
  Runat="Server" />

<p>State:
<br>
<asp:TextBox
 ID="txtState"
  Runat="Server" />

<p>ZIP:
<br>
<asp:TextBox
  ID="txtZIP"
  Runat="Server" />

The C# version of this code can be found on the CD-ROM.

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 Street'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
<%@ Register TagPrefix="SuperCompany" TagName="Address"
  Src="AddressProperties.ascx" %>

<Script Runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  lblOutput.Text = "<h2>You entered the following values:</h2>"
  lblOutput.Text &= "<b>Billing Address:</b>"
  lblOutput.Text &= "<li> Street: " & ctlBillingAddress.Street
  lblOutput.Text &= "<li> City: "   & ctlBillingAddress.City
  lblOutput.Text &= "<li> State: "  & ctlBillingAddress.State
  lblOutput.Text &= "<li> ZIP: "    & ctlBillingAddress.ZIP
  lblOutput.Text &= "<p>"
  lblOutput.Text &= "<b>Shipping Address:</b>"
  lblOutput.Text &= "<li> Street: " & ctlShippingAddress.Street
  lblOutput.Text &= "<li> City: "   & ctlShippingAddress.City
  lblOutput.Text &= "<li> State: "  & ctlShippingAddress.State
  lblOutput.Text &= "<li> ZIP: "    & ctlShippingAddress.ZIP
End Sub

</Script>

<html>
<head><title>DisplayAddressProperties.aspx</title>
<body>

<form Runat="Server">

<h2>Billing Address</h2>

<SuperCompany:Address
  ID="ctlBillingAddress"
  Runat="Server" />

<h2>Shipping Address</h2>

<SuperCompany:Address
  ID="ctlShippingAddress"
  Runat="Server" />

<p>
<asp:Button
  Text="Submit"
  OnClick="Button_Click"
  Runat="Server" />

<p>
<asp:Label
  ID="lblOutput"
  Runat="Server" />

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

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
<Script Runat="Server">

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

</Script>

<p>Street Address:
<br>
<asp:TextBox
  ID="Street"
  Runat="Server" />

<p>City:
<br>
<asp:TextBox
  ID="City"
  Runat="Server" />

<p>State:
<br>
<asp:TextBox
  ID="State"
  Runat="Server" />

<p>ZIP:
<br>
<asp:TextBox
  ID="ZIP"
  Runat="Server" />

The C# version of this code can be found on the CD-ROM.

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
<%@ Register TagPrefix="SuperCompany" TagName="Address"
  Src="AddressHashTable.ascx" %>

<Script Runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  Dim objItem As Object

  ' Show Billing Information
  lblOutput.Text = "<h2>You entered the following values:</h2>"
  lblOutput.Text &= "<b>Billing Address:</b>"
  lblOutput.Text &= "<li> Street: " & ctlBillingAddress.Values( "Street" )
  lblOutput.Text &= "<li> City: "   & ctlBillingAddress.Values( "City" )
  lblOutput.Text &= "<li> State: "  & ctlBillingAddress.Values( "State" )
  lblOutput.Text &= "<li> ZIP: "    & ctlBillingAddress.Values( "ZIP" )
  lblOutput.Text &= "<p>"

  ' Show Shipping Information
  lblOutput.Text &= "<b>Shipping Address:</b>"
  For Each objItem in ctlShippingAddress.Values
    lblOutput.Text &= "<li> " & objItem.Key & ": " & objItem.Value
  Next
End Sub

</Script>

<html>
<head><title>DisplayAddressHashTable.aspx</title>
<body>

<form Runat="Server">

<h2>Billing Address</h2>

<SuperCompany:Address
  ID="ctlBillingAddress"
  Runat="Server" />

<h2>Shipping Address</h2>

<SuperCompany:Address
  ID="ctlShippingAddress"
  Runat="Server" />

<p>
<asp:Button
  Text="Submit"
  OnClick="Button_Click"
  Runat="Server" />

<p>
<asp:Label
  ID="lblOutput"
  Runat="Server" />

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.


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 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
<Script Runat="Server">

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

</Script>

<asp:Label
  id="lblQuote"
  Runat="Server" />

The C# version of this code can be found on the CD-ROM.

The page in Listing 5.17 uses this user control to randomly display a quotation.

Listing 5.17 DisplayControlEvents.aspx
<%@ Register TagPrefix="SuperCompany" TagName="Quote"
  Src="ControlEvents.ascx" %>

<SuperCompany:Quote
  Runat="Server" />

The C# version of this code can be found on the CD-ROM.


Loading User Controls Programmatically

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 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
<%@ ClassName="FeaturedProduct1" %>

<Script runat="Server">

Public BackColor As String = "lightgreen"

</Script>

<table width="200" cellpadding="10"
  cellspacing="0" bgcolor="<%=BackColor%>">
<tr>
  <td>
  <h3>Hammers on Sale!</h2>
  Quality hammers are now on sale in the
  hardware department.
  </td>
</tr>
</table>

The C# version of this code can be found on the CD-ROM.

Listing 5.19 FeaturedProduct2.ascx
<%@ ClassName="FeaturedProduct2" %>

<Script runat="Server">

Public BackColor As String = "lightgreen"

</Script>

<table width="200" cellpadding="10"
  cellspacing="0" bgcolor="<%=BackColor%>">
<tr>
  <td>
  <h3>Blenders on Sale!</h3>
  See our selection of blenders in the
  home appliance deparment.
  </td>
</tr>
</table>

The C# version of this code can be found on the CD-ROM.

The page in Listing 5.20 demonstrates how you can randomly display one of the featured products.

Listing 5.20 DisplayFeaturedProduct.aspx
<Script runat="Server">

Sub Page_Load
  Dim strFeatured As String
  Dim RanNum As New Random
  Dim ctlControl As Control

  strFeatured = "FeaturedProduct" & RanNum.Next( 1, 3 ) & ".ascx"
  ctlControl = LoadControl( strFeatured )
  plhFeatured.Controls.Add( ctlControl )
End Sub

</Script>

<html>
<head><title>DisplayFeaturedProduct.aspx</title></head>
<body>

<asp:PlaceHolder
  id="plhFeatured"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

The page in Listing 5.20 uses the LoadControl() method to dynamically load a user control. This method takes the path to a user control as its only parameter.

NOTE

An example of an application that makes extensive use of dynamically loaded user controls is the ASP.NET Community Starter Kit. The Community Starter Kit takes advantage of user controls to skin the pages in the application. If you want to dramatically modify the appearance of a Web site created with the Community Starter Kit, then you can select a new set of skins (user controls) that are loaded for all the pages in the application. You can download the ASP.NET Community Starter Kit from www.ASP.net.

Specifying the Type of a User Control

One problem with the LoadControl method used in the previous section is that it returns an object of type Control. It does not return the actual type of the user control. This means that you cannot set any of the properties or call any of the methods of a user control loaded with the LoadControl method without doing a little extra work.

For example, the featured product user controls in Listings 5.18 and 5.19 both have a BackColor property. Since the LoadControl method returns these user controls as generic Control objects, you cannot assign a value to BackColor.

Before you set a property of a dynamically loaded user control, you must cast the user control to the proper type. If you reexamine Listings 5.18 and 5.19, you'll notice that both listings begin with a directive that specifies the user control's ClassName. The ClassName attribute declares the user control's type.

The page in Listing 5.21 illustrates how you can convert a dynamically loaded user control to the proper type and set its properties.

Listing 5.21 DisplayFeaturedProductType.aspx
<%@ Register TagPrefix="SuperCompany" TagName="FeaturedProduct1"
  Src="FeaturedProduct1.ascx" %>
<%@ Register TagPrefix="SuperCompany" TagName="FeaturedProduct2"
  Src="FeaturedProduct2.ascx" %>

<Script runat="Server">

Sub Page_Load
  Dim strFeatured As String
  Dim RanNum As New Random
  Dim ctlControl As Control

  Select RanNum.Next( 1, 3 )
  Case 1
    ctlControl = LoadControl( "FeaturedProduct1.ascx" )
    CType( ctlControl, FeaturedProduct1 ).BackColor = "Yellow"
  Case 2
    ctlControl = LoadControl( "FeaturedProduct2.ascx" )
    CType( ctlControl, FeaturedProduct2 ).BackColor = "Yellow"
  End Select
  plhFeatured.Controls.Add( ctlControl )
End Sub

</Script>

<html>
<head><title>DisplayFeaturedProductType.aspx</title></head>
<body>

<asp:PlaceHolder
  id="plhFeatured"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

The CType function is used in Listing 5.21 to convert the user control being displayed to the proper type. Notice that you must register any user control that you might use in the page with a Register directive. The top of Listing 5.21 contains the following two Register directives:

<%@ Register TagPrefix="SuperCompany" TagName="FeaturedProduct1"
  Src="FeaturedProduct1.ascx" %>
<%@ Register TagPrefix="SuperCompany" TagName="FeaturedProduct2"
  Src="FeaturedProduct2.ascx" %>

If you neglect to register a user control, you'll receive a Type is not defined error message when you attempt to convert the user control to a particular type.

Code-Behind and User Controls

Instead of registering each and every user control that you might use on a page, you can derive multiple user controls from a single class. For example, you can create a single FeaturedProduct class that has a BackColor property. Next, you can derive all of the FeaturedProduct user controls from this class. After you do this, you can set the BackColor property for any featured product by converting the user control to an instance of the FeaturedProduct class.

NOTE

In the next chapter, "Separating Code From Presentation," the topic of building custom classes and code-behind files is discussed in detail.

The first step is to create the FeaturedProduct class. This class is contained in Listing 5.22.

Listing 5.22 FeaturedProduct.vb
Imports System
Imports System.Web.UI
Imports System.Web.UI.WebControls

Public Class FeaturedProduct
Inherits UserControl

Public BackColor As String = "lightgreen"

End Class

The C# version of this code can be found on the CD-ROM.

Notice that the FeaturedProduct class inherits from the UserControl class. The class contains a single BackColor property.

After you create the source file for the class, you'll need to compile it. You can compile the class in Listing 5.22 by executing the following statement from the command line:

vbc /t:library /r:System.dll,System.Web.dll FeaturedProduct.vb

Next, you'll need to copy the compiled FeaturedProduct class file, FeaturedProduct.dll, to the bin directory located under your application's root directory (If this bin directory does not exist, you can create it).

Now that you have a FeaturedProduct class, you can derive each of the featured product user controls from this class as illustrated in Listings 5.23 and 5.24.

Listing 5.23 Featured1.ascx
<%@ Inherits="FeaturedProduct" %>

<table width="200" cellpadding="10"
  cellspacing="0" bgcolor="<%=BackColor%>">
<tr>
  <td>
  <h3>Hammers on Sale!</h2>
  Quality hammers are now on sale in the
  hardware department.
  </td>
</tr>
</table>

The C# version of this code can be found on the CD-ROM.

Listing 5.24 Featured2.ascx
<%@ Inherits="FeaturedProduct" %>

<table width="200" cellpadding="10"
  cellspacing="0" bgcolor="<%=BackColor%>">
<tr>
  <td>
  <h3>Blenders on Sale!</h3>
  See our selection of blenders in the
  home appliance deparment.
  </td>
</tr>
</table>

The C# version of this code can be found on the CD-ROM.

Both of the user controls contained in Listings 5.23 and 5.24 contain an Inherits attribute that indicates that both classes are derived from the base FeaturedProduct class.

Finally, Listing 5.25 illustrates how you can use the featured product user controls in an ASP.NET page. Notice that you do not need to include Register directives before using the controls.

Listing 5.25 DisplayFeaturedProductCodeBehind.aspx
<Script runat="Server">

Sub Page_Load
  Dim strFeatured As String
  Dim RanNum As New Random
  Dim ctlControl As Control

  strFeatured = "Featured" & RanNum.Next( 1, 3 ) & ".ascx"
  ctlControl = LoadControl( strFeatured )
  Ctype( ctlControl, FeaturedProduct ).BackColor="Orange"
  plhFeatured.Controls.Add( ctlControl )
End Sub

</Script>

<html>
<head><title>DisplayFeaturedProductCodeBehind.aspx</title></head>
<body>

<asp:PlaceHolder
  id="plhFeatured"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.


Summary

In this chapter, you learned how to create and work with user controls. First, you learned how to use user controls to include the same content on multiple ASP.NET pages.

Next, I discussed how you could combine multiple Web Forms controls into a single user control. You learned how to create a standard address user control that you can reuse on multiple pages or multiple times on the same page.

You also examined two methods of exposing the properties of a control: by declaring public variables and by declaring property accessor functions.

Finally, you learned how to dynamically load user controls in an ASP.NET page. You also learned how to set properties and call methods of a dynamically loaded user control.


Chapter 6. Separating Code from Presentation

IN THIS CHAPTER

  • Creating Business Components

  • Creating Multitiered Web Applications

  • Using Code-Behind

Web developers are not necessarily good designers. Most companies divide the task of building Web sites between two teams. Normally, one team is responsible for the design content of a page, and the other team is responsible for the application logic.

Maintaining this separation of tasks is difficult when both the design content and application logic are jumbled together on a single page. A carefully engineered ASP.NET page can be easily garbled after being loaded into a design program. Likewise, a beautifully designed page can quickly become mangled in the hands of an engineer.

In this chapter, you learn two methods of dividing your application code from your presentation content. In other words, you learn how to keep both your design and engineering teams happy.

First, you learn how to package application code into custom business components. Using a business component, you can place all your application logic into a separate Visual Basic class file. You also learn how to use business components to build multitiered Web applications.

Next, you learn how to take advantage of a feature of ASP.NET pages named code-behind. Using code-behind, you can place all your application logic into a file and create one or more ASP.NET pages that inherit from this file. Code-behind is the technology used by Visual Studio .NET to divide presentation content from application logic.

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/


Creating Business Components

In this section, you learn how to create business components using Visual Basic and use the components in an ASP.NET page. Business components have a number of benefits:

  • Business components enable you to separate presentation content from application logic. You can design an attractive ASP.NET page and package all the page's application logic into a business component.

  • Business components promote code reuse. You can write a library of useful subroutines and functions, package them into a business component, and reuse the same code on multiple ASP.NET pages.

  • Business components are compiled. You therefore can distribute a component without worrying about the source code being easily revealed or modified.

  • Business components can be written in multiple languages. Some developers like working with Visual Basic, some prefer C# or C++, and some even like COBOL and Perl. You can write components with different languages and combine the components into a single ASP.NET page. You can even call methods from a component written in one language from a component written in another language.

  • Business components enable you to build multitiered Web applications. For example, you can use components to create a data layer that abstracts away from the design specifics of a particular database. Or you can write a set of components that encapsulate your business logic.

Creating a Simple Business Component

A business component is a Visual Basic class file. Whenever you create a component, you need to complete each of the following three steps:

  1. Create a file that contains the definitions for one or more Visual Basic classes and save the file with the extension .vb.

  2. Compile the class file.

  3. Copy the compiled class file into your Web application's /BIN directory.

Start by creating a simple component that randomly displays different quotations. You can call this component the quote component. First, you need to create the Visual Basic class file for the component. The quote component is contained in Listing 6.1.

Listing 6.1 Quote.vb
Imports System

Namespace myComponents

Public Class Quote

  Dim myRand As New Random

  Public Function ShowQuote() As String
    Select myRand.Next( 3 )
      Case 0
        Return "Look before you leap"
      Case 1
        Return "Necessity is the mother of invention"
      Case 2
        Return "Life is full of risks"
    End Select
  End Function

End Class

End Namespace

The C# version of this code can be found on the CD-ROM.

The first line in Listing 6.1 imports the System namespace. You need to import this namespace because you use the Random class in your function, and the Random class is a member of the System namespace.

NOTE

You don't have to import the System, System.Collections, System.Collections.Specialized, System.Configuration, System.Text, System.Text.RegularExpressions, System.Web, System.Web.Caching, System.Web.Security, System.Web.SessionState, System.Web.UI, System.Web.UI.HTMLControls, or System.Web.UI.WebControls namespaces in an ASP.NET page because these namespaces are automatically imported by default. However, a Visual Basic class file does not have default namespaces.

Next, you need to create your own namespace for the class file. In Listing 6.1, you created a new namespace called myComponents. You could have created a namespace with any name you pleased. You need to import this namespace when you create the ASP.NET page that uses this component.

NOTE

If you don't create a namespace for a class, the class is added to something called the global namespace. In general, it's not a good idea to add classes to the global namespace since it increases the likelihood of naming conflicts.

The remainder of Listing 6.1 contains the declaration for your Visual Basic class. The class has a single function, named ShowQuote(), that randomly returns one of three quotations. This function is exposed as a method of the Quote class.

After you write your component, you need to save it in a file that ends with the extension .vb. Save the file in Listing 6.1 with the name Quote.vb.

NOTE

The Visual Basic compiler (vbc.exe) is located in your WINNT\Microsoft.NET\Framework\[version] directory. You might need to add vbc.exe to your PATH environment variable using the DOS Path command. If you install Visual Studio .NET, then you can execute the vbc.exe compiler by opening the Visual Studio .NET Command Prompt located at Start, Programs, Microsoft Visual Studio .NET, Visual Studio .NET Tools.

The next step is to compile your Quote.vb file by using the vbc command-line compiler included with the .NET framework. Open a DOS prompt, navigate to the directory that contains the Quote.vb file, and execute the following statement (see Figure 6.1):

vbc /t:library quote.vb
Figure 6.1. Compiling a component.

The /t option tells the compiler to create a DLL file rather than an EXE file.

NOTE

If you use either Web or HTML controls in your component, you need to add a reference to the system.web.dll assembly when you compile the component like this:

vbc /t:library /r:system.web.dll quote.vb

All the classes in the .NET framework are contained in assemblies. If you use a specialized class, you need to reference the proper assembly when you compile the component. If you look up a particular class in the .NET Framework SDK Documentation, it will list the assembly associated with the class.

If no errors are encountered during compilation, a new file named Quote.dll should appear in the same directory as the Quote.vb file. You now have a compiled business component.

The final step is to move the component to a directory where the Web server can find it. To use the component in your ASP.NET pages, you need to move it to a special directory named /BIN. If this directory does not already exist, you can create it.

ASP CLASSIC NOTE

You do not need to register the component in the server's Registry by using a tool such as regsvr32.exe. Information about ASP.NET components is not stored in the Registry. This means that you can copy a Web application to a new server, and all the components immediately work on that new server.

The /BIN directory must be an immediate subdirectory of your application's root directory. By default, the /BIN directory should be located under the wwwroot directory. However, if your application is contained in a Virtual Directory, you must create the /BIN directory in the root directory of the Virtual Directory.

Immediately after you copy the component to the /BIN directory, you can use it in your ASP.NET pages. For example, the page in Listing 6.2 uses the quote component to assign a random quote to a Label control.

ASP CLASSIC NOTE

Great news! You no longer need to stop and restart your Web server to use a component whenever you modify it. As soon as you move a component into the /BIN directory, the new component will be used for all new page requests.

ASP.NET components are not locked on disk because the Web server maintains shadow copies of all the components in a separate directory. When you replace a component in the /BIN directory, the Web server completes all the current page requests using the old version of the component in the shadow directory. As soon as all the current requests are completed, the shadow copy of the old component is automatically replaced with the new component.

Listing 6.2 ShowQuote.aspx
<%@ Import Namespace="myComponents" %>

<Script Runat="Server">

Sub Page_Load
  Dim myQuote As New Quote

  lblOutput.Text = myQuote.ShowQuote()
End Sub

</Script>

<html>
<head><title>ShowQuote.aspx</title></head>
<body>

And the quote is...
<br>
<asp:Label
  id="lblOutput"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

The first line in Listing 6.2 imports the namespace. After the namespace is imported, you can use your component just like any .NET class.

In the Page_Load subroutine, you create an instance of your component. Next, you call the ShowQuote() method of the component to assign a random quotation to the Label control (see Figure 6.2).

Figure 6.2. Output of the quote component.

Using Properties in a Component

You can add properties to a component in two ways. Either you can create public variables, or you can use property accessor syntax.

Adding properties by using public variables is the easiest method. For example, the component in Listing 6.3 has two variables named FirstValue and SecondValue. Because these variables are exposed as public variables, you can access them as properties of the object.

Listing 6.3 Adder.vb
Imports System

Namespace myComponents

Public Class Adder

  Public FirstValue As Integer
  Public SecondValue As Integer

  Function AddValues() As Integer
    Return FirstValue + SecondValue
  End Function

End Class

End Namespace

The C# version of this code can be found on the CD-ROM.

The component in Listing 6.3 is named the adder component. It simply adds together the values of whatever numbers are assigned to its properties and returns the sum in the AddValues() function.

The ASP.NET page in Listing 6.4 illustrates how you can use the adder component to add the values entered into two TextBox controls.

Listing 6.4 AddValues.aspx
<%@ Import Namespace="myComponents" %>

<Script Runat="Server">

  Sub Button_Click( s As Object, e As EventArgs )
    Dim myAdder As New Adder

    myAdder.FirstValue = txtVal1.Text
    myAdder.SecondValue = txtVal2.Text
    lblOutput.Text = myAdder.AddValues()
  End Sub

</Script>

<html>
<head><title>AddValues.aspx</title></head>
<body>

<form Runat="Server">

Value 1:
<asp:TextBox
  ID="txtVal1"
  Runat="Server" />

<p>

Value 2:
<asp:TextBox
  ID="txtVal2"
  Runat="Server" />

<p>
<asp:Button
  Text="Add!"
  OnClick="Button_Click"
  Runat="Server" />

<p>
Output:
<asp:Label
  ID="lblOutput"
  Runat="Server" />

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

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 C# version of this code can be found on the CD-ROM.

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:

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

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.

Using a Component to Handle Events

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 sub-routine 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

  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

The C# version of this code can be found on the CD-ROM.

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
<%@ Import Namespace="myComponents" %>
<Script Runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  Register.doRegister( txtFirstName.Text, txtLastName.Text, txtFavColor.Text )
End Sub

</Script>

<html>
<head><title>UserRegistration.aspx</title></head>
<body>

<form Runat="Server">

First Name:
<br>
<asp:TextBox
  ID="txtFirstName"
  Runat="Server" />

<p>
Last Name:
<br>
<asp:TextBox
  ID="txtLastName"
  Runat="Server" />

<p>
Favorite Color:
<br>
<asp:TextBox
  ID="txtFavColor"
  Runat="Server" />

<p>
<asp:Button
  Text="Register!"
  OnClick="Button_Click"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

Later in this chapter, in the discussion of code-behind, you learn how to move all your application logic into a separate compiled file.

Using the Current HttpContext in a Component

When using components, it would be nice if you could take advantage of ASP.NET intrinsics such as the Cache, Session, Application, or Trace objects. For example, if you are using a component to return database data, it would be great if there were a way to use the Cache object inside a component so that you could cache the database data returned by the component. Or, if you are trying to debug a component, it would be nice if there were a way to use the Trace.Warn() method to output tracing information to the page that hosts the component.

NOTE

The Cache, Session, and Application objects are discussed in Part IV of this book, "Working with ASP.NET Applications."

Within an ASP.NET page, you are able to use objects such as the Cache and Session objects since these objects are exposed as properties of the Page class. When you are creating a component, on the other hand, these objects are not available in the same way. If you want to use ASP.NET intrinsics within a component, you must first get a reference to the HttpContext object.

For example, if you want to use the Trace object to output a trace message, then you would need to use the following line of code:

System.Web.HttpContext.Current.Trace.Warn( "Trace message from a component!" )

The statement System.Web.HttpContext.Current returns an object that represents the current HttpContext. This object has properties that expose standard ASP.NET objects such as the Trace, Cache, and Session objects.

Since the HttpContext object is located in the System.Web namespace, you must add a reference to the System.Web.dll assembly when compiling a component that refers to the HttpContext object.


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.

Earlier in the chapter, 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 page represents the user interface layer.

  • BizObject— This component represents the business layer.

  • DataObject— This 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).

Listing 6.8 OrderForm.aspx
<%@ Import Namespace="myComponents" %>

<Script Runat="Server">

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

</Script>

<html>
<head><title>OrderForm.aspx</title></head>
<body>

<form Runat="Server">

<h2>Enter an Order:</h2>

<asp:Label
  id="lblError"
  ForeColor="Red"
  Font-Bold="True"
  EnableViewState="False"
  Runat="Server" />

<p>
Customer Name:
<br>
<asp:TextBox
  id="txtCustomer"
  Runat="Server" />

<asp:RequiredFieldValidator
  ControlToValidate="txtCustomer"
  Text="You must enter a customer name!"
  Runat="Server" />

<p>
Product:
<br>
<asp:ListBox
  id="dropProduct"
  Runat="Server">
  <asp:ListItem Text="Hair Dryer" />
  <asp:ListItem Text="Shaving Cream" />
  <asp:ListItem Text="Electric Comb" />
</asp:ListBox>

<asp:RequiredFieldValidator
  ControlToValidate="dropProduct"
  Text="You must select a product!"
  Runat="Server" />

<p>
Unit Price:
<br>
<asp:TextBox
  id="txtUnitPrice"
  Runat="Server" />

<asp:RequiredFieldValidator
  ControlToValidate="txtUnitPrice"
  Text="You must enter a unit price!"
  Runat="Server" />

<p>
Quantity:
<br>
<asp:TextBox
  id="txtQuantity"
  Runat="Server" />

<asp:RequiredFieldValidator
  ControlToValidate="txtQuantity"
  Text="You must enter a quantity!"
  Runat="Server" />

<asp:CompareValidator
  ControlToValidate="txtQuantity"
  Text="Quantity must be a number!"
  Operator="DataTypeCheck"
  Type="Integer"
  Runat="Server" />

<p>
Customer State:
<br>
<asp:DropDownList
  id="dropState"
  Runat="Server">
  <asp:ListItem Text="California" />
  <asp:ListItem Text="Nevada" />
  <asp:ListItem Text="Washington" />
</asp:DropDownList>

<p>
<asp:Button
  Text="Place Order"
  OnClick="Button_Click"
  Runat="Server" />

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 6.3. A simple order form.

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.

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 <= 0 Or 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 C# version of this code can be found on the CD-ROM.

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.

Figure 6.4. Raising an exception in the order form.

NOTE

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.

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 )

    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

The C# version of this code can be found on the CD-ROM.

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.

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.

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.

Listing 6.11 Jumble.aspx
<Script Runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  lblMessage.Text = "Hello!"
End Sub

</Script>

<html>
<head><title>Jumble.aspx</title></head>
<body>

<form Runat="Server">

<asp:Button
  Text="Click Here!"
  OnClick="Button_Click"
  Runat="Server" />

<p>

<asp:Label
  ID="lblMessage"
  Runat="Server" />

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

Listing 6.12 Presentation.aspx
<%@ Page Inherits="myCodeBehind" src="myCodeBehind.vb" %>
<html>
<head><title>Presentation.aspx</title></head>
<body>

<form Runat="Server">

<asp:Button
  Text="Click Here!"
  OnClick="Button_Click"
  Runat="Server" />

<p>

<asp:Label
  ID="lblMessage"
  Runat="Server" />

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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 myCodeBehind.vb
Imports System
Imports System.Web.UI
Imports System.Web.UI.WebControls
Imports 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

The C# version of this code can be found on the CD-ROM.

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 myCodeBehind.vb files anywhere on your Web server, you can immediately open the Presentation.aspx file in a Web browser, and the page will work as expected.

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.

How Code-Behind Really Works

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:

<%@ page inherits="myCodeBehind" src="myCodeBehind.vb" %>

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 'lblMessage' is not declared when attempting to open the presentation page in a browser.

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

<%@ Page Inherits="CodeBehind" src="CodeBehind.vb" %>

to

<%@ page inherits="CodeBehind" %>

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

Listing 6.14 CodeBehindMultiple.vb
Imports System
Imports System.Web.UI
Imports System.Web.UI.WebControls
Imports System.Web.UI.HtmlControls

Public Class CodeBehindMultiple
  Inherits Page

  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!<br>"
    strMessage &= "The current date is "
    strMessage &= DateTime.Now.ToString( "D" )
    Return strMessage
  End Function

End Class

The C# version of this code can be found on the CD-ROM.

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
<%@ Page Inherits="CodeBehindMultiple" src="CodeBehindMultiple.vb" %>

<html>
<head><title>PresentationMultiple1.aspx</title></head>
<body>

<form Runat="Server">

<asp:Label
  ID="lblHeader"
  Runat="Server" />

<h2>Page 1</h2>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

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
<%@ page Inherits="CodeBehindMultiple" src="CodeBehindMultiple.vb" %>

<Script Runat="Server">

Overrides Sub Page_Load
  lblHeader.Text = "Who cares about the current date?"
End Sub

</Script>

<html>
<head><title>PresentationMultiple2.aspx</title></head>
<body>

<form Runat="Server">

<asp:Label
  ID="lblHeader"
  Runat="Server" />

<h2>Page 2</h2>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

Now, try one last example. Imagine that you want to create a page that executes the Page_Load 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.

The presentation page in Listing 6.17 illustrates how you would do so.

Listing 6.17 PresentationMultiple3.aspx
<%@ page inherits="CodeBehindMultiple" src="CodeBehindMultiple.vb" %>

<Script Runat="Server">

Overrides Sub Page_Load
  myBase.Page_Load
  lblHeader.Text &= "<br>And the current time is "
  lblHeader.Text &= Now.ToString( "t" )
End Sub

</Script>

<html>
<head><title>PresentationMultiple3.aspx.aspx</title></head>
<body>

<form Runat="Server">

<asp:Label
  ID="lblHeader"
  Runat="Server" />

<h2>Page 3</h2>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

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

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

The code-behind file in Listing 6.18 contains a complete ASP.NET page in a Visual Basic class.

Listing 6.18 myPage.vb
Imports System
Imports System.Web.UI
Imports System.Web.UI.WebControls
Imports 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 = "<html><head><title>My Page</title></head>"
  strOpenHTML &= "<body>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( "<p>" ) )

  ' Add a Label
  lblLabel.ID = "myLabel"
  frmForm.Controls.Add( lblLabel )

  ' Add Closing HTML Tags
  Dim strCloseHTML As String
  strCloseHTML = "</form></body></html>"
  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 C# version of this code can be found on the CD-ROM.

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.

Listing 6.19 ShowMyPage.aspx
<%@ Page inherits="myPage" %>

The C# version of this code can be found on the CD-ROM.

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.


Summary

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.


Chapter 7. Targeting Mobile Devices with Mobile Controls

IN THIS CHAPTER

  • Introduction to Mobile Devices

  • Using Mobile Device Software Simulators

  • Using ASP.NET Mobile Controls

  • Creating Cross-Device-Compatible Mobile Pages

In this chapter, you'll learn how to write ASP.NET pages that work with mobile devices such as cellular phones and Pocket PC devices.

First, 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 ASP.NET Mobile Controls.

You'll also 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 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


Introduction to Mobile Devices

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.

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 alternatives to HTML were developed that are tailored specifically for mobile devices. These alternatives include HDML, WML, cHTML, and XHTML. The primary challenge of creating pages that are compatible with mobile devices is the challenge of creating pages that will render content that is compatible with all these different standards. The ASP.NET Mobile Controls do precisely that.


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 (see Figure 7.1). The Openwave simulator enables you to display a number of different phones by configuring different skins.

Figure 7.1. Openwave simulator. Image courtesy Openwave Systems Inc.

To download the Openwave phone simulator, go to http://developer.Openwave.com and download the OpenWave SDK.

NOTE

This code samples in this chapter were tested using the Openwave SDK 5.1, which includes both an XHTML and WML browser.

You can also download phone simulators from Nokia by going to forum.nokia.com. Finally, you can use the Microsoft Mobile Explorer Emulator version 3.0 (see Figure 7.2). This simulator is a free download from the Microsoft Web site (go to msdn.microsoft.com).

Figure 7.2. The Microsoft Mobile Explorer Emulator.

Regardless of the phone simulator that you have installed, make sure that you have the latest device updates from Microsoft. Mobile technology is evolving quickly. The device updates contain updates that enable the ASP.NET Mobile Controls to work with the latest mobile devices and simulators. You can download the latest device updates from either msdn.microsoft.com or www.asp.net (click the Mobile tab).

Finally, before downloading a simulator, you should check that the simulator is compatible with the ASP.NET Mobile Controls. Microsoft maintains a list of tested simulators and mobile devices at the www.asp.net Web site (click the Mobile tab).


Using ASP.NET Mobile Controls

The ASP.NET mobile controls were designed to be compatible with a wide variety of mobile devices. The controls will render WML 1.1, HTML 3.2, or XHTML content, depending on the device.

NOTE

The mobile controls also support cHTML (compact HTML). cHTML works with i-Mode devices, which are very popular in Japan.

For example, if you request a page with a cell phone, the mobile controls will render WML- or XHTML-compliant content. If you request a page using a Palm Pilot or Pocket PC browser, HTML 3.2 content will be displayed.

You can also display ASP.NET pages that contain mobile controls in Internet Explorer 6.0 (or higher). This is useful for debugging pages because full error messages can be displayed.

NOTE

In version 1.0 of the .NET Framework, you had to download a separate assembly to use the ASP.NET Mobile Controls. Now, with version 1.1, of the .NET Framework, the ASP.NET Mobile Controls have been integrated with the rest of the .NET Framework.

You can use the mobile controls in a page by including the following page directive:

<%@ Page Inherits="System.Web.UI.MobileControls.MobilePage" %>

All pages that use the mobile controls must inherit from a Code-Behind page called System.Web.UI.MobileControls.MobilePage.

Before you use the ASP.NET mobile controls, you'll need to make some configuration changes to your application. These are necessary to handle some of the limitations of mobile devices.

First, you'll need to enable cookieless sessions. Not all mobile devices support browser cookies. In order for session state to work with these devices, you'll need to use cookieless sessions. Furthermore, unlike a normal ASP.NET page, a mobile page uses session state for storing view state (this is done to preserve bandwidth). Since some of the mobile controls depend on view state, you'll need to enable cookieless sessions in order for these controls to function correctly.

A second change that you'll need to make involves the manner in which URLs are automatically generated. Some mobile devices require fully qualified URLs instead of relative URLs.

You can enable both cookieless sessions and fully qualified URLs by adding the following Web.Config file to the root directory of your application:

<configuration>
<system.web>
  <httpRuntime useFullyQualifiedRedirectUrl="true" />
  <sessionState cookieless="true" />
</system.web>
</configuration>

Creating Mobile Forms

Unlike a normal ASP.NET page, a mobile page can contain more than one form. There is a good reason for this. Mobile devices typically have very slow connection speeds. If more than one form can be transferred to a mobile device at once, then response times when users navigate to different forms can be improved.

The page in Listing 7.1 illustrates how to create a simple page that displays movie choices using the mobile controls.

Listing 7.1 SelectMovie.aspx
<%@ Page Inherits="System.Web.UI.MobileControls.MobilePage" %>

<Mobile:Form runat="server" id="form1">
<b>Select Movie:</b><br/>
<Mobile:Link runat="server" NavigateURL="#form2">Star Wars</Mobile:Link>
<Mobile:Link runat="server" NavigateURL="#form3">Gone with the Wind</Mobile:Link>
<Mobile:Link runat="server" NavigateURL="#form4">Citizen Kane</Mobile:Link>
</Mobile:Form>

<Mobile:Form runat="server" id="form2">
You selected:
<b>Star Wars</b>
</Mobile:Form>

<Mobile:Form runat="server" id="form3">
You selected:
<b>Gone with the Wind</b>
</Mobile:Form>

<Mobile:Form runat="server" id="form4">
You selected:
<b>Citizen Kane</b>
</Mobile:Form>

The C# version of this code can be found on the CD-ROM.

The page in Listing 7.1 contains four forms. The first form is a menu of three movie choices. Each choice is a link to one of the other three forms on the page. For example, if you click the Star Wars link, the mobile form labeled form2 is activated and the text "You selected: Star Wars" is displayed (see Figure 7.3).

Figure 7.3. A simple mobile ASP.NET page displayed in a cell phone. Image courtesy Openwave Systems Inc.

The page in Listing 7.1 will work with a number of mobile devices including devices that support XHTML, WML, cHTML or HTML 3.2. In addition to cell phones, the page in Listing 7.1 is compatible with Internet Explorer 6.0 (see Figure 7.4) and the Palm Pilot Web browser.

Figure 7.4. A simple mobile ASP.NET page displayed in Internet Explorer 6.0.

You should notice that each form is created with a mobile form tag. For example, the first form displayed by the page is created with the following tag:

<Mobile:Form runat="server" id="form1">
...
</Mobile:Form>

When you first open a mobile ASP.NET page, the first form in the page automatically becomes the active form. You can cause other forms to become active by linking to them or by modifying a page's ActiveForm property.

The links in Listing 7.1 are created with the Link control. For example, a link to form3 is created with the following tag:

<Mobile:Link runat="server" NavigateURL="#form3">Gone with the Wind</Mobile:Link>

When the target of a link begins with the # character, the link activates the corresponding form on the page. This link activates form3.

Dynamically Activating a Mobile Form

When you include multiple forms in a mobile control page, you can make any of the forms active by using the ActiveForm property. For example, the page in Listing 7.2 randomly displays one of three forms whenever the page is requested.

Listing 7.2 ShowQuote.aspx
<%@ Page Inherits="System.Web.UI.MobileControls.MobilePage" %>

<Script Runat="Server">

Sub Page_Load
  Dim myRand As New Random
  Select Case myRand.Next( 3 )
   Case 0
     ActiveForm = form0
   Case 1
     ActiveForm = form1
   Case 2
     ActiveForm = form2
  End Select
End Sub

</Script>

<Mobile:Form runat="server" id="form0">
<i>Look before you leap!</i>
</Mobile:Form>

<Mobile:Form runat="server" id="form1">
<i>He who hesitates is lost</i>
</Mobile:Form>

<Mobile:Form runat="server" id="form2">
<i>The early bird gets the worm</i>
</Mobile:Form>

The C# version of this code can be found on the CD-ROM.

The page in Listing 7.2 randomly displays one of three quotations (see Figure 7.5). Whenever the page is requested, the Page_Load subroutine is executed and one of the three forms in the page is activated. A form is activated using the ActiveForm property. Notice that the ActiveForm property does not take a string as its value. Because the value is a mobile form control, it does not appear within quotation marks.

Figure 7.5. A random quotation displayed by a mobile form. Image courtesy Openwave Systems Inc.

Displaying Text

You have three ways to display text in a mobile form control:

  • You can display text directly within a form.

  • You can display text in a Label control.

  • You can display text within a TextView control.

For example, if you simply need to display some bold text within a form, you can display the text like this:

<%@ Page Inherits="System.Web.UI.MobileControls.MobilePage" %>

<Mobile:Form runat="server">
Here is some text:
<b>Hello World!</b>
</Mobile:Form>

If you want to display a small amount of text without any formatting, and you need to have programmatic access to the text, you can use a Label control. For example, the page in Listing 7.3 displays the phrase "Hello World!" in a Label control.

Listing 7.3 Label.aspx
<%@ Page Inherits="System.Web.UI.MobileControls.MobilePage" %>

<Mobile:Form runat="server">
<Mobile:Label Runat="Server" />
Hello World!
</Mobile:Label>
</Mobile:Form>

The C# version of this code can be found on the CD-ROM.

One disadvantage of the Label control is that it cannot contain any HTML tags. You cannot use such tags as the <b> or <i> tags within a Label control. You'll receive an error if you try.

The main advantage of a Label control is that you can modify the control's content within your code. For example, the page in Listing 7.4 displays the current date and time in a Label control.

Listing 7.4 LabelDateTime.aspx
<%@ Page Inherits="System.Web.UI.MobileControls.MobilePage" %>

<Script runat="Server">
Sub Page_Load
  lblDateTime.Text = DateTime.Now()
End Sub
</Script>

<Mobile:Form runat="server">
<Mobile:Label
  ID="lblDateTime"
  Runat="Server"/>
</Mobile:Form>

The C# version of this code can be found on the CD-ROM.

If you need to programmatically assign text that contains HTML formatting to a control, you can use the TextView control. The TextView control can contain the following HTML tags:

  • <p>

  • <br>

  • <b>

  • <i>

  • <a>

For example, the following declaration of a TextView control is perfectly valid:

<Mobile:TextView runat="Server">
Here is some text:
<b>Hello World!</b>
</Mobile:TextView>
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.5 uses Center alignment.

Listing 7.5 Alignment.aspx
<%@ Page Inherits="System.Web.UI.MobileControls.MobilePage" %>

<Mobile:Form runat="server">
<Mobile:Label
  Alignment="Center"
  Runat="Server">
  Hello!
</Mobile:Label>

</Mobile:Form>

The C# version of this code can be found on the CD-ROM.

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.6 contains a long line of text in a form with wrapping disabled. When used with the OpenWave emulator, a long line of text will continue to scroll across the screen, creating a marquee effect.

Listing 7.6 Wrapping.aspx
<%@ Page Inherits="System.Web.UI.MobileControls.MobilePage" %>

<Mobile:Form runat="server">
<Mobile:TextView
  Wrapping="NoWrap"
  Runat="Server">

This is a line of text that goes off the side of the page because it is very long

</Mobile:TextView>
</Mobile:Form>

The C# version of this code can be found on the CD-ROM.

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.7 demonstrates how you can display a list of information using the List mobile control.

Listing 7.7 CurrentMovies.aspx
<%@ Page Inherits="System.Web.UI.MobileControls.MobilePage" %>

<Mobile:Form runat="server">
<b>Now Playing:</b>
<br/>
<Mobile:List runat="Server">
<Item Text="Star Wars" />
<Item Text="Gone with the Wind" />
<Item Text="Citizen Kane" />
</Mobile:List>
</Mobile:Form>

The C# version of this code can be found on the CD-ROM.

The List control is used in Listing 7.7 to display a list of three movies. When the page is opened in a device that supports HTML 3.2 or XHTML, the list is displayed within an HTML table. When displayed in a WML-compatible device, each list item is automatically separated by <br/> 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.8 displays the three movies in a numbered list.

Listing 7.8 ListNumbered.aspx
<%@ Page Inherits="System.Web.UI.MobileControls.MobilePage" %>

<Mobile:Form runat="server">
<b>Now Playing:</b>
<br/>
<Mobile:List
  Decoration="Numbered"
  Runat="Server">
<Item Text="Star Wars" />
<Item Text="Gone with the Wind" />
<Item Text="Citizen Kane" />
</Mobile:List>
</Mobile:Form>

The C# version of this code can be found on the CD-ROM.

Figure 7.6 illustrates how the Decoration property modifies the appearance of a list on Internet Explorer 6.0.

Figure 7.6. Setting Decoration to Numbered on Internet Explorer 6.0.

NOTE

Currently, the Decoration property only affects how a page is rendered in an HTML or XHTML-compatible device. The Decoration property is ignored when rendering WML content.

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.9 demonstrates how you can bind a List control named movies to an ArrayList collection named myArrayList.

Listing 7.9 ListBinding.aspx
<%@ Page Inherits="System.Web.UI.MobileControls.MobilePage" %>

<Script Runat="Server">

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

</Script>

<Mobile:Form runat="server">
<b>Now Playing:</b>
<br/>
<Mobile:List ID="lstMovies" Runat="Server"/>
</Mobile:Form>

The C# version of this code can be found on the CD-ROM.

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.

For example, the List control in Listing 7.10 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.10 ListPaginate.aspx
<%@ Page Inherits="System.Web.UI.MobileControls.MobilePage" %>

<Script Runat="Server">

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

</Script>

<Mobile:Form
  Paginate="true"
  runat="server">
<b>Now Playing:</b>
<br/>
<Mobile:List
  ID="lstMovies"
  Runat="Server"/>
</Mobile:Form>

The C# version of this code can be found on the CD-ROM.

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.7). The page in Listing 7.11 illustrates how to set all three of these properties in a form.

Listing 7.11 ListPagerStyle.aspx
<%@ Page Inherits="System.Web.UI.MobileControls.MobilePage" %>

<Script Runat="Server">

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

</Script>

<Mobile:Form
  Paginate="True"
  PagerStyle-PageLabel="Page {0} of {1}"
  PagerStyle-NextPageText="Continue"
  PagerStyle-PreviousPageText="Back"
  Runat="server">
<b>Now Playing:</b>
<br/>
<Mobile:List
  ID="lstMovies"
  Runat="Server"/>
</Mobile:Form>

The C# version of this code can be found on the CD-ROM.

Figure 7.7. Controlling pagination style.

Creating Interactive Lists

You can also use the List control to display a list of choices. The choices appear as links when displayed in WML, XHTML 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.

For example, the page in Listing 7.12 uses the List control to display a list of movie choices.

Listing 7.12 ListLinks.aspx
<%@ Page Inherits="System.Web.UI.MobileControls.MobilePage" %>

<Script runat="Server">

Sub List_ItemCommand( s As object, e As ListCommandEventArgs )
  lblMovie.Text = e.ListItem.Text
  ActiveForm = form2
End Sub

</Script>

<Mobile:Form runat="server" id="form1">
<b>Select Movie:</b><br/>
<Mobile:List
  OnItemCommand="List_ItemCommand"
  Runat="Server">
<Item text="Star Wars"/>
<Item text="Gone with the Wind"/>
<Item text="Citizen Kane"/>
</Mobile:List>
</Mobile:Form>

<Mobile:Form runat="server" id="form2">
<b>You selected:</b>
<br/>
<Mobile:Label
 ID="lblMovie"
 Runat="Server"/>
</Mobile:Form>

The C# version of this code can be found on the CD-ROM.

When a List control has an event handler, each list item is displayed as a link (see Figure 7.8). In Listing 7.12, 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.8. Displaying a list of links. Image courtesy Openwave Systems Inc.

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.13, the Value property is used to pass the category of each movie.

Listing 7.13 ListValue.aspx
<%@ Page Inherits="System.Web.UI.MobileControls.MobilePage" %>

<Script runat="Server">

Sub List_ItemCommand( s As object, e As ListCommandEventArgs )
  lblMovie.Text = e.ListItem.Text
  lblMovieCategory.Text = e.ListItem.Value
  ActiveForm = form2
End Sub

</Script>

<Mobile:Form runat="server" id="form1">
<b>Select Movie:</b><br/>
<Mobile:List
  OnItemCommand="List_ItemCommand"
  Runat="Server">
<Item text="Star Wars" value="scifi"/>
<Item text="Gone with the Wind" value="romance"/>
<Item text="Citizen Kane" value="drama"/>
</Mobile:List>
</Mobile:Form>

<Mobile:Form runat="server" id="form2">
<b>You selected:</b>
<br/>
<Mobile:Label
 ID="lblMovie"
 Runat="Server"/>
<br/>
<b>Category:</b>
<br/>
<Mobile:Label
 ID="lblMovieCategory"
 Runat="Server"/>
</Mobile:Form>

The C# version of this code can be found on the CD-ROM.

Each of the three items contained in the List control in Listing 7.13 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.

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.14 illustrates how you can use the ObjectList control to associate multiple commands with each list item.

Listing 7.14 ObjectList.aspx
<%@ Page Inherits="System.Web.UI.MobileControls.MobilePage" %>

<Script Runat="Server">

Sub Page_Load
  Dim colArrayList As New ArrayList

  If Not Page.IsPostBack Then
    colArrayList.Add( "Star Wars" )
    colArrayList.Add( "Citizen Kane" )

    lstMovies.Datasource = colArrayList
    lstMovies.Databind()
  End If

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

</Script>

<Mobile:Form id="displayMovies" runat="Server">

<Mobile:ObjectList
  ID="lstMovies"
  OnItemCommand="ObjectList_ItemCommand"
  Runat="Server">
<Command Name="Buy" Text="Buy" />
<Command Name="Desc" Text="Desc" />
</Mobile:ObjectList>

</Mobile:Form>

<Mobile:Form id="frmShowDesc" runat="Server">
  <b>Great Movie!</b>
</Mobile:Form>

<Mobile:Form id="frmBuyTicket" runat="Server">
  <b>Buy Ticket!</b>
</Mobile:Form>

The C# version of this code can be found on the CD-ROM.

The ObjectList control in Listing 7.14 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.

When the page in Listing 7.14 is displayed, each list item is rendered as a link (see Figure 7.9). When you click a link, the page in Figure 7.10 is displayed.

Figure 7.9. Displaying ObjectList links.

Figure 7.10. Output of ObjectList after clicking link.

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 movieTitle, movieCat, and ticketPrice columns and displays all these columns by using the ObjectList.

The ObjectList in Listing 7.15 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.

Listing 7.15 ObjectListMultiple.aspx
<%@ Page Inherits="System.Web.UI.MobileControls.MobilePage" %>

<Script Runat="Server">

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

End Class

Sub Page_Load
  Dim colArrayList As New ArrayList

  If Not Page.IsPostBack Then
    colArrayList.Add( new Movie( "Star Wars", "SciFi", "$7.98" ) )
    colArrayList.Add( new Movie( "Citizen Kane", "Drama", "$4.00" ) )

    lstMovies.Datasource = colArrayList
    lstMovies.Databind
  End If

End Sub

Sub ObjectList_ItemCommand( s As Object, e As ObjectListCommandEventArgs )
  ActiveForm = frmSelectMovie
End Sub

</Script>

<Mobile:Form id="frmDisplayMovies" runat="Server">

<Mobile:ObjectList
  id="lstMovies"
  AutoGenerateFields="False"
  LabelField="movieTitle"
  OnItemCommand="ObjectList_ItemCommand"
  Runat="Server">
<Field DataField="movieTitle" Title="Movie" />
<Field DataField="movieCat" Title="Category" />
<Field DataField="ticketPrice" Title="Price" />
</Mobile:ObjectList>

</Mobile:Form>

<Mobile:Form id="frmSelectMovie" runat="Server">

<b>Movie Selected!</b>

</Mobile:Form>

The C# version of this code can be found on the CD-ROM.

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.

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

Figure 7.11. Entering text on a cell phone. Image courtesy Openwave Systems Inc.

The page in Listing 7.16 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.16 TextBox.aspx
<%@ Page Inherits="System.Web.UI.MobileControls.MobilePage" %>

<Script Runat="Server">

Sub Command_Click( s As Object, e As EventArgs )
  lblContactName.Text = txtContactName.Text
  lblContactPhone.Text = txtContactPhone.Text
  ActiveForm = frmDisplayContact
End Sub

</Script>

<Mobile:Form id="frmAddContact" runat="Server">

<b>Name:</b>
<Mobile:Textbox
  ID="txtContactName"
  Runat="Server"/>
<br/>
<b>Phone:</b>
<Mobile:Textbox
  ID="txtContactPhone"
  Runat="Server"/>
<br/>
<Mobile:Command
  Text="Submit!"
  OnClick="Command_Click"
  Runat="Server" />
</Mobile:Form>

<Mobile:Form id="frmDisplayContact" runat="Server">
<b>You Entered:</b>
<br/>
<Mobile:Label
  ID="lblcontactName"
  Runat="Server"/>
<br/>
<Mobile:Label
  ID="lblContactPhone"
  Runat="Server"/>

</Mobile:Form>

The C# version of this code can be found on the CD-ROM.

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 HTML, XHTML, 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.

The page in Listing 7.17 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.17 TextBoxTypes.aspx
<%@ Page Inherits="System.Web.UI.MobileControls.MobilePage" %>

<Mobile:Form id="frmAddContact" runat="Server">

<b>User Name:</b>
<Mobile:Textbox
  ID="txtUsername"
  Runat="Server"/>
<br/>
<b>Password:</b>
<Mobile:Textbox
  ID="txtPassword"
  Password="True"
  Runat="Server"/>
<br/>
<b>PIN:</b>
<Mobile:Textbox
  ID="txtPIN"
  Numeric="True"
  Runat="Server"/>

</Mobile:Form>

The C# version of this code can be found on the CD-ROM.

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

  • 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. Checks whether a value has been entered into a control

  • ValidationSummary. 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.18 illustrates how to use the Mobile RequiredFieldValidator, RangeValidator, and ValidationSummary Validation controls.

Listing 7.18 Validation.aspx
<%@ Page Inherits="System.Web.UI.MobileControls.MobilePage" %>

<Script Runat="Server">

Sub Command_Click( s As Object, e As EventArgs )
  If Not isValid Then
    ActiveForm = frmBad
  Else
    ActiveForm = frmGood
  End If
End Sub

</Script>

<Mobile:Form id="frmInput" runat="Server">

<b>Enter a number between 3 and 12:</b>
<Mobile:Textbox
  ID="txtNumber"
  Runat="Server"/>

<Mobile:RequiredFieldValidator
  ErrorMessage="Input Required!"
  ControlToValidate="txtNumber"
  Runat="Server" />

<Mobile:RangeValidator
  ErrorMessage="Not in Range!"
  ControlToValidate="txtNumber"
  Type="Integer"
  MinimumValue="3"
  MaximumValue="12"
  Runat="Server" />

<Mobile:Command
  Text="Submit!"
  OnClick="Command_Click"
  Runat="Server"/>

</Mobile:Form>

<Mobile:Form id="frmBad" runat="Server">

<Mobile:ValidationSummary
  formToValidate="frmInput"
  Runat="Server" />

</Mobile:Form>

<Mobile:Form id="frmGood" runat="Server">

<i>Good Job!</i>

</Mobile:Form>

The C# version of this code can be found on the CD-ROM.

The first form in Listing 7.18 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.

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

Figure 7.12. Displaying a Validation Summary. Image courtesy Openwave Systems Inc.

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.19 illustrates how the Image control can be used with multiple devices.

Listing 7.19 Image.aspx
<%@ Page Inherits="System.Web.UI.MobileControls.MobilePage" %>

<Mobile:Form runat="Server">

<Mobile:Image
  ImageUrl="myImage.gif"
  AlternateText="myImage"
  Runat="Server">
  <DeviceSpecific>
    <Choice Filter="PrefersBmp" ImageURL="myImage.bmp" />
    <Choice Filter="PrefersWBmp" ImageURL="myImage.bmp" />
  </DeviceSpecific>
</mobile:Image>

</Mobile:Form>

The C# version of this code can be found on the CD-ROM.

By default, the Image control in Listing 7.19 displays the myImage.gif image. However, the Image control also contains a <DeviceSpecific> tag. When a device that prefers a BMP image requests the page, a BMP image is displayed instead of the GIF image.

NOTE

The <DeviceSpecific> 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 <DeviceSpecific> element, see the last section of this chapter, "Creating Cross-Device-Compatible Mobile Pages."

When a WML-compatible cell phone is detected, a BMP image is displayed (see Figure 7.13). When an Internet Explorer 6.0 browser or XHTML-compatible cell phone is detected, a GIF image is displayed.

Figure 7.13. Image displayed in a cell phone. Image courtesy Openwave Systems Inc.

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.

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 PhoneCall control with a device such as a cell phone, the control enables you to place a call. When the PhoneCall 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 PhoneCall 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.20 illustrates how to use the PhoneCall control.

Listing 7.20 Call.aspx
<%@ Page Inherits="System.Web.UI.MobileControls.MobilePage" %>

<Mobile:Form runat="Server">

<Mobile:PhoneCall
  Text="Call Information"
  PhoneNumber="411"
  Runat="Server" />

</Mobile:Form>

The C# version of this code can be found on the CD-ROM.

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

Listing 7.21 myAds.xml
<Advertisements>
   <Ad>
      <ImageUrl>ad1.gif</ImageUrl>
      <BmpImageUrl>ad1.bmp</BmpImageUrl>
      <NavigateUrl>http://www.AspWorkshops.com</NavigateUrl>
      <AlternateText>Click here for ASP.NET Training!</AlternateText>
      <Impressions>60</Impressions>
   </Ad>
   <Ad>
      <ImageUrl>ad2.gif</ImageUrl>
      <BmpImageUrl>ad2.bmp</BmpImageUrl>
      <NavigateUrl>http://www.superexpert.com</NavigateUrl>
      <AlternateText> Click here to visit superexpert.com! </AlternateText>
      <Impressions>60</Impressions>
   </Ad>
</Advertisements>

The C# version of this code can be found on the CD-ROM.

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

Listing 7.22 AdRotator.aspx
<%@ Page Inherits="System.Web.UI.MobileControls.MobilePage" %>

<Mobile:Form runat="Server">

<Mobile:AdRotator
  AdvertisementFile="myAds.xml"
  Runat="Server">
  <DeviceSpecific>
    <Choice Filter="PrefersBmp" ImageKey="BmpImageUrl"/>
    <Choice Filter="PrefersWBmp" ImageKey="WBmpImageUrl"/>
  </DeviceSpecific>
</Mobile:AdRotator>

</Mobile:Form>

The C# version of this code can be found on the CD-ROM.

The output of Listing 7.22 is displayed in Figure 7.14. The AdRotator control in Listing 7.22 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.14. Displaying a banner advertisement. Image courtesy Openwave Systems Inc.

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.

When the Mobile Calendar control is displayed in an HTML or XHTML-compatible device, a full calendar is rendered on the screen (see Figure 7.15). When the Calendar control is displayed in a WML device, you have a couple of options for selecting the date (see Figure 7.16). 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.15. Calendar control displayed in Internet Explorer 6.0.

Figure 7.16. Calendar control displayed in cell phone. Image courtesy Openwave Systems Inc.

The page in Listing 7.23 illustrates how to add a mobile Calendar control to a form.

Listing 7.23 Calendar.aspx
<%@ Page Inherits="System.Web.UI.MobileControls.MobilePage" %>

<Script Runat="Server">

Sub Calendar_SelectionChanged( s As Object, e As EventArgs )
  lblDate.Text = calSchedule.SelectedDate
  ActiveForm = frmResults
End Sub

</Script>

<Mobile:Form runat="Server">

<b>Schedule Meeting</b>
<br/>
<Mobile:Calendar
  ID="calSchedule"
  OnSelectionChanged="Calendar_SelectionChanged"
  Runat="Server" />

</Mobile:Form>

<Mobile:Form id="frmResults" runat="Server">

<i>You selected:</i>
<br/>
<Mobile:Label
  ID="lblDate"
  Runat="Server" />

</Mobile:Form>

The C# version of this code can be found on the CD-ROM.

When the page in Listing 7.23 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 7.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.

Detecting Mobile Capabilities

When you install the mobile controls, your server's system machine.config file is also automatically modified. Additional entries are added to the <browserCaps> 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.24 displays a number of features of the current device by displaying properties from the MobileCapabilities class.

Listing 7.24 MobileCapabilities.aspx
<%@ Page Inherits="System.Web.UI.MobileControls.MobilePage" %>

<Script Runat="Server">

Sub Page_Load
  Dim caps AS system.Web.Mobile.MobileCapabilities

  caps = Request.Browser
  lblBrowser.Text = caps.Browser
  lblType.Text = caps.Type
  lblPreferredRenderingType.Text = caps.PreferredRenderingType
  lblScreenCharactersWidth.Text = caps.ScreenCharactersWidth
  lblScreenCharactersHeight.Text = caps.ScreenCharactersHeight
End Sub

</Script>

<Mobile:Form runat="Server">

<b>Browser:</b>
<Mobile:Label
  id="lblBrowser"
  Runat="Server"/>
<br/>

<b>Type:</b>
<Mobile:Label
  id="lblType"
  Runat="Server"/>
<br/>

<b>PreferredRenderingType:</b>
<Mobile:Label
  id="lblPreferredRenderingType"
  Runat="Server"/>
<br/>

<b>ScreenCharactersWidth:</b>
<Mobile:Label
  id="lblScreenCharactersWidth"
  Runat="Server"/>
<br/>

<b>ScreenCharactersHeight:</b>
<Mobile:Label
  id="lblScreenCharactersHeight"
  Runat="Server"/>

</Mobile:Form>

The C# version of this code can be found on the CD-ROM.

The page in Listing 7.24 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.17).

Figure 7.17. Displaying device capabilities. Image courtesy Openwave Systems Inc.

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.24. 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 .NET Framework SDK documentation (look up Device Capatibilities Table in the index).

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

The <DeviceSpecific> element works (very roughly) like a Visual Basic Select Case statement. The <DeviceSpecific> element can contain multiple <Choice> elements that match different device criteria. When a <Choice> element is matched, its content is returned.

Earlier in this chapter, in the section titled "Displaying Images," you saw how the <DeviceSpecific> 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 <DeviceSpecific> with other controls, such as the List and ObjectList controls.

For example, the page in Listing 7.25 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.25 DeviceSpecific.aspx
<%@ Page Inherits="System.Web.UI.MobileControls.MobilePage" %>
<Script Runat="Server">

Sub Page_Load()
  Dim colMovies As New ArrayList()
  colMovies.Add( "Star Wars" )
  colMovies.Add( "Gone with the Wind" )
  colMovies.Add( "Citizen Kane" )

  lstMovies.DataSource = colMovies
  lstMovies.DataBind()
End Sub

Sub List_ItemCommand( s As object, e As ListCommandEventArgs )
  lblMovie.Text = e.ListItem.Text
  ActiveForm = form2
End Sub

</Script>

<Mobile:Form runat="server" id="form1">
<Mobile:List
  ID="lstMovies"
  OnItemCommand="List_ItemCommand"
  Runat="Server">

<DeviceSpecific>
<Choice Filter="IsHTML32">
  <HeaderTemplate>
  <h2>Welcome to the Movie Reservation System!</h2>
  </HeaderTemplate>

  <ItemTemplate>
  <p>
  <asp:Button
   Text='<%# Container.ToString() %>'
   Runat="Server" />
  </ItemTemplate>

  <FooterTemplate>
  <p>
  <small>All Contents &copy; copyright 2002 by the Company</small>
  </FooterTemplate>
</Choice>
</DeviceSpecific>

</Mobile:List>
</Mobile:Form>

<Mobile:Form runat="server" id="form2">
<b>You selected:</b>
<br/>
<Mobile:Label
 ID="lblMovie"
 Runat="Server"/>
</Mobile:Form>

The C# version of this code can be found on the CD-ROM.

When the page in Listing 7.25 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.18).

Figure 7.18. Using templates with Internet Explorer 6.0.

When the page in Listing 7.25 is displayed in a WML-compatible browser, all the templates are ignored. The List control is displayed with its default formatting.

Using Form Template Sets

As you saw in the previous section, the <DeviceSpecific> element can be used with Image, List, and ObjectList controls. You can also use the <DeviceSpecific> 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 <Choice> elements. By creating multiple templates, you can display different content for different devices.

The page in Listing 7.26 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.26 FormTemplates.aspx
<%@ Page Inherits="System.Web.UI.MobileControls.MobilePage" %>

<Mobile:Form runat="Server">
<DeviceSpecific>
<Choice Filter="IsWML">
  <HeaderTemplate>
    <b>Welcome to this Web site!</b>
    <br/>
  </HeaderTemplate>
  <FooterTemplate>
    <small>All Content Copyrighted 2001</small>
  </FooterTemplate>
</Choice>
<Choice Filter="IsHTML32">
  <HeaderTemplate>
    <table width="100%" bgcolor="yellow">
    <tr>
      <td>
      Welcome to this Web site!
      </td>
    </tr>
    </table>
  </HeaderTemplate>
  <FooterTemplate>
    <hr>
    <small>All Content Copyrighted &copy; 2001</small>
  </FooterTemplate>
</Choice>
<Choice>
  <HeaderTemplate>
    <b>Welcome to this Web site!</b>
    <br/>
  </HeaderTemplate>
</Choice>
</DeviceSpecific>

<Mobile:Label
  Runat="Server">
  Here is the main body of the page.
</Mobile:Label>

</Mobile:Form>

The C# version of this code can be found on the CD-ROM.


Summary

In this chapter, you learned how to use the Mobile controls to create ASP.NET pages that are compatible with cell phones, PDAs, and other mobile devices. First, you looked at how to use the Microsoft Mobile controls. These Mobile controls can be used to display text, lists, and other display elements, such as images and interactive calendars. Mobile controls can also be used to accept and validate text input with the TextBox control.

Finally, you examined how to make pages that are cross-device compatible and how to use the MobileCapabilities class to detect the features of a particular device. As you saw, the <DeviceSpecific> element can be used to display different content for devices with different features.


Chapter 8. Additional Samples and Controls

IN THIS CHAPTER

  • Using the Microsoft Internet Explorer WebControls

  • Using the ASP.NET Starter Kits

To make it easier for developers to build real-world Web sites, Microsoft offers several controls and sample applications that are not included with the .NET Framework SDK. These additional controls and sample applications are the focus of this chapter.

The first half of this chapter looks at the controls developed by the Internet Explorer team at Microsoft. These controls are known collectively as the Microsoft Internet Explorer WebControls. The Internet Explorer WebControls enable you to create user interface elements such as tree views, tab strips, and toolbars.

In the second half of this chapter, I discuss the ASP.NET Starter Kits. The Starter Kits are complete ASP.NET applications (including source code). They are intended to illustrate best practices for creating applications in the ASP.NET Framework and to act as a starting point for creating new applications. There are five Starter Kits: The Community Starter Kit, the Reports Starter Kit, the Time Tracker Starter Kit, the Commerce Starter Kit, and the Portal Starter Kit.


Using the Microsoft Internet Explorer WebControls

The Microsoft Internet Explorer WebControls are not part of the core ASP.NET Framework. Before you can use these controls, you must first download them from the Microsoft Web site. These controls are available for free download at the following URL:

http://msdn.microsoft.com/downloads/samples/internet/asp_dot_net_servercontrols/webcontrols/

NOTE

As I write this, the IE WebControls have not been updated for ASP.NET 1.1. I've included this section on the IE WebControls since they proved to be extremely popular for ASP.NET 1.0 and it is my hope that they will be updated for ASP.NET 1.1 by the time that you read this note.

After you download the controls, you can execute the setup program to install the controls on any machine configured with the .NET Framework.

NOTE

The documentation for the Microsoft Internet Explorer WebControls is located at the MSDN Web site (msdn.microsoft.com). Visit the following URL:

http://msdn.microsoft.com/workshop/webcontrols/webcontrols_entry.asp

Three main controls are included with the Internet Explorer WebControls: TreeView, Toolbar, and TabStrip. I discuss how you can use each control in an ASP.NET page in the following sections.

Using the TreeView Control

You can use the TreeView control to create a user interface that enables users to navigate through a hierarchy of pages or items. For example, you can create a TreeView control that represents the table of contents and sections for a book. When a user clicks a node displayed in the tree, he is brought to the proper section of the book.

The TreeView control supports both up-level and down-level browsers. When a page containing the TreeView control is requested by Internet Explorer 5.5 (or higher), the control uses DHTML behaviors. When it is requested by any other browser, the control renders standard HTML 3.2 content.

Creating a Simple Tree View

To create a simple tree view, complete the following steps:

  1. Import the proper namespace and register the TreeView control.

  2. Add a TreeView control to your page.

  3. Add one or more TreeNode controls to the TreeView control.

The page in Listing 8.1, for example, displays a parent node, labeled ASP.NET Books, with two child nodes, labeled ASP.NET Unleashed and ASP.NET Tips, Code, and Tutorials.

Listing 8.1 SimpleTreeView.aspx
<%@ Import Namespace="Microsoft.Web.UI.WebControls" %>
<%@ Register TagPrefix="IE" Namespace="Microsoft.Web.UI.WebControls"
Assembly ="Microsoft.Web.UI.WebControls" %>

<html>
<head><title>SimpleTreeView.aspx</title></head>
<body>
<form runat="Server">

<IE:TreeView
  Runat="Server">

<TreeNode
  Text="ASP.NET Books">

  <TreeNode
    Text="ASP.NET Unleashed"/>

  <TreeNode
    Text="ASP.NET Tips, Code, and Tutorials" />

</TreeNode>

</IE:TreeView>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

When you open the page in Listing 8.1, the tree view in Figure 8.1 is displayed in your browser.

Figure 8.1. A simple tree view.

The first line in Listing 8.1 imports the Microsoft.Web.UI.WebControls namespace. Because the TreeView control is not part of the core ASP.NET Framework, you need to explicitly import this namespace to refer to classes in the Internet Explorer WebControls assembly.

Next, you register the tag prefix IE for the Internet Explorer WebControls. Registering the prefix maps the prefix to the proper namespace for the controls. Notice that you use the IE prefix when declaring the TreeView control within the body of the page.

The tree view in the body of the page consists of one TreeView control that contains three TreeNode child controls. Each TreeNode has a Text property that specifies the label for the node. The ASP.NET Books node contains the ASP.NET Unleashed and ASP.NET Tips, Code, and Tutorials nodes.

Detecting TreeView Node Selections

In Listing 8.1, you created a simple tree view. If you click the ASP.NET Books node, the node expands to display its child nodes. However, if you click any of the child nodes, nothing happens. You need to add some logic to the page in Listing 8.1 to capture tree view events.

The TreeView control supports four events:

  • Checked— Raised when a node's check box is clicked

  • Collapse— Raised when a node is collapsed

  • Expand— Raised when a node is expanded

  • SelectedIndexChanged— Raised when a new node is selected

If you want to detect when users select a particular node, you need to capture the SelectedIndexChanged event. The page in Listing 8.2 illustrates how you can associate a subroutine with this event.

Listing 8.2 TreeViewSelectedIndexChanged.aspx
<%@ Import Namespace="Microsoft.Web.UI.WebControls" %>
<%@ Register TagPrefix="IE" Namespace="Microsoft.Web.UI.WebControls"
Assembly ="Microsoft.Web.UI.WebControls" %>

<Script runat="Server">
Sub TreeView_SelectedIndexChanged( s As Object, e As TreeViewSelectEventArgs )
  lblSelectedNode.Text = treeBooks.GetNodeFromIndex( e.NewNode ).Text
End Sub
</Script>

<html>
<head><title>TreeViewSelectedIndexChanged.aspx</title></head>
<body>
<form runat="Server">

<IE:TreeView
  id="treeBooks"
  AutoPostBack="True"
  OnSelectedIndexChange="TreeView_SelectedIndexChanged"
  Runat="Server">

<TreeNode
  Text="ASP.NET Books" >

  <TreeNode
    Text="ASP.NET Unleashed" />

  <TreeNode
    Text="ASP.NET Tips, Code, and Tutorials" />

</TreeNode>

</IE:TreeView>

<p>

<asp:Label
  id="lblSelectedNode"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

Listing 8.3 TreeViewNavigateUrl.aspx
<%@ Import Namespace="Microsoft.Web.UI.WebControls" %>
<%@ Register TagPrefix="IE" Namespace="Microsoft.Web.UI.WebControls"
Assembly ="Microsoft.Web.UI.WebControls" %>

<html>
<head><title>TreeViewNavigateUrl.aspx</title></head>
<body>
<form runat="Server">

<IE:TreeView
  Runat="Server">

<TreeNode
  Text="ASP.NET Web Sites" >

  <TreeNode
    Text="ASP Workshops"
    NavigateUrl="http://www.AspWorkshops.com" />

  <TreeNode
    Text="Superexpert"
    NavigateUrl="http://www.Superexpert.com" />

</TreeNode>

</IE:TreeView>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

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

Listing 8.4 TreeViewImageUrl.aspx
<%@ Import Namespace="Microsoft.Web.UI.WebControls" %>
<%@ Register TagPrefix="IE" Namespace="Microsoft.Web.UI.WebControls"
Assembly ="Microsoft.Web.UI.WebControls" %>

<html>
<head><title>TreeViewImageUrl.aspx</title></head>
<body>
<form runat="Server">

<IE:TreeView
  ImageUrl="dir.gif"
  ExpandedImageUrl="dir_open.gif"
  Runat="Server">

<TreeNode
  Text="ASP.NET Web Sites">

  <TreeNode
    Text="ASP Workshops"
    NavigateUrl="http://www.AspWorkshops.com"/>

  <TreeNode
    Text="Superexpert"
    NavigateUrl="http://www.Superexpert.com"/>

</TreeNode>

</IE:TreeView>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 8.2. Tree view with images.

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

Listing 8.5 TreeViewNodeType.aspx
<%@ Import Namespace="Microsoft.Web.UI.WebControls" %>
<%@ Register TagPrefix="IE" Namespace="Microsoft.Web.UI.WebControls"
Assembly ="Microsoft.Web.UI.WebControls" %>

<html>
<head><title>TreeViewNodeType.aspx</title></head>
<body>
<form runat="Server">

<IE:TreeView
  ImageUrl="dir.gif"
  ExpandedImageUrl="dir_open.gif"
  Runat="Server">

<TreeNodeType
  Type="Document"
  ImageUrl="text.gif" />

<TreeNode
  Text="ASP.NET Web Sites">

  <TreeNode
    Text="ASP Workshops"
    Type="Document"
    NavigateUrl="http://www.AspWorkshops.com" />

  <TreeNode
    Text="Superexpert"
    Type="Document"
    NavigateUrl="http://www.Superexpert.com"/>

</TreeNode>

</IE:TreeView>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 8.3. Tree view with multiple images.

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

Listing 8.6 TreeViewCheckBox.aspx
<%@ Import Namespace="Microsoft.Web.UI.WebControls" %>
<%@ Register TagPrefix="IE" Namespace="Microsoft.Web.UI.WebControls"
Assembly ="Microsoft.Web.UI.WebControls" %>

<Script runat="Server">

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 &= "<li>" & tnNode.Text
    End If
    ShowChecked( tnNode.Nodes )
  Next
End Sub

</Script>

<html>
<head><title>TreeViewCheckbox.aspx</title></head>
<body>
<form runat="Server">

<IE:TreeView
  id="treeIceCream"
  AutoPostBack="True"
  Runat="Server">

<TreeNode
  Text="Ice Cream" >

  <TreeNode
    CheckBox="True"
    Text="Rocky Road"/>
  <TreeNode
    CheckBox="True"
    Text="Chocolate"/>
  <TreeNode
    CheckBox="True"
    Text="Strawberry"/>
  <TreeNode
    CheckBox="True"
    Text="Vanilla"/>

</TreeNode>

</IE:TreeView>

<p>
<asp:Label
  id="lblCheckedNodes"
  EnableViewState="False"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 8.4. Tree view with check boxes.

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.

The TreeView control in Listing 8.7, for example, is bound to an XML file named TreeNodes.xml.

Listing 8.7 TreeViewNodeSrc.aspx
<%@ Import Namespace="Microsoft.Web.UI.WebControls" %>
<%@ Register TagPrefix="IE" Namespace="Microsoft.Web.UI.WebControls"
Assembly ="Microsoft.Web.UI.WebControls" %>

<html>
<head><title>TreeViewNodeSrc.aspx</title></head>
<body>
<form runat="Server">

<IE:TreeView
  TreeNodeSrc="TreeNodes.xml"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

The contents of the TreeNode.xml file, contained in Listing 8.8, are used to create all the nodes in the tree.

Listing 8.8 TreeNodes.xml
<TREENODES>
  <TREENODE TEXT="ASP.NET Web Sites">
    <TREENODE
       TEXT="ASP Workshops"
       NAVIGATEURL="http://www.AspWorkshops.com" />
    <TREENODE
       TEXT="Superexpert"
       NAVIGATEURL="http://www.Superexpert.com" />
  </TREENODE>
</TREENODES>

The C# version of this code can be found on the CD-ROM.

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
<%@ Import Namespace="Microsoft.Web.UI.WebControls" %>
<%@ Register TagPrefix="IE" Namespace="Microsoft.Web.UI.WebControls"
Assembly ="Microsoft.Web.UI.WebControls" %>

<html>
<head><title>TreeViewNodeSources.aspx</title></head>
<body>
<form runat="Server">

<IE:TreeView
  AutoPostBack="True"
  Runat="Server">

<TreeNode
  Text="Microsoft ASP.NET Sites"
  TreeNodeSrc="MicrosoftASPNET.xml" />

<TreeNode
  Text="Community ASP.NET Sites"
  TreeNodeSrc="CommunityASPNET.xml" />

</IE:TreeView>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

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
<TREENODES>
    <TREENODE
       TEXT="ASP.NET"
       NAVIGATEURL="http://www.asp.net" />
    <TREENODE
       TEXT="GotDotNet"
       NAVIGATEURL="http://www.GotDotNet.com" />
</TREENODES>

The C# version of this code can be found on the CD-ROM.

Listing 8.11 CommunityASPNET.xml
<TREENODES>
    <TREENODE
       TEXT="AspWorkshops"
       NAVIGATEURL="http://www.AspWorkshops.com" />
    <TREENODE
       TEXT="Superexpert"
       NAVIGATEURL="http://www.Superexpert.com" />
</TREENODES>

The C# version of this code can be found on the CD-ROM.

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

Listing 8.12 TreeViewDataSet.aspx
<%@ Import Namespace="Microsoft.Web.UI.WebControls" %>
<%@ Register TagPrefix="IE" Namespace="Microsoft.Web.UI.WebControls"
Assembly ="Microsoft.Web.UI.WebControls" %>

<html>
<head><title>TreeViewDataSet.aspx</title></head>
<body>
<form runat="Server">

<IE:TreeView
  AutoPostBack="True"
  TreeNodeSrc="Categories.aspx"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 8.5. Tree view of the DataSet.

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
[View full width]
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<Script runat="Server">

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

</Script>

The C# version of this code can be found on the CD-ROM.

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 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 WriteXml method is called to output the contents of the DataSet as XML.

When the Categories.aspx page is requested, it renders the following XML document:

<TREENODES>
  <TreeNode Text="Beverages" TreeNodeSrc="products.aspx?catid=1" />
  <TreeNode Text="Condiments" TreeNodeSrc="products.aspx?catid=2" />
  <TreeNode Text="Confections" TreeNodeSrc="products.aspx?catid=3" />
  <TreeNode Text="Dairy Products" TreeNodeSrc="products.aspx?catid=4" />
  <TreeNode Text="Grains/Cereals" TreeNodeSrc="products.aspx?catid=5" />
  <TreeNode Text="Meat/Poultry" TreeNodeSrc="products.aspx?catid=6" />
  <TreeNode Text="Produce" TreeNodeSrc="products.aspx?catid=7" />
  <TreeNode Text="Seafood" TreeNodeSrc="products.aspx?catid=8" />
</TREENODES>

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.

The Products.aspx file is contained in Listing 8.14.

Listing 8.14 Products.aspx
[View full width]
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<Script runat="Server">

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"
    dstProducts.WriteXml( Response.OutputStream )
  conNorthwind.Close()
End Sub

</Script>

The C# version of this code can be found on the CD-ROM.

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 down-level 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 a button that can be selected or unselected

  • ToolbarCheckGroup— Groups multiple ToolbarCheckButton controls and causes them to function like radio buttons (only one can be selected at a time)

  • ToolbarDropDownList— Renders a drop-down list that can be used as a simple menu

  • ToolbarLabel— Renders a text or image label

  • ToolbarSeparator— Renders a separator between items in the Toolbar control

  • ToolbarTextBox— Renders 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.

  3. Add one or more ToolbarButton, ToolbarCheckButton, ToolbarCheckGroup, ToolbarDropDownList, ToolbarLabel, ToolbarSeparator, or ToolbarTextBox controls to the Toolbar control.
Figure 8.6. A simple toolbar.

Listing 8.15, for example, contains a single Toolbar control that contains six ToolbarButton controls separated by ToolbarSeparator controls.

Listing 8.15 SimpleToolbar.aspx
<%@ Import Namespace="Microsoft.Web.UI.WebControls" %>
<%@ Register TagPrefix="IE" Namespace="Microsoft.Web.UI.WebControls"
Assembly ="Microsoft.Web.UI.WebControls" %>

<html>
<head><title>SimpleToolbar.aspx</title></head>
<body>
<form runat="Server">

<IE:Toolbar
  Runat="Server">
  <IE:ToolbarButton
    Text="File" />
  <IE:ToolbarSeparator />
  <IE:ToolbarButton
    Text="Edit" />
  <IE:ToolbarSeparator />
  <IE:ToolbarButton
    Text="View" />
  <IE:ToolbarSeparator />
  <IE:ToolbarButton
    Text="Favorites" />
  <IE:ToolbarSeparator />
  <IE:ToolbarButton
    Text="Tools" />
  <IE:ToolbarSeparator />
  <IE:ToolbarButton
    Text="Help" />
</IE:Toolbar>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

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

Listing 8.16 ToolbarEvents.aspx
<%@ Import Namespace="Microsoft.Web.UI.WebControls" %>
<%@ Register TagPrefix="IE" Namespace="Microsoft.Web.UI.WebControls"
Assembly ="Microsoft.Web.UI.WebControls" %>

<Script runat="Server">

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

</Script>

<html>
<head><title>ToolbarEvents.aspx</title></head>
<body>
<form runat="Server">

<IE:Toolbar
  AutoPostBack="True"
  OnButtonClick="Toolbar_ButtonClick"
  Runat="Server">
  <IE:ToolbarLabel
    Text="Text" />
  <IE:ToolbarTextBox
    id="txtSampleText" />
  <IE:ToolbarButton
    Text="Update!" />
  <IE:ToolbarSeparator />
  <IE:ToolbarCheckGroup
    ID="chkgColor">
    <IE:ToolbarCheckButton
      Text="Red"  Selected="True" />
    <IE:ToolbarCheckButton
      Text="Green" />
    <IE:ToolbarCheckButton
      Text="Blue" />
  </IE:ToolbarCheckGroup>
</IE:Toolbar>

<p>
<asp:Label
  id="lblText"
  Font-Size="16pt"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 8.7. Capturing toolbar events.

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.

Listing 8.17 ToolbarStyle.aspx
<%@ Import Namespace="Microsoft.Web.UI.WebControls" %>
<%@ Register TagPrefix="IE" Namespace="Microsoft.Web.UI.WebControls"
Assembly ="Microsoft.Web.UI.WebControls" %>

<html>
<head><title>ToolbarStyle.aspx</title></head>
<body>
<form runat="Server">

<IE:Toolbar
  Width="10px"
  Font-Name="Impact"
  Font-Size="16pt"
  BackColor="Orange"
  Orientation="Vertical"
  Runat="Server">
  <IE:ToolbarCheckButton
    Text="File" />
  <IE:ToolbarCheckButton
    Text="Edit" />
  <IE:ToolbarCheckButton
    Text="View" />
  <IE:ToolbarCheckButton
    Text="Favorites" />
  <IE:ToolbarCheckButton
    Text="Tools" />
  <IE:ToolbarCheckButton
    Text="Help" />
</IE:Toolbar>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

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 style applied to the Toolbar button when the button is selected

  • 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
<%@ Import Namespace="Microsoft.Web.UI.WebControls" %>
<%@ Register TagPrefix="IE" Namespace="Microsoft.Web.UI.WebControls"
Assembly ="Microsoft.Web.UI.WebControls" %>

<html>
<head><title>ToolbarButtonStyle.aspx</title></head>
<body>
<form runat="Server">

<IE:Toolbar
  DefaultStyle="padding:10px;color:blue;font: 14pt Arial"
  SelectedStyle="padding:10px;color:green;font: 14pt Arial"
  HoverStyle="padding:10px;color:red;font: 14pt Arial"
  Runat="Server">
  <IE:ToolbarCheckButton
    Text="File" />
  <IE:ToolbarCheckButton
    Text="Edit" />
  <IE:ToolbarCheckButton
    Text="View" />
  <IE:ToolbarCheckButton
    Text="Favorites" />
  <IE:ToolbarCheckButton
    Text="Tools" />
  <IE:ToolbarCheckButton
    Text="Help" />
</IE:Toolbar>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

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.

Listing 8.19 illustrates how you can add a simple TabStrip to an ASP.NET page (see Figure 8.8).

Listing 8.19 SimpleTabStrip.aspx
<%@ Import Namespace="Microsoft.Web.UI.WebControls" %>
<%@ Register TagPrefix="IE" Namespace="Microsoft.Web.UI.WebControls"
Assembly ="Microsoft.Web.UI.WebControls" %>

<html>
<head><title>SimpleTabStrip.aspx</title></head>
<body>
<form runat="Server">

<IE:TabStrip
  TargetID="mpgPages"
  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;"
  Runat="Server">
  <IE:Tab
    Text="Tab 1" />
  <IE:TabSeparator />
  <IE:Tab
    Text="Tab 2" />
  <IE:TabSeparator />
  <IE:Tab
    Text="Tab 3" />
  <IE:TabSeparator
    DefaultStyle="width:100%" />
</IE:TabStrip>

<IE:MultiPage
  id="mpgPages"
  Style="width:400px;padding:10px;border:solid 1px blue;border-top:none"
  Runat="Server">
  <IE:PageView>

  Page 1

  </IE:PageView>
  <IE:PageView>

  Page 2

  </IE:PageView>
  <IE:PageView>

  Page 3

  </IE:PageView>

</IE:MultiPage>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 8.8. A simple TabStrip.

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

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.

CAUTION

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 the style attributes applied to the selected Tab

  • TabHoverStyle— Specifies the style attributes applied to a Tab when the mouse pointer hovers over it

  • SepDefaultStyle— Specifies the style attributes applied to each TabSeparator control by default

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

Listing 8.20 TabStripImage.aspx
<%@ Import Namespace="Microsoft.Web.UI.WebControls" %>
<%@ Register TagPrefix="IE" Namespace="Microsoft.Web.UI.WebControls"
Assembly ="Microsoft.Web.UI.WebControls" %>

<html>
<head><title>TabStripImage.aspx</title></head>
<body>
<form runat="Server">

<IE:TabStrip
  TargetID="mpgPages"
  Runat="Server">
  <IE:Tab
    DefaultImageUrl="tab1.gif"
    SelectedImageUrl="tab1_selected.gif" />
  <IE:TabSeparator />
  <IE:Tab
    DefaultImageUrl="tab2.gif"
    SelectedImageUrl="tab2_selected.gif" />
  <IE:TabSeparator />
  <IE:Tab
    DefaultImageUrl="tab3.gif"
    SelectedImageUrl="tab3_selected.gif" />
  <IE:TabSeparator
    DefaultStyle="width:100%" />
</IE:TabStrip>

<IE:MultiPage
  id="mpgPages"
  Runat="Server">
  <IE:PageView>

  Page 1

  </IE:PageView>
  <IE:PageView>

  Page 2

  </IE:PageView>
  <IE:PageView>

  Page 3

  </IE:PageView>

</IE:MultiPage>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 8.9. TabStrip with images.

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.

Listing 8.21 TabStripVertical.aspx
<%@ Import Namespace="Microsoft.Web.UI.WebControls" %>
<%@ Register TagPrefix="IE" Namespace="Microsoft.Web.UI.WebControls"
Assembly ="Microsoft.Web.UI.WebControls" %>

<html>
<head><title>TabStripVertical.aspx</title></head>
<body>
<form runat="Server">

<table cellpadding=0 cellspacing=0 border=0>
<tr><td>

<IE:TabStrip
  TargetID="mpgPages"
  Orientation="Vertical"
  Style="width:50px;height:400px;font:bold 10pt Arial"
  TabDefaultStyle="border:solid 1px blue;background:#dddddd;padding:5px"
  TabHoverStyle="color:red"
  TabSelectedStyle="background:white;border-right:none"
  SepDefaultStyle="height:10px;border-right:solid 1px blue;"
  Runat="Server">
  <IE:Tab
    Text="Tab 1" />
  <IE:TabSeparator />
  <IE:Tab
    Text="Tab 2" />
  <IE:TabSeparator />
  <IE:Tab
    Text="Tab 3" />
  <IE:TabSeparator
    DefaultStyle="height:100%" />
</IE:TabStrip>

</td><td>

<IE:MultiPage
  id="mpgPages"
  Style="height:400px;width:600px;padding:30px;border:solid 1px blue; border-left:none"
  Runat="Server">
  <IE:PageView>

  Page 1

  </IE:PageView>
  <IE:PageView>

  Page 2

  </IE:PageView>
  <IE:PageView>

  Page 3

  </IE:PageView>

</IE:MultiPage>

</td></tr>
</table>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 8.10. Vertical TabStrip.

    [ Team LiB ]


Using the ASP.NET Starter Kits

The ASP.NET Starter Kits illustrate a number of best practices for building applications using the ASP.NET Framework. There are five Starter Kits: The Community Starter Kit, the Reports Starter Kit, the Time Tracker Starter Kit, the Commerce Starter Kit, and the Portal Starter Kit. All five Starter Kits can be freely downloaded at www.Asp.Net (click the Starter Kits tab).

Each Starter Kit includes all of its source code. There are four versions of each Starter Kit:

  • VBSDK— The VB.NET SDK version. This version does not require Visual Studio .NET. The source code uses single file ASP.NET pages.

  • CSSDK— The C# SDK version. This version does not require Visual Studio .NET. The source code uses single file ASP.NET pages.

  • VBVS— The VB.NET Visual Studio .NET version. This version is designed to be used with Visual Studio .NET. The pages use a code-behind model.

  • CSVS— The C# Visual Studio .NET version. This version is designed to be used with Visual Studio .NET. The pages use a code-behind model.

NOTE

There are two additional versions of the Community Starter Kit: a J# SDK and J# VS version.

There are forums devoted to discussing the Starter Kits at the www.ASP.net Web site (click the Forums tab).

You are encouraged to use the source code from these Starter Kits in your applications. You can freely reuse the source code for both commercial and non-commercial applications (see the End Users License Agreement for details).

The ASP.NET Community Starter Kit

The ASP.NET Community Starter Kit enables you to quickly build a community Web site such as a user group site, developer resource site, or news site. For example, you can use the Community Starter Kit to quickly build a .NET User Group Web site (see Figure 8.11).

Figure 8.11. The ASP.NET Community Starter Kit.

NOTE

In the interest of full disclosure, I should mention that my company designed and built the ASP.NET Community Starter Kit for Microsoft. So, for obvious reasons, I think that it is the most interesting of the Starter Kits.

The Community Starter Kit includes nine standard content modules:

  • Articles— This module can be used for posting articles such as news articles

  • Events— This module can be used for posting events such as upcoming user group events

  • Books— This module can be used for posting books. For example, your user group might want to list books and book reviews.

  • Photo Gallery— This module can be used for displaying photos. For example, you can use this module to display photos from user group meetings.

  • Downloads— This module can be used for posting downloadable files. For example, you might want to enable users to download sample files or PowerPoint presentations from speakers.

  • Links— This module can be used for posting links to other Web sites. For example, you might want to display a list of links to .NET resources.

  • HTML— This module can be used for posting HTML pages.

  • Static Page— This module can be used for creating a custom section with a user control.

  • Parent— This module can be used for grouping other sections.

All of the modules support the following properties:

  • Security— You can specify roles for viewing, adding, editing, and deleting content for any section.

  • Moderation— You can enable moderation for any section enabling an administrator to approve or reject any content posted to the section.

  • Comments— You can enable users to comment on any section.

  • Ratings— You can enable users to rate content added to any section.

  • Web Service— You can expose the content of any section through an XML Web service.

For example, you can create an articles section and enable users to comment and rate the articles. You can also prevent anyone except members of the Administrators role from adding new articles.

In addition to these standard content modules, the Community Starter Kit supports the following special features:

  • Skins— You can easily customize the appearance of a community by selecting from a list of included skins. Alternatively, if you want to create a community with a unique appearance, then you can create your own skins.

  • Multiple Communities— You can use a single installation of the Community Starter Kit to host multiple communities. The individual communities can be distinguished by domain or subdomain name.

  • Newsletters— You can send a newsletter to the registered members of your community with the newsletter module.

  • Voting— You can create a voting poll and enable your community users to vote on any topic.

Creating a Community with the Community Starter Kit

When you install the Community Starter Kit, a sample community named Lunar .NET is created (the sample community represents a fictitious .NET user group located on the moon). The sample community illustrates each of the standard modules.

Unless you are planning on starting a user group on the moon, you'll want to modify this sample community. To do this, you'll need to log in as an Administrator of the community. You can log in as an Administrator by clicking the Login link and entering the username Admin and password Admin.

After you log in, you can access the Community Admin pages by clicking the Admin link in the section menu. The main page for the Community Admin menu is displayed in Figure 8.12.

Figure 8.12. The Community Admin pages.

Selecting a Theme

No one wants their Web site to look like other Web sites on the Internet. Everyone wants to be special.

You can easily and radically modify the appearance of your community by selecting a new theme. A theme determines the layout and style of the pages in your community.

To select a new theme, click the Edit Site link in the Community Admin pages. In the Edit Theme box, select a skin and a style from the drop-down list boxes. Figures 8.13 and 8.14 illustrate the home page of the Community Starter Kit with two different themes selected.

Figure 8.13. The Professional Theme.

Figure 8.14. The Arc Theme.

The Community Starter Kit is packaged with eight standard themes. If you don't like any of these standard themes, you can easily create a new one of your own. A theme consists of a set of user controls (.ascx files) that contain the HTML for laying out the pages.

All the themes are found in a folder named Themes in the root Community Starter Kit folder. To create a new theme, just copy one of the existing themes (such as Professional) into a new folder under the Themes directory. After you create the new folder, you can open the individual .ascx files and modify the HTML contained in the files.

Creating and Editing Sections

A Community Starter Kit Web site consists of sections of content. For example, a community might have one or more article, download, or photo gallery sections.

You can add a new section or modify an existing section by using the Community Admin pages. For example, follow these steps to add a new article section to your community:

  1. Click the Edit Sections link.

  2. Click the Add New Section link at the bottom of the page.

  3. Enter a name, title, and description for your new section. For example, My Articles, My Articles Section, This section contains articles.

  4. In the Contents box, select Articles.

  5. Click the Add Section button.

Immediately after you add the new section, a link to the section will appear in the section menu on the home page of your community.

The ASP.NET Reports Starter Kit

The Reports Sample Kit is actually a collection of eight sample report applications:

  • Tabular Report— Displays list of categories and list of matching products under each category

  • Visual Report— Displays report using Pie Chart, Bar Chart, or Tabular display.

  • Cross Tab Report— Displays column and row totals

  • Master Details Report— Displays a report filtered by year and quarter

  • Simple Report— Displays a simple list of customer contact information

  • Text Report— Displays a list of employees and employee information

  • Hierarchical Report— Displays three dependent reports showing progressively more detailed information

  • Drill-Down Report— Displays a list of customers and enables you to click on a customer to see a list of orders associated with the customer

The screenshot in Figure 8.15, for example, is from the Visual Report.

Figure 8.15. The Visual Report.

If you need to add a report to your ASP.NET application, then you can easily copy the sample code in the Reports Starter Kit to do it. Two of the more interesting features of the Reports Starter Kit are its use of GDI+ for generating the Pie and Bar charts and its support for printing reports.

The ASP.NET Time Tracker Starter Kit

The ASP.NET Time Tracker Starter Kit enables you to track time spent on projects. There are several interesting features of this Starter Kit:

  • Reports— The Time Tracker enables you to generate reports and charts (the charts are generated with GDI+).

  • Windows Authentication— The Time Tracker uses the Active Directory or the NT SAM for account information.

  • Mobile Support— The Time Tracker includes support for mobile devices such as cell phones and PDAs.

The screenshot in Figure 8.16 displays the home page for the Time Tracker.

Figure 8.16. The Time Tracker Starter Kit.

The ASP.NET Commerce Starter Kit

The ASP.NET Commerce Starter Kit is a sample application that illustrates how to create a simple online store by using the ASP.NET Framework (see Figure 8.17).

Figure 8.17. The Commerce Starter Kit.

Here are some of the more interesting features of this Starter Kit:

  • Shopping Cart— The Commerce Starter Kit illustrates how to create a shopping cart that stores items in a database.

  • Web Service Support— The Commerce Starter Kit contains an InstantOrder Web service that enables you to remotely place orders or query the current status of an order.

  • Search— The Commerce Starter Kit illustrates how to create a simple search page that returns matching products.

NOTE

The ASP.NET Commerce Starter Kit was originally named the IBuySpy Store.

The ASP.NET Portal Starter Kit

The ASP.NET Portal Starter Kit can be used to quickly create a portal Web site (see Figure 8.18). The ASP.NET Portal includes the following standard modules:

  • Announcements— This module can be used to display a list of news items.

  • Contacts— This module can be used to display contact information for a group of people.

  • Discussion— This module can be used to display a threaded discussion group.

  • Documents— This module can be used to display links to downloadable documents.

  • Events— This module can be used to display a list of upcoming events.

  • HTML— This module can be used to display an HTML page.

  • Image— This module can be used to display a single image.

  • Links— This module can be used to display a list of hyperlinks.

  • Quick Links— This module can be used to display a compact set of hyperlinks.

  • XML/XSL— This module can be used to display the output of an XSL transform on an XML document.

Figure 8.18. The ASP.NET Portal Starter Kit.

The ASP.NET Portal Starter Kit is similar to the ASP.NET Community Starter Kit. However, the two applications were created for different audiences. The ASP.NET Community Starter Kit was designed for building community Web sites such as user group, developer resource, and news sites. Unlike the Portal Starter Kit, the Community Starter Kit includes content management features. For example, the Community Starter Kit enables you to specify who can view, add, delete, and update content. The Community Starter Kit also supports moderation so that an administrator can approve or reject any content submitted to a community.

The ASP.NET Portal Starter Kit, on the other hand, is intended to be used as a starting point for intranet or Internet portal sites. Since the Portal Starter Kit is older than the Community Starter Kit, there are currently more third-party modules for the Portal Starter Kit than the Community Starter Kit.

NOTE

The ASP.NET Portal Starter Kit was originally named the IBuySpy Portal.


Summary

This chapter discussed additional ASP.NET controls and sample applications from Microsoft 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 were provided with a overview of the ASP.NET Starter Kits. You learned about the Community Starter Kit, the Reports Starter Kit, the Time Tracker Starter Kit, the Commerce Starter Kit, and the Portal Starter Kit.


Part III: Working with ADO.NET

IN THIS PART

CHAPTER 9 Introduction to ADO.NET

CHAPTER 10 Binding Data to Web Controls

CHAPTER 11 Using the DataList and DataGrid Controls

CHAPTER 12 Working with DataSets

CHAPTER 13 Working with XML

CHAPTER 14 Using ADO.NET to Create a Search Page


Chapter 9. Introduction to ADO.NET

IN THIS CHAPTER

  • An Overview of ADO.NET

  • Performing Common Database Tasks

  • Improving Database Performance

  • Advanced Database Topics

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 and maintainability 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, if you examine the classes contained in these namespaces, you'll notice that the classes are very similar. There is a simple reason for this. Microsoft has created separate namespaces that are optimized for working with different data providers (different types of databases).

The following data provider specific namespaces are included with ADO.NET:

  • System.Data.SqlClient— Contains classes for connecting to Microsoft SQL Server version 7.0 or higher

  • System.Data.OleDb— Contains classes for connecting to a data source that has an OLE DB provider

  • System.Data.Odbc— Contains classes for connecting to a data source that has an ODBC driver

  • System.Data.OracleClient— Contains classes for connecting to an Oracle database server

NOTE

It is expected that additional data provider-specific namespaces will be released over time. Microsoft has already released a separate set of classes for working with Microsoft SQL Server in the Compact Framework, and a separate set of classes for working with XML generated from SQL Server 2000. For more information on these additional namespaces, see the msdn.microsoft.com Web site.

Oracle has also released their own namespace for working with Oracle databases. You can download the Oracle provider for .NET (ODP.NET) from the Oracle Web site.

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.

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 one of the other data provider-specific namespaces.

For example, if you want to connect to a Microsoft Access database, then you'll need to use the classes from the System.Data.OleDb namespace. The System.Data.OleDb namespace includes the following classes:

  • OleDbConnection

  • OleDbCommand

  • OleDbDataReader

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.

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 different versions for different types of databases? By creating separate sets of classes, Microsoft was able to optimize the classes.

For example, 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 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- and Oracle-specific classes if you use the System.Data.OleDb namespace.

In general, the classes in the different namespaces duplicate the same functionality. Just remember to use the appropriate classes for your data provider. We'll be concentrating on the classes from the System.Data.SqlClient and System.Data.OleDb namespaces in this chapter.

NOTE

You examine another important set of ADO.NET classes in Chapter 12, "Working with DataSets." 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:

<%@ Import Namespace="System.Data.SqlClient" %>

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:

<%@ Import Namespace="System.Data.OleDb" %>

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
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
  Dim conPubs As SqlConnection

  conPubs = New SqlConnection( "Server=localhost;uid=sa;pwd=secret;database=pubs" )
  conPubs.Open()
End Sub

</Script>

Connection Opened!

The C# version of this code can be found on the CD-ROM.

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.

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.

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
[View full width]
<%@ Import Namespace="System.Data.OleDb" %>

<Script Runat="Server">

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

</Script>

Connection Opened!

The C# version of this code can be found on the CD-ROM.

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 using an OLE DB provider, you use the MSDAORA provider.

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

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:

SELECT column1, column2...
FROM tablename1, tablename2...
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'

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

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

Listing 9.3 SqlDataReader.aspx
<%@ Import Namespace="System.Data.SqlClient" %>
<%
Dim conPubs As SqlConnection
Dim cmdSelectAuthors As SqlCommand
Dim dtrAuthors As SqlDataReader

conPubs = New SqlConnection( "Server=localhost;uid=sa;pwd=secret;database=pubs" )
conPubs.Open()
cmdSelectAuthors = New SqlCommand( "Select au_lname From Authors", conPubs )
dtrAuthors = cmdSelectAuthors.ExecuteReader()
While dtrAuthors.Read()
  Response.Write( "<li>" )
  Response.Write( dtrAuthors( "au_lname" ) )
End While
dtrAuthors.Close()
conPubs.Close()
%>

The C# version of this code can be found on the CD-ROM.

Figure 9.1. Displaying the contents of a SqlDataReader.

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.

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( "<li>" )
  Response.Write( dtrAuthors( "au_lname" ) )
End While

The Read() method of the SqlDataReader class does two things whenever it is called. First, the method returns the value True if another record exists, but it returns False otherwise.

Second, the method advances the DataReader to the next record if a next record exists. By combining these functions, the Read() method enables you to quickly loop through the contents of a DataReader.

CAUTION

Remember to call the Read() method at least once before displaying a record with the DataReader. When a DataReader is first returned, the first record is not retrieved until you call the Read() method.

The ASP.NET pages in Listing 9.3 work only with Microsoft SQL Server. To use other databases, you must use the appropriate provider-specific namespace such as System.Data.OleDb or System.Data.OracleClient.

The page in Listing 9.4 illustrates how you would execute a query against a Microsoft Access database. The page retrieves and displays records from an Access table named Authors.

Listing 9.4 OleDbDataReader.aspx
[View full width]
<%@ Import Namespace="System.Data.OleDb" %>
<%
Dim conAuthors As OleDbConnection
Dim cmdSelectAuthors As OleDbCommand
Dim dtrAuthors As OleDbDataReader

conAuthors = New OleDbConnection( "PROVIDER=Microsoft.Jet.OLEDB.4.0;DATA Source=c:\Authors
  .mdb" )
conAuthors.Open()
cmdSelectAuthors = New OleDbCommand( "Select au_lname From Authors", conAuthors )
dtrAuthors = cmdSelectAuthors.ExecuteReader()
While dtrAuthors.Read()
  Response.Write( "<li>" )
  Response.Write( dtrAuthors( "au_lname" ) )
End While
dtrAuthors.Close()
conAuthors.Close()
%>

The C# version of this code can be found on the CD-ROM.

Notice that the page in Listing 9.4 is the same as the page in Listing 9.3, with two exceptions. First, the System.Data.OleDb namespace rather than the System.Data.SqlClient namespace is imported at the top of the page. Second, OleDb classes are used in all the statements instead of Sql classes.

After you finish displaying the records from a DataReader, be sure to explicitly close it (using the Close() method). Otherwise, the database connection will continue to be tied up.

Checking Whether Rows Were Returned

With the release of ASP.NET 1.1, the DataReader object has a new property that you can use to determine whether any records were returned from a database query. The new property is called the HasRows property. You can use the HasRows property to display a message when no records are retrieved in a search query like this:

If dtrAuthors.HasRows Then
  ' Display the records
Else
  Response.Write( "No records retrieved!" )
End If

The HasRows property returns either the value True or False. Unlike the Read() method, it does not advance the DataReader to the next row.

Notice that the HasRows property does not return the number of records contained in the DataReader. The DataReader does not have a property that returns a count of records.

Retrieving a Single Database Record

In many situations, you need to retrieve only a single record from a database. For example, you might need to look up someone's password given a username. Or you might want to retrieve the phone number for Andrew Jones from a database table.

Another common situation in which you need to retrieve a single value concerns aggregate functions. SQL Server supports several aggregate functions, such as Count(*), which returns the number of records in a database table, and AVG(), which returns the average value for a column in a table, or MIN(), which returns the minimum value of a column in a table, or MAX(), which returns the maximum value of a column in a table.

In the previous section, you learned how to use a DataReader to represent the results of a database query. If you only need to retrieve a single result from a query, the ExecuteScalar() method is many times more efficient than the ExecuteReader() method. The ExecuteScalar() method returns the value of the first column of the first row returned by a query.

The page in Listing 9.5 retrieves a count of the records in the Authors database table. It uses the ExecuteScalar() method to retrieve and display the value of the SQL Count(*) aggregate function (see Figure 9.2).

Listing 9.5 SqlExecuteScalar.aspx
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
  Dim conPubs As SqlConnection
  Dim cmdSelectCount As SqlCommand

  conPubs = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;database=Pubs" )
  conPubs.Open()
  cmdSelectCount = New SqlCommand( "Select Count(*) From Authors", conPubs )
  lblResults.Text = cmdSelectCount.ExecuteScalar()
  conPubs.Close()
End Sub

</Script>

<html>
<head><title>SqlExecuteScalar.aspx</title></head>
<body>

There are

<asp:Label
  ID="lblResults"
  Runat="Server" />

in the Authors database table.

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 9.2. The ExecuteScalar() method.

The page in Listing 9.5 works with a Microsoft SQL Server database (version 7.0 and higher). To rewrite the page to work with an Access or Oracle database, you would need to use the classes from the System.Data.OleDb or System.Data.OracleClient namespace rather than the System.Data.SqlClient namespace.

Executing a Query from a Form

Just for fun, we'll create a simple ASP.NET Web Forms Page that enables you to execute any SQL query and display the results. This exercise gives you some practice working with DataReaders and Web forms.

The page displays a form with two TextBox controls. When you enter a SQL Select statement in the top TextBox control and click the Execute Query button, the results of the query are displayed in the bottom TextBox control (see Figure 9.3).

Figure 9.3. The SQL Query tool.

CAUTION

To keep things simple, no error-checking code has been added to the form. So, if you enter an invalid Select statement, you receive an error message.

The trick to making this page work is to take advantage of the FieldCount property of the DataReader. The FieldCount property returns the number of columns represented by a DataReader. You can automatically display all the records in a DataReader by using the following code:

While dtrResults.Read()
  txtResults.Text &= vbNewLine
  For intField = 0 To dtrResults.FieldCount - 1
    txtResults.Text &= dtrResults( intField ).ToString().PadRight( 15 )
  Next
End While

This code fragment uses a While...End While loop to iterate through all the rows in the DataReader. It also uses a For...Next loop to loop through all the columns. It appends the value of each column for each row to a TextBox control named txtResults. The PadRight() method adds some padding around each column when it is displayed.

The complete code for the page is contained in Listing 9.6.

Listing 9.6 SqlQueryTool.aspx
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  Dim conPubs As SqlConnection
  Dim cmdSelect As SqlCommand
  Dim dtrResults As SqlDataReader
  Dim intField As Integer

  conPubs = New SqlConnection( "Server=localhost;uid=sa;pwd=secret;database=pubs" )
  conPubs.Open()
  cmdSelect = New SqlCommand( txtQuery.Text, conPubs )
  dtrResults = cmdSelect.ExecuteReader()
  txtResults.Text = ""
  While dtrResults.Read()
    txtResults.Text &= vbNewLine
    For intField = 0 To dtrResults.FieldCount - 1
      txtResults.Text &= dtrResults( intField ).ToString().PadRight( 15 )
    Next
  End While
  dtrResults.Close()
  conPubs.Close()
End Sub

</Script>

<html>
<head><title>SqlQueryTool.aspx</title></head>
<body>

<form Runat="Server">

<asp:TextBox
  ID="txtQuery"
  TextMode="MultiLine"
  Columns="80"
  Rows="4"
  Runat="Server" />
<br>
<asp:Button
  Text="Execute Query"
  OnClick="Button_Click"
  Runat="Server" />
<p>
<asp:TextBox
  ID="txtResults"
  TextMode="MultiLine"
  Columns="80"
  Rows="15"
  Wrap="False"
  ReadOnly="True"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Using Parameters with Queries

Typically, you perform queries using parameters. For example, you need to retrieve all the books from the Titles table in a certain category. Or you want to display all the authors who have a certain last name from the table.

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 Microsoft Access). 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"

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

Listing 9.7 SqlParameterSelect.aspx
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

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

</Script>

<html>
<head><title>SqlParameterSelect.aspx</title></head>
<body>
<form Runat="Server">

<h2>Author Phone Lookup</h2>

<b>First Name:</b>
<br>
<asp:TextBox
  ID="txtFirstname"
  Runat="Server" />
<p>
<b>Last Name:</b>
<br>
<asp:TextBox
  ID="txtLastname"
  Runat="Server" />
<p>
<asp:Button
  Text="Lookup!"
  OnClick="Button_Click"
  Runat="Server" />
<p>
<b>Phone:</b>
<asp:Label
  ID="lblPhone"
  EnableViewState="False"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 9.4. Executing a parameterized query.

The page in Listing 9.7 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.

In Listing 9.7, we allowed ADO.NET to infer the data type and size of both the @firstname and @lastname parameters. If we wanted to be explicit about the data type and size, we could have created the parameters like this:

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:

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 look up the phone number for Bertrand Russell.

Listing 9.8 OleDbParameterSelect.aspx
[View full width]
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.OleDb" %>

<Script Runat="Server">

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

</Script>

<html>
<head><title>OleDbParameterSelect.aspx</title></head>
<body>
<form Runat="Server">

<h2>Author Phone Lookup</h2>

<b>First Name:</b>
<br>
<asp:TextBox
  ID="txtFirstname"
  Runat="Server" />
<p>
<b>Last Name:</b>
<br>
<asp:TextBox
  ID="txtLastname"
  Runat="Server" />
<p>
<asp:Button
  Text="Lookup!"
  OnClick="Button_Click"
  Runat="Server" />
<p>
<b>Phone:</b>
<asp:Label
  ID="lblPhone"
  EnableViewState="False"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

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

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:

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.

Listing 9.9 SqlInsert.aspx
<%@ Import Namespace="System.Data.SqlClient" %>
<%
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 ( 'Milk', 12.45 )"
cmdInsert = New SqlCommand( strInsert, conNorthwind )
conNorthwind.Open()
cmdInsert.ExecuteNonQuery()
conNorthwind.Close()
%>
New Product Added!

The C# version of this code can be found on the CD-ROM.

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 ASP.NET page in Listing 9.10 demonstrates how you would add a new record to a Microsoft Access database table using classes from the System.Data.OleDb namespace.

Listing 9.10 OleDbInsert.aspx
[View full width]
<%@ Import Namespace="System.Data.OleDb" %>
<%
Dim conAuthors As OleDbConnection
Dim strInsert As String
Dim cmdInsert As OleDbCommand

conAuthors = New OleDbConnection( "PROVIDER=Microsoft.Jet.OLEDB.4.0;DATA Source=c:\Authors
  .mdb" )
strInsert = "Insert Into Authors ( au_fname, au_lname ) Values ( 'Bertrand', 'Russell' )"
cmdInsert = New OleDbCommand( strInsert, conAuthors )
conAuthors.Open()
cmdInsert.ExecuteNonQuery()
conAuthors.Close()
%>
New Author Added!

The C# version of this code can be found on the CD-ROM.

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

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.

Listing 9.11 SqlFormInsert.aspx
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<Script Runat="Server">

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
</Script>

<html>
<head><title>SqlFormInsert.aspx</title></head>
<body>
<form Runat="Server">

<h2>Add New Product</h2>

<b>Product Name:</b>
<br>
<asp:TextBox
  ID="txtProductName"
  Runat="Server" />
<p>
<b>Unit Price:</b>
<br>
<asp:TextBox
  ID="txtUnitPrice"
  Runat="Server" />
<p>
<asp:Button
  Text="Add!"
  OnClick="Button_Click"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

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:

  1. Create and open a database connection.

  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 SqlUpdate.aspx
<%@ Import Namespace="System.Data.SqlClient" %>
<%
Dim conPubs As SqlConnection
Dim strUpdate As String
Dim cmdUpdate As SqlCommand

conPubs = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;database=Pubs" )
strUpdate = "Update Authors Set au_lname='Smith' Where au_lname='Bennet'"
cmdUpdate = New SqlCommand( strUpdate, conPubs )
conPubs.Open()
cmdUpdate.ExecuteNonQuery()
conPubs.Close()
%>
Author Updated!

The C# version of this code can be found on the CD-ROM.

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 SqlCommand class is called. At this point, the Update statement is transmitted to SQL Server and executed.

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 a Microsoft Access database table, 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 OleDbUpdate.aspx
[View full width]
<%@ Import Namespace="System.Data.OleDb" %>
<%
Dim conAuthors As OleDbConnection
Dim strUpdate As String
Dim cmdUpdate As OleDbCommand

conAuthors = New OleDbConnection( "PROVIDER=Microsoft.Jet.OLEDB.4.0;DATA Source=c:\Authors
  .mdb" )
strUpdate = "Update Authors Set au_lname='Frege' Where au_lname='Russell'"
cmdUpdate = New OleDbCommand( strUpdate, conAuthors )
conAuthors.Open()
cmdUpdate.ExecuteNonQuery()
conAuthors.Close()
%>
Author Updated!

The C# version of this code can be found on the CD-ROM.

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 SqlUpdateRecordsAffected.aspx
<%@ Import Namespace="System.Data.SqlClient" %>
<%
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 au_lname='Smith' Where au_lname='Bennet'"
cmdUpdate = New SqlCommand( strUpdate, conPubs )
conPubs.Open()
intUpdateCount = cmdUpdate.ExecuteNonQuery()
conPubs.Close()
%>
<%=intUpdateCount%> Records Updated!

The C# version of this code can be found on the CD-ROM.

Creating a Form to Update Records

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.

Listing 9.15 SqlFormUpdate.aspx
<%@ Import Namespace="System.Data.SqlClient" %>
<Script Runat="Server">

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
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
</Script>

<html>
<head><title>SqlFormUpdate.aspx</title></head>
<body>
<form Runat="Server">

<h2>Update Phone Number</h2>

<b>First Name:</b>
<br>
<asp:TextBox
  ID="txtFirstName"
  Runat="Server" />
<p>
<b>Last Name:</b>
<br>
<asp:TextBox
  ID="txtLastName"
  Runat="Server" />
<p>
<b>New Phone:</b>
<br>
<asp:TextBox
  ID="txtPhone"
  Runat="Server" />
<p>
<asp:Button
  Text="Update Phone Number!"
  OnClick="Button_Click"
  Runat="Server" />
<p>
<asp:Label
  ID="lblResults"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

Deleting 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:

  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 SqlDelete.aspx
<%@ Import Namespace="System.Data.SqlClient" %>
<%
Dim conPubs As SqlConnection
Dim strDelete As String
Dim cmdDelete As SqlCommand

conPubs = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;database=Pubs" )
strDelete = "Delete Authors Where au_lname='Smith'"
cmdDelete = New SqlCommand( strDelete, conPubs )
conPubs.Open()
cmdDelete.ExecuteNonQuery()
conPubs.Close()
%>
Records Deleted!

The C# version of this code can be found on the CD-ROM.

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.

The page contained in Listing 9.17 illustrates how you would delete a record from a Microsoft Access database table named Authors using the System.Data.OleDb namespace.

Listing 9.17 OleDbDelete.aspx
[View full width]
<%@ Import Namespace="System.Data.OleDb" %>
<%
Dim conAuthors As OleDbConnection
Dim strDelete As String
Dim cmdDelete As OleDbCommand

conAuthors = New OleDbConnection( "PROVIDER=Microsoft.Jet.OLEDB.4.0;DATA Source=c:\Authors
  .mdb" )
strDelete = "Delete From Authors Where au_lname='Frege'"
cmdDelete = New OleDbCommand( strDelete, conAuthors )
conAuthors.Open()
cmdDelete.ExecuteNonQuery()
conAuthors.Close()
%>
Records Deleted!

The C# version of this code can be found on the CD-ROM.

Notice that you must use Delete From rather than just Delete when working with a Microsoft Access database.

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 SqlDeleteRecordsAffected.aspx
<%@ Import Namespace="System.Data.SqlClient" %>
<%
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='Bennet'"
cmdDelete = New SqlCommand( strDelete, conPUbs )
conPubs.Open()
intDeleteCount = cmdDelete.ExecuteNonQuery()
conPubs.Close()
%>
<%=intDeleteCount %> Records Deleted!

The C# version of this code can be found on the CD-ROM.

Creating a Form to Delete Records

Typically, you delete records by using a Web form. The page in Listing 9.19 illustrates how you can create a simple Web form that enables you to delete authors with a certain last name (see Figure 9.6).

Listing 9.19 SqlFormDelete.aspx
<%@ Import Namespace="System.Data.SqlClient" %>
<Script Runat="Server">

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
</Script>

<html>
<head><title>SqlFormDelete.aspx</title></head>
<body>
<form Runat="Server">

<h2>Delete Authors</h2>

<b>Last Name:</b>
<br>
<asp:TextBox
  ID="txtLastName"
  Runat="Server" />
<p>
<asp:Button
  Text="Delete Author!"
  OnClick="Button_Click"
  Runat="Server" />
<p>
<asp:Label
  ID="lblResults"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 9.6. Deleting database records.

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

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.

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.

Building stored procedures takes a little more work than executing SQL statements directly on a page. However, you can improve the performance and maintainability of your application by taking advantage of stored procedures.

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 SqlInsert.aspx
<%@ Import Namespace="System.Data.SqlClient" %>
<%
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 ( 'Milk', 12.45 )"
cmdInsert = New SqlCommand( strInsert, conNorthwind )
conNorthwind.Open()
cmdInsert.ExecuteNonQuery()
conNorthwind.Close()
%>
New Product Added!

The C# version of this code can be found on the CD-ROM.

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 )

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

Figure 9.7. Creating a stored procedure with Query Analyzer.

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.

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 SqlStoredProcedure.aspx
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Data" %>
<%
Dim conNorthwind As SqlConnection
Dim strInsert As String
Dim cmdInsert As SqlCommand

conNorthwind = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;database=Northwind" )
cmdInsert = New SqlCommand( "InsertProducts", conNorthwind )
cmdInsert.CommandType = CommandType.StoredProcedure
cmdInsert.Parameters.Add( "@ProductName", "Milk" )
cmdINsert.Parameters.Add( "@UnitPrice", 12.45 )
conNorthwind.Open()
cmdInsert.ExecuteNonQuery()
conNorthwind.Close()
%>
New Product Added!

The C# version of this code can be found on the CD-ROM.

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 SqlStoredProcedureQuery.aspx
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Data" %>
<%
Dim conPubs As SqlConnection
Dim cmdSelect As SqlCommand
Dim dtrAuthors As SqlDataReader

conPubs = New SqlConnection( "Server=localhost;uid=sa;pwd=secret;database=pubs" )
cmdSelect = New SqlCommand( "SelectAuthors", conPubs )
cmdSelect.CommandType = CommandType.StoredProcedure
conPubs.Open()
dtrAuthors = cmdSelect.ExecuteReader()
While dtrAuthors.Read()
  Response.Write( "<li>" )
  Response.Write( dtrAuthors( "au_lname" ) )
End While
dtrAuthors.Close()
conPubs.Close()
%>

The C# version of this code can be found on the CD-ROM.

The page in Listing 9.22 executes a stored procedure named SelectAuthors. This stored procedure is executed when the ExecuteReader() method is called.

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.

CAUTION

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
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Data" %>
<%
Dim conPubs As SqlConnection
Dim cmdAuthorCount As SqlCommand
Dim parmReturnValue As SqlParameter
Dim intAuthorCount As Integer

conPubs = New SqlConnection( "Server=localhost;uid=sa;pwd=secret;database=pubs" )
cmdAuthorCount = New SqlCommand( "GetAuthorCount", conPubs )
cmdAuthorCount.CommandType = CommandType.StoredProcedure
parmReturnValue = cmdAuthorCount.Parameters.Add( "ReturnValue", SqlDbType.Int )
parmReturnValue.Direction = ParameterDirection.ReturnValue
conPubs.Open()
cmdAuthorCount.ExecuteNonQuery()
intAuthorCount = cmdAuthorCount.Parameters( "ReturnValue" ).Value
conPubs.Close()
%>
There are <%=intAuthorCount%> records in the Authors table

The C# version of this code can be found on the CD-ROM.

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.

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.

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.

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 SqlGetAuthorLastname.aspx
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Data" %>
<%
Dim conPubs As SqlConnection
Dim cmdLastName As SqlCommand
Dim parmLastName As SqlParameter
Dim strLastName As String

conPubs = New SqlConnection( "Server=localhost;uid=sa;pwd=secret;database=pubs" )
cmdLastName = New SqlCommand( "getLastname", conPubs )
cmdLastName.CommandType = CommandType.StoredProcedure

' FirstName Input Parameter
cmdLastName.Parameters.Add( "@firstname", "Ann" )

' LastName Output Parameter
parmLastName = cmdLastName.Parameters.Add( "@lastname", SqlDbType.Varchar )
parmLastName.Size = 40
parmLastName.Direction = ParameterDirection.Output

' Execute Command
conPubs.Open()
cmdLastName.ExecuteNonQuery()

' Retrieve value of output parameter
If Not IsDBNull( cmdLastName.Parameters( "@lastname" ).Value ) Then
  strLastName = cmdLastName.Parameters( "@lastname" ).Value
Else
  strLastName = "Unknown"
End If
conPubs.Close()
%>
The last name is <%=strLastName%>

The C# version of this code can be found on the CD-ROM.

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.

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:

Create Procedure AddProduct
(
  @ProductName NVarchar( 80 ),
  @UnitPrice Money
)
As
Insert Products ( ProductName, UnitPrice )
  Values ( @ProductName, @UnitPrice )

Return @@Identity

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

Listing 9.25 SqlAddProduct.aspx
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Data" %>
<%
Dim conNorthwind As SqlConnection
Dim cmdAddProduct As SqlCommand
Dim parmProductID As SqlParameter
Dim intProductID As Integer

conNorthwind = New SqlConnection( "Server=localhost;uid=sa;pwd=secret;database=Northwind" )
cmdAddProduct = New SqlCommand( "AddProduct", conNorthwind )
cmdAddProduct.CommandType = CommandType.StoredProcedure

' Add Return Value Parameter
parmProductID = cmdAddProduct.Parameters.Add( "ReturnValue", SqlDbType.Int )
parmProductID.Direction = ParameterDirection.ReturnValue

' Add firstname Input Parameter
cmdAddProduct.Parameters.Add( "@ProductName", "Biscuits" )

' Add lastname Input Parameter
cmdAddProduct.Parameters.Add( "@UnitPrice", 23.87 )

conNorthwind.Open()
cmdAddProduct.ExecuteNonQuery()
intProductID = cmdAddProduct.Parameters( "ReturnValue" ).Value
conNorthwind.Close()
%>
The ID for the new product is <%=intProductID%>

The C# version of this code can be found on the CD-ROM.

Figure 9.8. Retrieving the @@Identity value.

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:

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 )

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.

The page in Listing 9.26 illustrates how to use this stored procedure from within an ASP.NET page.

Listing 9.26 SqlAddUniqueProduct.aspx
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Data" %>
<%
Dim conNorthwind As SqlConnection
Dim cmdAddunique As SqlCommand
Dim parmReturnValue As SqlParameter

conNorthwind = New SqlConnection( "Server=localhost;uid=sa;pwd=secret;database=Northwind" )
cmdAddUnique = New SqlCommand( "AddUniqueProduct", conNorthwind )
cmdAddUnique.CommandType = CommandType.StoredProcedure

' Add Return Value Parameter
parmReturnValue = cmdAddUnique.Parameters.Add( "ReturnValue", SqlDbType.Int )
parmReturnValue.Direction = ParameterDirection.ReturnValue

' Add product name Input Parameter
cmdAddUnique.Parameters.Add( "@ProductName", "Milk" )

' Add unit price name Input Parameter
cmdAddUnique.Parameters.Add( "@UnitPrice", 43.89 )

' Execute Command
conNorthwind.Open()
cmdAddUnique.ExecuteNonQuery()

If cmdAddUnique.Parameters( "ReturnValue" ).Value = 0 Then
  Response.Write( "New Product Added!" )
Else
  Response.Write( "Product already exists!" )
End If
conNorthwind.Close()
%>

The C# version of this code can be found on the CD-ROM.

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.

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.

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.

NOTE

The web.config file and Application state are both discussed in Chapter 15, "Creating ASP.NET Applications."

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— Indicates whether connections should be reset when they are returned to the pool. The default value is true.

  • Enlist— Indicates whether a connection should be automatically enlisted in the current transaction context. The default value is true.

  • Max Pool Size— The maximum number of connections allowed in a single connection pool. The default value is 100.

  • Min Pool Size— The minimum number of connections allowed in a single connection pool. The default value is 0.

  • Pooling— 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'

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.

Creating an ADO.NET Transaction

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
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<%
Dim objTransaction As SqlTransaction
Dim conBank As SqlConnection
Dim strUpdateAccountA As String
Dim strUpdateAccountB As String
Dim cmdUpdateAccountA As SqlCommand
Dim cmdUpdateAccountB As SqlCommand

' Initialize objects
conBank = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;database=Bank" )

strUpdateAccountA = "Update AccountA Set Balance = Balance - 999.99 WHERE Customer='Smith'"
cmdUpdateAccountA = New SqlCommand( strUpdateAccountA, conBank )

strUpdateAccountB = "Update AccountB Set Balance = Balance + 999.99 WHERE Customer='Smith'"
cmdUpdateAccountB = New SqlCommand( strUpdateAccountB, conBank )

' Open connection
conBank.Open()

' Retrieve transaction from connection
objTransaction = conBank.BeginTransaction

' Assign Transaction to commands
cmdUpdateAccountA.Transaction = objTransaction
cmdUpdateAccountB.Transaction = objTransaction

' Try executing both commands
Try
  cmdUpdateAccountA.ExecuteNonQuery()
  cmdUpdateAccountB.ExecuteNonQuery()

  ' Commit the transaction
  objTransaction.Commit
  Response.Write( "Transaction Successful!" )
Catch ex As Exception
  objTransaction.RollBack
  Response.Write( "Transaction Failed!" )
Finally
  conBank.Close()
End Try
%>

The C# version of this code can be found on the CD-ROM.

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— Indicates that the page does not execute within a transaction.

  • Supported— If a transaction already exists, the page will execute within the context of the transaction. However, it will not create a new transaction.

  • 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— 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 roll back 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" %>

<%
Dim conBank As SqlConnection
Dim strUpdateAccountA As String
Dim strUpdateAccountB As String
Dim cmdUpdateAccountA As SqlCommand
Dim cmdUpdateAccountB As SqlCommand

' Initialize objects
conBank = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;database=Bank" )

strUpdateAccountA = "Update AccountA Set Balance = Balance - 999.99 WHERE Customer='Smith'"
cmdUpdateAccountA = New SqlCommand( strUpdateAccountA, conBank )

strUpdateAccountB = "Update AccountB Set Balance = Balance + 999.99 WHERE Customer='Smith'"
cmdUpdateAccountB = New SqlCommand( strUpdateAccountB, conBank )

' Open connection
conBank.Open()

' Try executing both commands
Try
  cmdUpdateAccountA.ExecuteNonQuery()
  cmdUpdateAccountB.ExecuteNonQuery()

  ' Commit the transaction
  ContextUtil.SetComplete()
  Response.Write( "Transaction Successful!" )
Catch ex As Exception
  ContextUtil.SetAbort()
  Response.Write( "Transaction Failed!" )
Finally
  conBank.Close()
End Try
%>

The C# version of this code can be found on the CD-ROM.

In Listing 9.28, if the two SQL Update commands succeed then the SetComplete() method of the ContextUtil class is called. Otherwise, the SetAbort() method is called. To use these methods, you must import the System.EnterpriseServices namespace.

Specifying a Command Behavior

When you call the ExecuteReader() method of the Command object you can pass an optional CommandBehavior parameter. By supplying the CommandBehavior parameter, you can gain greater control over how the ExecuteReader() method retrieves data from a database.

The CommandBehavior enumeration has the following values:

  • CloseConnection— Automatically closes an open database connection after the DataReader is closed.

  • KeyInfo— Retrieves column and primary key with the data. Executes the query with the FOR BROWSE clause.

  • SchemaOnly— Retrieves column and table schema information without retrieving data.

  • SequentialAccess— Enables access to database columns that contain a large amount of information.

  • SingleResult— Optimizes the command to retrieve only a single result.

  • SingleRow— Optimizes the command to retrieve only a single row. If multiple rows are returned, additional rows are discarded.

The CloseConnection command behavior is useful when you want to return a DataReader from a function. For example, the page in Listing 9.29 uses the CloseConnection behavior when returning the contents of the Authors table from the GetAuthors() function (see Figure 9.9).

Listing 9.29 SqlCloseConnection.aspx
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Dim conPubs As SqlConnection

Sub Page_Load
  Dim dtrAuthors As SqlDataReader

  dtrAuthors = GetAuthors()
  While dtrAuthors.Read
    lblAuthors.Text &= "<li>"
    lblAuthors.Text &= dtrAuthors( "au_lname" )
  End While
  dtrAuthors.Close()
  lblAuthors.Text &= "<hr> Connection is:" & conPubs.State.ToString()
End Sub

Function GetAuthors() As SqlDataReader
  Dim cmdAuthors As SqlCommand

  conPubs = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;database=pubs" )
  cmdAuthors = New SqlCommand( "select * from Authors", conPubs )
  conPubs.Open()
  Return cmdAuthors.ExecuteReader( CommandBehavior.CloseConnection )
End Function

</Script>

<html>
<head><title>SqlCloseConnection.aspx</title></head>
<body>

<asp:Label
  id="lblAuthors"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 9.9. The CloseConnection CommandBehavior.

By including the CommandBehavior.CloseConnection parameter in the function Return statement, you can automatically close the database connection when the Close() method is called on the SqlDataReader. In Listing 9.29, the state of the database connection is displayed by retrieving the value of the State property of the SqlConnection class.

You also can use the CommandBehavior parameter to improve the performance of your queries. For example, the CommandBehavior.SingleRow value can improve the performance of a query when the query retrieves a single row of data from a database table. A good situation to use this value is when you need to retrieve information on a single title from the Titles database table.

NOTE

Don't confuse the ExecuteScalar() method with the CommandBehavior.SingleRow value. ExecuteScalar() returns a single result (the value of the first column from the first row). The CommandBehavior.SingleRow value indicates that a complete row should be returned (the value of all columns from the first row returned).

Retrieving Table Schema Information

If you need to retrieve information about the columns contained in a database table, then you can use the GetSchemaTable() method of the DataReader class. The GetSchemaTable() method returns an instance of the DataTable class.

NOTE

The DataTable class is discussed in detail in Chapter 12, "Working with DataSets."

Typically, you'll use the GetSchemaTable() method in conjunction with the CommandBehavior.KeyInfo or CommandBehavior.SchemaOnly values from the CommandBehavior enumeration (see the previous section).

For example, the page in Listing 9.30 retrieves information on all the columns in the Authors table (see Figure 9.10).

Listing 9.30 SqlGetSchemaTable.aspx
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<Script runat="Server">

Sub Page_Load
  Dim conPubs As SqlConnection
  Dim cmdSelect As SqlCommand
  Dim dtrSchema As SqlDataReader
  Dim dtblSchema As DataTable

  conPubs = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;database=pubs" )
  cmdSelect = New SqlCommand( "Select * from Authors", conPubs )
  conPubs.Open
  dtrSchema = cmdSelect.ExecuteReader( CommandBehavior.KeyInfo Or CommandBehavior.SchemaOnly )
  dtblSchema = dtrSchema.GetSchemaTable()
  dgrdSchema.DataSource = dtblSchema
  dgrdSchema.DataBind()
  dtrSchema.Close
  conPubs.Close
End Sub

</Script>

<html>
<head><title>SqlGetSchemaTable.aspx</title></head>
<body>

<asp:DataGrid
  id="dgrdSchema"
  CellPadding="4"
  HeaderStyle-BackColor="lightgreen"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 9.10. The Authors table schema.

The page in Listing 9.30 displays information about a number of the properties of each of the columns in the Authors table. For example, the size of each column is displayed. Also, the identity, key, and autoincrement columns are identified.


Summary

This chapter introduced ADO.NET. First, you learned about the classes in the ADO.NET framework and the differences between the different data providers such as the System.Data.SqlClient, System.Data.OleDb, System.Data.Odbc, and System.Data.OracleClient classes.

Next, you learned how to open database connections to different types of databases, such as Microsoft SQL Server and Microsoft Access. You learned how to use the Connection, Command, and DataReader classes to both retrieve and modify database records and use the classes from ADO.NET with Web forms.

In the next section, you learned two methods of improving the database access performance of your ASP.NET pages. You learned how to package SQL statements into stored procedures. You were also provided with an overview of database connection pooling.

Finally, we explored several advanced database topics. You learned how to execute statements in the context of a transaction, control the behavior of commands, and retrieve database table schema information.


Chapter 10. Binding Data to Web Controls

IN THIS CHAPTER

  • Overview of Data Binding

  • Binding a Server Control to a Data Source

  • Creating Master/Detail Forms

  • Data Binding without Data Binding Expressions

This chapter focuses on the topic of data binding. The first section provides an overview of data binding. You learn how to take advantage of data binding when working with any control.

Next, you learn how to use a control that completely depends on data binding: the ASP.NET Repeater control. You learn how to use the Repeater control to display database data and format the data with templates.

After examining the Repeater control, you tackle the subject of using data binding with controls such as DropDownList, RadioButtonList, and ListBox. For example, you learn how to grab records from a database and display them within a drop-down list.

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 look at some slightly more complicated examples of data binding. You learn how to build master/detail forms by binding controls to database data. You also learn how to use templates without using data binding expressions.


Overview of Data Binding

Data binding refers to the process of dynamically assigning a value to a property of a control at runtime. For example, you can use data binding to bind the properties of a control to a data source such as the contents of a SQL Server database table.

It's important to emphasize that even though this chapter appears in the "Working with ADO.NET" part of this book, the topic of data binding is not limited to working with database data. You can bind the properties of a control to expressions, properties, methods, collections, or even the properties of another control.

The distinctive aspect of data binding is not the data part, but the binding part. Data binding enables you to control exactly when a value is bound to a property.

The best way to get an idea of how data binding works is to look at some examples. The page in Listing 10.1 binds the value of a function to the Text property of a Label control.

Listing 10.1 SimpleBind.aspx
<Script Runat="Server">

Sub Page_Load
  lblMessage.DataBind()
End Sub

Function GetTime() As String
  Return DateTime.Now.ToString( "T" )
End Function

</Script>

<html>
<head><title>SimpleBind.aspx</title></head>
<body>
<form Runat="Server">

<asp:Label
  ID="lblMessage"
  Text='<%# GetTime() %>'
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Notice how a value is assigned to the Text property of the Label control:

Text='<%# getTime %>'

The special <%# and %> tags delimit the data binding expression. In this case, the value of the data binding expression is the value of the GetTime() function.

NOTE

You wrap the value of a data binding expression with apostrophe characters rather than the usual quotation marks. Getting in the habit of always using apostrophes is a good idea because quotation marks can cause errors. Using quotation marks generates an error when the data binding expression itself contains quotation marks.

The Page_Load subroutine in Listing 10.1 looks like this:

Sub Page_Load
  lblMessage.DataBind()
End Sub

The DataBind() method is called on the Label control named lblMessage. When the DataBind() method is called, the value of the GetTime() function is assigned to the Text property of the Label control.

The important feature of data binding is that you can control when a data binding expression is evaluated. If the DataBind() method is never called in the page, the lblMessage Text property is never assigned a value.

Every control has a DataBind() method. It is a method of the base Control class used by both the Web and HTML controls. When you call the DataBind() method on a control, all the data binding expressions associated with that control are evaluated. Data binding occurs for both the control itself and all the child controls of the control.

Remember that an ASP.NET page is also a control. If you call DataBind() without specifying a control, the Page control's DataBind() method is called, and all the data binding expressions in the page are evaluated.

The page in Listing 10.2, for example, contains two Label controls that are bound to the GetTime() function (see Figure 10.1).

Listing 10.2 SimplePageDataBind.aspx
<Script Runat="Server">

Sub Page_Load
  DataBind()
End Sub

Function GetTime() As String
  Return DateTime.Now.ToString( "T" )
End Function

</Script>

<html>
<head><title>SimplePageDataBind.aspx</title></head>
<body>
<form Runat="Server">

<asp:Label
  ID="lblMessage1"
  Text='<%# GetTime() %>'
  Runat="Server" />

<p>

<asp:Label
  ID="lblMessage2"
  Text='<%# GetTime() %>'
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 10.1. Calling DataBind() for a page.

When the Page_Load subroutine executes in Listing 10.2, the DataBind() method is called. When this method is called, the data binding expressions in both Label controls are evaluated. Both Label controls are assigned the current time from the GetTime() function.

Now, look at two quick examples to test your understanding of data binding. The page in Listing 10.3 contains a function, named GetNext(), which increments a number and returns it. The page also contains two Label controls named lblMessage1 and lblMessage2, which are bound to the function. Look closely at the calls to the DataBind() method and guess what numbers are assigned to each Label control.

Listing 10.3 DataBindTest1.aspx
<Script Runat="Server">

Dim intNextNum As Integer

Sub Page_Load
  lblMessage2.DataBind()
  lblMessage1.DataBind()
End Sub

Function GetNext() As String
  intNextNum += 1
  Return intNextNum
End Function

</Script>

<html>
<head><title>DataBindTest1.aspx</title></head>
<body>
<form Runat="Server">

<asp:Label
  ID="lblMessage1"
  Text='<%# getNext %>'
  Runat="Server" />

<p>

<asp:Label
  ID="lblMessage2"
  Text='<%# getNext %>'
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Notice that the DataBind() method is called on the lblMessage2 control before the method is called on the lblMessage1 control in the Page_Load subroutine. Therefore, the value 2 is assigned to the first Label control, and the value 1 is assigned to the second Label control (see Figure 10.2).

Figure 10.2. The output of DataBindTest1.aspx.

Here's one last test. Look at the page in Listing 10.4 and guess what values are assigned to the two Label controls.

Listing 10.4 DataBindTest2.aspx
<Script Runat="Server">

Dim intNextNum As Integer

Sub Page_Load
  lblMessage1.DataBind()
  lblMessage2.DataBind()
  DataBind()
End Sub

Function GetNext() As String
  intNextNum += 1
  Return intNextNum
End Function

</Script>

<html>
<head><title>DataBindTest2.aspx</title></head>
<body>
<form Runat="Server">

<asp:Label
  ID="lblMessage1"
  Text='<%# GetNext() %>'
  Runat="Server" />

<p>

<asp:Label
  ID="lblMessage2"
  Text='<%# GetNext() %>'
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

In Listing 10.4, the DataBind() method is called three times:

myLabel1.DataBind()
myLabel2.DataBind()
DataBind()

When the first DataBind() method is called, the first Label control is assigned the value 1. When the second DataBind() method is called, the second Label control is assigned the value 2.

Here's the interesting part: When the DataBind() method is called a third time, data binding occurs for all the controls on the page all over again. So, when all is said and done and the page is displayed, the first Label control displays the value 3, and the second Label control displays the value 4 (see Figure 10.3).

Figure 10.3. The output of DataBindTest2.aspx.

    [ Team LiB ]


Binding a Server Control to a Data Source

In the following sections, you learn how to use data binding with several ASP.NET controls, such as the Repeater, DropDownList, RadioButtonList, and ListBox controls. You learn how to use the DataSource property to bind the items from a data source such as a database table or an ArrayList to a control. You also learn how to use templates to format how the data is displayed.

Binding to the Repeater Control

The Repeater control does not display anything unless it is bound to a data source. Typically, you use the Repeater control to display the records from a database table, although you can also bind the control to other data sources such as collections.

Imagine that you have a database table named Authors and you want to display all the rows from this database table within an ASP.NET page. The page in Listing 10.5 demonstrates how you can display the rows by using a Repeater control (see Figure 10.4).

Listing 10.5 Repeater.aspx
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
  Dim conPubs As SqlConnection
  Dim cmdSelect As SqlCommand
  Dim dtrAuthors As SqlDataReader

  ' Retrieve records from database
  conPubs = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Pubs" )
  cmdSelect = New SqlCommand( "Select * From Authors", conPubs )
  conPubs.Open()
  dtrAuthors = cmdSelect.ExecuteReader()

  ' Bind to Repeater
  rptAuthors.DataSource = dtrAuthors
  rptAuthors.DataBind()

  dtrAuthors.Close()
  conPubs.Close()
End Sub

</Script>

<html>
<head><title>Repeater.aspx</title></head>
<body>
<form Runat="Server">

<asp:Repeater
  ID="rptAuthors"
  Runat="Server">

  <ItemTemplate>
  <%# Container.DataItem( "au_lname" ) %>
  </ItemTemplate>

</asp:Repeater>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 10.4. The Repeater control.

All the work in Listing 10.5 is accomplished within the Page_Load subroutine. This subroutine opens a connection to a Microsoft SQL Server database and retrieves all the records from the Authors database table into a SqlDataReader. Next, the SqlDataReader is assigned to the DataSource property of the Repeater control. Finally, the DataBind() method is called, and the items from the SqlDataReader are bound to the Repeater control.

The Repeater control is declared with the following tags:

<asp:Repeater
  ID="dtrAuthors"
  Runat="Server">

  <ItemTemplate>
  <%# Container.DataItem( "au_lname" ) %>
  </ItemTemplate>

</asp:Repeater>

The au_lname column for each of the records retrieved from the Authors table is displayed by the Repeater control. The following data binding expression represents the au_lname column:

<%# Container.DataItem( "au_lname" ) %>

Notice that this expression uses the data binding tags <%# and %>. This expression does not have a value until the Repeater control's DataBind() method is called.

Using Templates

If you look closely at the ASP.NET page in Listing 10.5, you notice that the Repeater control uses a tag named <ItemTemplate>. The ItemTemplate is one example of a template. The Repeater control uses templates to control formatting.

Templates enable you to apply complicated formatting to each of the items displayed by a control. You can place any HTML tags that you please within a template. A template can also contain other controls and even inline ASP.NET code.

The Repeater control actually supports five types of templates:

  • HeaderTemplate— Controls how the header of the Repeater control is formatted

  • ItemTemplate— Controls the formatting of each item displayed by the Repeater control

  • AlternatingItemTemplate— Controls how alternate items are formatted

  • SeparatorTemplate— Displays a separator between each item displayed by the Repeater control

  • FooterTemplate— Controls how the footer of the Repeater control is formatted

Now, imagine that you want every other item displayed by the Repeater control to have a purple and italic font, and you want a horizontal rule to separate the items (see Figure 10.5). You can make these changes by using the following Repeater control declaration:

<asp:Repeater
  ID="rptAuthors"
  Runat="Server">

  <ItemTemplate>
    <%# Container.DataItem( "au_lname" ) %>
  </ItemTemplate>

  <AlternatingItemTemplate>
    <font color="purple"><i>
    <%# Container.DataItem( "au_lname" ) %>
    </i></font>
  </AlternatingItemTemplate>

  <SeparatorTemplate>
    <hr>
  </SeparatorTemplate>

</asp:Repeater>
Figure 10.5. Using templates with a Repeater control.

You also can use templates with a Repeater control to format the items displayed by the control in an HTML table, as illustrated by the page in Listing 10.6.

Listing 10.6 RepeaterTable.aspx
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
  Dim conPubs As SqlConnection
  Dim cmdSelect As SqlCommand
  Dim dtrAuthors As SqlDataReader

  ' Retrieve records from database
  conPubs = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Pubs" )
  cmdSelect = New SqlCommand( "Select * From Authors", conPubs )
  conPubs.Open()
  dtrAuthors = cmdSelect.ExecuteReader()

  ' Bind to Repeater
  rptAuthors.DataSource = dtrAuthors
  rptAuthors.DataBind()

  dtrAuthors.Close()
  conPubs.Close()
End Sub

</Script>

<html>
<head><title>RepeaterTable.aspx</title></head>
<body>
<form Runat="Server">

<asp:Repeater
  ID="rptAuthors"
  Runat="Server">

  <HeaderTemplate>
    <table border=1 cellpadding=4>
    <tr bgcolor="#eeeeee">
      <th>First Name</th>
      <th>Last Name</th>
      <th>Phone</th>
    </tr>
  </HeaderTemplate>

  <ItemTemplate>
    <tr>
      <td><%# Container.DataItem( "au_fname" ) %></td>
      <td><%# Container.DataItem( "au_lname" ) %></td>
      <td><%# Container.DataItem( "phone" ) %></td>
    </tr>
  </ItemTemplate>

  <AlternatingItemTemplate>
    <tr bgcolor="lightyellow">
      <td><%# Container.DataItem( "au_fname" ) %></td>
      <td><%# Container.DataItem( "au_lname" ) %></td>
      <td><%# Container.DataItem( "phone" ) %></td>
    </tr>
  </AlternatingItemTemplate>

  <FooterTemplate>
    </table>
  </FooterTemplate>

</asp:Repeater>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

In the page in Listing 10.6, the opening HTML <table> tag is created in the Repeater control's HeaderTemplate. Each table row is displayed by either the ItemTemplate (with a white background) or the AlternatingItemTemplate (with a yellow background). Finally, the FooterTemplate closes the HTML table (see Figure 10.6).

Figure 10.6. Displaying an HTML table with the Repeater control.

View State and the Repeater Control

The Repeater control, like other server controls, participates in a page's view state. By default, this means that every time you create a Repeater control on a page, all the information for each item in the control is copied into the hidden VIEWSTATE form field.

Typically, you use the Repeater control simply to display a list of items, and you are not interested in preserving its view state across multiple page submissions. Because preserving a Repeater control's view state can slow down the rendering of a page, it is wise to disable view state for this control when you don't need it.

To disable view state for an individual control, set the EnableViewState property to the value False like this:

<asp:Repeater
  ID="rptAuthors"
  EnableViewState="False"
  Runat="Server">

Binding to the DropDownList Control

Binding data to a DropDownList control is similar to binding data to a Repeater control. The general technique is to assign a data source to the DropDownList control's DataSource property and call the DataBind() method.

NOTE

For more information on the DropDownList control, see Chapter 2, "Building Forms with Web Server Controls."

You must set one additional property with the DropDownList control. You must set its DataTextField property to indicate the column that you want to bind to the control.

NOTE

The DropDownList control has a DataValueField property. You need to set this property when you want different Value and Text properties associated with each item in a DropDownList control.

The page in Listing 10.7, for example, contains a DropDownList control that is bound to the au_lname column in the Authors database table (see Figure 10.7).

Listing 10.7 DropDownList.aspx
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
  If Not IsPostBack Then
    Dim conPubs As SqlConnection
    Dim cmdSelect As SqlCommand
    Dim dtrAuthors As SqlDataReader

    conPubs = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Pubs" )
    conPubs.Open()
    cmdSelect = New SqlCommand( "Select au_lname From Authors", conPubs )
    dtrAuthors = cmdSelect.ExecuteReader()

    dropAuthors.DataSource = dtrAuthors
    dropAuthors.DataTextField = "au_lname"
    dropAuthors.DataBind()

    dtrAuthors.Close()
    conPubs.Close()
  End If
End Sub

</Script>

<html>
<head><title>DropDownList.aspx</title></head>
<body>
<form Runat="Server">

<asp:DropDownList
  ID="dropAuthors"
  Runat="Server" />

<asp:Button
  Text="Pick Author!"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 10.7. Binding data to a DropDownList control.

In Listing 10.7, the database connection is opened, and the records from the Authors database table are retrieved into the DataReader in the Page_Load subroutine. The DataReader is bound to the DropDownList control's DataSource property, the DataTextField is assigned the value au_lname, and the DataBind() method is called.

One thing you should notice about the Page_Load subroutine is that it uses the IsPostBack property to check whether the page has already been submitted. The database records are retrieved and bound to the DropDownList control when the page is first opened, but not thereafter.

The list of author last names is preserved in the page's view state when the page is posted back to the server. You need to retrieve the records from the database only the first time the page is opened, and the records will be preserved in the page's hidden __VIEWSTATE form field.

Neglecting to check the IsPostBack property produces two bad results. First, it hurts the performance of the page because the records need to be retrieved from the database every time the page is opened. Retrieving records from a database is a resource-intensive task.

Second, if a user selects an author from the DropDownList control, and you rebind the DropDownList to the database table, the user's selection is lost. Binding a DropDownList resets the SelectedIndex and SelectedItem properties of the control.

Binding to the RadioButtonList Control

You can bind a RadioButtonList control to a data source by setting its DataSource and DataTextField properties and calling its DataBind() method.

NOTE

For more information on the RadioButtonList control, see Chapter 2.

The page in Listing 10.8, for example, contains a RadioButtonList control that is bound to the Categories table in the Northwind database (see Figure 10.8).

Listing 10.8 RadioButtonList.aspx
[View full width]
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
  If Not IsPostBack Then
    Dim conNorthwind As SqlConnection
    Dim cmdSelect As SqlCommand
    Dim dtrCategories As SqlDataReader

    conNorthwind = New SqlConnection(
   "Server=localhost;UID=sa;PWD=secret;Database=Northwind" )
    cmdSelect = New SqlCommand( "Select CategoryName From Categories", conNorthwind )
    conNorthwind.Open()
    dtrCategories = cmdSelect.ExecuteReader()

    radlCategories.DataSource = dtrCategories
    radlCategories.DataTextField = "CategoryName"
    radlCategories.DataBind()

    dtrCategories.Close()
    conNorthwind.Close()
  End If
End Sub

</Script>

<html>
<head><title>RadioButtonList.aspx</title></head>
<body>
<form Runat="Server">

<asp:RadioButtonList
  ID="radlCategories"
  Runat="Server" />
<p>
<asp:Button
  Text="Select A Category!"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 10.8. Binding data to a RadioButtonList control.

Remember to use the IsPostBack property to bind the RadioButtonList to the DataReader only when the page is opened the first time. If you neglect to do so, the selected radio button is lost every time the page is submitted.

NOTE

The RadioButtonList control has a DataValueField property. You need to set this property when you want different Value and Text properties associated with each item in a RadioButtonList control.

Binding to the CheckBoxList Control

You can bind a data source, such as a database table, to a CheckBoxList control by setting the control's DataSource and DataTextField properties and calling its DataBind() method.

NOTE

For more information on the CheckBoxList control, see Chapter 2.

The page in Listing 10.9 illustrates how you can bind a CheckBoxList control to the Titles table in the Pubs database (see Figure 10.9).

Listing 10.9 CheckBoxList.aspx
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
  If Not IsPostBack Then
    Dim conPubs As SqlConnection
    Dim cmdSelect As SqlCommand
    Dim dtrTitles As SqlDataReader

    conPubs = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Pubs" )
    cmdSelect = New SqlCommand( "Select Title From Titles", conPubs )
    conPubs.Open()
    dtrTitles = cmdSelect.ExecuteReader()

    chklTitles.DataSource = dtrTitles
    chklTitles.DataTextField = "Title"
    chklTitles.DataBind()

    dtrTitles.Close()
    conPubs.Close()
  End If
End Sub

</Script>

<html>
<head><title>CheckBoxList.aspx</title></head>
<body>
<form Runat="Server">

<asp:CheckBoxList
  ID="chklTitles"
  Runat="Server" />
<p>
<asp:Button
  Text="Select A Title!"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 10.9. Binding data to a CheckBoxList control.

Again, remember to check the IsPostBack property so that you bind the CheckBoxList control only the first time the page is opened. If you neglect to do so, the selected check boxes are lost when the form is submitted.

NOTE

The CheckBoxList control has a DataValueField property. You need to set this property when you want different Value and Text properties associated with each item in a CheckBoxList control.

Binding to a ListBox Control

The procedure for binding to a ListBox control is the same as for the other list controls. To bind a data source—such as a database table or ArrayList—to a ListBox control, set the control's DataSource and DataTextField properties and call the control's DataBind() method.

NOTE

For more information on the ListBox control, see Chapter 2.

The page in Listing 10.10 illustrates how you can bind a ListBox control to the Products table in the Northwind database (see Figure 10.10).

Listing 10.10 ListBox.aspx
[View full width]
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
  If Not IsPostBack Then
    Dim conNorthwind As SqlConnection
    Dim cmdSelect As SqlCommand
    Dim dtrProducts As SqlDataReader

    conNorthwind = New SqlConnection(
   "Server=localhost;UID=sa;PWD=secret;Database=Northwind" )
    conNorthwind.Open()
    cmdSelect = New SqlCommand( "Select ProductName From Products", conNorthwind )
    dtrProducts = cmdSelect.ExecuteReader()

    lstProducts.DataSource = dtrProducts
    lstProducts.DataTextField = "ProductName"
    lstProducts.DataBind()

    dtrProducts.Close()
    conNorthwind.Close()
  End If
End Sub

</Script>

<html>
<head><title>ListBox.aspx</title></head>
<body>
<form Runat="Server">

<asp:ListBox
  ID="lstProducts"
  Runat="Server" />
<p>
<asp:Button
  Text="Select A Product!"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 10.10. Binding data to a ListBox control.

Remember to check the IsPostBack property before binding to the ListBox control. If you bind it after the page is submitted, the selected list item is lost.

NOTE

The ListBox control has a DataValueField property. You need to set this property when you want different Value and Text properties associated with each item in a ListBox control.

Binding to Other Controls

The following sections describe methods of binding to controls that do not have a DataSource property. You learn how to bind to a list of Image, HyperLink, Button, and LinkButton controls.

Binding to Image Controls

The trick to binding to a list of Image controls to a data source is to place the controls in a Repeater control. You can bind the Repeater control to a data source, thereby indirectly binding all the Image controls to the data source.

Imagine that you have a list of image URLs stored in a database table and you want to display these images in an ASP.NET page (see Figure 10.11). The page in Listing 10.11 illustrates how you can do so.

Listing 10.11 ImageList.aspx
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
  If Not IsPostBack Then
    Dim conMyData As SqlConnection
    Dim cmdSelect As SqlCommand
    Dim dtrImages As SqlDataReader

    conMyData = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=myData" )
    conMyData.Open()
    cmdSelect = New SqlCommand( "Select image_url From myImages", conMyData )
    dtrImages = cmdSelect.ExecuteReader()

    rptImages.DataSource = dtrImages
    rptImages.DataBind()

    dtrImages.Close()
    conMyData.Close()
  End If
End Sub

</Script>

<html>
<head><title>ImageList.aspx</title></head>
<body>
<form Runat="Server">

<asp:Repeater
  ID="rptImages"
  Runat="Server">

<ItemTemplate>
<br>
<asp:Image
  ImageURL='<%# Container.DataItem( "image_url" ) %>'
  Runat="Server" />
</ItemTemplate>

</asp:Repeater>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 10.11. Displaying a list of images.

In the Page_Load subroutine in this listing, the image URLs are retrieved into a DataReader from the myImages database table. The DataReader is bound to the DataSource property of the Repeater control, which displays all the images from the database table by rendering an Image control for each image URL. The Image control's ImageUrl property is bound to the image_url column of the data source, and the image is displayed.

NOTE

You can create the myImages database table by executing the following statements from SQL Query Analyzer:

Create Table myImages
(
  Image_URL Varchar( 200 )
)
go
Insert myImages Values ( 'starburst.gif' )
Insert myImages Values ( 'gear.gif' )
Insert myImages Values ( 'flower.gif' )
go

You'll need to change the database parameter in the SQL connection string to the proper value for your database before you can use this table.

Instead of using Image controls to display images, you also can use the HTML <img> tag. Using the <img> tag instead of the Image control requires fewer server resources. Here's how you would bind to an HTML <img> tag in a Repeater control:

<asp:Repeater
  ID="rptImages"
  Runat="Server">

<ItemTemplate>
<br>
<img src='<%# Container.DataItem( "image_url" ) %>'>
</ItemTemplate>

</asp:Repeater>
Binding to HyperLink Controls

You can display a list of HyperLink controls with the Repeater control. To do so, simply place the HyperLink control within the ItemTemplate of the Repeater control. The page in Listing 10.12 illustrates how to use the Repeater control to display a list of favorite links (see Figure 10.12).

Listing 10.12 HyperLinkList.aspx
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
  If Not IsPostBack Then
    Dim conMyData As SqlConnection
    Dim cmdSelect As SqlCommand
    Dim dtrLinks As SqlDataReader

    conMyData = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=myData" )
    cmdSelect = New SqlCommand( "Select * From FavLinks", conMyData )
    conMyData.Open()
    dtrLinks = cmdSelect.ExecuteReader()

    rptLinks.DataSource = dtrLinks
    rptLinks.DataBind()

    dtrLinks.Close()
    conMyData.Close()
  End If
End Sub

</Script>

<html>
<head><title>HyperLinkList.aspx</title></head>
<body>
<form Runat="Server">

<asp:Repeater
  ID="rptLinks"
  Runat="Server">

<ItemTemplate>
<li>
<ASP:HyperLink
  Text='<%# Container.DataItem( "link_title" ) %>'
  NavigateURL='<%# Container.DataItem( "link_url" ) %>'
  Runat="Server" />
</ItemTemplate>

</asp:Repeater>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 10.12. Displaying a list of hyperlinks.

The Repeater control in Listing 10.12 displays a list of links from a table named FavLinks.

NOTE

You can create the FavLinks table by executing the following statements from SQL Query Analyzer:

Create Table FavLinks
(
  Link_Title Varchar( 200 ),
  Link_URL Varchar( 200 )
)
go
Insert FavLinks Values
  ( 'Superexpert', 'http://www.Superexpert.com' )
Insert FavLinks Values
  ( 'AspWorkshops', 'http://www.AspWorkshops.com' )

Instead of using HyperLink controls to display hyperlinks, you can use the HTML anchor tag. Using the anchor tag instead of the HyperLink control requires slightly fewer server resources. Here's how you would bind to an HTML anchor tag in a Repeater control:

[View full width]

<asp:Repeater
  ID="rptLinks"
  Runat="Server">

<ItemTemplate>
<li>
<a href='<%# Container.DataItem( "link_url" ) %>'><%# Container.DataItem ( "link_title" )
  %></a>
</ItemTemplate>

</asp:Repeater>
Binding to Button and LinkButton Controls

You can bind Button and LinkButton controls to a data source by using the Repeater control. To do so, simply place a Button or LinkButton control within the ItemTemplate of the Repeater control. The page in Listing 10.13 illustrates how to bind LinkButton controls to a data source (see Figure 10.13).

Listing 10.13 LinkButtonList.aspx
[View full width]
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
  If Not IsPostBack Then
    Dim conNorthwind As SqlConnection
    Dim cmdSelect As SqlCommand
    Dim dtrCategories As SqlDataReader

    conNorthwind = New SqlConnection(
   "Server=localhost;UID=sa;PWD=secret;Database=Northwind" )
    cmdSelect = New SqlCommand( "Select CategoryName From Categories", conNorthwind )
    conNorthwind.Open()
    dtrCategories = cmdSelect.ExecuteReader()

    rptCategories.DataSource = dtrCategories
    rptCategories.DataBind()

    dtrCategories.Close()
    conNorthwind.Close()
  End If
End Sub

Sub Button_Click( s As Object, e As RepeaterCommandEventArgs )
  Dim lbtnSelected As LinkButton

  lbtnSelected = e.Item.Controls( 1 )
  lblCategory.Text = "You selected " & lbtnSelected.Text
End Sub

</Script>

<html>
<head><title>LinkButtonList.aspx</title></head>
<body>
<form Runat="Server">

<asp:Repeater
  ID="rptCategories"
  OnItemCommand="Button_Click"
  Runat="Server">

<ItemTemplate>
  <li>
  <asp:LinkButton
   Text='<%# Container.DataItem( "CategoryName" ) %>'
   Runat="Server" />
</ItemTemplate>

</asp:Repeater>

<p>
<asp:Label
  ID="lblCategory"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 10.13. Displaying a list of LinkButton controls.

One question that arises when working with Button and LinkButton controls is how to discover which control is clicked within the Repeater control. The page in Listing 10.13 captures and displays the LinkButton control that was clicked by taking advantage of event bubbling. Event bubbling enables you to create one subroutine that handles all the events raised by the controls contained within a control.

When you click a LinkButton control in the Repeater control, the subroutine associated with the Repeater control's OnItemCommand is executed. In the page in Listing 10.13, the Button_Click subroutine is executed when you click a LinkButton. Within the Button_Click subroutine, the Text property of the selected LinkButton is retrieved and displayed in a Label control.

Event bubbling is discussed in detail in Chapter 11, "Using the DataList and DataGrid Controls."


Creating Master/Detail Forms

The following sections describe an advanced application of data binding. Two methods of creating master/detail forms are discussed. You learn how to create both single-page and multipage master/detail forms.

What's a master/detail form? Imagine that you have two database tables named Products and Categories. Each product in the Products table is associated with a category in the Categories table. Now, suppose that you want the users of your Web site to be able to view different lists of products by selecting different categories. In this case, you need to build a master/detail form.

Or, imagine that you have a database table named Authors. For each author, you have such information as the author's first and last name, phone number, and so on. Suppose that you want to be able to select from a list of author last names and view details on a particular author. This is another situation in which you need to build a master/detail form.

Creating Single-Page Master/Detail Forms

One approach to building master/detail forms is to place both the master and detail information on the same page. For example, the page in Listing 10.14 contains a DropDownList control that represents product categories and a Repeater control that represents a list of products. When you select a product category, the corresponding products are displayed (see Figure 10.14).

Listing 10.14 MasterDetail.aspx
[View full width]
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
  If Not IsPostBack Then
    Dim conNorthwind As SqlConnection
    Dim cmdSelect As SqlCommand
    Dim dtrCategories As SqlDataReader

    conNorthwind = New SqlConnection(
   "Server=localhost;UID=sa;PWD=secret;Database=Northwind" )
    cmdSelect = New SqlCommand( "Select CategoryID, CategoryName From Categories",
   conNorthwind )
    conNorthwind.Open()
    dtrCategories = cmdSelect.ExecuteReader()

    dropCategories.DataSource = dtrCategories
    dropCategories.DataTextField = "CategoryName"
    dropCategories.DataValueField = "CategoryID"
    dropCategories.DataBind()

    dropCategories.Items.Insert( 0, New ListItem( "none selected", -1 ) )

    dtrCategories.Close()
    conNorthwind.Close()
  End If
End Sub

Sub BindProducts( intCatID As Integer )
  Dim conNorthwind As SqlConnection
  Dim cmdSelect As SqlCommand
  Dim strSelect As String
  Dim dtrProducts As SqlDataReader

  conNorthwind = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Northwind" )
  conNorthwind.Open()

  strSelect = "Select ProductName, UnitPrice From Products Where CategoryID=@catID"
  cmdSelect = New SqlCommand( strSelect, conNorthwind )
  cmdSelect.Parameters.Add( "@catID", intCatID )
  dtrProducts = cmdSelect.ExecuteReader()

  rptProducts.DataSource = dtrProducts
  rptProducts.DataBind()

  dtrProducts.Close()
  conNorthwind.Close()
End Sub

Sub dropCategories_SelectedIndexChanged( s As Object, e As EventArgs )
  Dim intCatID As Integer

  intCatID = dropCategories.SelectedItem.Value
  If intCatID <> -1 Then
    BindProducts( intCatID )
  End If
End Sub

</Script>

<html>
<head><title>MasterDetail.aspx</title></head>
<body>
<form Runat="Server">

<b>Select Category:</b>
<asp:DropDownList
  ID="dropCategories"
  AutoPostBack="True"
  OnSelectedIndexChanged="dropCategories_SelectedIndexChanged"
  Runat="Server"/>

<p>

<asp:Repeater
  ID="rptProducts"
  EnableViewState="False"
  Runat="Server">

<ItemTemplate>
<li><%# Container.DataItem( "ProductName" ) %>
- <%# String.Format( "{0:c}", Container.DataItem( "UnitPrice" ) ) %>
</ItemTemplate>

</asp:Repeater>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 10.14. Displaying a single page master/detail form.

When you first open the page in Listing 10.14, the Page_Load subroutine is executed, and the DropDownList control is bound to the Categories table in the Northwind database. If you look closely at the Page_Load subroutine, you notice the following line of code:

dropCategories.Items.Insert( 0, New ListItem( "none selected", -1 ) )

This line inserts an additional item as the first item in the DropDownList control. When the page is first opened, you want the DropDownList control to display none selected for the current category.

When you pick a category with the DropDownList control, the dropCategories_SelectedIndexChanged subroutine is executed. If a category other than none selected has been selected, the products for the selected category are pulled from the Products database table and bound to the Repeater control.

Creating Multipage Master/Detail Forms

Instead of placing the master and detail information on the same page, you might want to place the information on separate pages. For example, suppose you want to create one page that contains a directory of employees who work at your company and a separate page that lists details for each employee.

The page in Listing 10.15 uses a Repeater control to display a list of employees (see Figure 10.15).

Listing 10.15 Master.aspx
[View full width]
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
  Dim conNorthwind As SqlConnection
  Dim cmdSelect As SqlCommand
  Dim dtrEmployees As SqlDataReader

  conNorthwind = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Northwind" )
  cmdSelect = New SqlCommand( "Select EmployeeID,LastName From Employees", conNorthwind )
  conNorthwind.Open()
  dtrEmployees = cmdSelect.ExecuteReader()

  rptEmployees.DataSource = dtrEmployees
  rptEmployees.DataBind()

  dtrEmployees.Close()
  conNorthwind.Close()
End Sub

</Script>

<html>
<head><title>Master.aspx</title></head>
<body>
<form Runat="Server">

<h2>Employee Directory</h2>

<asp:Repeater
  ID="rptEmployees"
  Runat="Server">

<ItemTemplate>
<li>
<a href='<%# String.Format( "details.aspx?id={0}", Container.DataItem( "EmployeeID" ) )
  %>'><%#
Container.DataItem( "LastName" ) %></a>
</ItemTemplate>

</asp:Repeater>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 10.15. The master form.

The employee's last names are displayed as a list of hyperlinks by the Repeater control. Each employee is a link to the Details.aspx page. The value of the EmployeeID column is passed in the link to the Details.aspx page as a query string variable.

The Details.aspx page is contained in Listing 10.16.

Listing 10.16 Details.aspx
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
  Dim intEmployeeID As Integer
  Dim conNorthwind As SqlConnection
  Dim strSelect As String
  Dim cmdSelect As SqlCommand
  Dim dtrEmployee As SqlDataReader

  ' Retrieve Employee ID
  intEmployeeID = Int32.Parse( Request.QueryString( "id" ) )

  ' Get Employee Detail Information
  conNorthwind = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Northwind" )
  strSelect = "Select * From Employees Where EmployeeID=@employeeID"
  cmdSelect = New SqlCommand( strSelect, conNorthwind )
  cmdSelect.Parameters.Add( "@employeeID", intEmployeeID )
  conNorthwind.Open()
  dtrEmployee = cmdSelect.ExecuteReader( CommandBehavior.SingleRow )

  ' Display in Label Controls
  If dtrEmployee.Read Then
    lblEmployee.Text = dtrEmployee( "LastName" )
    lblEmployee.Text &= ", " & dtrEmployee( "FirstName" )
    lblPhone.Text = dtrEmployee( "HomePhone" )
    lblAddress.Text = dtrEmployee( "Address" )
    lblCity.Text = dtrEmployee( "City" ) & ", "
    lblRegion.Text = dtrEmployee( "Region" ).ToString()
    lblPostalCode.Text = dtrEmployee( "PostalCode" )
  End If

  dtrEmployee.Close()
  conNorthwind.Close()
End Sub

</Script>

<html>
<head><title>Details.aspx</title></head>
<body>
<form Runat="Server">

<h2>
<asp:Label
  ID="lblEmployee"
  Runat="Server"/>
</h2>

<p>

<b>Phone:</b>
<blockquote>
  <asp:Label
    ID="lblPhone"
    Runat="Server" />
</blockquote>

<b>Address:</b>
<blockquote>
  <asp:Label
    ID="lblAddress"
    Runat="Server" />
  <br>
  <asp:Label
    ID="lblCity"
    Runat="Server" />
  <asp:Label
    ID="lblRegion"
    Runat="Server" />
  &nbsp;&nbsp;
  <asp:Label
    ID="lblPostalCode"
    Runat="Server" />
</blockquote>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

The Details.aspx page grabs whatever ID value was passed to it from the Master.aspx page and retrieves the information for the employee in the Employees database with that ID. The information is returned in a DataReader. The values for each of the columns in the DataReader are assigned to Label controls, and the selected employee's phone and address information is then displayed (see Figure 10.16).

Figure 10.16. The details form.

    [ Team LiB ]


Data Binding without Data Binding Expressions

Earlier in this chapter, when we bound the contents of a database table to a Repeater control, we used data binding expressions in the Repeater control's ItemTemplate to display the values retrieved from the database. In this section, we examine how you can avoid using data binding expressions in templates.

Why would you want to avoid using data binding expressions? There is nothing wrong with using data binding expressions, and you can always use data binding expressions if you prefer. However, you'll discover that when you need to perform complicated actions inside a template, your code can be more readable when you avoid using data binding expressions.

The trick for avoiding data binding expressions is to handle the ItemDataBound event of the Repeater control. The ItemDataBound event is raised for each item retrieved from the data source. So, if you are binding a Repeater control to a database table that contains 11 authors, then the ItemDataBound event is raised 11 times—once for each author.

NOTE

In addition to the ItemDataBound event, the Repeater control also supports the ItemCreated event. The ItemCreated event is raised when each RepeaterItem is created.

The page in Listing 10.17 illustrates how you can handle the ItemDataBound event to display the contents of the Authors database table in a Repeater control.

Listing 10.17 ItemDataBound.aspx
[View full width]
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
  Dim conPubs As SqlConnection
  Dim cmdSelect As SqlCommand
  Dim dtrAuthors As SqlDataReader

  ' Retrieve records from database
  conPubs = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Pubs" )
  cmdSelect = New SqlCommand( "Select * From Authors", conPubs )
  conPubs.Open()
  dtrAuthors = cmdSelect.ExecuteReader()

  ' Bind to Repeater
  rptAuthors.DataSource = dtrAuthors
  rptAuthors.DataBind()

  dtrAuthors.Close()
  conPubs.Close()
End Sub

Sub Repeater_ItemDataBound(s As Object, e As RepeaterItemEventArgs)
  If e.Item.ItemType = ListItemType.Item Or e.Item.ItemType = ListItemType.AlternatingItem
   Then
    Dim lblAuthor As Label = e.Item.FindControl( "lblAuthor" )
    lblAuthor.Text = e.Item.DataItem( "au_lname" )
  End If
End Sub

</Script>

<html>
<head><title>Repeater.aspx</title></head>
<body>
<form Runat="Server">

<asp:Repeater
  ID="rptAuthors"
  OnItemDataBound="Repeater_ItemDataBound"
  Runat="Server">

  <ItemTemplate>
  <asp:Label id="lblAuthor" Runat="Server"/>
  </ItemTemplate>

</asp:Repeater>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

In Listing 10.17, the Repeater_ItemDataBound subroutine is associated with the Repeater control's ItemDataBound event. This subroutine first checks whether it is being called for an ItemTemplate or AlternatingItemTemplate (we want to skip other types of templates such as the HeaderTemplate). Next, the subroutine uses the FindControl() method to retrieve the lblAuthor Label control from the template. Finally, the value of the current DataItem is assigned to the Label.

NOTE

The Repeater control is not the only control that supports the ItemDataBound event. The DataList and DataGrid controls also support the ItemDataBound event.


Summary

In this chapter, you learned how to use data binding expressions and bind server controls to a data source. The first section provided a general overview of data binding. You learned how to use data binding expressions with the DataBind() method.

Next, you learned how a Repeater control can be bound to a data source. You were also introduced to the topic of templates. You learned how to use a Repeater control's templates to format the data displayed by the control.

Later in the chapter, you examined methods of binding list controls to a data source. You learned how to bind list controls, such as DropDownList and RadioButtonList, to a database table. You also examined methods of binding the Image, HyperLink, Button, and LinkButton controls.

Finally, you read about two methods of creating master/detail forms. You built both a single-page master/detail form and a multipage master/detail form.


Chapter 11. Using the DataList and DataGrid Controls

IN THIS CHAPTER

  • Overview of the DataList and DataGrid Controls

  • Using the DataList Control

  • Using the DataGrid Control

This chapter tackles two of the most feature-rich controls included with the ASP.NET framework: the DataList and DataGrid controls.

The first section examines some of the features shared between both of these controls. You will learn how these controls use event bubbling, templates, and the DataKeys collection.

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/

Next, you will explore the DataList control in depth. You will learn how to use it to display data in multiple columns and edit database data.

The remainder, and bulk, of the chapter is devoted to the DataGrid control. You will learn how to use it to display, edit, sort, and page through database data.


Overview of the DataList and DataGrid Controls

The DataList and DataGrid controls share many of the same features. Before getting into the details of how each of these controls work, you will learn about some of the common properties of the controls. In the following sections, you will learn how both controls support event bubbling, templates, and the DataKeys collection.

Understanding Event Bubbling

Three of the standard controls included with the ASP.NET framework support event bubbling: the Repeater, DataList, and DataGrid controls. These controls enable you to capture events in their child controls. When an event is raised in a child control, the event "bubbles up" to the containing control, and the containing control can then execute a subroutine to handle the event.

As you saw in the preceding chapter, for example, you can display a list of LinkButton controls in a Repeater control. When you click a LinkButton control, it raises a Click event. Instead of writing code to handle the Click event for each LinkButton individually, you can associate one subroutine with any Click event that is raised in the Repeater control.

The Repeater, DataList, and DataGrid controls enable you to associate a subroutine with an event bubbled up from a child control by specifying a value for the OnItemCommand property. For example, in the case of the Repeater control, you would associate a subroutine with the ItemCommand event like this:

<asp:Repeater
  OnItemCommand="Repeater_ItemCommand"
  Runat="Server">

<ItemTemplate>
  <li>
  <asp:LinkButton
   Text='<%# Container.DataItem( "CategoryName" ) %>'
   Runat="Server" />
</ItemTemplate>

</asp:Repeater>

When any of the LinkButton controls rendered by the Repeater control are clicked and the Click event occurs, the Repeater_ItemCommand subroutine executes to handle the event.

The DataList and DataGrid controls enable you to go one step further. Both of these controls enable you to make distinctions between the events raised by their child controls.

The DataList and DataGrid controls support the following five events:

  • ItemCommand

  • UpdateCommand

  • EditCommand

  • DeleteCommand

  • CancelCommand

When you create the child controls in a DataList or DataGrid, you can indicate the type of event to raise in the containing control by using the CommandName property. For example, if you want to raise an UpdateCommand event, you would specify the CommandName property like this:

<asp:LinkButton
  Text="Update Field!"
  CommandName="update"
  Runat="Server" />

Three ASP.NET controls have a CommandName property: LinkButton, Button, and ImageButton. All three controls also have a CommandArgument property that enables you to specify an optional argument that is passed when the control is clicked. So, you can bubble up an optional argument by using an event like this:

<asp:LinkButton
  Text="Update Field!"
  CommandName="update"
  CommandArgument="12"
  Runat="Server" />

Using Templates

Templates were introduced in the preceding chapter. The Repeater control supports several templates that enable you to format the output of the control. For example, you can use the ItemTemplate to format how the Repeater control renders each item that it displays.

The DataList and DataGrid controls also support templates. The DataList control supports all the same templates as the Repeater control, in addition to two others:

  • HeaderTemplate— Controls how the header of the DataList is formatted

  • ItemTemplate— Controls the formatting of each item displayed by the DataList

  • AlternatingItemTemplate— Controls how alternate items are formatted

  • SeparatorTemplate— Displays a separator between each item displayed by the DataList

  • FooterTemplate— Controls how the footer of the DataList is formatted

  • SelectedItemTemplate— Controls how a selected item is formatted

  • EditItemTemplate— Controls how an item selected for editing is formatted

When you select an item in a DataList, the SelectedItem template is displayed. When you select an item for editing in a DataList, the EditItem template is displayed.

The DataGrid control supports the following four templates when you display a column using a TemplateColumn:

  • HeaderTemplate— Controls how the header of the DataGrid is formatted

  • ItemTemplate— Controls the formatting of items displayed by the DataGrid

  • FooterTemplate— Controls how the footer of the DataGrid is formatted

  • EditItemTemplate— Controls how an item selected for editing is formatted

The EditItemTemplate is displayed when you select an item for editing in a DataGrid.

When working with the DataList and DataGrid controls, you often run into situations in which you need to retrieve a particular control contained in a template. You can find a particular control in a template by using the FindControl() method.

You can use the FindControl() method, for example, with the Controls collection of a DataListItem to find a particular control in a DataList template. You can use the FindControl() method with the Controls collection of a DataGridItem to find a particular control in a DataGrid template. I will discuss some concrete examples of how to use the FindControl() method later in this chapter.

Using the DataKeys Collection

Both the DataList and DataGrid controls have a special collection named DataKeys. This collection represents the primary key field associated with each item in the control.

Imagine, for example, that you have a database table named Products that you want to display and edit with a DataGrid control. Imagine, furthermore, that the Products table has an identity column named ProductID.

You can associate the value of the identity column with each item in the DataGrid by assigning the name of the identity column to the DataKeyField property of the DataGrid like this:

<asp:DataGrid
  DataKeyField="ProductID"
  Runat="Server" />

The advantage of associating the identity column with each item in the DataGrid is that you can retrieve the value of this column when you edit a field in the DataGrid. If you update a field in the DataGrid and want to update the corresponding field in the underlying database table, you can use the DataKeys collection to uniquely identify the correct row in the database table to update.

You'll come across several situations in which the DataKeys collection will prove valuable later in this chapter.


Using the DataList Control

In the following sections, you will examine in depth how to use the DataList control. You will learn how to use it to format and display data from a database table and create multicolumn menus. Finally, you will examine how to use the DataList to edit data in a database table.

Displaying Data in a DataList

You can use a DataList like a Repeater control—to display records from a database table. However, unlike a Repeater control, a DataList control's default behavior is to show the database records within an HTML table.

The page in Listing 11.1, for example, illustrates how you can bind a DataList to a database table named Titles in the Pubs database.

Listing 11.1 DataList.aspx
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
  Dim conPubs As SqlConnection
  Dim cmdSelect As SqlCommand
  Dim dtrTitles As SqlDataReader

  conPubs = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Pubs" )
  cmdSelect = New SqlCommand( "Select Title From Titles", conPubs )
  conPubs.Open()
  dtrTitles = cmdSelect.ExecuteReader()

  dlstTitles.DataSource = dtrTitles
  dlstTitles.DataBind()

  dtrTitles.Close()
  conPubs.Close()
End Sub

</Script>

<html>
<head><title>DataList.aspx</title></head>
<body>
<form Runat="Server">
<asp:DataList
  ID="dlstTitles"
  Runat="Server">

<ItemTemplate>
  <%# Container.DataItem( "Title" )%>
</ItemTemplate>

</asp:DataList>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Each row from the Titles database table is displayed in a separate HTML table row when the DataList displays the records (see Figure 11.1).

Figure 11.1. The DataList control.

If you prefer, you can modify the RepeatLayout property to display the items in a DataList within HTML <span> tags instead of HTML table cells. By default, the RepeatLayout property has the value Table. If you set RepeatLayout to Flow, each item is displayed in a <span> tag and is automatically separated by a <br> tag:

<asp:DataList
  RepeatLayout="Flow"
  Runat="Server">

When RepeatLayout has the value Table, you can display lines around each cell by setting the GridLines property. For example, when GridLines has the value Both, both horizontal and vertical lines are drawn for each cell:

<asp:DataList
  GridLines="Both"
  Runat="Server">

The other possible values for GridLines are Horizontal and Vertical.

When RepeatLayout has the value Table, you also can control the padding in table cells and amount of spacing between table cells by assigning values to the CellPadding and CellSpacing properties:

<asp:DataList
  GridLines="Both"
  CellPadding="20"
  CellSpacing="20"
  Runat="Server">

Using Templates with a DataList

You can use templates with a DataList to control how displayed items are formatted. For example, suppose that you want to display multiple database fields for each item displayed by the DataList. You can format the appearance of each DataList item by placing an HTML table in the ItemTemplate.

The page in Listing 11.2 illustrates how to create an HTML table in the ItemTemplate (see Figure 11.2).

Listing 11.2 DataListTemplate.aspx
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
  Dim conPubs As SqlConnection
  Dim cmdSelect As SqlCommand
  Dim dtrAuthors As SqlDataReader
  conPubs = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Pubs" )
  cmdSelect = New SqlCommand( "Select * From Authors", conPubs )
  conPubs.Open()
  dtrAuthors = cmdSelect.ExecuteReader()

  dlstAuthors.DataSource = dtrAuthors
  dlstAuthors.DataBind()

  dtrAuthors.Close()
  conPubs.Close()
End Sub

</Script>

<html>
<head><title>DataListTemplate.aspx</title></head>
<body>
<form Runat="Server">

<asp:DataList
  ID="dlstAuthors"
  Runat="Server">

<HeaderTemplate>
  <h2>Author Phone Numbers:</h2>
</HeaderTemplate>

<ItemTemplate>
  <table>
  <tr>
    <td align="right">First Name:</td>
    <td><%# Container.DataItem( "au_fname" )%></td>
  </tr>
  <tr>
    <td align="right">Last Name:</td>
    <td><%# Container.DataItem( "au_lname" )%></td>
  </tr>
  <tr>
    <td align="right">Phone:</td>
    <td><%# Container.DataItem( "phone" )%></td>
  </tr>
  </table>
</ItemTemplate>

<SeparatorTemplate>
  <hr>
</SeparatorTemplate>

</asp:DataList>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 11.2. Using templates with a DataList.

Three templates are used in Listing 11.2: an ItemTemplate, a HeaderTemplate, and a SeparatorTemplate. The HeaderTemplate displays the heading Author Phone Numbers at the top of the DataList. The ItemTemplate displays each author's first name, last name, and phone number. The SeparatorTemplate renders a horizontal rule (an <hr> tag) between each item.

Using Styles with a DataList

The DataList control supports seven style objects that you can use to format its output. Each style object is an instance of the TableItemStyle class, which has the following properties:

  • BackColor— The background color displayed for an item

  • BorderColor— The border color displayed for an item

  • BorderStyle— The style used for borders around cells (possible values are Dashed, Dotted, Double, Groove, Inset, None, NotSet, Outset, Ridge, and Solid)

  • BorderWidth— The width of the border

  • CssClass— The Cascading Style Sheet class associated with the item

  • Font— An instance of the FontInfo class, which indicates how the font should be formatted

  • ForeColor— The foreground color used for text

  • Height— The height of the item

  • HorizontalAlign— The horizontal alignment of the item (possible values are Center, Justify, Left, NotSet, and Right)

  • VerticalAlign— The vertical alignment of the item (possible values are Bottom, Middle, NotSet, and Top)

  • Width— The width of the item

  • Wrap— If True, the contents of an item are word-wrapped (the default value is True)

You can use any of these style properties with the AlternatingItemStyle, EditItemStyle, FooterStyle, HeaderStyle, ItemStyle, SelectedItemStyle, and SeparatorStyle styles of a DataList control. When you set a style property while declaring a control, you use a hyphen (-) instead of the normal period (.) to indicate a property of an object.

The page in Listing 11.3, for example, displays each item in the DataList with a dashed border, and alternating items are displayed with a pink background (see Figure 11.3).

Listing 11.3 DataListStyles.aspx
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
  Dim conNorthwind As SqlConnection
  Dim cmdSelect As SqlCommand
  Dim dtrCategories As SqlDataReader

  conNorthwind = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Northwind" )
  cmdSelect = New SqlCommand( "Select CategoryName From Categories", conNorthwind )
  conNorthwind.Open()
  dtrCategories = cmdSelect.ExecuteReader()

  dlstCategories.DataSource = dtrCategories
  dlstCategories.DataBind()

  dtrCategories.Close()
  conNorthwind.Close()
End Sub

</Script>

<html>
<head><title>DataListStyles.aspx</title></head>
<body>
<form Runat="Server">

<asp:DataList
  id="dlstCategories"
  CellSpacing="20"
  CellPadding="10"
  ItemStyle-BorderStyle="Dashed"
  AlternatingItemStyle-BackColor="pink"
  Runat="Server">

<ItemTemplate>
  <%# Container.DataItem( "CategoryName" )%>
</ItemTemplate>

</asp:DataList>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 11.3. Using styles with a DataList.

Notice that you can set properties of the AlternatingItemStyle even when the DataList control does not include an AlternatingItemTemplate. Templates and styles are independent of each other.

The Font property of the TableItemStyle class is a little more complicated than the other properties, so it merits separate discussion. The Font property represents an instance of the FontInfo class, which has the following properties:

  • Bold— When True, the font is displayed in bold

  • Italic— When True, the font is displayed in italics

  • Name— The name of the typeface to use for the font

  • Names— A comma-separated list of typeface names to use for the font

  • Overline— If True, a line appears above the font

  • Size— The size of the font

  • Strikeout— If True, a line appears through the font

  • Underline— If True, a line appears below the font

So, to format the text displayed by each item in the DataList with a bold, italic, Script font, you would declare the DataList control like this:

<asp:DataList
  ItemStyle-Font-Italic="True"
  ItemStyle-Font-Bold="True"
  ItemStyle-Font-Name="Script"
  Runat="Server">

Or, if you want to format alternating items with a blue, 14-point, Arial font, you would declare the DataList control like this:

<asp:DataList
  AlternatingItemStyle-ForeColor="Blue"
  AlternatingItemStyle-Font-Name="Arial"
  AlternatingItemStyle-Font-Size="14pt"
  Runat="Server">

Creating Multiple Columns in a DataList

One nice feature of a DataList is that it enables you to display items in multiple columns. You control the column layout of a DataList by setting its RepeatColumns and RepeatDirection properties.

The RepeatColumns property determines the number of columns to display. For example, if you want to display the items in a DataList in four columns, you would set this property to the value 4.

The RepeatDirection property determines whether columns should be repeated horizontally or vertically. By default, RepeatDirection has the value Vertical. So, if RepeatColumns has the value 4, columns are displayed like this:

Column 1 | Column3 | Column5 | Column7
Column 2 | Column4 | Column6 | Column8

If you set RepeatDirection to the value Horizontal, and RepeatColumns has the value 4, the columns are displayed like this:

Column1 | Column2 | Column3 | Column4
Column5 | Column6 | Column7 | Column8

Notice that four columns are displayed even when RepeatDirection has the value Vertical. RepeatColumns always refers to the number of columns to repeat, not the number of rows.

The page in Listing 11.4 displays a menu of links in four columns, where the columns are repeated horizontally (see Figure 11.4).

Listing 11.4 DataListMultiColumn.aspx
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
  Dim conNorthwind As SqlConnection
  Dim cmdSelect As SqlCommand
  Dim dtrProducts As SqlDataReader

  conNorthwind = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Northwind" )
  cmdSelect = New SqlCommand( "Select ProductName From Products", conNorthwind )
  conNorthwind.Open()
  dtrProducts = cmdSelect.ExecuteReader()

  dlstProducts.DataSource = dtrProducts
  dlstProducts.DataBind()

  dtrProducts.Close()
  conNorthwind.Close()
End Sub

</Script>

<html>
<head><title>DataListMultiColumn.aspx</title></head>
<body>
<form Runat="Server">

<asp:DataList
  ID="dlstProducts"
  RepeatColumns="4"
  RepeatDirection="Horizontal"
  CellSpacing="10"
  Runat="Server">

<ItemTemplate>
  <asp:LinkButton
    Text='<%# Container.DataItem( "ProductName" )%>'
    Runat="Server"/>
</ItemTemplate>

</asp:DataList>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 11.4. A multi-column DataList.

Capturing Events Raised in a DataList Control

As mentioned in the overview section of this chapter, the DataList control supports event bubbling. You can capture events raised by controls contained within a DataList and handle them with a generic subroutine.

The DataList control can raise the following five events in response to events raised by its child controls:

  • CancelCommand— Raised by a child control with CommandName="cancel"

  • DeleteCommand— Raised by a child control with CommandName="delete"

  • EditCommand— Raised by a child control with CommandName="edit"

  • ItemCommand— Raised by a child control when an event is not handled by CancelCommand, DeleteCommand, EditCommand, or UpdateCommand

  • UpdateCommand— Raised by a child control with CommandName="update"

The Button, LinkButton, and ImageButton controls contained in a DataList can raise these events. For example, if you set a LinkButton control's CommandName property to the value delete, clicking the LinkButton raises the DeleteCommand event in the DataList.

Notice that ItemCommand is the default event raised by the DataList. If a Button, LinkButton, or ImageButton control contained in a DataList is clicked and the control does not have a CommandName property, the DataList control's ItemCommand event is raised.

The page in Listing 11.5 displays three LinkButton controls labeled Edit!, Delete!, and Update! for each item in the DataList. The three Button controls have different CommandName properties. When you click different Button controls, different events are raised in the DataList, and different subroutines are executed.

Listing 11.5 DataListEvents.aspx
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
  If Not IsPostBack Then
    Dim conPubs As SqlConnection
    Dim cmdSelect As SqlCommand
    Dim dtrTitles As SqlDataReader

    conPubs = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Pubs" )
    cmdSelect = New SqlCommand( "Select Title From Titles", conPubs )
    conPubs.Open()
    dtrTitles = cmdSelect.ExecuteReader()

    dlstTitles.DataSource = dtrTitles
    dlstTitles.DataBind()

    dtrTitles.Close()
    conPubs.Close()
  End If
End Sub

Sub DataList_ItemCommand( s As Object, e As DataListCommandEventArgs )
  lblMessage.Text = "<li>Item Command!"
End Sub

Sub DataList_EditCommand( s As Object, e As DataListCommandEventArgs )
  lblMessage.Text &= "<li>Editing Item!"
End Sub

Sub DataList_DeleteCommand( s As Object, e As DataListCommandEventArgs )
  lblMessage.Text = "<li>Deleting Item!"
End Sub

Sub DataList_UpdateCommand( s As Object, e As DataListCommandEventArgs )
  lblMessage.Text = "<li>Updating Item!"
End Sub

</Script>

<html>
<head><title>DataListEvents.aspx</title></head>
<body>

<form Runat="Server">

<asp:Label
  ID="lblMessage"
  BackColor="yellow"
  Runat="Server" />
<p>
<asp:DataList
  ID="dlstTitles"
  OnItemCommand="DataList_ItemCommand"
  OnEditCommand="DataList_EditCommand"
  OnDeleteCommand="DataList_DeleteCommand"
  OnUpdateCommand="DataList_UpdateCommand"
  GridLines="Both"
  CellPadding="4"
  Runat="Server">

<ItemTemplate>
  <%# Container.DataItem( "Title" )%>
  <br>
  <asp:LinkButton
    Text="Edit!"
    CommandName="edit"
    Runat="Server"/>
  <asp:LinkButton
    Text="Delete!"
    CommandName="delete"
    Runat="Server"/>
  <asp:LinkButton
    Text="Update!"
    CommandName="update"
    Runat="Server"/>
</ItemTemplate>

</asp:DataList>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Each of the three LinkButton controls displayed with each title in the DataList is associated with a distinct subroutine. For example, when you click the LinkButton control named Delete!, the DeleteCommand event is raised in the DataList control. This event is associated with the DataList_DeleteCommand subroutine.

The DataList_ItemCommand, DataList_EditCommand, DataList_DeleteCommand, and DataList_UpdateCommand subroutines don't actually do anything complicated. They simply assign different messages to a Label control.

Notice that each subroutine associated with the DataList is declared with a DataListCommandEventArgs parameter. This parameter represents the information passed to the subroutine from the DataList. The DataListCommandEventArgs class has the following properties:

  • CommandArgument— Represents the value of the CommandArgument property from the control that raised the event

  • CommandName— Represents the name of the command that raised the event

  • CommandSource— Represents the DataList control that raised the event

  • Item— Represents an item from the DataList (a DataListItem)

In the following sections, you learn how to take advantage of event bubbling to select and edit items in a DataList.

Selecting Items in a DataList Control

You can select items in a DataList and display the selected item with special formatting. This capability is useful, for example, when you want to display a menu of options and highlight the currently selected item in the menu.

The selected item can be formatted by using either the SelectedItem template, the SelectedItemStyle object, or both. So, using these two options, you have a great deal of flexibility in controlling how a selected item is displayed.

The page in Listing 11.6 uses a DataList to display a list of author last names from the Authors database table. Each of the author last names is rendered as a LinkButton control. When you select an author, the selected author is formatted with the SelectedItemTemplate (see Figure 11.5).

Listing 11.6 DataListSelected.aspx
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
  If Not IsPostBack
    BindDataList
  End If
End Sub

Sub BindDataList
  Dim conPubs As SqlConnection
  Dim cmdSelect As SqlCommand
  Dim dtrAuthors As SqlDataReader

  conPubs = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Pubs" )
  cmdSelect = New SqlCommand( "Select au_lname From Authors", conPubs )
  conPubs.Open()
  dtrAuthors = cmdSelect.ExecuteReader()

  dlstAuthors.DataSource = dtrAuthors
  dlstAuthors.DataBind()

  dtrAuthors.Close()
  conPubs.Close()
End Sub

Sub DataList_ItemCommand( s As Object, e As DataListCommandEventArgs )
  dlstAuthors.SelectedIndex = e.Item.ItemIndex
  BindDataList
End Sub

</Script>

<html>
<head><title>DataListSelected.aspx</title></head>
<body>
<form Runat="Server">

<asp:DataList
  ID="dlstAuthors"
  OnItemCommand="DataList_ItemCommand"
  Runat="Server">

<ItemTemplate>
  <asp:LinkButton
    Text='<%# Container.DataItem( "au_lname" )%>'
    Runat="Server" />
</ItemTemplate>

<SelectedItemTemplate>
  <b><i><%# Container.DataItem( "au_lname" )%></i></b>
</SelectedItemTemplate>

</asp:DataList>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 11.5. Selecting items in a DataList.

When you click one of the LinkButton controls displayed by the page in Listing 11.6, the ItemCommand event is raised. This event is wired up to the DataList_ItemCommand subroutine.

The DataList_ItemCommand subroutine consists of the following two lines of code:

dlstAuthors.SelectedIndex = e.Item.ItemIndex
BindDataList

The first statement assigns the index of the item selected to the SelectedIndex property of the DataList control. The next line rebinds the DataList control to the data source. When the DataList is rendered, the item that corresponds to the value of the SelectedIndex property is rendered with the SelectedItem template.

Using the DataKeys Collection with the DataList Control

When you select an item in a DataList, you often need to retrieve the value of the primary key associated with the item. You can retrieve the value of the primary key associated with an item by using the DataKeys collection.

Imagine that you want to display a database table named Products, which contains two columns named ProductID and ProductName, in a DataList. When a user selects an item in the DataList, you want to retrieve the value of the ProductID column associated with the selected item. To do so, you need to set the DataKeyField property of the DataList control like this:

<asp:DataList
  ID="dlstProducts"
  DataKeyField="ProductID"
  OnItemCommand="dlstProducts_ItemCommand"
  Runat="Server">

If you assign the name of the primary key column of a database table to the DataKeyField property, a special collection called the DataKeys collection is populated automatically when you bind the DataList to the Products table. The DataKeys collection contains all the primary key values from the database table in the same order as the items in the DataList.

NOTE

You can use the DataKeys collection only when using a database table that has a single primary key column. (A primary key column cannot contain duplicate values or Null values.)

After the DataKeys collection is created, you can retrieve the value of the primary key associated with any item in the DataList by passing the index number of the item to the DataKeys collection. For example, to retrieve the value of the primary key associated with the third item displayed by a DataList, you would use a statement like this:

PrimaryKeyValue = dlstProducts.DataKeys( 2 )

Creating a Master/Detail Form with a DataList Control

In this section, you build a master/detail form by using a DataList control to display a menu of categories and a Repeater control to display the products for the selected category (see Figure 11.6).

Figure 11.6. Master/detail form with a DataList.

You use the Categories and Products tables from the Northwind database for the master and detail records. These tables share the CategoryID column, which you use to retrieve the products that match the selected category.

The master/detail form is contained in Listing 11.7.

Listing 11.7 DataListMasterDetail.aspx
[View full width]
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
  If Not IsPostBack
    BindDataList
  End If
End Sub

Sub BindDataList
  Dim conNorthwind As SqlConnection
  Dim cmdSelect As SqlCommand
  Dim dtrCategories As SqlDataReader

  conNorthwind = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Northwind" )
  cmdSelect = New SqlCommand( "Select CategoryID, CategoryName From Categories",
   conNorthwind )
  conNorthwind.Open()
  dtrCategories = cmdSelect.ExecuteReader()

  dlstCategories.DataSource = dtrCategories
  dlstCategories.DataBind()

  dtrCategories.Close()
  conNorthwind.Close()
End Sub

Sub BindRepeater( intCatID As Integer )
  Dim conNorthwind As SqlConnection
  Dim strSelect As String
  Dim cmdSelect As SqlCommand
  Dim dtrProducts As SqlDataReader

  conNorthwind = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Northwind" )
  strSelect = "Select ProductName From Products Where CategoryID=@catID"
  cmdSelect = New SqlCommand( strSelect, conNorthwind )
  cmdSelect.Parameters.Add( "@catID", intCatID )
  conNorthwind.Open()
  dtrProducts = cmdSelect.ExecuteReader()

  rptProducts.DataSource = dtrProducts
  rptProducts.DataBind()

  dtrProducts.Close()
  conNorthwind.Close()
End Sub

Sub dlstCategories_ItemCommand( s As Object, e As DataListCommandEventArgs )
  Dim intCatID As Integer

  dlstCategories.SelectedIndex = e.Item.ItemIndex
  BindDataList
  intCatID = dlstCategories.DataKeys( e.Item.ItemIndex )
  BindRepeater( intCatID )
End Sub

</Script>

<html>
<head><title>DataListMasterDetail.aspx</title></head>
<body>
<form Runat="Server">

<table width="100%">
<tr><td valign="top">

<asp:DataList
  ID="dlstCategories"
  OnItemCommand="dlstCategories_ItemCommand"
  DataKeyField="CategoryID"
  ItemStyle-BorderStyle="Solid"
  ItemStyle-BorderColor="Blue"
  Width="200"
  CellPadding="5"
  CellSpacing="10"
  BackColor="lightgrey"
  Runat="Server">

<ItemTemplate>
  <asp:LinkButton
    Text='<%# Container.DataItem( "CategoryName" )%>'
    Runat="Server" />
</ItemTemplate>

<SelectedItemTemplate>
  <b><i><%# Container.DataItem( "CategoryName" )%></i></b>
</SelectedItemTemplate>

</asp:DataList>

</td><td valign="top" width="100%">

<asp:Repeater
  ID="rptProducts"
  Runat="Server">

<ItemTemplate>
  <%# Container.DataItem( "ProductName" ) %>
</ItemTemplate>

<SeparatorTemplate>
  <hr>
</SeparatorTemplate>

</asp:Repeater>

</td></tr>
</table>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

When you click a LinkButton control in the DataList, the dlstCategories_ItemCommand subroutine is executed. This subroutine contains the following lines of code:

dlstCategories.SelectedIndex = e.Item.ItemIndex
BindDataList
intCatID = dlstCategories.DataKeys( e.Item.ItemIndex )
BindRepeater( intCatID )

The first two statements set the SelectedIndex in the DataList and rebind the DataList to the data source. The next two statements retrieve the CategoryID of the selected category. The CategoryID is retrieved from the DataKeys collection.

If you look closely at the declaration of the DataList in Listing 11.7, you notice that the DataList control's DataKeyField property is assigned the value CategoryID. This property causes the values from the CategoryID column from the Categories table to be automatically copied into the DataKeys collection when the Categories table is bound to the DataList.

So, for any item in the DataList, you can look up the corresponding primary key value for the item by passing the index number of the item to the DataKeys collection.

After the primary key associated with the selected item has been retrieved, it is passed to the BindRepeater subroutine, which displays all the products that match the CategoryID.

Editing Items in a DataList Control

You can use a DataList control to edit a set of records in a database table (see Figure 11.7). You should be warned, however, that it takes a little work to do so. In this section, you examine the process of editing records in a DataList control, step by step, and then tackle a complete example at the end of the section.

Figure 11.7. Editing data with a DataList.

The DataList control includes a template called the EditItemTemplate. You can place form controls in the EditItemTemplate that enable you to edit a particular item in the DataList.

The following DataList, for example, is declared with both an ItemTemplate, for displaying a record, and an EditItemTemplate, for editing a record:

<asp:DataList
  ID="dlstAuthors"
  Runat="Server">

<ItemTemplate>
  <%# Container.DataItem( "au_lname" )%>
</ItemTemplate>

<EditItemTemplate>
  <asp:TextBox
    ID="txtLastName"
    Text='<%# Container.DataItem( "au_lname" )%>'
    Runat="Server" />
</EditItemTemplate>

</asp:DataList>

Normally, each record is displayed by the ItemTemplate. However, if you select a record for editing, the EditItemTemplate is displayed. In this example, the EditItemTemplate simply contains a TextBox control that has a field from the data source as its default value.

You select a record for editing by using the EditItemIndex property of the DataList control. Only one item in a DataList can be selected for editing at a time. Typically, you select a record for editing by adding a LinkButton control to the ItemTemplate like this:

<ItemTemplate>
  <%# Container.DataItem( "au_lname" )%>
  <br>
  <asp:LinkButton
    Text="Edit!"
    CommandName="edit"
    Runat="Server" />
</ItemTemplate>

The LinkButton in this ItemTemplate has its CommandName property set to the value edit. When you click the LinkButton control, it raises an EditCommand event in the DataList control that contains it.

You can associate a subroutine with the DataList that executes when the EditCommand event is raised like this:

<asp:DataList
  ID="dlstAuthors"
  OnEditCommand="dlstAuthors_EditCommand"
  Runat="Server">

This DataList control associates the dlstAuthors_EditCommand subroutine with the EditCommand event. Within the dlstAuthors_EditCommand subroutine, you need to select the proper item in the DataList for editing. The following subroutine selects a record for editing by setting the EditItemIndex property of the DataList control:

Sub dlstAuthors_EditCommand( s As Object, e As DataListCommandEventArgs )
  dlstAuthors.EditItemIndex = e.Item.ItemIndex
  BindDataList
End Sub

When this subroutine executes, the item that raised the EditCommand event is selected for editing. The item is displayed with the EditItemTemplate rather than the normal ItemTemplate.

You typically want to add Update, Delete, and Cancel commands to the EditItemTemplate. So, a more complete EditItemTemplate would look like this:

<EditItemTemplate>
  <asp:TextBox
    ID="txtLastName"
    Text='<%# Container.DataItem( "au_lname" )%>'
    Runat="Server" />
  <p>
  <asp:LinkButton
    Text="Update!"
    CommandName="update"
    Runat="Server" />
  <asp:LinkButton
    Text="Delete!"
    CommandName="delete"
    Runat="Server" />
  <asp:LinkButton
    Text="Cancel!"
    CommandName="cancel"
    Runat="Server" />
</EditItemTemplate>

This EditItemTemplate contains three LinkButton controls labeled Update!, Delete!, and Cancel! The LinkButton control named Update! has its CommandName property set to update, the LinkButton control named Delete! has its CommandName property set to delete, and the LinkButton control named Cancel! has its CommandName property set to cancel.

You can associate subroutines with the events raised by each of these commands by setting the OnUpdateCommand, OnDeleteCommand, and OnCancelCommand properties of the DataList control like this:

<asp:DataList
  ID="dlstAuthors"
  OnEditCommand="dlstAuthors_EditCommand"
  OnUpdateCommand="dlstAuthors_UpdateCommand"
  OnDeleteCommand="dlstAuthors_DeleteCommand"
  OnCancelCommand="sdlstAuthors_CancelCommand"
  Runat="Server">

The EditCommand event triggers the dlstAuthors_EditCommand subroutine, the UpdateCommand event triggers the dlstAuthors_UpdateCommand subroutine, the DeleteCommand event triggers the dlstAuthors_DeleteCommand subroutine, and the CancelCommand event triggers the dlstAuthors_CancelCommand subroutine.

Within the dlstAuthors_UpdateCommand subroutine, you need to write code to update the record selected for editing in the underlying database. Within the dlstAuthors_ DeleteCommand subroutine, you need to write code to delete the record selected for editing in the underlying database. Finally, in the dlstAuthors_CancelCommand subroutine, you need to write code to cancel the current editing operation.

For example, the dlstAuthors_UpdateCommand subroutine might look like this:

Sub dlstAuthors_UpdateCommand( s As Object, e As DataListCommandEventArgs )
  Dim conPubs As SqlConnection
  Dim strUpdate As String
  Dim cmdUpdate As SqlCommand
  Dim strAuthorID As String
  Dim txtLastName As TextBox
  Dim txtPhone As TextBox

  strAuthorID = dlstAuthors.DataKeys( e.Item.ItemIndex )
  txtLastName = e.Item.FindControl( "txtLastName" )
  txtPhone = e.Item.FindControl( "txtPhone" )
  conPubs = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Pubs" )
  strUpdate = "Update Authors Set au_lname=@lastname, phone=@phone Where au_id=@authorID"
  cmdUpdate = New SqlCommand( strUpdate, conPubs )
  cmdUpdate.Parameters.Add( "@authorID", strAuthorID )
  cmdUpdate.Parameters.Add( "@lastname", txtLastName.Text )
  cmdUpdate.Parameters.Add( "@phone", txtPhone.Text )
  conPubs.Open()
  cmdUpdate.ExecuteNonQuery()
  conPubs.Close()
  dlstAuthors.EditItemIndex = -1
  BindDataList
End Sub

Notice that the DataList does not handle updating the database table for you; you must write all the code yourself. In this subroutine, the values entered into the TextBox controls named txtLastName and txtPhone are retrieved by using the FindControl() method, which grabs these controls from the DataList item selected for editing.

NOTE

The FindControl() method retrieves a control by searching through a container for a control with a certain ID. If the FindControl() method fails to find the control, it returns the value 0.

Finally, the underlying database is updated by executing a SqlCommand. The SqlCommand updates the Authors table, setting a new value for the au_lname and phone columns, where the author has a certain au_id.

A complete example of editing records with a DataList is included in Listing 11.8. This example enables you to update and delete author last names and phone numbers from the Authors table.

Listing 11.8 DataListEdit.aspx
[View full width]
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
  If Not IsPostBack Then
    BindDataList
  End If
End Sub

Sub BindDataList
  Dim conPubs As SqlConnection
  Dim cmdSelect As SqlCommand
  Dim dtrAuthors As SqlDataReader

  conPubs = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Pubs" )
  cmdSelect = New SqlCommand( "Select au_id, au_lname, phone From Authors Order by
   au_lname", conPubs )
  conPubs.Open()
  dtrAuthors = cmdSelect.ExecuteReader()

  dlstAuthors.DataSource = dtrAuthors
  dlstAuthors.DataBind()

  dtrAuthors.Close()
  conPubs.Close()
End Sub

Sub dlstAuthors_EditCommand( s As Object, e As DataListCommandEventArgs )
  dlstAuthors.EditItemIndex = e.Item.ItemIndex
  BindDataList
End Sub

Sub dlstAuthors_CancelCommand( s As Object, e As DataListCommandEventArgs )
  dlstAuthors.EditItemIndex = -1
  BindDataList
End Sub

Sub dlstAuthors_DeleteCommand( s As Object, e As DataListCommandEventArgs )
  Dim conPubs As SqlConnection
  Dim strDelete As String
  Dim cmdDelete As SqlCommand
  Dim strAuthorID As String

  strAuthorID = dlstAuthors.DataKeys( e.Item.ItemIndex )
  conPubs = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Pubs" )
  strDelete = "Delete Authors Where au_id=@authorID"
  cmdDelete = New SqlCommand( strDelete, conPubs )
  cmdDelete.Parameters.Add( "@authorID", strAuthorID )
  conPubs.Open()
  cmdDelete.ExecuteNonQuery()
  conPubs.Close()
  dlstAuthors.EditItemIndex = -1
  BindDataList
End Sub

Sub dlstAuthors_UpdateCommand( s As Object, e As DataListCommandEventArgs )
  Dim conPubs As SqlConnection
  Dim strUpdate As String
  Dim cmdUpdate As SqlCommand
  Dim strAuthorID As String
  Dim txtLastName As TextBox
  Dim txtPhone As TextBox

  strAuthorID = dlstAuthors.DataKeys( e.Item.ItemIndex )
  txtLastName = e.Item.FindControl( "txtLastName" )
  txtPhone = e.Item.FindControl( "txtPhone" )
  conPubs = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Pubs" )
  strUpdate = "Update Authors Set au_lname=@lastname, phone=@phone Where au_id=@authorID"
  cmdUpdate = New SqlCommand( strUpdate, conPubs )
  cmdUpdate.Parameters.Add( "@authorID", strAuthorID )
  cmdUpdate.Parameters.Add( "@lastname", txtLastName.Text )
  cmdUpdate.Parameters.Add( "@phone", txtPhone.Text )
  conPubs.Open()
  cmdUpdate.ExecuteNonQuery()
  conPubs.Close()
  dlstAuthors.EditItemIndex = -1
  BindDataList
End Sub

</Script>

<html>
<head><title>DataListEdit.aspx</title></head>
<body>
<form Runat="Server">

<asp:DataList
  ID="dlstAuthors"
  DataKeyField="au_id"
  OnEditCommand="dlstAuthors_EditCommand"
  OnCancelCommand="dlstAuthors_CancelCommand"
  OnDeleteCommand="dlstAuthors_DeleteCommand"
  OnUpdateCommand="dlstAuthors_UpdateCommand"
  RepeatColumns="4"
  GridLines="Both"
  CellPadding="10"
  EditItemStyle-BackColor="lightgrey"
  Runat="Server">

<ItemTemplate>
  <%# Container.DataItem( "au_lname" )%>
  - <%# Container.DataItem( "phone" )%>
  <br>
  <asp:LinkButton
    Text="Edit!"
    CommandName="edit"
    Runat="Server" />
</ItemTemplate>

<EditItemTemplate>
  <b>Last Name:</b>
  <br>
  <asp:TextBox
    ID="txtLastName"
    Text='<%# Container.DataItem( "au_lname" )%>'
    Runat="Server" />
  <p>
  <b>Phone:</b>
  <br>
  <asp:TextBox
    ID="txtPhone"
    Text='<%# Container.DataItem( "phone" )%>'
    Runat="Server" />
  <p>
  <asp:LinkButton
    Text="Update!"
    CommandName="update"
    Runat="Server" />
  <asp:LinkButton
    Text="Delete!"
    CommandName="delete"
    Runat="Server" />
  <asp:LinkButton
    Text="Cancel!"
    CommandName="cancel"
    Runat="Server" />

</EditItemTemplate>

</asp:DataList>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.


Using the DataGrid Control

The DataGrid control is the most feature-rich (and most complicated) control included with the ASP.NET framework. Like the Repeater and DataList controls, it enables you to format and display records from a database table. However, it has several advanced features, such as support for sorting and paging through records, which make it unique.

You can display records in a DataGrid without using templates. You can simply bind a data source to the DataGrid, and it automatically displays the records. For example, the DataGrid in Listing 11.9 displays all the records from the Titles database table in a DataGrid (see Figure 11.8).

Listing 11.9 DataGrid.aspx
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

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 * From Titles", conPubs )
  conPubs.Open()
  dgrdTitles.DataSource = cmdSelect.ExecuteReader()
  dgrdTitles.DataBind()
  conPubs.Close()
End Sub

</Script>

<html>
<head><title>DataGrid.aspx</title></head>
<body>

<asp:DataGrid
  ID="dgrdTitles"
  EnableViewState="False"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 11.8. Displaying data with a DataGrid.

By default, a DataGrid displays gridlines around its items. You can modify how the gridlines appear by setting the GridLines property; possible values are Both, Horizontal, None, or Vertical. For example, to completely disable GridLines, you would declare the DataGrid like this:

<asp:DataGrid
  GridLines="None"
  Runat="Server" />

You also can control the cell spacing and cell padding of the cells in a DataGrid by modifying the DataGrid control's CellSpacing and CellPadding properties like this:

<asp:DataGrid
  CellSpacing="20"
  CellPadding="20"
  Runat="Server" />

You can also specify a background image for a DataGrid by assigning the name of an image to the BackImageUrl property. The image is tiled behind the records the DataGrid displays. For example, the following DataGrid displays an image named Bricks.Gif in the background:

<asp:DataGrid
  BackImageUrl="Bricks.Gif"
  Runat="Server" />

Finally, you can display or hide headers and footers for the columns in a DataGrid by enabling or disabling the ShowHeader and ShowFooter properties. The ShowHeader property has the value True by default, and the ShowFooter property has the value False by default. If you want to prevent column headers from being displayed, you would declare a DataGrid like this:

<asp:DataGrid
  ShowHeader="False"
  Runat="Server" />

Creating Columns in a DataGrid Control

The DataGrid control supports several column types. You can create columns to control how records are formatted or to display links for editing records. The DataGrid control supports the following five types of columns:

  • BoundColumn— The default column for a DataGrid; displays records

  • HyperLinkColumn— Displays records as hyperlinks

  • TemplateColumn— Displays a record using a template

  • ButtonColumn— Displays Button controls

  • EditCommandColumn— Displays edit commands, such as Edit, Update, and Cancel

By default, a DataGrid simply displays all the columns from its data source. However, if you assign the value False to the DataGrid control's AutoGenerateColumns property, you can create each column individually and have more control over the formatting.

Adding a BoundColumn to a DataGrid

The default column used in a DataGrid is a BoundColumn. If you want to display only certain columns, but not all columns, from a data source, or you want to control the order in which the columns are displayed, you can explicitly declare one or more BoundColumn controls.

The page in Listing 11.10, for example, displays only the Title and Price columns from the Titles table (see Figure 11.9).

Listing 11.10 DataGridBoundColumns.aspx
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

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 * From Titles", conPubs )
    conPubs.Open()
    dgrdTitles.DataSource = cmdSelect.ExecuteReader()
    dgrdTitles.DataBind()
    conPubs.Close()
End Sub

</Script>

<html>
<head><title>DataGridBoundColumns.aspx</title></head>
<body>

<asp:DataGrid
  ID="dgrdTitles"
  AutoGenerateColumns="False"
  EnableViewState="False"
  Runat="Server">

<Columns>
  <asp:BoundColumn DataField="Title" />
  <asp:BoundColumn DataField="Price" />
</Columns>

</asp:DataGrid>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 11.9. Using BoundColumn controls.

Notice that the DataGrid control's AutoGenerateColumns property is set to the value False. If you do not set this property to False, all the columns from the Titles table are displayed, and the Title and Price columns are displayed twice.

The BoundColumns are declared inside the DataGrid control's <Columns> tag. This tag contains a BoundColumn for the Titles column and a BoundColumn for the Price column. The DataField property indicates the field from the data source to display in the BoundColumn.

NOTE

For performance reasons, you should select only the records that you intend to display in a page when retrieving records from a database. In other words, Select * is your enemy.

You can format the output of a BoundColumn by assigning a format string to its DataFormatString property. This capability is useful, for example, when you need to display a currency or date/time value. To display the Price column with a currency format string, you would declare the BoundColumn like this:

<asp:BoundColumn
  DataField="Price"
  DataFormatString="{0:c}"/>

NOTE

For more information on using format strings, see Chapter 24, "Working with Collections and Strings."

Finally, you can control the appearance of the header and footer used for a BoundColumn by modifying the HeaderText, FooterText, and HeaderImageUrl properties. The HeaderText and FooterText properties enable you to display text at the top and bottom of a BoundColumn. The HeaderImageUrl enables you to display a picture for the header.

The following DataList control, for example, displays the text Book Titles for the header and footer:

<asp:DataGrid
  AutoGenerateColumns="False"
  EnableViewState="False"
  ShowFooter="True"
  Runat="Server">

<Columns>
  <asp:BoundColumn
    DataField="Title"
    HeaderText="Book Titles"
    FooterText="Book Titles"
    />
  <asp:BoundColumn
    DataField="Price"
    DataFormatString="{0:c}"/>
</Columns>

</asp:DataGrid>

Notice that the DataGrid control's ShowFooter property is assigned the value True. Before you can use the FooterText property, you must modify the ShowFooter property.

The HeaderImageUrl property displays an image for a header. The image is not displayed in the background, so you cannot combine the HeaderImageUrl property with the HeaderText property to display both an image and text. If you want to display both an image and text in a header, you need to assign the HTML tags for an image to the HeaderText property like this:

HeaderText="<img src=myImage.Gif>Titles"

Following is a list of some of the important properties supported by a BoundColumn:

  • DataField— The field from the data source displayed by the BoundColumn

  • DataFormatString— A format string that formats the DataField

  • FooterText— Text that appears at the bottom of the BoundColumn

  • HeaderImageUrl— The path to an image that appears at the top of a BoundColumn

  • HeaderText— Text that appears at the top of the BoundColumn

Adding a HyperLinkColumn to a DataGrid

If you want to display links to other pages from your DataGrid, you can use a HyperLinkColumn. This type of column is useful in two situations.

First, you can use a HyperLinkColumn when creating a master/detail form. You can display a set of master records in a DataGrid and use a HyperLinkColumn in the DataGrid to link to a separate page that contains detailed information.

You also can use a HyperLinkColumn to display links to different pages at your Web site, or links to other Web sites. For example, you can use a HyperLinkColumn to display a list of useful ASP.NET Web sites.

The page in Listing 11.11 could be used as the master page in a master/detail form.

Listing 11.11 DataGridHyperLink.aspx
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

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_id, au_lname From Authors", conPubs )
    conPubs.Open()
    dgrdAuthors.DataSource = cmdSelect.ExecuteReader()
    dgrdAuthors.DataBind()
    conPubs.Close()
End Sub

</Script>

<html>
<head><title>DataGridHyperLink.aspx</title></head>
<body>
<form Runat="Server">

<asp:DataGrid
  ID="dgrdAuthors"
  AutoGenerateColumns="False"
  EnableViewState="False"
  CellPadding="10"
  Runat="Server">

<Columns>
  <asp:BoundColumn
    HeaderText="Author"
    DataField="au_lname" />
  <asp:HyperLinkColumn
    HeaderText="Details"
    DataNavigateUrlField="au_id"
    DataNavigateUrlFormatString="Details.aspx?id={0}"
    Text="view details" />
</Columns>

</asp:DataGrid>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

The DataGrid in Listing 11.11 is bound to the Authors database table. The values from the author last name column (au_lname) are displayed by the BoundColumn, and links to a page with detailed author information are displayed by the HyperLinkColumn.

Each hyperlink displayed by the HyperLinkColumn is displayed with the help of the DataNavigateUrlField, DataNavigateUrlFormatString, and Text properties. The DataNavigateUrlField and DataNavigateUrlFormatString properties create the URL for the link. In this case, the links point to a page named Details.aspx. The au_id column is passed to Details.aspx in a query string variable named ID.

The Text property specifies the label for the link. In Listing 11.11, each link is simply labeled with the text view details.

Following is a list of some of the important properties of a HyperLinkColumn:

  • DataNavigateUrlField— A field from the DataGrid control's data source for creating the URL of the hyperlink

  • DataNavigateUrlFormatString— A format string that formats the value of the DataNavigateUrlField

  • DataTextField— A field from the DataGrid control's data source used for the label of the hyperlink

  • DataTextFormatString— A format string that formats the value of the DataTextField

  • FooterText— Text displayed at the bottom of the HyperLinkColumn

  • HeaderImageUrl— Image that is displayed at the top of the HyperLinkColumn

  • HeaderText— Text displayed at the top of the HyperLinkColumn

  • NavigateUrl— The URL of the hyperlink

  • Target— The window or frame that the hyperlink targets

  • Text— Text displayed as the label of the hyperlink

Notice that you can use the DataTextField and DataTextFormatString to display different labels for each hyperlink. For example, the page in Listing 11.12 displays a list of hyperlinks to different Web sites that contain information on ASP.NET (see Figure 11.10).

Listing 11.12 DataGridDataFormatString.aspx
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
    Dim conMyData As SqlConnection
    Dim cmdSelect As SqlCommand

    conMyData = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=MyData" )
    cmdSelect = New SqlCommand( "Select link_title, link_url From FavLinks", conMyData )
    conMyData.Open()
    dgrdLinks.DataSource = cmdSelect.ExecuteReader()
    dgrdLinks.DataBind()
    conMyData.Close()
End Sub

</Script>

<html>
<head><title>DataGridDataFormatString.aspx</title></head>
<body>
<form Runat="Server">

<asp:DataGrid
  ID="dgrdLinks"
  AutoGenerateColumns="False"
  EnableViewState="False"
  ShowHeader="False"
  CellPadding="10"
  Runat="Server">

<Columns>
  <asp:HyperLinkColumn
    DataNavigateUrlField="link_url"
    DataTextField="link_title" />
</Columns>

</asp:DataGrid>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 11.10. Using the DataTextFormatString property.

The HyperLinkColumn displays each link by using the DataNavigateUrlField and DataTextField properties. The DataNavigateUrlField property contains the URL of the link, and the DataTextField property contains the text label of the link.

Adding a TemplateColumn to a DataGrid

If you need more control over the formatting of a column displayed by a DataGrid, you can use a TemplateColumn, which enables you to control the formatting of a column through the following templates:

  • HeaderTemplate— Formats the text that appears at the top of the TemplateColumn

  • ItemTemplate— Formats each item displayed by the TemplateColumn

  • EditItemTemplate— Formats an item selected for editing

  • FooterTemplate— Formats the text that appears at the bottom of the TemplateColumn

Imagine that you want to display a DataGrid that contains two columns. The first column displays author last names, and the second column displays more detailed information about the author (see Figure 11.11). The page in Listing 11.13 illustrates how you can create such a DataGrid by using a TemplateColumn.

Listing 11.13 DataGridTemplate.aspx
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

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 * From Authors", conPubs )
  conPubs.Open()
  dgrdAuthors.DataSource = cmdSelect.ExecuteReader()
  dgrdAuthors.DataBind()
  conPubs.Close()
End Sub

</Script>

<html>
<head><title>DataGridTemplate.aspx</title></head>
<body>
<form Runat="Server">

<asp:DataGrid
  ID="dgrdAuthors"
  AutoGenerateColumns="False"
  EnableViewState="False"
  ShowHeader="False"
  CellPadding="10"
  Runat="Server">

<Columns>
  <asp:BoundColumn
    DataField="au_lname" />

  <asp:TemplateColumn>
    <itemTemplate>
    <table>
    <tr>
      <td>Phone:</td>
      <td><%# Container.DataItem( "phone" )%></td>
    </tr>
    <tr>
      <td>City:</td>
      <td><%# Container.DataItem( "city" )%></td>
    </tr>
    </table>
    </itemTemplate>
  </asp:TemplateColumn>
</Columns>

</asp:DataGrid>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 11.11. Using a TemplateColumn.

In Listing 11.13, the TemplateColumn displays the phone and city fields from the Authors table. These two fields are formatted with an HTML table.

Notice that templates work differently when used with a DataGrid than when used with either a Repeater or DataList control. When you create templates for a Repeater or DataList, the template formats rows of data displayed by the control. When a template is used with a DataGrid, in contrast, the template formats a single column, not the whole row.

A TemplateColumn supports the following properties:

  • FooterText— Text that appears at the bottom of the column

  • HeaderImageUrl— The path to an image that appears at the top of the column

  • HeaderText— Text that appears at the top of the column

Adding a ButtonColumn to a DataGrid

You can use a ButtonColumn to add command buttons to a DataGrid. You can use these command buttons to perform any action you wish. For example, you can use a ButtonColumn to create an Add To Cart button on a DataGrid.

The page in Listing 11.14 illustrates how you can use a ButtonColumn to select rows in a DataGrid. When you click the Select! link displayed by the first ButtonColumn, the selected row appears with a light green background and bold text (see Figure 11.12).

Listing 11.14 DataGridButtonColumn.aspx
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
  If Not IsPostBack Then
    Dim conPubs As SqlConnection
    Dim cmdSelect As SqlCommand

    conPubs = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Pubs" )
    cmdSelect = New SqlCommand( "Select * From Titles", conPubs )
    conPubs.Open()
    dgrdTitles.DataSource = cmdSelect.ExecuteReader()
    dgrdTitles.DataBind()
    conPubs.Close()
  End If
End Sub

Sub dgrdTitles_ItemCommand( s As Object, e As DataGridCommandEventArgs )
  If e.CommandName="select" Then
    e.Item.BackColor = System.Drawing.Color.LightGreen
    e.Item.Font.Bold = True
  Else
    e.Item.BackColor = System.Drawing.Color.White
    e.Item.Font.Bold = False
  End If
End Sub

</Script>

<html>
<head><title>DataGridButtonColumn.aspx</title></head>
<body>
<form Runat="Server">

<asp:DataGrid
  ID="dgrdTitles"
  OnItemCommand="dgrdTitles_ItemCommand"
  AutoGenerateColumns="False"
  CellPadding="10"
  Runat="Server">
<Columns>
  <asp:BoundColumn
    HeaderText="Book Titles"
    DataField="Title" />

  <asp:ButtonColumn
    CommandName="select"
    Text="Select!" />

  <asp:ButtonColumn
    CommandName="unselect"
    Text="UnSelect!" />

</Columns>
</asp:DataGrid>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 11.12. Using a ButtonColumn.

When you click the Select! button displayed by the first ButtonColumn, the subroutine associated with the DataGrid control's OnItemCommand property is executed. This subroutine, named dgrdTitles_ItemCommand, displays the background of the selected row in light green with bold text.

When you click the UnSelect! button displayed by the second ButtonColumn, the item reverts to a white background and normal font weight. The dgrdTitles_ItemCommand subroutine distinguishes between the Select! and UnSelect! buttons with the help of the value of the buttons' CommandName properties.

A ButtonColumn supports the following properties:

  • ButtonType— The type of button to display (possible values are LinkButton and PushButton)

  • CommandName— A string that enables you to differentiate between events raised by different ButtonColumns

  • DataTextField— A field from the DataGrid control's data source displayed for the label of the button

  • DataTextFormatString— A format string that formats the value of the text displayed on the button

  • FooterText— Text displayed at the bottom of the column

  • HeaderImageUrl— The URL of an image to display at the top of the column

  • HeaderText— Text displayed at the top of the column

  • Text— Text displayed for the label of the button

Adding an EditCommandColumn to a DataGrid Control

An EditCommandColumn displays the most common editing commands (see Figure 11.13). You can use it to display Edit, Update, and Cancel buttons. It doesn't actually perform any editing, it just displays the user interface for editing a row.

Figure 11.13. Using an EditCommandColumn.

The DataGrid in Listing 11.15, for example, includes an EditCommandColumn.

Listing 11.15 DataGridEditCommandColumn.aspx
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
  If Not IsPostBack Then
    BindDataGrid
  End If
End Sub

Sub BindDataGrid
  Dim conPubs As SqlConnection
  Dim cmdSelect As SqlCommand

  conPubs = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Pubs" )
  cmdSelect = New SqlCommand( "Select * From Titles", conPubs )
  conPubs.Open()
  dgrdTitles.DataSource = cmdSelect.ExecuteReader()
  dgrdTitles.DataBind()
  conPubs.Close()
End Sub

Sub dgrdTitles_EditCommand( s As Object, e As DataGridCommandEventArgs )
  dgrdTitles.EditItemIndex = e.Item.ItemIndex
  BindDataGrid
End Sub

</Script>

<html>
<head><title>DataGridEditCommandColumn.aspx</title></head>
<body>
<form Runat="Server">

<asp:DataGrid
  ID="dgrdTitles"
  OnEditCommand="dgrdTitles_EditCommand"
  AutoGenerateColumns="False"
  CellPadding="10"
  Runat="Server">
<Columns>
  <asp:BoundColumn
    HeaderText="Book Titles"
    DataField="Title" />

  <asp:EditCommandColumn
    EditText="Edit!"
    UpdateText="Update!"
    CancelText="Cancel!" />

</Columns>
</asp:DataGrid>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Exactly what an EditCommandColumn displays depends on the current value of the DataGrid control's EditItemIndex property, which selects a DataGrid item for editing.

When an item is not selected for editing, the EditCommandColumn displays an Edit command. When an item is selected for editing, the EditCommandColumn displays Update and Cancel commands.

You can select an item for editing by clicking the Edit command displayed by an EditCommandColumn. In Listing 11.15, when you click the Edit command link, the selectEdit subroutine executes and assigns the index number of the selected item to the EditItemIndex property. This results in the item being selected for editing.

The EditCommandColumn supports the following properties:

  • ButtonType— The type of link or pushbutton to be displayed for the command buttons (possible values are LinkButton and PushButton)

  • CancelText— Text displayed by the Cancel button

  • EditText— Text displayed by the Edit button

  • FooterText— Text displayed at the bottom of the column

  • HeaderImageUrl— The URL of an image to display at the top of the column

  • HeaderText— Text displayed at the top of the column

  • UpdateText— Text displayed by the Update button

Using Styles with a DataGrid

The DataGrid control supports style objects that you can use to customize its appearance. Each style object is an instance of the TableItemStyle class, which has the following properties:

  • BackColor— The background color displayed for an item

  • BorderColor— The border color displayed for an item

  • BorderStyle— The style used for borders around cells (possible values are Dashed, Dotted, Double, Groove, Inset, None, NotSet, Outset, Ridge, and Solid)

  • BorderWidth— The width of the border

  • CssClass— The Cascading Style Sheet class associated with the item

  • Font— An instance of the FontInfo class, which indicates how the font should be formatted

  • ForeColor— The foreground color used for text

  • Height— The height of the item

  • HorizontalAlign— The horizontal alignment of the item (possible values are Center, Justify, Left, NotSet, and Right)

  • VerticalAlign— The vertical alignment of the item (possible values are Bottom, Middle, NotSet, and Top)

  • Width— The width of the item

  • Wrap— If True, the contents of an item are word-wrapped (the default value is True)

You can use any of these style properties with the AlternatingItemStyle, EditItemStyle, FooterStyle, HeaderStyle, ItemStyle, and SelectedItemStyle styles of the DataGrid control. When you set a style property while declaring a control, you use a hyphen (-) instead of the normal period (.) to indicate a property of an object.

The page in Listing 11.16, for example, displays the header with a bold, Arial font and a yellow background. Every item is displayed with a 10-point Arial font. Alternate items are displayed with an Alice Blue background (see Figure 11.14).

Listing 11.16 DataGridStyles.aspx
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
  If Not isPostBack Then
    Dim conPubs As SqlConnection
    Dim cmdSelect As SqlCommand

    conPubs = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Pubs" )
    cmdSelect = New SqlCommand( "Select * From Titles", conPubs )
    conPubs.Open()
    dgrdTitles.DataSource = cmdSelect.ExecuteReader()
    dgrdTitles.DataBind()
    conPubs.Close()
  End If
End Sub

</Script>

<html>
<head><title>DataGridStyles.aspx</title></head>
<body>
<form Runat="Server">

<asp:DataGrid
  ID="dgrdTitles"
  CellPadding="10"
  HeaderStyle-Font-Name="Arial"
  HeaderStyle-Font-Bold="True"
  HeaderStyle-BackColor="lightyellow"
  ItemStyle-Font-Name="Arial"
  ItemStyle-Font-Size="10pt"
  AlternatingItemStyle-BackColor="AliceBlue"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 11.14. Using styles with a DataGrid.

In Listing 11.16, the style objects modify every item displayed by a DataGrid. You also can apply style objects to individual columns in a DataGrid by using different style objects with different columns. For example, the following DataGrid has different style objects applied to its two BoundColumns:

<asp:DataGrid
  AutoGenerateColumns="False"
  CellPadding="10"
  Runat="Server" >
<Columns>
<asp:BoundColumn
  DataField="Title"
  HeaderText="Book Titles"
  HeaderStyle-Font-Name="Script"
  />
<asp:BoundColumn
  DataField="Price"
  HeaderText="Book Prices"
  HeaderStyle-Font-Name="Verdana"
  />
</Columns>
</asp:DataGrid>

When this DataGrid is displayed, the header text for Book Titles appears with a different font than the header text for Book Prices.

Finally, you can apply style properties directly to a DataGrid. For example, if you need to change the appearance of the border of a DataGrid, you can set the BorderStyle property directly on the DataGrid like this:

<asp:DataGrid
  BorderStyle="Dashed"
  BorderWidth="8px"
  BorderColor="Blue"
  Runat="Server" />
</asp:DataGrid>

This DataGrid appears with a blue dashed border that is 8 pixels wide.

The Font properties used with the Style object can be a little confusing at first because they are actually properties of the FontInfo class. (The TableItemStyle object has a property named Font that represents the FontInfo class.) The FontInfo class has the following properties:

  • Bold— When True, the font is displayed in bold

  • Italic— When True, the font is displayed in italics

  • Name— The name of the typeface to use for the font

  • Names— A comma-separated list of typeface names to use for the font

  • Overline— If True, a line appears above the font

  • Size— The size of the font

  • Strikeout— If True, a line appears through the font

  • Underline— If True, a line appears below the font

Sorting Columns in a DataGrid Control

The DataGrid control has built-in support for sorting columns. You can enable sorting for all the columns in a DataGrid or enable sorting for only particular columns.

To enable sorting for all the columns in a DataGrid, set the AllowSorting property to True and associate a subroutine with the SortCommand event. The page in Listing 11.17 illustrates how you can do so (see Figure 11.15).

Listing 11.17 DataGridSort.aspx
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
  If Not IsPostBack Then
    BindDataGrid( "Title" )
  End If
End Sub

Sub BindDataGrid( strSortField As String )
  Dim conPubs As SqlConnection
  Dim cmdSelect As SqlCommand

  conPubs = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Pubs" )
  cmdSelect = New SqlCommand( "Select * From Titles Order By " & strSortField, conPubs )
  conPubs.Open()
  dgrdTitles.DataSource = cmdSelect.ExecuteReader()
  dgrdTitles.DataBind()
  conPubs.Close()
End Sub

Sub dgrdTitles_SortCommand( s As Object, e As DataGridSortCommandEventArgs )
  BindDataGrid( e.SortExpression )
End Sub

</Script>

<html>
<head><title>DataGridSort.aspx</title></head>
<body>
<form Runat="Server">

<asp:DataGrid
  ID="dgrdTitles"
  AllowSorting="True"
  OnSortCommand="dgrdTitles_SortCommand"
  CellPadding="10"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 11.15. Sorting columns in a DataGrid.

Because the AllowSorting property is enabled for the DataGrid in Listing 11.17, the header text for all the columns appears as hypertext links. When you click a link, the SortCommand event is raised. This event is associated with the dgrdTitles_SortCommand subroutine by the DataGrid control's OnSortCommand property.

The dgrdTitles_SortCommand subroutine retrieves the name of the column selected for sorting and passes it to the BindDataGrid subroutine. The actual sorting is performed in the BindDataGrid subroutine. The records are sorted with the help of a SQL Order By clause.

You can enable and disable sorting for particular columns by using the SortExpression property. Sorting on certain columns, such as ProductID, might not make any sense. For example, the DataGrid in Listing 11.18 enables you to sort the first two columns, but not the last (see Figure 11.16).

Listing 11.18 DataGridSortExpression.aspx
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
  If Not isPostBack Then
    BindDataGrid( "Title" )
  End If
End Sub

Sub BindDataGrid( strSortField As String )
  Dim conPubs As SqlConnection
  Dim cmdSelect As SqlCommand

  conPubs = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Pubs" )
  cmdSelect = New SqlCommand( "Select * From Titles Order By " & strSortField, conPubs )
  conPubs.Open()
  dgrdTitles.DataSource = cmdSelect.ExecuteReader()
  dgrdTitles.DataBind()
  conPubs.Close()
End Sub

Sub dgrdTitles_SortCommand( s As Object, e As DataGridSortCommandEventArgs )
  BindDataGrid( e.SortExpression )
End Sub

</Script>

<html>
<head><title>DataGridSortExpression.aspx</title></head>
<body>
<form Runat="Server">

<asp:DataGrid
  ID="dgrdTitles"
  AllowSorting="True"
  OnSortCommand="dgrdTitles_SortCommand"
  AutoGenerateColumns="False"
  CellPadding="10"
  Runat="Server">
<Columns>
  <asp:BoundColumn
    DataField="Title"
    HeaderText="Sort Titles"
    SortExpression="title"/>
  <asp:BoundColumn
    HeaderText="Sort Price"
    DataField="price"
    DataFormatString="{0:c}"
    SortExpression="price"/>
  <asp:BoundColumn
    DataField="Notes" />
</Columns>
</asp:DataGrid>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 11.16. Sorting only certain columns in a DataGrid.

The first two BoundColumns have their SortExpression properties set. The first BoundColumn sorts the title database field, and the second BoundColumn sorts the price database field. Because no SortExpression appears for the BoundColumn named Notes, you cannot sort on this field.

Paging Through Records in a DataGrid

The DataGrid control has built-in support for paging through the records of a data source. Imagine, for example, that you want to display a product list that contains hundreds of records. You would not want to display all these records on a page at once. Instead, you would want to divide the records into multiple logical pages.

You enable paging for a DataGrid by enabling the AllowPaging property and creating a subroutine to change the current page.

There is one other important requirement for enabling paging. You can enable paging only if a DataGrid control's data source implements the ICollection interface. A DataReader doesn't implement this interface, so you must use a DataTable instead.

NOTE

DataTables and DataSets are discussed in the next chapter, Chapter 12, "Working with DataSets."

The page in Listing 11.19 illustrates how you can add paging functionality to a DataGrid control (see Figure 11.17).

Listing 11.19 DataGridPaging.aspx
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Data" %>

<Script Runat="Server">

Sub Page_Load
  If Not isPostBack Then
    BindDataGrid
  End If
End Sub

Sub BindDataGrid
  Dim conPubs As SqlConnection
  Dim dadTitles As SqlDataAdapter
  Dim dstTitles As DataSet

  conPubs = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Pubs" )
  dadTitles = New SqlDataAdapter( "Select * From Titles Order By Title", conPubs )
  dstTitles = New DataSet
  dadTitles.Fill( dstTitles )

  dgrdTitles.DataSource = dstTitles
  dgrdTitles.DataBind()
End Sub

Sub dgrdTitles_PageIndexChanged( s As Object, e As DataGridPageChangedEventArgs )
  dgrdTitles.CurrentPageIndex = e.NewPageIndex
  BindDataGrid
End Sub

</Script>

<html>
<head><title>DataGridPaging.aspx</title></head>
<body>
<form Runat="Server">

<asp:DataGrid
  ID="dgrdTitles"
  AllowPaging="True"
  PageSize="5"
  OnPageIndexChanged="dgrdTitles_PageIndexChanged"
  CellPadding="3"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 11.17. Enabling paging for a DataGrid.

The DataGrid in Listing 11.19 displays five records at a time from the Titles database table. By clicking the < and > links displayed at the bottom of the DataGrid, you can move one page forward or backward.

The number of records displayed in a page is determined by the DataGrid control's PageSize property. By default, this property has the value 10. It is set to 5 in the listing to display fewer records per page.

A new page is selected with the dgrdTitles_PageIndexChanged subroutine. This subroutine assigns the value of the NewPageIndex to the DataGrid control's CurrentPageIndex. The subroutine then rebinds the DataGrid to the data source, displaying the new page of records.

You can control the appearance of the page links by modifying the properties of the DataGridPagerStyle style object. This style object supports all the properties of the TableItemStyle style object in addition to the following properties:

  • Mode— Specifies how the links for navigating to different pages appear (possible values are NextPrev and NumericPages)

  • NextPageText— Contains text that appears on the next page link

  • PageButtonCount— Sets the number of page numbers to display before using ellipsis (the default value is 10)

  • Position— Indicates where the page navigation links appear (possible values are Bottom, Top, and TopAndBottom)

  • PrevPageText— Contains text that appears on the previous page link

The DataGridPagerStyle object is exposed in a DataGrid through the PagerStyle property. For example, the following DataGrid displays page navigation links, using a list of numbers for each page, a coral background, and a bold Verdana font (see Figure 11.18):

<asp:DataGrid
  id="dgrdTitles"
  AllowPaging="True"
  PageSize="5"
  OnPageIndexChanged="dgrdTitles_PageIndexChanged"
  CellPadding="3"
  PagerStyle-Mode="NumericPages"
  PagerStyle-Font-Name="Verdana"
  PagerStyle-Font-Bold="True"
  PagerStyle-BackColor="Coral"
  Runat="Server" />
Figure 11.18. Specifying PagerStyle properties.

Implementing Custom Paging

One disadvantage of using the DataGrid control's built-in paging support is performance. When you use the method of paging through records discussed in the preceding section, all the records must be retrieved from the data source every time you navigate to a new page. So, if you are paging through a database table with 2 million records, those 2 million records must be retrieved into memory every time you move to a new page.

In this section, you learn how to implement a custom paging solution to get around this limitation. Instead of retrieving every record to display each page, you retrieve only the records you need.

The custom paging solution you examine here makes a strong assumption about the contents of the database table with which it is used. This approach works only with database tables that have a column that uniquely indexes each database row. For example, it works with a table that has an identity column when the identity column is not missing any values.

CAUTION

If certain values are missing from the identity column, the DataGrid displays fewer records for some pages than others.

This custom paging approach is implemented in Listing 11.20.

Listing 11.20 DataGridCustomPaging.aspx
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Data" %>

<Script Runat="Server">

Dim conNorthwind As SqlConnection
Dim strSelect As String
Dim intStartIndex As Integer
Dim intEndIndex As Integer

Sub Page_Load
  Dim cmdSelect As SqlCommand

  conNorthwind = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Northwind" )
  If Not IsPostBack Then
    ' Get Total Pages
    strSelect = "Select Count(*) From Products"
    cmdSelect = New SqlCommand( strSelect, conNorthwind )
    conNorthwind.Open()
    dgrdProducts.VirtualItemCount = ( cmdSelect.ExecuteScalar() / dgrdProducts.PageSize )
    conNorthwind.Close()
    BindDataGrid
  End If
End Sub

Sub BindDataGrid
  Dim dadProducts As SqlDataAdapter
  Dim dstProducts As DataSet

  intEndIndex = intStartIndex + dgrdProducts.PageSize
  strSelect = "Select * From Products Where ProductID > @startIndex " _
    & "And ProductID <= @endIndex Order By ProductID"
  dadProducts = New SqlDataAdapter( strSelect, conNorthwind )
  dadProducts.SelectCommand.Parameters.Add( "@startIndex", intStartIndex )
  dadProducts.SelectCommand.Parameters.Add( "@endIndex", intEndIndex )
  dstProducts = New DataSet
  dadProducts.Fill( dstProducts )

  dgrdProducts.DataSource = dstProducts
  dgrdProducts.DataBind()
End Sub

Sub dgrdProducts_PageIndexChanged( s As Object, e As DataGridPageChangedEventArgs )
  intStartIndex = ( e.NewPageIndex * dgrdProducts.PageSize )
  dgrdProducts.CurrentPageIndex = e.NewPageIndex
  BindDataGrid
End Sub

</Script>

<html>
<head><title>DataGridCustomPaging.aspx</title></head>
<body>
<form Runat="Server">

<asp:DataGrid
  ID="dgrdProducts"
  AllowPaging="True"
  AllowCustomPaging="True"
  PageSize="3"
  OnPageIndexChanged="dgrdProducts_PageIndexChanged"
  PagerStyle-Mode="NumericPages"
  CellPadding="3"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

The page in Listing 11.20 enables you to page through the records in the Products database table three records at a time. (You can change the number of records displayed at a time by changing the value of the PageSize property.)

A count of the total number of records in the Products table is retrieved in the Page_Load subroutine. This value is assigned to the VirtualItemCount property of the DataGrid. The DataGrid control uses this property when rendering the paging links.

When you click a page number link, the dgrdProducts_PageIndexChanged subroutine is executed. This subroutine calculates the starting index of the record to display by multiplying the index of the selected page by the page size. The correct set of records is retrieved from the Products database table with this SQL statement:

Select * From Products Where ProductID > @startIndex
  And ProductID <= @endIndex Order By ProductID"

This Select statement retrieves all the records that have a ProductID greater than the startIndex and less than or equal to the endIndex. The endIndex is calculated by adding the startIndex to the value of the PageSize property of the DataGrid.

The advantage of this approach to paging through records is that the only records retrieved from the database are the records displayed in the DataGrid. In other words, this approach works great with huge database tables.

Selecting Rows in a DataGrid Control

You can select a row in a DataGrid by modifying the value of the control's SelectedIndex property. This capability is useful, for example, when you want to display a menu of choices or create a master/detail form with a DataGrid.

The page in Listing 11.21 contains two DataGrid controls. The first DataGrid lists categories and category descriptions, and the second DataGrid lists product information. When you select a category from the first DataGrid, the products in that category are displayed in the second DataGrid (see Figure 11.19).

Listing 11.21 DataGridMasterDetail.aspx
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
  If Not IsPostBack Then
    BindMasterGrid
  End If
End Sub

Sub BindMasterGrid
  Dim conNorthwind As SqlConnection
  Dim cmdSelect As SqlCommand

  conNorthwind = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Northwind" )
  cmdSelect = New SqlCommand( "Select * From Categories", conNorthwind )
  conNorthwind.Open()
  dgrdCategories.DataSource = cmdSelect.ExecuteReader()
  dgrdCategories.DataBind()
  conNorthwind.Close()
End Sub

Sub BindDetailGrid( intCatID As Integer )
  Dim conNorthwind As SqlConnection
  Dim strSelect As String
  Dim cmdSelect As SqlCommand

  conNorthwind = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Northwind" )
  strSelect = "Select * From Products Where CategoryID=@catID"
  cmdSelect = New SqlCommand( strSelect, conNorthwind )
  cmdSelect.Parameters.Add( "@catID", intCatID )
  conNorthwind.Open()
  dgrdProducts.DataSource = cmdSelect.ExecuteReader()
  dgrdProducts.DataBind()
  conNorthwind.Close()
End Sub

Sub dgrdCategories_ItemCommand( s As Object, e As DataGridCommandEventArgs )
  Dim intCatID As Integer

  intCatID = dgrdCategories.DataKeys( e.Item.ItemIndex )
  dgrdCategories.SelectedIndex = e.Item.ItemIndex
  BindDetailGrid( intCatID )
End Sub

</Script>

<html>
<head><title>DataGridMasterDetail.aspx</title></head>
<body>
<form Runat="Server">

<asp:DataGrid
  ID="dgrdCategories"
  OnItemCommand="dgrdCategories_ItemCommand"
  DataKeyField="CategoryID"
  AutoGenerateColumns="False"
  SelectedItemStyle-BackColor="LightGreen"
  ShowHeader="False"
  Runat="Server">
<Columns>
  <asp:TemplateColumn>
  <ItemTemplate>
  <asp:LinkButton
    Text='<%# Container.DataItem( "CategoryName" ) %>'
    Runat="Server"/>
  </ItemTemplate>
  </asp:TemplateColumn>
  <asp:BoundColumn DataField="Description" />
</Columns>
</asp:DataGrid>

<p>

<asp:DataGrid
  ID="dgrdProducts"
  HeaderStyle-BackColor="yellow"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 11.19. Selecting DataGrid rows.

When you select a category in the first DataGrid, the dgrdCategories_ItemCommand subroutine is executed. This subroutine retrieves the primary key associated with the selected category from the DataKeys collection. The primary key is passed to the BindDetailGrid subroutine to display the correct products for the selected category.

Next, the subroutine assigns the index of the selected category to the SelectedIndex property of the DataGrid. The selected category appears with a light green background.

Editing Items in a DataGrid Control

You can edit items in a DataGrid by adding an EditCommandColumn to the DataGrid and associating subroutines with the DataGrid control's EditCommand, UpdateCommand, and CancelCommand events.

The page in Listing 11.22, for example, enables you to edit records in the Products database table (see Figure 11.20).

Listing 11.22 DataGridEditProducts.aspx
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Dim conNorthwind As SqlConnection
Dim cmdSql As SqlCommand
Dim strSql As String

Sub Page_Load
  conNorthwind = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Northwind" )
  If Not IsPostBack Then
    BindDataGrid
  End If
End Sub

Sub BindDataGrid
  cmdSql = New SqlCommand( "Select * From Products", conNorthwind )
  conNorthwind.Open()
  dgrdProducts.DataSource = cmdSql.ExecuteReader()
  dgrdProducts.DataBind()
  conNorthwind.Close()
End Sub

Sub dgrdProducts_EditCommand( s As Object, e As DataGridCommandEventArgs )
  dgrdProducts.EditItemIndex = e.Item.ItemIndex
  BindDataGrid
End Sub

Sub dgrdProducts_UpdateCommand( s As Object, e As DataGridCommandEventArgs )
  Dim intProductID As Integer
  Dim txtProductName As TextBox
  Dim txtUnitPrice As TextBox
  Dim strProductName As String
  Dim decUnitPrice As Decimal

  intProductID = dgrdProducts.DataKeys( e.Item.ItemIndex )
  txtProductName = e.Item.Cells( 1 ).Controls( 0 )
  txtUnitPrice = e.Item.Cells( 2 ).Controls( 0 )
  strProductName = txtProductName.Text
  decUnitPrice = txtUnitPrice.Text
  strSql = "Update Products Set ProductName=@ProductName, " _
   & "UnitPrice=@UnitPrice Where ProductID=@ProductID"
  cmdSql = New SqlCommand( strSql, conNorthwind )
  cmdSql.Parameters.Add( "@ProductName", strProductName )
  cmdSql.Parameters.Add( "@UnitPrice", decUnitPrice )
  cmdSql.Parameters.Add( "@ProductID", intProductID )
  conNorthwind.Open()
  cmdSql.ExecuteNonQuery()
  conNorthwind.Close()
  dgrdProducts.EditItemIndex = -1
  BindDataGrid
End Sub

Sub dgrdProducts_CancelCommand( s As Object, e As DataGridCommandEventArgs )
  dgrdProducts.EditItemIndex = -1
  BindDataGrid
End Sub

</Script>

<html>
<head><title>DataGridEditProducts.aspx</title></head>
<body>
<form Runat="Server">

<asp:DataGrid
  ID="dgrdProducts"
  OnEditCommand="dgrdProducts_EditCommand"
  OnUpdateCommand="dgrdProducts_UpdateCommand"
  OnCancelCommand="dgrdProducts_CancelCommand"
  DataKeyField="ProductID"
  AutoGenerateColumns="False"
  CellPadding="10"
  HeaderStyle-BackColor="Salmon"
  Runat="Server">
<Columns>
  <asp:BoundColumn
    HeaderText="Product ID"
    DataField="ProductID"
    ReadOnly="True" />
  <asp:BoundColumn
    HeaderText="Product Name"
    DataField="ProductName" />
  <asp:BoundColumn
    HeaderText="Price"
    DataField="UnitPrice"
    DataFormatString="{0:c}" />
  <asp:EditCommandColumn
    EditText="Edit!"
    UpdateText="Update!"
    CancelText="Cancel!" />
</Columns>
</asp:DataGrid>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 11.20. Editing data with a DataGrid.

When you select an item for editing, the dgrdProducts_EditCommand subroutine executes. This subroutine sets the value of the EditItemIndex property of the DataGrid. Any bound columns selected for editing automatically appear with TextBox controls.

When a product is selected for editing in Listing 11.22, the BoundColumns for the ProductName and UnitPrice appear with TextBox controls. The ProductID column is excluded from editing because its ReadOnly property is set to the value True.

When the Update button is clicked, the dgrdProducts_UpdateCommand subroutine executes. This subroutine retrieves the value of the ProductID associated with the row selected for editing by retrieving the value from the DataKeys collection.

The values of the TextBox controls named ProductName and UnitPrice are retrieved with the following lines:

txtProductName = e.Item.Cells( 1 ).Controls( 0 )
txtUnitPrice = e.Item.Cells( 2 ).Controls( 0 )
strProductName = txtProductName.Text
decUnitPrice = txtUnitPrice.Text

Each row of a DataGrid consists of cells, and each cell contains controls. The TextBox control named txtProductName is retrieved by grabbing the first control located in the second cell of the DataGrid. (The first cell contains the ProductID.) The TextBox control named txtUnitPrice is retrieved by grabbing the first control in the third cell.

After the TextBox controls named txtProductName and txtUnitPrice have been retrieved, the current values of these TextBox controls can be assigned to the strProductName and decUnitPrice variables.

After a product is updated in the database, the DataGrid control's EditItemIndex property is assigned the value -1. This value unselects any item for editing in the DataGrid.

Editing Items in a DataGrid Control with Templates

The approach followed in the preceding section for editing records in a DataGrid has a couple of significant limitations. These limitations are derived from the fact that you are using BoundColumns to automatically display TextBox controls to edit records.

The first limitation is that you are forced to use only TextBox controls. You can't, for example, enable a user to select a value from a DropDownList control.

Second, you cannot use Validation controls with the method of editing records described in the preceding section. For example, if you enter the string "apple" for the value of the TextBox control named txtUnitPrice, you receive an error message. And because you cannot use Validation controls, there's nothing preventing you from doing that.

In this section, you examine a second method of updating records in a DataGrid. Here, you use a TemplateColumn, which has both an ItemTemplate and an EditTemplate, to update records.

The page in Listing 11.23 demonstrates how you can use a TemplateColumn when updating records in a DataGrid (see Figure 11.21).

Listing 11.23 DataGridEditTemplate.aspx
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Dim conNorthwind As SqlConnection
Dim cmdSql As SqlCommand
Dim strSql As String

Sub Page_Load
  conNorthwind = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Northwind" )
  If Not IsPostBack Then
    BindDataGrid
  End If
End Sub

Sub BindDataGrid
  cmdSql = New SqlCommand( "Select * From Products", conNorthwind )
  conNorthwind.Open()
  dgrdProducts.DataSource = cmdSql.ExecuteReader()
  dgrdProducts.DataBind()
  conNorthwind.Close()
End Sub

Sub dgrdProducts_EditCommand( s As Object, e As DataGridCommandEventArgs )
  dgrdProducts.EditItemIndex = e.Item.ItemIndex
  BindDataGrid
End Sub

Sub dgrdProducts_UpdateCommand( s As Object, e As DataGridCommandEventArgs )
  Dim intProductID As Integer
  Dim txtUnitPrice As TextBox
  Dim decUnitPrice As Decimal

  If IsValid Then
    intProductID = dgrdProducts.DataKeys( e.Item.ItemIndex )
    txtUnitPrice = e.Item.FindControl( "txtUnitPrice" )
    decUnitPrice = txtUnitPrice.Text
    strSql = "Update Products Set UnitPrice=@UnitPrice " _
     & "Where ProductID=@ProductID"
    cmdSql = New SqlCommand( strSql, conNorthwind )
    cmdSql.Parameters.Add( "@UnitPrice", decUnitPrice )
    cmdSql.Parameters.Add( "@ProductID", intProductID )
    conNorthwind.Open()
    cmdSql.ExecuteNonQuery()
    conNorthwind.Close()
    dgrdProducts.EditItemIndex = -1
    BindDataGrid
  End If
End Sub

Sub dgrdProducts_CancelCommand( s As Object, e As DataGridCommandEventArgs )
  dgrdProducts.EditItemIndex = -1
  BindDataGrid
End Sub

</Script>

<html>
<head><title>DataGridEditTemplate.aspx</title></head>
<body>
<form Runat="Server">

<asp:DataGrid
  ID="dgrdProducts"
  OnEditCommand="dgrdProducts_EditCommand"
  OnUpdateCommand="dgrdProducts_Updatecommand"
  OnCancelCommand="dgrdProducts_CancelCommand"
  DataKeyField="ProductID"
  AutoGenerateColumns="False"
  CellPadding="10"
  HeaderStyle-BackColor="Salmon"
  Runat="Server">
<Columns>
  <asp:BoundColumn
    HeaderText="Product Name"
    DataField="ProductName"
    ReadOnly="True" />
  <asp:TemplateColumn>
  <HeaderTemplate>
    Price
  </HeaderTemplate>
  <ItemTemplate>
    <%# Container.DataItem( "UnitPrice" ) %>
  </ItemTemplate>
  <EditItemTemplate>
    <asp:TextBox
      ID="txtUnitPrice"
      Text='<%# Container.DataItem( "UnitPrice" )%>'
      Runat="Server" />
    <asp:RequiredFieldValidator
      ControlToValidate="txtUnitPrice"
      Display="Dynamic"
      Text="Required!"
      Runat="Server" />
    <asp:CompareValidator
      ControlToValidate="txtUnitPrice"
      Display="Dynamic"
      Text="Must be Currency!"
      Operator="DataTypeCheck"
      Type="Currency"
      Runat="Server" />
  </EditItemTemplate>
  </asp:TemplateColumn>
  <asp:EditCommandColumn
    EditText="Edit!"
    UpdateText="Update!"
    CancelText="Cancel!" />
</Columns>
</asp:DataGrid>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 11.21. Editing data with templates in a DataGrid.

The DataGrid in Listing 11.23 enables you to edit its UnitPrice column, which is displayed by a TemplateColumn. The EditItemTemplate contains two Validation controls that require you to enter a value for UnitPrice and enter a currency value as the value.

When the Update! link is clicked, the dgrdProducts_UpdateCommand subroutine executes and updates the value of the UnitPrice column in the underlying database table. Notice that the UnitPrice is retrieved from the EditItemTemplate with the help of the FindControl() method.


Summary

This chapter examined two of the most feature-rich controls included with the ASP.NET framework: the DataList and DataGrid controls. In the first section, you learned about the common properties of these controls, event bubbling, templates, and the DataKeys collection.

Next, you examined the DataList control in detail. You learned how to format a DataList with style objects, select items in a DataList, create a multicolumn DataList, and edit items in a DataList.

Finally, you learned about all the features of the DataGrid control. You learned how to format a DataGrid with style objects, use different DataGrid column types, sort items, page through records, select items, and edit items in a DataGrid.


Chapter 12. Working with DataSets

IN THIS CHAPTER

  • nderstanding DataSets

  • Understanding DataTables

  • Understanding DataViews

  • Using DataSets with ASP.NET Pages

In this chapter, you will learn how to represent database data in a disconnected, memory-resident database by taking advantage of ADO.NET DataSets.

The first section discusses how you can build DataSets. You will learn how to add the contents of existing database tables to a DataSet and bind DataSets to ASP.NET controls such as the DataGrid control.

Next, you will learn how to work with the tables contained in a DataSet and modify table and column properties. You also will examine how to define relationships between the tables in a DataSet such as parent/child relationships.

The chapter next tackles the topic of working with DataViews. A DataView enables you to filter, sort, and find rows in a table contained in a DataSet.

Finally, you will examine methods of using DataSets in your ASP.NET applications. You will learn how to improve the performance of your Web site by caching database data in DataSets and displaying the contents of the DataSets in your ASP.NET pages.


Understanding DataSets

In Chapter 9, "Introduction to ADO.NET," you learned how to use the Command and DataReader classes to modify and represent database data. In this chapter, you will examine a separate set of classes that also enables you to work with database data. Why two sets of classes?

The two sets of classes are useful in different applications. If you simply need to grab some database records and quickly display them on a Web page, you should use the Command and DataReader classes. If, on the other hand, you need to work with a disconnected and memory-resident representation of database records, you need to use DataSets.

In Chapter 9, for example, you learned how to use a DataReader to represent database records. However, the DataReader represents only one database record at a time. You must call the Read() method to fetch each new record from the underlying database table into memory. Each time you call Read() again, the previously fetched record is lost.

The DataReader, therefore, cannot be used to work with the results of a database query as a whole. For example, you cannot sort, or filter, or retrieve a count of the number of records in a DataReader because only one record from a database query is represented by a DataReader at a time.

Furthermore, for it to work, the DataReader must remain connected to a database table. A DataReader is tied down to its underlying data source. This means, for example, that you cannot cache a DataReader in your server's memory so that the same DataReader can be used on multiple pages or over multiple requests to the same page.

A DataSet, on the other hand, enables you to represent the results of a database query in your server's memory. Because a DataSet provides you with a memory-resident representation of data, you can work with the results of a database query as a whole. For example, a DataSet includes methods for sorting, filtering, and returning a count of the records from a database query.

ASP CLASSIC NOTE

If you're familiar with previous versions of ActiveX Data Objects (ADO), it might be helpful to think of a DataReader as a forward-only recordset. A DataSet, on the other hand, is similar to a disconnected, client-side, static recordset.

Unlike a DataReader, a DataSet represents a disconnected set of records. When a DataSet is populated with records, you can break the connection to its underlying data source. This means that you can easily cache a DataSet in your server's memory and achieve dramatic performance benefits.

When used wisely, DataSets can dramatically improve the performance of your Web applications. If you need to represent the same set of records over and over again in multiple pages or across multiple requests to the same page, you can represent the records in a DataSet and cache it in your server's memory. Caching the database records enables you to avoid connecting to your database server, which is a resource-intensive task.

When used badly, however, DataSets can hurt the performance of your Web site. DataSets can occupy a lot of memory. If you use a DataSet in an ASP.NET page to represent a table with 10,000 database records, then 10,000 records must be represented in memory. If 100 people request the page at the same time, 1 million records must be represented in your server's memory.

NOTE

DataSets also require more overhead to create and populate than DataReaders. This is true even though, behind the scenes, a DataSet actually uses a DataReader to fetch the records.

So, my general advice is that if you need to retrieve a different set of records whenever you request a page, use a DataReader. If you need to display the same set of records (or a filtered or sorted version of the same set of records) when you request a page, you should use a DataSet.

Elements of DataSets

When working with DataSets, you use the following ADO.NET classes:

  • DataAdapter

  • DataSet

  • DataTable

  • DataRelation

  • DataView

A DataSet can contain one or more DataTables that represent database tables. Relationships between the tables (such as parent/child relationships) can be defined using DataRelation classes. Finally, a DataView represents a particular filtered or sorted view of a DataTable.

The DataSet, DataTable, DataRelation, and DataView classes all live in the System.Data namespace. There's one more class that you need to use when working with DataSets: the DataAdapter.

The DataAdapter class represents the bridge between a DataSet and the data source it represents. You use a DataAdapter to populate a DataSet from an existing database table. You can also use a DataAdapter to update an existing database table with changes made to a DataSet.

When working with a Microsoft SQL Server (version 7.0 or higher) database, you use the SqlDataAdapter, which you can find in the System.Data.SqlClient namespace. When working with other databases, such as Oracle or Microsoft Access, you use the OleDbDataAdapter, which you can find in the System.Data.OleDb namespace.

Adding DataTables to a DataSet

You add the contents of existing database tables to a DataSet by using the DataAdapter. To add a database table to a DataSet, follow these steps:

  1. Create a DataSet.

  2. Create a database connection.

  3. Create a DataAdapter with a SQL Select statement and the database connection.

  4. Call the Fill method of the DataAdapter, passing the name of the DataSet and the name of the new DataTable.

The page in Listing 12.1, for example, illustrates how you can add the Authors database table from the Pubs database to a DataSet.

Listing 12.1 SqlDataAdapter.aspx
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%
Dim dstAuthors As DataSet
Dim conPubs As SqlConnection
Dim dadAuthors As SqlDataAdapter

dstAuthors = New DataSet()
conPubs = New SqlConnection( "Server=localhost;Database=Pubs;UID=sa;PWD=secret" )
dadAuthors = New SqlDataAdapter( "Select * From Authors", conPubs )
dadAuthors.Fill( dstAuthors, "Authors" )
%>
Authors table added to DataSet!

The C# version of this code can be found on the CD-ROM.

When the Fill method is called in Listing 12.1, a new DataTable is added to the DataSet named Authors. Notice that a connection to a database is never opened. The Fill method automatically opens a database connection when it is not already opened and then automatically closes the connection that it opens.

NOTE

If a database connection is already opened when you call the Fill method, the Fill method does not close the connection. This allows you to avoid opening and closing a database connection multiple times (a resource-intensive task) when you need to reuse the same database connection.

The page in Listing 12.1 works only with a Microsoft SQL Server (version 7.0 and higher) database. It uses the SqlConnection and SqlDataAdapter classes from the System.Data.SqlClient namespace. If you want to work with another type of database, such as Microsoft Access or Oracle, you must use the classes from the System.Data.OleDb namespace.

The page in Listing 12.2 illustrates how you can add a DataTable from a Microsoft Access database to a DataSet.

Listing 12.2 OleDbDataAdapter.aspx
[View full width]
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.OleDb" %>

<%
Dim dstAuthors As DataSet
Dim conAuthors As OleDbConnection
Dim dadAuthors As OleDbDataAdapter

dstAuthors = New DataSet()
conAuthors = New OleDbConnection( "PROVIDER=Microsoft.Jet.OLEDB.4.0;DATA Source=c:\Authors
  .mdb" )
dadAuthors = New OleDbDataAdapter( "Select * From Authors", conAuthors )
dadAuthors.Fill( dstAuthors, "Authors" )
%>
Authors Table added to DataSet!

The C# version of this code can be found on the CD-ROM.

In Listing 12.2, the classes from the System.Data.OleDb namespace are used. Instances of the OleDbConnection and OleDbAdapter classes are created.

There's nothing wrong with adding multiple DataTables to the same DataSet. In fact, you often need to do so when creating relationships between DataTables, such as parent/child relationships.

The page in Listing 12.3, for example, demonstrates how you add both Categories and Products tables to the same DataSet.

Listing 12.3 DataSetDoubleFill.aspx
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<%
Dim dstNorthwind As DataSet
Dim conNorthwind As SqlConnection
Dim dadNorthwind As SqlDataAdapter

' Create the DataSet
dstNorthwind = New DataSet()

' Open database connection
conNorthwind = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Northwind" )
conNorthwind.Open()

' Add two tables
dadNorthwind = New SqlDataAdapter( "Select * From Categories", conNorthwind )
dadNorthwind.Fill( dstNorthwind, "Categories" )
dadNorthwind.SelectCommand = New SqlCommand( "Select * From Products", conNorthwind )
dadNorthwind.Fill( dstNorthwind, "Products" )

' Close the connection
conNorthwind.Close()
%>
Categories and Products tables added to DataSet!

The C# version of this code can be found on the CD-ROM.

In Listing 12.3, the same SqlDataAdapter adds both the Categories and Products tables to the same DataSet. A new SQL Select statement is assigned to the DataAdapter class's SelectCommand property to retrieve the second table, the Products table, into the DataSet.

Notice that we explicitly open the connection in Listing 12.3. This prevents the Fill() method from opening and closing the connection each time the method is called.

If necessary, you can even add DataTables to a DataSet from different data sources. For example, you can populate a DataSet with both a Microsoft SQL Server database table and a Microsoft Access database table. Or, as you see in the next chapter, "Working with XML," you can add a DataTable that represents an XML file.

Binding Controls to a DataSet

So, you might be thinking, these DataSets are all very nice, but how do I use them in an ASP.NET page? You can use a DataSet with the Repeater, DataList, and DataGrid controls in almost exactly the same way as you would use these controls with a DataReader.

The page in Listing 12.4, for example, displays the contents of the Products database table in a DataGrid control (see Figure 12.1).

Listing 12.4 DataGrid.aspx
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
  Dim dstProducts As DataSet
  Dim conNorthwind As SqlConnection
  Dim dadProducts As SqlDataAdapter

  ' Create the DataSet
  dstProducts = New DataSet()
  conNorthwind = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Northwind" )
  dadProducts = New SqlDataAdapter( "Select * From Products", conNorthwind )
  dadProducts.Fill( dstProducts, "Products" )

  ' Bind dataset to DataGrid
  dgrdProducts.DataSource = dstProducts
  dgrdProducts.DataBind()
End Sub
</Script>

<html>
<head><title>DataGrid.aspx</title></head>
<body>

<asp:DataGrid
  ID="dgrdProducts"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 12.1. Displaying the contents of a DataSet.

The DataGrid control is bound to the Products database table with the following two lines of code:

dgrdProducts.DataSource = dstProducts
dgrdProducts.DataBind()

The first statement assigns the first table in the DataSet to the DataGrid control's DataSource property. The second statement actually binds the data from the DataSet to the DataGrid control, populating the DataGrid with the items from the DataSet.

If a DataSet contains multiple DataTables, you can indicate which of the DataTables to use with the DataGrid by using the DataMember property of the DataGrid control. For example, imagine that the DataSet contains both Categories and Products database tables. In that case, you can bind the Products database table to the DataGrid like this:

dgrdProducts.DataSource = dstProducts
dgrdProducts.DataMember = "Products"
dgrdProducts.DataBind()


Understanding DataTables

A DataTable is a memory-resident representation of a database table in a DataSet. In the following sections, you will learn how to work with the properties and methods of a DataTable. For example, you will learn how to retrieve key and schema information for a DataTable, create relationships between multiple DataTables in a DataSet, and update the rows in a DataTable.

Creating DataTables Programmatically

Typically, you create a DataTable from an existing database table. However, you also have the option of programmatically creating a DataTable and adding it to a DataSet. You might want to do so, for example, if you want to create a purely memory-resident shopping cart and take advantage of a DataTable's rich set of properties and methods when working with the shopping cart.

A DataTable has a collection of rows, represented by its Rows property, and a collection of columns, represented by its Columns property. You can build a DataTable programmatically and populate it with records by adding DataColumns to the columns collection and adding DataRows to the rows collection.

In Listing 12.5, for example, a new DataTable is created with four columns: ProductID, ProductName, ProductCategory, and ProductPrice. Next, two rows are added to the DataTable. The contents of the DataTable are then displayed.

Listing 12.5 CreateDataTable.aspx
<%@ Import Namespace="System.Data" %>
<%
Dim dtblCart As DataTable
Dim dcolColumn As DataColumn
Dim drowItem As DataRow

' Create DataTable
dtblCart = New DataTable( "Cart" )

' Create Columns
dcolColumn = New DataColumn( "ProductID", GetType( Int32 ) )
dtblCart.Columns.Add( dcolColumn )
dcolColumn = New DataColumn( "ProductName", GetType( String ) )
dtblCart.Columns.Add( dcolColumn )
dcolColumn = New DataColumn( "ProductCategory", GetType( String ) )
dtblCart.Columns.Add( dcolColumn )
dcolColumn = New DataColumn( "ProductPrice", GetType( Decimal ) )
dtblCart.Columns.Add( dcolColumn )

' Add Rows
drowItem = dtblCart.NewRow()
drowItem( "ProductID" ) = 12
drowItem( "ProductName" ) = "Anchor Steam"
drowItem( "ProductCategory" ) = "Beer"
drowItem( "ProductPrice" ) = 7.90
dtblCart.Rows.Add( drowItem )

drowItem = dtblCart.NewRow()
drowItem( "ProductID" ) = 13
drowItem( "ProductName" ) = "Samuel Adams"
drowItem( "ProductCategory" ) = "Beer"
drowItem( "ProductPrice" ) = 8.67
dtblCart.Rows.Add( drowItem )

' Display DataTable
For each drowItem in dtblCart.Rows
  For each dcolColumn in dtblCart.Columns
    Response.Write( drowItem( dcolColumn ) )
  Next
Next
%>

The C# version of this code can be found on the CD-ROM.

In Listing 12.5, an instance of the DataTable class—named dtblCart—is created. Next, four columns are added to the DataTable by adding DataColumns to the DataTable's columns collection. Each column is created with a name and data type.

Next, two rows are added to the DataTable. Calling the DataTable's NewRow() method returns a new row with the correct column schema for the DataTable. After a value has been assigned to each column, the row is added to the DataTable's rows collection.

Finally, the contents of the DataTable are displayed by looping through the rows and columns collections of the DataTable. The value of each column for each row is displayed.

Setting DataTable Properties

You can set the following four properties of a DataTable:

  • CaseSensitive— Determines whether comparisons when searching, sorting, or filtering records are case sensitive (the default value is False)

  • MinimumCapacity— Specifies the initial number of rows that the DataTable accepts (the default value is 25)

  • PrimaryKey— Specifies the array of columns to use as the primary keys for the DataTable

  • TableName— Specifies the name of the DataTable

If you want to perform case-sensitive comparisons in a DataTable, you can modify the DataTable's CaseSensitive property. For example, you might want to perform a case-sensitive comparison of a password that a user enters into a form against the rows of a DataTable.

The MinimumCapacity property indicates the initial number of rows to create for the DataTable. If you are copying a large database table into a DataSet, you can improve the performance of the operation by assigning a higher value to this property.

Finally, the PrimaryKey property enables you to specify one or more primary keys for a DataTable. Creating a primary key for a table is useful, for example, when you're defining relationships between DataTables in a DataSet.

The page in Listing 12.6 illustrates how to use the CaseSensitive, MinimumCapacity, and PrimaryKey properties.

Listing 12.6 DataTableProperties.aspx
<%@ Import Namespace="System.Data" %>

<Script Runat="Server">

Sub Page_Load
  Dim dtblPasswords As DataTable
  Dim dcolColumn As DataColumn
  Dim arrColumns( 1 ) As DataColumn
  Dim drowRow As DataRow
  Dim dvwPasswords As DataView
  Dim intCounter As Integer

  ' Create the DataTable
  dtblPasswords = New DataTable( "passwords" )
  dtblPasswords.MinimumCapacity = 200
  dtblPasswords.CaseSensitive = True

  ' Add the Password Column as a Primary Key
  dcolColumn = New DataColumn( "password", GetType( String ) )
  dtblPasswords.Columns.Add( dcolColumn )
  arrColumns( 0 ) = dcolColumn
  dtblPasswords.PrimaryKey = arrColumns

  ' Add some rows
  For intCounter = 1 to 100
    drowRow = dtblPasswords.NewRow()
    drowRow( "password" ) = "SECRET" & intCounter.ToString()
    dtblPasswords.Rows.Add( drowRow )
    drowRow = dtblPasswords.NewRow()
    drowRow( "password" ) = "secret" & intCounter.ToString()
    dtblPasswords.Rows.Add( drowRow )
  Next

  ' Bind DataTable to DataGrid
  dvwPasswords = dtblPasswords.DefaultView
  dvwPasswords.RowFilter = "password='secret1'"
  dgrdPasswords.DataSource = dvwPasswords
  dgrdPasswords.DataBind()
End Sub

</Script>

<html>
<head><title>DataTableProperties.aspx</title></head>
<body>

<asp:DataGrid
  ID="dgrdPasswords"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

In the Page_Load subroutine contained in Listing 12.6, a DataTable is created with the name dtblPasswords. This DataTable is case sensitive and has a minimum capacity of 200 rows. Next, a single column named password is added to the DataTable. The password column is marked as the primary key column for the DataTable.

The process of specifying a primary key column can be a little confusing. Because you might need to specify multiple columns for a table's primary key (a first and last name), you must assign an array of columns instead of a single column to the PrimaryKey property. In Listing 12.6, an array of DataColumns named arrColumns is assigned to the PrimaryKey property.

After the DataTable has been created, 200 rows are added to the table. For each iteration of the counter loop, uppercase and lowercase versions of the same password are added. So the passwords SECRET1, secret1, SECRET2, secret2, and so on are added to the DataTable.

Finally, a DataView filters out any row that does not contain the password secret1. (You will learn about DataViews later in this chapter in the section titled "Understanding DataViews"). Because the DataTable is case sensitive, only the row that contains the password secret1, not the row that contains SECRET1, is retrieved and displayed.

Setting Column Properties in a DataTable

A DataColumn supports several useful properties. You can use these properties both when populating a DataSet with existing tables and when creating DataTables programmatically.

Following is a list of some of the more useful DataColumn properties:

  • AllowDBNull— Enables or disables Null values in the column (the default value is True)

  • AutoIncrement— Creates a column that increments its values automatically

  • AutoIncrementSeed— A Long value that represents the initial value for an autoincrement column

  • AutoIncrementStep— A Long value that represents the increment used by an autoincrement column

  • ColumnName— Specifies the name of the column

  • DataType— Specifies the .NET data type of the column

  • DefaultValue— Specifies the default value assigned to the column when no value is specified

  • Expression— Creates calculated or aggregate columns

  • MaxLength— Specifies the maximum length of a text column

  • ReadOnly— A Boolean value that indicates whether the column allows updates

  • Unique— Requires all values in the column to be unique (the default value is False)

You examine how to use several of these properties in the following sections.

Assigning Default Values to Columns

You can use the DefaultValue property to assign a default value to a DataColumn. For example, in the page in Listing 12.7, a default value is assigned to the Price column in the Title database table (see Figure 12.2).

Listing 12.7 DefaultValue.aspx
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
  Dim dstTitles As DataSet
  Dim conPubs As SqlConnection
  Dim dadTitles As SqlDataAdapter
  Dim dtblTitles As DataTable
  Dim dcolPrice As DataColumn
  Dim drowTitle As DataRow

  ' Grab Titles Table
  dstTitles = New DataSet()
  conPubs = New SqlConnection( "Server=localhost;Database=Pubs;UID=sa;PWD=secret" )
  dadTitles = New SqlDataAdapter( "Select Title, Price From Titles", conPubs )
  dadTitles.Fill( dstTitles, "Titles" )

  ' Set Default Value
  dtblTitles = dstTitles.Tables( "Titles" )
  dcolPrice = dtblTitles.Columns( "Price" )
  dcolPrice.DefaultValue = 34.67

  ' Add New Row
  drowTitle = dtblTitles.NewRow()
  drowTitle( "Title" ) = "ASP.NET Unleashed"
  dtblTitles.Rows.Add( drowTitle )

  ' Bind to DataGrid
  dgrdTitles.DataSource = dstTitles
  dgrdTitles.DataBind()
End Sub

</Script>

<html>
<head><title>DefaultValue.aspx</title></head>
<body>

<asp:DataGrid
  ID="dgrdTitles"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 12.2. Assigning a default value.

When a new row is added to the DataTable in Listing 12.7, and a value for the Price column is not supplied, the column assumes a default value of 34.67.

Making Columns Unique

You can specify a unique column in a DataTable in two ways: You can use the Unique property of the DataColumn class, or you can create a unique constraint.

You can indicate that a column should contain only unique values by using the Unique property like this:

dcolColumn = myDataSet.Tables( "Titles" ).Columns( "Price" )
dcolColumn.Unique = True

If you want to add a unique constraint to a table, you need to add the constraint to the Constraints collection of the DataTable class. In Listing 12.8, a unique constraint is added to the ProductName column of the Products database table.

Listing 12.8 UniqueConstraint.aspx
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
  Dim dstProducts As DataSet
  Dim conNorthwind As SqlConnection
  Dim dadProducts As SqlDataAdapter
  Dim dtblProducts As DataTable
  Dim objConstraint As UniqueConstraint

  ' Grab Products Table
  dstProducts = New DataSet()
  conNorthwind = New SqlConnection( "Server=localhost;Database=Northwind;UID=sa;PWD=secret" )
  dadProducts = New SqlDataAdapter( "Select * from Products", conNorthwind )
  dadProducts.Fill( dstProducts, "Products" )

  ' Add Unique Constraint
  dtblProducts = dstProducts.Tables( "Products" )
  objConstraint = New UniqueConstraint( dtblProducts.Columns( "ProductName" ) )
  dtblProducts.Constraints.Add( objConstraint )

  ' Bind to DataGrid
  dgrdProducts.DataSource = dstProducts
  dgrdProducts.DataBind()
End Sub

</Script>

<html>
<head><title>UniqueConstraint.aspx</title></head>
<body>

<asp:DataGrid
  ID="dgrdProducts"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

If the Products table includes duplicate product names, an error is generated when the unique constraint is added to the table. An error also is generated if you attempt to add a new row with a duplicate value for ProductName.

Adding AutoIncrement Columns

You can use an AutoIncrement column to uniquely number each row in a DataTable. An AutoIncrement column automatically increments its value whenever a new row is added to a DataTable. By default, an AutoIncrement column starts with the value 0 and increments by 1 for each new row.

NOTE

You can modify the initial value and change the number that is used to increment an AutoIncrement column by modifying its AutoIncrementSeed and AutoIncrementStep properties.

Imagine that you have a SQL Server database table named Products that has an identity column named ProductID. Now, suppose that some records have been deleted in the Products table so that some holes now appear in the values of the identity column. You can fix up the table's identity column when loading the DataTable into a DataSet by creating a new AutoIncrement column to replace the identity column programmatically. The page in Listing 12.9 illustrates how to do so (see Figure 12.3).

Listing 12.9 AutoIncrement.aspx
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
  Dim dstProducts As DataSet
  Dim conNorthwind As SqlConnection
  Dim dadProducts As SqlDataAdapter
  Dim dtblProducts As DataTable
  Dim dcolProductID As DataColumn

  dstProducts = New DataSet()
  dtblProducts = New DataTable( "Products" )
  dcolProductID = New DataColumn( "ProductID", GetType( Int32 ) )
  dcolProductID.AutoIncrement = True
  dtblProducts.Columns.Add( dcolProductID )
  dstProducts.Tables.Add( dtblProducts )

  conNorthwind = New SqlConnection( "Server=localhost;Database=Northwind;UID=sa;PWD=secret" )
  dadProducts = New SqlDataAdapter( "Select ProductName From Products", conNorthwind )
  dadProducts.Fill( dstProducts, "Products" )

  dgrdProducts.DataSource = dstProducts
  dgrdProducts.DataBind()
End Sub

</Script>

<html>
<head><title>AutoIncrement.aspx</title></head>
<body>

<asp:DataGrid
  ID="dgrdProducts"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 12.3. Creating an AutoIncrement column.

The DataTable is created in the Page_Load subroutine in Listing 12.9. First, a Products table is created programmatically, and an AutoIncrement column is added to it. Next, the Products database table is added to the DataSet. At this point, the DataTable has no rows.

The next step is to fill the Products database table with the values of the ProductName column from the database table. When the ProductName column is copied into the DataSet, the ProductID column is automatically assigned a unique value for each row.

Finally, the DataSet is bound to a DataGrid and the improved Products table is displayed. The original ProductID column is replaced with a new and improved ProductID column that is not missing any values.

Creating Calculated and Aggregate Columns

You can assign an expression to a DataColumn by using the Expression property. You can use this property to represent calculated columns or aggregate columns.

A calculated column enables you to calculate a value based on values from other columns. A common use of calculated columns includes creating a tax or discount column that is calculated from a price column.

The page in Listing 12.10, for example, demonstrates how you would create a calculated column named SalePrice (see Figure 12.4).

Listing 12.10 CalculatedColumn.aspx
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
  Dim dstProducts As DataSet
  Dim conNorthwind As SqlConnection
  Dim dadproducts As SqlDataAdapter
  Dim dcolSalePrice As DataColumn

  ' Grab the products table
  dstProducts = New DataSet()
  conNorthwind = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Northwind" )
  dadProducts = New SqlDataAdapter( "Select * From Products", conNorthwind )
  dadProducts.Fill( dstProducts, "Products" )

  ' Add the calculated column
  dcolSalePrice = New DataColumn( "SalePrice", GetType( Decimal ) )
  dcolSalePrice.Expression = "UnitPrice - (UnitPrice * .10)"
  dstProducts.Tables( "Products" ).Columns.Add( dcolSalePrice )

  ' Bind to the DataGrid
  dgrdProducts.DataSource = dstProducts
  dgrdProducts.DataBind()
End Sub
</Script>

<html>
<head><title>CalculatedColumn.aspx</title></head>
<body>

<asp:DataGrid
  ID="dgrdProducts"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 12.4. Creating a calculated column.

In Listing 12.10, the Products table from the Northwind database is retrieved into a DataSet. Next, a calculated column, named SalePrice, is added to the Products database table in the DataSet. The SalePrice column reduces the UnitPrice by 10%.

You can get pretty fancy with calculated columns. You can build a calculated column using a number of different operators and functions (such as ISNULL, SUBSTRING, LEN, and IFF). For more information, see the .NET Framework SDK Documentation.

You also can use the Expression property for aggregate values. You can use any of the following aggregate functions with calculated columns:

  • Avg— The average value of a column

  • Count— A count of the number of rows in a column

  • Max— The maximum value of a column

  • Min— The minimum value of a column

  • StDev— The standard deviation of values in a column

  • Sum— The sum of values in a column

  • Var— The variance of values in a column

Typically, you use these aggregate functions when you have two tables in a parent/child relationship. For example, you might want to display a category table and, next to each category, display a count of the number of products in the category.

You will learn how you can define relationships between tables later in this chapter in the section titled "Defining Relationships between DataTables." However, Listing 12.11 illustrates how you can use aggregate functions with the Categories and Products tables (see Figure 12.5).

Listing 12.11 AggregateColumn.aspx
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
  Dim dstNorthwind As DataSet
  Dim conNorthwind As SqlConnection
  Dim dadNorthwind As SqlDataAdapter
  Dim dcolProductCount As DataColumn

  ' Grab the Categories and Products table
  dstNorthwind = New DataSet()
  conNorthwind = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Northwind" )
  dadNorthwind = New SqlDataAdapter( "Select * From Categories", conNorthwind )
  conNorthwind.Open()
  dadNorthwind.Fill( dstNorthwind, "Categories" )
  dadNorthwind.SelectCommand = New SqlCommand( "Select * From Products", conNorthwind )
  dadNorthwind.Fill( dstNorthwind, "Products" )
  conNorthwind.Close()

  ' Add Parent/Child Relationship
  dstNorthwind.Relations.Add( _
   "catprods", _
    dstNorthwind.Tables( "Categories" ).Columns( "CategoryID" ), _
    dstNorthwind.Tables( "Products" ).Columns( "CategoryID" ) )

  ' Add the aggregate column
  dcolProductCount = New DataColumn( "ProductCount", GetType( Decimal ) )
  dcolProductCount.Expression = "Count( Child.ProductID )"
  dstNorthwind.Tables( "Categories" ).Columns.Add( dcolProductCount )

  ' Bind to the DataGrid
  dgrdNorthwind.DataSource = dstNorthwind
  dgrdNorthwind.DataMember = "Categories"
  dgrdNorthwind.DataBind()
End Sub
</Script>

<html>
<head><title>AggregateColumn.aspx</title></head>
<body>

<asp:DataGrid
  ID="dgrdNorthwind"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 12.5. Displaying an aggregate value.

The Categories and Products tables in Listing 12.11 have a parent/child relationship. An aggregate column is added to the Categories database table that retrieves a count of the number of child products for each category. When the Categories table is bound to the DataGrid, a count of the number of products in each category is also displayed in the DataGrid.

Computing Column Values in a DataTable

You can use the Compute method of the DataTable class to compute a value for a column. To do so, you can use any of the aggregate functions discussed in the preceding section, such as Sum() or Avg().

NOTE

You can use the Compute method with only one DataTable column at a time. If you need to compute a value based on two or more columns, you should create a calculated column that contains multiple columns and compute the value based on the calculated column.

Imagine that you want to display a list of products in a shopping cart, and you want to display the total price of the order. The page in Listing 12.12 illustrates how you can do so by using the Compute method (see Figure 12.6).

Listing 12.12 Compute.aspx
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Dim dstProducts As DataSet

Sub Page_Load
  Dim conNorthwind As SqlConnection
  Dim dadProducts As SqlDataAdapter

  ' Grab Products Table
  dstProducts = New DataSet()
  conNorthwind = New SqlConnection( "Server=localhost;Database=Northwind;UID=sa;PWD=secret" )
  dadProducts = New SqlDataAdapter( "Select Top 10 * from Products", conNorthwind )
  dadProducts.Fill( dstProducts, "Products" )

  ' Bind to DataGrid
  dgrdProducts.DataSource = dstProducts
  DataBind()
End Sub

Function OrderTotal() As String
  Dim dtblProducts As DataTable
  Dim intSumTotal As Integer

  dtblProducts = dstProducts.Tables( "Products" )
  intSumTotal = dtblProducts.Compute( "Sum( UnitPrice )", Nothing )
  Return intSumTotal.ToString( "c" )
End Function
</Script>

<html>
<head><title>Compute.aspx</title></head>
<body>

<asp:DataGrid
  ID="dgrdProducts"
  Runat="Server" />
<p>
<b>Order Total:</b>
<asp:Label
  Text='<%# OrderTotal() %>'
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 12.6. Displaying a computed value.

The OrderTotal() function is bound to a Label control to display the total price of the order. Within the OrderTotal() function, the DataTable Compute method computes the sum of the values contained in the UnitPrice column. Two parameters are passed to the Compute method: an expression with an aggregate function and the value Nothing.

The second parameter of the Compute method restricts the DataRows used with the aggregate function. For example, if you want to compute the total cost of only those products that are in stock, you would use the following statement:

inSsumTotal = dtblProducts.Compute( "Sum( UnitPrice )", "UnitsInStock > 0" )

This statement computes a sum for only those rows where the UnitsInStock column has a value greater than 0.

Defining Relationships Between DataTables

If a DataSet contains multiple DataTables, you can define relationships between the DataTables by using the DataRelation class.

The most common type of relationship is a parent/child relationship in which each record in the parent table has one or more corresponding records in the child table. For example, you might have a table of categories and a table of products where each category contains multiple products.

To define a relationship between two tables, the two tables must share a common key. For example, both the Categories and Products tables could have a CategoryID column that links the records in the two tables together.

A DataSet has a Relations property that represents a collection of DataRelation classes between the DataTables contained in the DataSet. To add a new relationship to a DataSet, you simply need to add a new DataRelation to the collection of DataRelation classes.

The page in Listing 12.13, for example, displays the name of each category from the Categories table and displays the name of each product for each category from the Products table (see Figure 12.7).

Listing 12.13 DataRelation.aspx
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
  Dim dstNorthwind As DataSet
  Dim conNorthwind As SqlConnection
  Dim dadNorthwind As SqlDataAdapter
  Dim drowParent As DataRow
  Dim drowChild As DataRow

  ' Grab the Categories and Products table
  dstNorthwind = New DataSet()
  conNorthwind = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Northwind" )
  dadNorthwind = New SqlDataAdapter( "Select * From Categories", conNorthwind )
  conNorthwind.Open()
  dadNorthwind.Fill( dstNorthwind, "Categories" )
  dadNorthwind.SelectCommand = New SqlCommand( "Select * From Products", conNorthwind )
  dadNorthwind.Fill( dstNorthwind, "Products" )
  conNorthwind.Close()

  ' Add Parent/Child Relationship
  dstNorthwind.Relations.Add( _
   "Categories_Products", _
    dstNorthwind.Tables( "Categories" ).Columns( "CategoryID" ), _
    dstNorthwind.Tables( "Products" ).Columns( "CategoryID" ) )

  ' Display each Category and Child Products
  For each drowParent in dstNorthwind.Tables( "Categories" ).Rows
    lblOutput.Text &= "<h3>" & drowParent( "CategoryName" ) & "</h3>"
    For each drowChild in drowParent.GetChildRows( "Categories_Products" )
      lblOutput.Text &= "<li>" & drowChild( "ProductName" )
    Next
  Next
End Sub
</Script>

<html>
<head><title>DataRelation.aspx</title></head>
<body>

<asp:Label
  ID="lblOutput"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 12.7. Defining table relationships.

In Listing 12.13, a DataRelation named Products_Categories is added to the DataSet's collection of DataRelation objects. Next, a For...Next loop is used to loop through and display each row in the Categories database table. For each row, the GetChildRows() method is called to retrieve an array of child rows for that row. A For...Each loop is used to loop through and display each child product for a category.

Retrieving DataTable Schema Information

By default, when you call the Fill() method to create a DataTable from an existing database table, no key or schema information is retrieved. This means that a DataTable won't represent such information as the primary key of the database table.

There are two ways that you can retrieve this information: you can use the FillSchema() method, or you can use the MissingSchemaAction property.

The page in Listing 12.14, for example, uses the FillSchema() method to retrieve schema information for the Products database table.

Listing 12.14 FillSchema.aspx
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
  Dim dstProducts As DataSet
  Dim conNorthwind As SqlConnection
  Dim dadProducts As SqlDataAdapter
  Dim dtblProducts As DataTable
  Dim dcolPrimaryKey As DataColumn

  ' Retrieve Schema Information
  dstProducts = New DataSet()
  conNorthwind = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Northwind" )
  dadProducts = New SqlDataAdapter( "Select * From Products", conNorthwind )
  dadProducts.FillSchema( dstProducts, SchemaType.Mapped, "Products" )

  ' Display Primary Keys
  dtblProducts = dstProducts.Tables( "Products" )
  For each dcolPrimaryKey in dtblProducts.PrimaryKey
    lblPrimaryKeys.Text &= "<li>" & dcolPrimaryKey.ColumnName
  Next
End Sub
</Script>

<html>
<head><title>FillSchema.aspx</title></head>
<body>

<asp:Label
  ID="lblPrimaryKeys"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

In Listing 12.14, a connection to the Northwind database is opened. Next, the FillSchema() method of the SqlDataAdapter is called to retrieve schema information on the Products database table. Finally, a For...Each loop is used to display each of the primary keys defined for the Products table.

The FillSchema() method modifies several of the properties of a DataTable:

  • AllowDBNull— Indicates whether a column accepts Null values

  • AutoIncrement— Indicates whether a column is an autoincrement column

  • Constraints— Contains a collection of constraints that represent the constraints of the underlying database table

  • MaxLength— Indicates the maximum number of characters in a text column

  • PrimaryKey— Contains an array of columns that represents the table's primary key (If a table does not have a primary key, but it has a unique constraint that prevents Null values, the unique columns are used instead)

  • ReadOnly— Indicates whether a column can be modified

  • Unique— Indicates whether a column can contain duplicate values

The FillSchema() method does not retrieve any rows from a database table. It just creates an empty table that contains the schema information. If you need to retrieve the schema for a table and data in a table in a single step, then you should use the MissingSchemaAction property.

The MissingSchemaAction property can accept any of the following values from the MissingSchemaAction enumeration:

  • Add— Adds whatever additional columns are necessary to the DataTable when adding new rows (the default value)

  • AddWithKey— Adds all necessary columns to the DataTable when adding new rows

  • Error— Raises an error when adding rows that don't match the existing DataTable

  • Ignore— Ignores extra columns when adding rows that contain columns that are not present in the DataTable

The page in Listing 12.15 illustrates how you can use the MissingSchemaAction property with the Products database table.

Listing 12.15 MissingSchemaAction.aspx
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
  Dim dstProducts As DataSet
  Dim conNorthwind As SqlConnection
  Dim dadProducts As SqlDataAdapter
  Dim dtblProducts As DataTable
  Dim dcolPrimaryKey As DataColumn

  ' Initialize DataAdapter
  conNorthwind = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Northwind" )
  dadProducts = New SqlDataAdapter( "Select * From Products", conNorthwind )

  ' Set Missing Schema Action Property
  dadProducts.MissingSchemaAction = MissingSchemaAction.AddWithKey

  ' Retrieve Products
  dstProducts = New DataSet()
  dadProducts.Fill( dstProducts, "Products" )
  dgrdProducts.DataSource = dstProducts
  dgrdProducts.DataBind()

  ' Display Primary Keys
  dtblProducts = dstProducts.Tables( "Products" )
  For each dcolPrimaryKey in dtblProducts.PrimaryKey
    lblPrimaryKeys.Text &= "<li>" & dcolPrimaryKey.ColumnName
  Next
End Sub
</Script>

<html>
<head><title>MissingSchemaAction.aspx</title></head>
<body>

<asp:DataGrid
  id="dgrdProducts"
  Runat="Server" />

<p>

<asp:Label
  ID="lblPrimaryKeys"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Since the MissingSchemaAction property is set before calling the Fill() method in Listing 12.15, the new rows are added to the DataTable with all the schema information from the underlying database table. The page displays all the products and a list of the primary key columns.

Updating Records in a DataTable

Up to this point, you have used DataSets exclusively for retrieving and displaying database data. However, you also can use a DataSet to modify database data.

The DataAdapter acts as a bridge between a DataSet and its data source. You've been using the DataAdapter's Fill() method to copy data from a data source into a DataSet. The opposite operation—copying changes from a DataSet to a data source—can be accomplished by using the DataAdapter's Update() method.

The page in Listing 12.16, for example, retrieves the contents of the Titles database table into a DataSet. Next, rows are deleted, updated, and added to the DataTable that represents the Titles table. Finally, the Update method is called, and the changes to the DataTable are copied back to the original Titles database table (see Figure 12.8).

Listing 12.16 UpdateDataSet.aspx
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
  Dim dstPubs As DataSet
  Dim conPubs As SqlConnection
  Dim dadTitles As SqlDataAdapter
  Dim dtblTitles As DataTable
  Dim drowTitle As DataRow
  Dim objCommandBuilder As New SqlCommandBuilder

  ' Grab Titles Table
  dstPubs = New DataSet()
  conPubs = New SqlConnection( "Server=localhost;Database=Pubs;UID=sa;PWD=secret" )
  dadTitles = New SqlDataAdapter( "Select * from Titles", conPubs )
  dadTitles.Fill( dstPubs, "Titles" )
  dtblTitles = dstPubs.Tables( "Titles" )

  ' Display Original Titles Table
  dgrdOriginalTitles.DataSource = dstPubs
  dgrdOriginalTitles.DataBind()

  ' Add a Row
  drowTitle = dtblTitles.NewRow()
  drowTitle( "Title_id" ) = "xxxx"
  drowTitle( "Title" ) = "ASP.NET Unleashed"
  drowTitle( "Price" ) = 1200.00
  drowTitle( "Type" ) = "Mystery"
  drowTitle( "PubDate" ) = #12/25/1966#
  dtblTitles.Rows.Add( drowTitle )

  ' Delete the First Row
  dtblTitles.Rows( 0 ).Delete()

  ' Double the price of the Second Row
  drowTitle = dtblTitles.Rows( 2 )
  drowTitle( "Price" ) *= 2

  ' Generate the SQL Commands
  objCommandBuilder = New SqlCommandBuilder( dadTitles )

  ' Update Titles Table
  dadTitles.Update( dstPubs, "Titles" )

  ' Display New Titles Table
  dgrdNewTitles.DataSource = dstPubs
  dgrdNewTitles.DataBind()
End Sub

</Script>

<html>
<head><title>UpdateDataSet</title></head>
<body>

<h2>Original Titles Table</h2>
<asp:DataGrid
  id="dgrdOriginalTitles"
  Runat="Server" />

<h2>New Titles Table</h2>
<asp:DataGrid
  id="dgrdNewTitles"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 12.8. Updating a DataSet.

In the Page_Load subroutine in Listing 12.16, the Titles database table is displayed in a DataGrid. Next, a new title is added to the Titles table, the first row of the Titles table is deleted, and the price of the second title is doubled. After these modifications are made, the Update() method is called. Calling the Update() method copies the changes from the DataSet to the underlying database. Finally, the modified Titles table is displayed in a second DataGrid.

A DataAdapter has four properties that represent four database commands:

  • SelectCommand

  • InsertCommand

  • UpdateCommand

  • DeleteCommand

If you add a new row to a DataTable and call the Update method, the SQL statement represented by the InsertCommand property is executed. If you modify a row in a DataTable and call the Update method, the SQL statement represented by the UpdateCommand property is executed. And if you delete a row, the SQL command represented by the DeleteCommand property is executed.

If you prefer, you can assign a SQL command to each of these properties yourself. You might want to do so, for example, if you want to use stored procedures to improve the performance of the Update method.

In Listing 12.14, the SQL commands used with the Update method were generated automatically by the CommandBuilder class. Right before the Update method is called, notice that the DataAdapter is passed to the CommandBuilder class.

Some requirements must be met when you're using the CommandBuilder class to automatically generate commands. First, the DataTable must represent a single database table. Second, it must represent a database table with a primary key or unique column.


Understanding DataViews

A DataView represents a particular view on a DataTable. You can use a DataView to display a filtered and sorted view of the rows in a DataTable. You also can use a DataView to find rows that have certain column values in a DataTable.

You can create a new DataView from an existing DataTable by passing a DataTable to the DataView's constructor like this:

dvwDataView = New DataView( dtblDataTable )

Alternatively, you can create a new DataView by calling the DefaultView() method of the DataTable class like this:

dvwDataView = dtblDataTable.DefaultView()

Which method you use to create a DataView is entirely a matter of preference.

ADVANCED NOTE

Technically, you can bind only a DataView, not a DataSet or DataTable, to a control. Thestatement

dgrdDataGrid.DataSource = dstDataSet

is actually shorthand for

dgrdDataGrid.DataSource = dstDataSet.Tables( 0 ).DefaultView()

There is no disadvantage to using the shorthand syntax; it works just as well.

Filtering Rows in a DataView

You can filter rows in a DataTable by creating a DataView for it and setting the DataView's RowFilter property.

The page in Listing 12.17 contains a simple example of filtering rows. The page displays only the products from the Products database table that have a CategoryID of 4.

Listing 12.17 SimpleRowFilter.aspx
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
  Dim dstNorthwind As DataSet
  Dim conNorthwind As SqlConnection
  Dim dadProducts As SqlDataAdapter
  Dim dvwProducts As DataView

  dstNorthwind = New DataSet()
  conNorthwind = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Northwind" )
  dadProducts = New SqlDataAdapter( "Select * From Products", conNorthwind )
  dadProducts.Fill( dstNorthwind, "Products" )
  dvwProducts = dstNorthwind.Tables( "Products" ).DefaultView
  dvwProducts.RowFilter = "CategoryID=4"
  dgrdProducts.DataSource = dvwProducts
  dgrdProducts.DataBind()
End Sub

</Script>

<html>
<head><title>SimpleRowFilter.aspx</title></head>
<body>

<asp:DataGrid
  ID="dgrdProducts"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

In Listing 12.17, a DataView is created from the Products database table. The expression "CategoryID=4" is assigned to the RowFilter property. When the DataView is bound to the DataGrid, only those products with a CategoryID of 4 are displayed.

A more complicated example of using the RowFilter property is included in Listing 12.18. In this example, a RowFilter displays all the products for a particular category. The list of categories is displayed by a Repeater control. The list of products for each category is listed by a DataGrid control contained in the ItemTemplate of the Repeater control (see Figure 12.9).

Listing 12.18 NestedRowFilter.aspx
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Dim dstNorthwind As DataSet
Dim dvwProducts As DataView

Sub Page_Load
  Dim conNorthwind As SqlConnection
  Dim dadNorthwind As SqlDataAdapter

  dstNorthwind = New DataSet()
  conNorthwind = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Northwind" )
  dadNorthwind = New SqlDataAdapter( "Select * From Categories", conNorthwind )
  dadNorthwind.Fill( dstNorthwind, "Categories" )
  dadNorthwind.SelectCommand = New SqlCommand( "Select * From Products", conNorthwind )
  dadNorthwind.Fill( dstNorthwind, "Products" )

  dvwProducts = dstNorthwind.Tables( "Products" ).DefaultView()

  rptCategories.DataSource = dstNorthwind
  rptCategories.DataMember = "Categories"
  rptCategories.DataBind()
End Sub

Function FilterProducts( intCatID As Integer ) As DataView
  dvwProducts.RowFilter = "CategoryID=" & intCatID
  Return dvwProducts
End Function

</Script>

<html>
<head><title>NestedRowFilter.aspx</title></head>
<body>

<asp:Repeater
  ID="rptCategories"
  Runat="Server">
<ItemTemplate>
<h2><%# Container.DataItem( "CategoryName" )%></h2>
<asp:DataGrid
  DataSource='<%# FilterProducts( Container.DataItem( "CategoryID" ) )%>'
  Runat="Server" />
</ItemTemplate>
</asp:Repeater>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 12.9. Filtering a DataView.

The Categories table is bound to the Repeater control. The ItemTemplate of the Repeater control displays the value of the CategoryName column. Additionally, the ItemTemplate contains a DataGrid.

The DataGrid displayed by the Repeater control's ItemTemplate displays all the products for a category. A function named FilterProducts() is assigned to the DataGrid's DataSource property. The CategoryID of the current category is passed to the function.

Within the FilterProducts() function, a DataView that represents the Products table is filtered with the CategoryID passed to the function. The FilterProducts function returns this DataView. The end result is that each DataGrid shows only a certain subset of products.

Sorting Rows in a DataView

You can sort the rows in a DataView by assigning the names of one or more columns to the Sort property of the DataView class. For example, to sort the rows in a DataView in order of price, you would use the following statement:

myDataView.Sort = "Price"

To sort rows using multiple columns, you need to separate each column name with a comma. For example, to sort rows by author last and first name, you would use the following:

myDataView.Sort = "au_lname, au_fname"

By default, rows are sorted in ascending order (least to most). You can use the DESC keyword to sort the rows in the opposite order (most to least). For example, to display the most expensive products first, you would use the following statement:

myDataView.Sort = "Price Desc"

The page in Listing 12.19 illustrates how to use the Sort property with a DataGrid control.

Listing 12.19 SortDataView.aspx
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Dim dvwProducts As DataView

Sub Page_Load
  Dim conNorthwind As SqlConnection
  Dim dadProducts As SqlDataAdapter
  Dim dstProducts As DataSet

  ' Create Products DataSet
  dstProducts = New DataSet()
  conNorthwind = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Northwind" )
  dadProducts = New SqlDataAdapter( "Select * From Products", conNorthwind )
  dadProducts.Fill( dstProducts, "Products" )

  ' Create default DataView
  dvwProducts = dstProducts.Tables( "Products" ).DefaultView()

  ' Bind to datagrid
  dgrdProducts.DataSource = dstProducts
  dgrdProducts.DataBind()
End Sub

Sub dgrdProducts_SortCommand( s As Object, e As DataGridSortCommandEventArgs )
  ' Sort DataView
  dvwProducts.Sort = e.SortExpression

  ' Rebind to DataGrid
  dgrdProducts.DataSource = dvwProducts
  dgrdProducts.DataBind()
End Sub
</Script>

<html>
<head><title>SortDataView.aspx</title></head>
<body>

<form Runat="Server">
<asp:DataGrid
  ID="dgrdProducts"
  AllowSorting="True"
  OnSortCommand="dgrdProducts_SortCommand"
  Runat="Server" />
</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

When a DataGrid control has its AllowSorting property enabled, all the column headers appear as links. When you click a link, the DataGridSortCommand event is raised and the dgrdProducts_SortCommand subroutine is executed. This subroutine uses the Sort property of a DataView to sort its rows.

Finding Rows in a DataView

You can use a DataView's Find method to find the index of a row that has a certain value. You can pass a single value to the Find method or an array of values.

To pass a single value to the Find method, simply use the following:

intItemIndex = MyDataView.Find( "Smith" )

If Smith is found, the index of the row that contains this name is assigned to intItemIndex.

To pass multiple values to the Find method, use the following:

Dim arrValues( 2 ) As Object
arrValues( 0 ) = "Smith"
arrValues( 1 ) = "Robert"
intItemIndex = dvwDataView.Find( arrValues )

If Robert Smith is found, the Find method returns the index of the row. Otherwise, it returns the value -1 (meaning no matching row was found).

If multiple rows match the values being searched, the index of only one of the matched rows is returned.

The Find method requires that the rows in a DataTable are sorted in the same order as the values passed to it. If you want to find an author with a certain last name, for example, the table must be sorted in order of author last names.

The page in Listing 12.20 searches for an author named Ann Dull. If this author's name is found, her phone number is displayed.

Listing 12.20 FindDataView.aspx
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<%
Dim dstAuthors As DataSet
Dim conPubs As SqlConnection
Dim dadAuthors As SqlDataAdapter
Dim dvwAuthors As DataView
Dim arrValues(2) As Object
Dim intRowIndex As Integer

' Grab Authors Table
dstAuthors = New DataSet()
conPubs = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Pubs" )
dadAuthors = New SqlDataAdapter( "Select * From Authors", conPubs )
dadAuthors.Fill( dstAuthors, "Authors" )

' Create DataView
dvwAuthors = dstAuthors.Tables( "Authors" ).DefaultView()
dvwAuthors.Sort = "au_lname, au_fname"

' Find Ann Dull
arrValues( 0 ) = "Dull"
arrValues( 1 ) = "Ann"
intRowIndex = dvwAuthors.Find( arrValues )

' Display Phone Number
If intRowIndex <> -1 Then
  Response.Write( dvwAuthors( intRowIndex ).Row( "Phone" ) )
Else
  Response.Write( "Can't find Ann Dull" )
End If
%>

The C# version of this code can be found on the CD-ROM.

In Listing 12.20, the Authors table is retrieved into a DataSet. Next, a DataView is created from the DataSet and sorted in order of author last and first name.

The Find method searches for an author named Ann Dull. If this name is found (the intRowIndex does not equal -1), the author's phone number is displayed. Otherwise, you see a message stating that she can't be found.


Using DataSets with ASP.NET Pages

When used properly, DataSets can dramatically improve the performance of your Web site. When used improperly, however, DataSets can waste precious server resources and slow down the performance of your Web site.

In the following sections, you will examine some good uses of DataSets. You will learn how a DataSet can be cached in memory and used over multiple requests to the same or different pages.

Caching DataSets

When you place an item in your application's cache, the item is automatically preserved between page requests in your server's memory. You can cache a DataSet, DataTable, or DataView by using the Cache object.

To cache a DataSet, for example, you would use the following statement:

Cache( "CachedDataSet" ) = dstDataSet

This statement adds a new item to the cache, called CachedDataSet, and assigns the DataSet named dstDataSet to this item.

It's important to be aware that if system resources become low or the application is restarted, the items in the cache are removed. In other words, you should never assume that an item stays in the cache after you put it there. Whenever you retrieve an item from the Cache object, you should use logic like this:

dstDataSet = Cache( "CachedDataSet" )
If myDataSet Is Nothing Then
  ' Recreate the DataSet
  Cache( "CachedDataSet" ) = dstDataSet
End If

Always check whether an item retrieved from the cache is equal to Nothing. If it is, you need to re-create it and add the item to the cache once again.

NOTE

You must use Is rather than = when comparing the contents of the cache to Nothing. You must use this operator because you are comparing objects (reference types), not values.

You can perform sophisticated operations with the Cache object. For example, you can add items to the cache with sliding or absolute expirations or file and key dependencies. For example, you can automatically update an item stored in the cache every hour.

NOTE

Caching is discussed in more detail in Chapter 17, "Caching ASP.NET Applications."

Displaying Cached Data

Imagine that you are building an online store and that you want to display a list of products from the Products table in a DataGrid. Furthermore, imagine that you want to enable your customers to sort the products in order of different columns. In this situation, it would not make sense to retrieve the list of products from the database each and every time the customer sorts the products table. The data hasn't changed, only the order of the data has changed.

The page in Listing 12.21 illustrates how you can cache a DataView that represents a list of products from the Products table and sort the cached data (see Figure 12.10).

Listing 12.21 CacheProducts.aspx
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
  If Not IsPostBack Then
    ' Bind to datagrid
    dgrdProducts.DataSource = GetProducts()
    dgrdProducts.DataBind()
  End If
End Sub

Function GetProducts() As DataView
  Dim dstProducts As DataSet
  Dim conNorthwind As SqlConnection
  Dim dadProducts As SqlDataAdapter
  Dim dvwProducts As DataView

  dvwProducts = Cache( "Products" )
  If dvwProducts Is Nothing Then
    dstProducts = New DataSet()
   conNorthwind = New SqlConnection(
"Server=localhost;UID=sa;PWD=secret;Database=Northwind" )
    dadProducts = New SqlDataAdapter( "Select * from Products", conNorthwind )
    dadProducts.Fill( dstProducts, "Products" )
    dvwProducts = dstProducts.Tables( "Products" ).DefaultView()
    Cache( "Products" ) = dvwProducts
  End If
  Return dvwProducts
End Function

Sub dgrdProducts_SortCommand( s As Object, e As DataGridSortCommandEventArgs )
  Dim dvwProducts As DataView

  ' Sort DataView
  dvwProducts = GetProducts()
  dvwProducts.Sort = e.SortExpression

  ' Rebind to DataGrid
  dgrdProducts.DataSource = dvwProducts
  dgrdProducts.DataBind()
End Sub
</Script>

<html>
<head><title>CacheProducts.aspx</title></head>
<body>

<form Runat="Server">
<asp:DataGrid
  ID="dgrdProducts"
  AllowSorting="True"
  OnSortCommand="dgrdProducts_SortCommand"
  Runat="Server" />
</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 12.10. Sorting cached products.

The DataGrid that displays the list of products uses the DataView returned by the GetProducts() function as its data source. The GetProducts() function attempts to retrieve a DataView containing the products from the Cache object. If the Products DataView is not present in the cache, it is created.

The AllowSorting property of the DataGrid is assigned the value True. You can sort a column by clicking the link that appears at the top of each column. When the rows in the DataGrid are sorted, the cached list of products are sorted and rebound to the DataGrid.

Caching the products in the Cache object dramatically improves the performance of this ASP.NET page. By storing the products in memory, the database does not need to be queried for the list of products each time the page is requested and each time the products are resorted.

If you need to alter the list of products, you can remove the Products item from the cache by using the following line of code:

Cache.Remove( "Products" )

You might want to include this statement, for example, within a form that adds or updates the products in the Products table. After this statement is executed, the page in Listing 12.21 automatically retrieves the new list of products from the database the next time the page is requested.

Filtering Cached Data

In the preceding section, you learned how to cache the products displayed by an online store. If your server has sufficient memory, caching the complete list of products sold through the Web site might be worthwhile.

You can enable users to select a category and display a list of products from the selected category. Both the Categories and Products tables can be placed in the cache. When a category is selected, you can retrieve the list of products from the cache and filter the products for the selected category.

The page in Listing 12.22 demonstrates how to do so (see Figure 12.11).

Listing 12.22 CacheStore.aspx
[View full width]
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Dim dstStore As DataSet

Sub Page_Load
  Dim conNorthwind As SqlConnection
  Dim dadNorthwind As SqlDataAdapter

  dstStore = Cache( "Store" )
  If dstStore Is Nothing Then
    dstStore = New DataSet()
    conNorthwind = New SqlConnection(
   "Server=localhost;UID=sa;PWD=secret;Database=Northwind" )
    dadNorthwind = New SqlDataAdapter( "Select CategoryID, CategoryName From Categories",
   conNorthwind )
    conNorthwind.Open()
    dadNorthwind.Fill( dstStore, "Categories" )
    dadNorthwind.SelectCommand = New SqlCommand( "Select * From Products", conNorthwind )
    dadNorthwind.Fill( dstStore, "Products" )
    conNorthwind.Close()
    Cache( "Store" ) = dstStore
  End If
  If Not IsPostBack Then
    dgrdCategories.DataSource = dstStore
    dgrdCategories.DataMember = "Categories"
    dgrdCategories.DataBind()
  End If
End Sub

Sub dgrdCategories_ItemCommand( s As Object, e As DataListCommandEventArgs )
  Dim dvwProducts As DataView
  Dim intCategoryID

  ' Select the Chosen Category
  dgrdCategories.SelectedIndex = e.Item.ItemIndex

  ' Get CategoryID from DataKeys Collection
  intCategoryID = dgrdCategories.DataKeys( e.Item.ItemIndex )

  ' Filter the Products
  dvwProducts = dstStore.Tables( "Products" ).DefaultView()
  dvwProducts.RowFilter = "CategoryID=" & intCategoryID
  dgrdProducts.DataSource = dvwProducts
  dgrdProducts.DataBind()
End Sub
</Script>

<html>
<head><title>CacheStore.aspx</title></head>
<body>

<form Runat="Server">

<table width="100%">
<tr><td valign="top" width="200">

<asp:DataList
  ID="dgrdCategories"
  OnItemCommand="dgrdCategories_ItemCommand"
  SelectedItemStyle-BackColor="Yellow"
  DataKeyField="CategoryID"
  Runat="Server">
<ItemTemplate>
  <asp:LinkButton
    Text='<%# Container.DataItem( "CategoryName" ) %>'
    Runat="Server" />
</ItemTemplate>
</asp:DataList>

</td><td valign="top">

<asp:DataGrid
  ID="dgrdProducts"
  Runat="Server" />

</td></tr>
</table>

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 12.11. Filtering a cached DataSet.

Both the Categories and Products tables are cached in a DataSet within the Page_Load subroutine. If the DataSet is missing from the cache, it is created and added to the cache.

When you select a product category, the dgrdCategories_ItemCommand subroutine executes, displaying the selected category with a yellow background. It also creates a DataView for the Products database table. The DataView is filtered to display only the products for the selected category. Finally, the DataView is bound to the DataGrid control named dgrdProducts, and the proper products are displayed.

The list of categories and products is retrieved from the Categories and Products database tables only the first time the page is requested. When the page is requested (by any user) in the future, the cached DataSet is used.

Finding a Row in a Cached DataSet

Imagine that your company wants to build a private intranet Web site that contains a directory of employees. Suppose, furthermore, that your company wants to create a form that enables a user to search through the list of employees to display information on a particular employee (the employee name, phone number, and so on).

You can cache all the information about the employees in a DataView. To display detailed information for a particular employee, you can use the Find method with the cached DataView.

The page in Listing 12.23 illustrates how to do so (see Figure 12.12).

Listing 12.23 CacheEmployees.aspx
[View full width]
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  Dim dstEmployees As DataSet
  Dim conNorthwind As SqlConnection
  Dim dadEmployees As SqlDataAdapter
  Dim dvwEmployees As DataView
  Dim arrValues( 1 ) As Object
  Dim intEmployeeIndex As Integer

  ' Get cached DataView
  dvwEmployees = Cache( "Employees" )
  If dvwEmployees Is Nothing Then
    dstEmployees = New DataSet()
    conNorthwind = New SqlConnection(
   "Server=localhost;UID=sa;PWD=secret;Database=Northwind" )
    dadEmployees = New SqlDataAdapter( "Select * From Employees", conNorthwind )
    dadEmployees.Fill( dstEmployees, "Employees" )
    dvwEmployees = dstEmployees.Tables( "Employees" ).DefaultView()
    dvwEmployees.Sort = "LastName, FirstName"
    Cache( "Employees" ) = dvwEmployees
  End If

  ' Find the employee
  arrValues( 0 ) = txtLastName.Text
  arrValues( 1 ) = txtFirstName.Text
  intEmployeeIndex = dvwEmployees.Find( arrValues )
  If intEmployeeIndex <> -1 Then
    lblName.Text = txtLastName.Text & ", " & txtFirstName.Text
    lblPhone.Text = dvwEmployees( intEmployeeIndex ).Row( "HomePhone" )
    lblNotes.Text = dvwEmployees( intEmployeeIndex ).Row( "Notes" )
  Else
    lblError.Text = "Employee Not Found!"
  End If
End Sub

</Script>

<html>
<head><title>CacheEmployees.aspx</title></head>
<body>

<h2>Employee Directory</h2>

<form Runat="Server">
<b>First Name:</b>
<asp:TextBox
  ID="txtFirstName"
  Runat="Server" />
<p>
<b>Last Name:</b>
<asp:TextBox
  ID="txtLastName"
  Runat="Server" />
<asp:Button
  Text="Find!"
  OnClick="Button_Click"
  Runat="Server" />
<hr>
<asp:Label
  ID="lblError"
  ForeColor="Red"
  EnableViewState="False"
  Runat="Server" />
<asp:Label
  ID="lblName"
  EnableViewState="False"
  Runat="Server" />
<p>
<asp:Label
  ID="lblPhone"
  EnableViewState="False"
  Runat="Server" />
<p>
<asp:Label
  ID="lblNotes"
  EnableViewState="False"
  Runat="Server" />
</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 12.12. Finding a row in a cached DataView.

The form in Listing 12.23 contains two TextBox controls labeled First Name and Last Name. When you enter a first and last name and click Find, the Button_Click subroutine is executed.

The Button_Click subroutine retrieves a DataView representing the Employees database table from the Cache object. If the DataView does not exist in the cache, it is created and added to the cache.

After the Employees DataView is retrieved, the Find method finds an employee record that matches the first and last name entered into the form. If the employee is found, the employee's name, phone number, and notes are displayed in Label controls. Otherwise, the message "Employee Not Found!" is displayed.

An alternative approach to creating the page in Listing 12.23 is to use the DataView's RowFilter property rather than the DataView's Find method to display the correct employee. (The RowFilter property was discussed in the preceding section.) One advantage of using the RowFilter property is that it enables you to display multiple matching employees instead of just one.


Summary

In this chapter, you examined a set of ADO.NET classes that enable you to create a memory-resident, disconnected representation of a database.

In the first section, you were provided with an overview of the features and elements of the DataSet class. You learned how to create a DataSet and add DataTables to a DataSet from an existing database table.

Next, you learned how to work with DataTables. For example, you learned how to define parent/child relationships between multiple tables contained in a DataSet.

You also learned how to represent the rows in a DataTable by using a DataView. This part of the chapter discussed how you can sort, filter, and find rows in a DataView.

Finally, you learned how to cache a DataSet by using the Cache object so that you can preserve the same DataSet over multiple page requests. You also learned how to display, filter, and find rows in a cached DataSet.


Chapter 13. Working with XML

IN THIS CHAPTER

  • Overview of the XML Classes

  • Using XML with DataSets

  • Transforming XML with XSL Stylesheets

  • Using the ASP.NET Xml Control

  • Using the XslTransform Class

  • Using Strongly Typed DataSets

In this chapter, you learn how to use the eXtensible Markup Language (XML) with DataSets and ASP.NET controls. First, you learn how to represent XML data in a DataSet. I discuss how you can load XML data directly into a DataSet by using the ReadXml method. You also learn how to mirror the XML data in a DataSet by using an XmlDataDocument class.

Next, you learn two methods of transforming XML documents with eXtensible Stylesheet Language (XSL) stylesheets. First, you examine how to use XSL stylesheets with the ASP.NET Xml control, which enables you to transform XML into other document formats such as HTML. You also learn how to use the XslTransform class to perform the same tasks independently of the Xml control.

Finally, you examine how to use an XML schema to generate a strongly typed DataSet. You can use a strongly typed DataSet to provide a more intuitive representation of data.


Overview of the XML Classes

The .NET framework contains a rich set of classes for working with XML data; these classes are divided into multiple namespaces. This book does not cover all these classes; however, I want to provide you with an overview of the most important ones.

The main classes for working with XML data are as follows:

  • XmlTextReader— This class provides fast, forward-only access to the raw data contained in an XML file. It parses XML data into tokens, but it does not represent the XML data in an object model such as the W3C XML Document Object Model (DOM).

  • XmlTextWriter— This class provides a fast, forward-only method to write data to an XML file. It ensures that the data written to the file conforms to the W3C XML 1.0 standard.

  • XmlDocument— This class represents XML using the W3C XML DOM levels 1 and 2. You can use this class to both navigate and edit the nodes in an XML document tree.

  • XmlDataDocument— This class enables you to represent both XML and relational data in W3C XML DOM levels 1 and 2. You can use this class with a DataSet to provide both relational and non-relational views of the same underlying data.

  • XmlNodeReader— This class provides fast, forward-only access to data represented by the XmlDocument or XmlDataDocument class.

  • DocumentNavigator— This class enables you to efficiently navigate an XML document represented by the XmlDocument class. It supports the XPath data model for navigation.

  • DataDocumentNavigator— This class enables you to efficiently navigate an XML document represented by the XmlDataDocument class. It supports the XPath data model for navigation.

  • XslTransform— This class enables you to transform an XML document with XSL stylesheets. It supports XSLT 1.0 stylesheet syntax.

These XML classes can be roughly divided into four types. The first two classes, XmlTextReader and XmlTextWriter, enable you to perform the basic operations of reading and writing XML data to a file.

If you need to represent XML data in a static, memory-resident structure, you use either the XmlDocument or XmlDataDocument class. These classes enable you to create an XML DOM to represent an XML document in a memory-resident tree.

NOTE

To learn more details about the XML Document Object Model (DOM), visit the World Wide Web Consortium (W3C) Web site at http://www.w3.org/dom.

The XmlDocument class enables you to represent XML data in a DOM. XmlDataDocument extends the XmlDocument class to enable you to represent the XML data in either a DOM or a DataSet.

If you want to efficiently navigate the nodes of an XML document represented by the XmlDocument or XmlDataDocument class, you can use the XmlNodeReader, DocumentNavigator, or DataDocumentNavigator class. The latter two classes support XPath expressions.

Finally, the XslTransform class enables you to perform transformations on an XML document. Typically, you use the XslTransform class either to convert one XML document into another XML document or to convert an XML document into an HTML document. However, you also can use the XslTransform class to convert an XML document into any other file format.

NOTE

To learn more details about XSL (eXtensible Stylesheet Language), visit the W3C Web site at http://www.w3.org/style/xsl.


Using XML with DataSets

In Chapter 12, "Working with DataSets," you learned how to use a DataSet to represent data retrieved from a database such as Microsoft SQL Server. A DataSet can also be used to represent XML data.

A DataSet can represent XML data in two ways: You can load XML data directly into a DataSet, or you can build an XmlDataDocument class from an existing DataSet. In the following sections, you examine both ways of working with XML data.

Reading an XML Document into a DataSet

You can read an XML file directly from the hard drive into a DataSet by using the ReadXml() method of the DataSet class. For example, imagine that you want to represent the XML document contained in Listing 13.1 with a DataSet.

Listing 13.1 Menu.xml
<Menu>
  <MenuItem>
    <Food>
      French Toast
    </Food>
    <Price>
      12.45
    </Price>
  </MenuItem>
  <MenuItem>
    <Food>
      Scrambled Eggs
    </Food>
    <Price>
      3.89
    </Price>
  </MenuItem>
</Menu>

The C# version of this code can be found on the CD-ROM.

Listing 13.1 contains a simple XML file that represents a menu from a restaurant. The menu contains two menu items. The first item on the menu is French Toast, which costs $12.45, and the second item is Scrambled Eggs, which costs $3.89.

The ASP.NET page in Listing 13.2 demonstrates how you can read the Menu.xml file into a DataSet (see Figure 13.1).

Listing 13.2 ReadMenu.aspx
<%@ Import Namespace="System.Data" %>

<Script Runat="Server">

Sub Page_Load
  Dim dstMenu As DataSet

  dstMenu = New DataSet()
  dstMenu.ReadXml( MapPath( "Menu.xml" ) )

  dgrdMenu.DataSource = dstMenu
  dgrdMenu.DataBind()
End Sub

</Script>

<html>
<head><title>ReadMenu.aspx</title></head>
<body>

<asp:DataGrid
  ID="dgrdMenu"
  cellpadding="10"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 13.1. Displaying the Menu.xml file.

In the Page_Load subroutine in Listing 13.2, an instance of the DataSet class is created. Next, the ReadXml() method is called to read the Menu.xml file from the hard drive. The DataSet is bound to the DataGrid control, and the menu is displayed.

NOTE

You might notice that the MapPath method is used in Listing 13.2 with the ReadXml() method. The MapPath() method translates a virtual file path, such as /xmlfiles/menu.xml, into a physical file path, such as c:\inetpub\wwwroot\_xmlfiles\menu.xml. The ReadXml() method expects a physical path.

You can use the same method to bind XML data to any of the standard ASP.NET controls that have a DataSource property. For example, you could bind an XML file that contains a list of countries to a DropDownList or ListBox control.

Using a Schema with ReadXml

In the preceding section, you learned how to use the ReadXml() method to load an XML file into a DataSet. You might have been surprised that you did not have to supply either a Document Type Definition (DTD) or XML schema when loading the XML file. How does the DataSet determine the structure of the XML document? It makes its best guess.

When you read the XML file into the DataSet, the DataSet infers the structure of the XML file. For example, it checks whether the elements directly under the root node can be interpreted as tables. If they can be, the root node is interpreted as a DataSet. Otherwise, the root node represents a table.

There are some significant disadvantages to allowing the DataSet to guess the structure of an XML document. One problem is that the DataSet must interpret all the elements as representing strings. So, even the Price element in the Menu.xml file in Listing 13.1 is interpreted as a string, when it should, more accurately be represented as a decimal.

If you are simply displaying the XML file in a control such as a DataGrid, you might not care how the value of the element is represented. However, in certain applications, you want the data types of the elements to be correctly represented. To do so, you need to supply an XML schema with the XML file.

NOTE

To learn more details about XML schemas, visit the W3C Web site at http://www.w3.org/xml/schema.

You can specify a schema with an XML file in two ways. You can include the schema in the same document as the XML file, or you can read the schema separately by using the ReadXmlSchema() method.

The file in Listing 13.3 contains both a schema and XML data in the same file.

Listing 13.3 SchemaData.xml
<Menu>

  <xsd:schema id="Menu"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
    <xsd:element name="MenuItem">
      <xsd:complexType>
        <xsd:all>
          <xsd:element name="Food" minOccurs="0" type="xsd:string"/>
          <xsd:element name="Price" minOccurs="0" type="xsd:decimal"/>
        </xsd:all>
      </xsd:complexType>
    </xsd:element>
    <xsd:element name="Menu" msdata:IsDataSet="true">
      <xsd:complexType>
        <xsd:choice maxOccurs="unbounded">
          <xsd:element ref="MenuItem"/>
        </xsd:choice>
      </xsd:complexType>
    </xsd:element>
  </xsd:schema>

  <MenuItem>
    <Food>
      French Toast
    </Food>
    <Price>
      12.45
    </Price>
  </MenuItem>
  <MenuItem>
    <Food>
      Scrambled Eggs
    </Food>
    <Price>
      3.89
    </Price>
  </MenuItem>
</Menu>

The C# version of this code can be found on the CD-ROM.

The schema in Listing 13.3 specifies the data types for both the Food and Price elements: The Food element contains string values, and the Price element contains decimal values.

The ASP.NET page in Listing 13.4 displays all the menu items from the SchemaData.xml file by using a Repeater control (see Figure 13.2).

Listing 13.4 ReadSchemaData.aspx
<%@ Import Namespace="System.Data" %>

<Script Runat="Server">

Sub Page_Load
  Dim dstMenu As DataSet

  dstMenu = New DataSet()
  dstMenu.ReadXml( MapPath( "SchemaData.xml" ) )

  rptMenu.DataSource = dstMenu
  rptMenu.DataBind()
End Sub

</Script>

<html>
<head><title>ReadSchemaData.aspx</title></head>
<body>

<asp:Repeater
  ID="rptMenu"
  Runat="Server" >

<ItemTemplate>
  <hr>
  <p><b>Food:</b>
  <br><%# Container.DataItem( "Food" )%>

  <p><b>Food Data Type:</b>
  <br><%# Container.DataItem( "Food" ).GetType %>

  <p><b>Price:</b>
  <br><%# Container.DataItem( "Price" )%>

  <p><b>Price Data Type:</b>
  <br><%# Container.DataItem( "Price" ).GetType %>
</ItemTemplate>

</asp:Repeater>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 13.2. Displaying menu items in a Repeater control.

In the Page_Load subroutine in Listing 13.4, the ReadXml() method retrieves the SchemaData.xml file into the DataSet. The DataSet is then bound to a Repeater control, which displays the values of all the elements.

Additionally, to illustrate that the Food column is represented as a string and the Price column is represented as a decimal, the data type of each element is displayed by the Repeater control. The data type is returned with the GetType() method.

If you prefer, you can place an XML schema in a separate file and read the schema by using the ReadXmlSchema() method instead. For example, Listing 13.5 contains the schema for the Menu.xml file.

Listing 13.5 MenuSchema.xml
<xsd:schema id="Menu"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
  <xsd:element name="MenuItem">
    <xsd:complexType>
      <xsd:all>
        <xsd:element name="Food" minOccurs="0" type="xsd:string"/>
        <xsd:element name="Price" minOccurs="0" type="xsd:decimal"/>
      </xsd:all>
    </xsd:complexType>
  </xsd:element>
  <xsd:element name="Menu" msdata:IsDataSet="true">
    <xsd:complexType>
      <xsd:choice maxOccurs="unbounded">
        <xsd:element ref="MenuItem"/>
      </xsd:choice>
    </xsd:complexType>
  </xsd:element>
</xsd:schema>

The C# version of this code can be found on the CD-ROM.

The page in Listing 13.6 illustrates how you can read the MenuSchema.xml file by using the ReadXmlSchema() method.

Listing 13.6 ReadSchema.aspx
<%@ Import Namespace="System.Data" %>

<Script Runat="Server">

Sub Page_Load
  Dim dstMenu As DataSet

  dstMenu = New DataSet()
  dstMenu.ReadXmlSchema( MapPath( "MenuSchema.xml" ) )
  dstMenu.ReadXml( MapPath( "menu.xml" ) )

  dgrdMenu.DataSource = dstMenu
  dgrdMenu.DataBind()
End Sub

</Script>

<html>
<head><title>ReadSchema.aspx</title></head>
<body>

<asp:DataGrid
  ID="dgrdMenu"
  cellpadding="10"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

The ASP.NET page in Listing 13.6 uses both the MenuSchema.xml and Menu.xml files to load the XML data. After the XML data is retrieved into the DataSet, the DataSet is bound to a DataGrid control, and the XML data is displayed.

Writing an XML Document from a DataSet

The DataSet class includes several methods for retrieving an XML representation of the data contained in a DataSet. You can use these methods regardless of whether you originally loaded the DataSet with data from an XML file or from a database table.

If you want to retrieve a string representation of XML data, you can use the GetXml() method. For example, the page in Listing 13.7 uses the GetXml() method to output an XML representation of a DataSet to the browser (see Figure 13.3).

Listing 13.7 GetXml.aspx
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
  Dim conPubs As SqlConnection
  Dim dadTitles As SqlDataAdapter
  Dim dstTitles As DataSet
  Dim strXmlData As String

  conPubs = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Pubs" )
  dadTitles = New SqlDataAdapter( "Select * From Titles", conPubs )
  dstTitles = New DataSet()
  dadTitles.Fill( dstTitles, "Titles" )

  strXmlData = dstTitles.GetXml()
  Response.Write( "<pre>" & Server.HtmlEncode( strXmlData ) & "</pre>" )
End Sub

</Script>

The C# version of this code can be found on the CD-ROM.

Figure 13.3. An XML representation of data contained in a DataSet.

In Listing 13.7, a DataSet is populated from a SQL Server database table named Titles. After the Titles table is added to the DataSet, the GetXml() method retrieves a string that contains an XML representation of the DataSet. This string is assigned to a variable named strXmlData and outputted using the Response object.

NOTE

The Server.HtmlEncode() method in Listing 13.7 converts the < and > characters to HTML-safe character entities so they can be properly displayed in a browser.

The GetXml() method actually retrieves two things: the XML data itself and the XML schema for the DataSet. If you want to retrieve only the XML schema, not the XML data, you can use the GetXmlSchema() method.

The DataSet class also contains two methods for writing an XML representation of the DataSet to a stream: WriteXml() and WriteXmlSchema().

The page in Listing 13.8, for example, illustrates how you can write an XML representation of a DataSet to a file named myXml.xml by using the WriteXml() method.

Listing 13.8 WriteXml.aspx
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
  Dim conPubs As SqlConnection
  Dim dadTitles As SqlDataAdapter
  Dim dstTitles As DataSet

  conPubs = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Pubs" )
  dadTitles = New SqlDataAdapter( "Select * From Titles", conPubs )
  dstTitles = New DataSet()
  dadTitles.Fill( dstTitles, "Titles" )

  dstTitles.WriteXml( "c:\myXml.xml" )
  Response.Write( "Saved myXml.xml file!" )
End Sub

</Script>

The C# version of this code can be found on the CD-ROM.

In Listing 13.8, the DataSet that is created contains the Titles table from the Pubs database. An XML representation of the DataSet is written to a file named myXml.xml located at the root of the C: drive.

The WriteXml() method accepts an additional parameter that specifies how the XML data should be saved. This parameter can accept any of the following values of the XmlWriteMode enumeration:

  • DiffGram— Writes the XML data so that it can be used with SQL Server 2000 UpdateGrams

  • IgnoreSchema— Writes the XML data without including the XML schema

  • WriteSchema— Writes both the XML data and the XML schema (the default)

The page in Listing 13.9, for example, writes an XML representation of a DataSet to the Response object's output stream. The XmlWriteMode parameter is assigned the value IgnoreSchema to display only the XML data. On recent versions of Internet Explorer (for example, Internet Explorer 5.0 and higher), the XML file is displayed directly in the browser (see Figure 13.4).

Listing 13.9 WriteXMLResponse.aspx
<%@ Page ContentType="text/xml" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
  Dim conPubs As SqlConnection
  Dim dadTitles As SqlDataAdapter
  Dim dstTitles As DataSet

  conPubs = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Pubs" )
  dadTitles = New SqlDataAdapter( "Select * From Titles", conPubs )
  dstTitles = New DataSet()
  dadTitles.Fill( dstTitles, "Titles" )

  dstTitles.WriteXml( Response.OutputStream, XmlWriteMode.IgnoreSchema )
End Sub

</Script>

The C# version of this code can be found on the CD-ROM.

Figure 13.4. Writing XML to the output stream.

CAUTION

A page directive at the top of Listing 13.9 specifies the content type of the output of the page. Internet Explorer has an unpleasant tendency to cache the content type of a page. So, if you open the page once in Internet Explorer without specifying the content type correctly, you'll have a difficult time displaying the page correctly in the browser thereafter. If you get desperate, you can force Internet Explorer to recognize a new page content type by renaming the page.

Using XMLDataDocuments with DataSets

In the previous sections, you learned how to load an XML document into a DataSet. A DataSet provides you with a relational view of the XML data; all the information is represented with tables, rows, and columns.

In certain situations, however, working with a nonrelational view of the data contained in a DataSet is more convenient. In other words, sometimes it makes more sense to represent data as nodes on a tree rather than as tables, rows, and columns.

Consider, for example, the XML document in Listing 13.10.

Listing 13.10 Recipes.xml
<RecipeList>
  <Recipe>
    <Name>
      French Toast
    </Name>
    <Ingredients>
      <Ingredient>
        Bread
      </Ingredient>
      <Ingredient>
        Butter
      </Ingredient>
      <Ingredient>
        Sugar
      </Ingredient>
    </Ingredients>
  </Recipe>
  <Recipe>
    <Name>
      Tomato Soup
    </Name>
    <Ingredients>
      <Ingredient>
        Tomatoes
      </Ingredient>
      <Ingredient>
        Water
      </Ingredient>
    </Ingredients>
  </Recipe>
</RecipeList>

The C# version of this code can be found on the CD-ROM.

The XML document in Listing 13.10 represents a list of recipes. It contains recipes for French Toast and Tomato Soup. Each recipe, in turn, contains a list of ingredients.

In the Recipes.xml document, notice that the <Ingredients> tag is nested under the <Recipe> tag. There is nothing wrong with nesting XML tags, and it provides an intuitive representation of the data. However, translating nested elements into tables, rows, and columns can be somewhat complex.

You can, in fact, load the XML document in Listing 13.10 into a DataSet. However, the DataSet must do some extra work to represent the structure of the XML document using a relational view. The page in Listing 13.11 demonstrates how a DataSet would translate the Recipes.xml file (see Figure 13.5).

Listing 13.11 ReadRecipes.aspx
<%@ Import Namespace="System.Data" %>

<Script Runat="Server">

Sub Page_Load
  Dim dstRecipes As DataSet

  dstRecipes = New DataSet()
  dstRecipes.ReadXml( MapPath( "Recipes.xml" ) )

  lblTable1.Text = dstRecipes.Tables( 0 ).TableName
  dgrdTable1.DataSource = dstRecipes
  dgrdTable1.DataMember = dstRecipes.Tables( 0 ).TableName

  lblTable2.Text = dstRecipes.Tables( 1 ).TableName
  dgrdTable2.DataSource = dstRecipes
  dgrdTable2.DataMember = dstRecipes.Tables( 1 ).TableName

  lblTable3.Text = dstRecipes.Tables( 2 ).TableName
  dgrdTable3.DataSource = dstRecipes
  dgrdTable3.DataMember = dstRecipes.Tables( 2 ).TableName

  DataBind()
End Sub

</Script>

<html>
<head><title>ReadRecipes.aspx</title></head>
<body>

<asp:Label
  ID="lblTable1"
  Font-Size="14pt"
  Runat="Server"/>
<br>
<asp:DataGrid
  ID="dgrdTable1"
  CellPadding="10"
  Runat="Server" />
<p>
<asp:Label
  ID="lblTable2"
  Font-Size="14pt"
  Runat="Server"/>
<br>
<asp:DataGrid
  ID="dgrdTable2"
  CellPadding="10"
  Runat="Server" />
<p>
<asp:Label
  ID="lblTable3"
  Font-Size="14pt"
  Runat="Server"/>
<br>
<asp:DataGrid
  ID="dgrdTable3"
  CellPadding="10"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM..

Figure 13.5. Relational view of XML data.

In the Page_Load subroutine in Listing 13.11, the Recipes.xml file is loaded into a DataSet. When the XML file is loaded, it is automatically converted into three tables: the Recipe, Ingredients, and Ingredient tables. The contents of each table are displayed by a DataGrid.

The Recipe table represents the name of each recipe and a recipe ID (the ID column is automatically generated for you). The Ingredient table represents each ingredient for a recipe. Finally, the Ingredients table joins the Recipe and Ingredient tables using the Recipe_ID and Ingredient_ID columns.

Instead of viewing the contents of the Recipes.xml document as a set of tables, you can view the contents of the XML document nonrelationally by using the XmlDataDocument class, which represents data as nodes on a tree. You create a new XmlDataDocument by using a DataSet in the XmlDataDocument's constructor.

In Listing 13.12, for example, an instance of the XmlDataDocument class is created from a DataSet. The XmlDataDocument then displays a list of all the ingredients in the Recipes.xml file.

Listing 13.12 XmlDataDocument.aspx
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Xml" %>

<Script Runat="Server">

Sub Page_Load
  Dim dstRecipes As DataSet
  Dim objDataDocument As XmlDataDocument
  Dim objRecipes As XmlNodeList
  Dim objRecipe As XmlNode

  dstRecipes = New DataSet()
  dstRecipes.ReadXml( MapPath( "Recipes.xml" ) )

  objDataDocument = New XmlDataDocument( dstRecipes )
  objRecipes = objDataDocument.GetElementsByTagName( "Ingredient" )
  For Each objRecipe In objRecipes
    lblOutput.Text &= objRecipe.InnerXml
  Next
End Sub

</Script>

<html>
<head><title>XmlDataDocument.aspx</title></head>
<body>

<h2>All Ingredients</h2>
<asp:Label
  ID="lblOutput"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM

NOTE

You must import the System.Xml namespace before using the XmlDataDocument class.

In the Page_Load subroutine in Listing 13.12, an instance of the XmlDataDocument class is created from a DataSet. Next, the GetElementsByTagName() method retrieves a list of nodes that have the tag name Ingredient. Finally, this list of nodes is displayed in a Label control. When the page is displayed, a combined list of ingredients for all the recipes is displayed.

Using the XmlDataDocument class with the DataSet class provides you with a great deal of flexibility. You can use these two classes to provide very different views on the same underlying data. If you want to represent data with tables, columns, and rows, you can use a DataSet. If you want to represent the same data as nodes on a tree, you can use the XmlDataDocument class.


Transforming XML with XSL Stylesheets

The eXtensible Stylesheet Language (XSL) enables you to perform transformations on the contents of an XML document. The .NET classes support the W3C XSL Transformations (XSLT) specification.

NOTE

To learn more details about XSL, visit the W3C Web site at http://www.w3.org/style/xsl.

XSL has several applications. One common use for XSL stylesheets is converting an XML representation of data into nicely formatted HTML that can be displayed in a browser. For example, you can take an XML document that represents a list of recipes and, by using different XSL stylesheets, display the same information in different ways. You might want to display only the recipes for breakfast foods, or you might want to display the recipes in a particular order.

Another use of XSL is for data format conversion. If you have an application that requires data to be organized with a particular format, you can use XSL to convert an XML document into the correct format for the application. In other words, XSL enables you to easily share information between different applications.

You can use XSL stylesheets from within an ASP.NET application in two ways: You can use the ASP.NET Xml control, or you can use the XSLTransform class. You explore both methods in the following sections.


Using the ASP.NET Xml Control

You can use the ASP.NET Xml control to simply display the raw contents of an XML document. Alternatively, you can use the control with an XSL stylesheet to format an XML document.

The Xml control supports the following four properties:

  • DocumentSource— The path to an XML file

  • TransformSource— The path to an XSL file

  • Document— A preloaded XML document

  • Transform— A preloaded XSL stylesheet

If you want to grab an XML file from the file system and transform the XML data with an XSL stylesheet, you can use the DocumentSource and TransformSource properties. For example, the page in Listing 13.13 uses the Xml control to format the contents of the Recipes.xml file (see Figure 13.6).

Listing 13.13 XmlControl.aspx
<html>
<head><title>XMLControl.aspx</title></head>
<body>

<asp:Xml
  DocumentSource="recipes.xml"
  TransformSource="TransformRecipes.xsl"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 13.6. Displaying an XML document with the Xml control.

The Xml control in Listing 13.13 uses an XSL stylesheet named TransformRecipes.xsl to format the contents of the recipes.xml file. The TransformRecipes.xsl file is contained in Listing 13.14.

Listing 13.14 TransformRecipes.xsl
<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="Recipe">
    <h2><xsl:value-of select="Name" /></h2>
    <xsl:for-each select="Ingredients/Ingredient" >
      <li><xsl:value-of select="." /></li>
    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>

The C# version of this code can be found on the CD-ROM.

In Listing 13.14, an XSL stylesheet uses XPath expressions to match elements in an XML file. When an element is matched, a template indicates how the element should be formatted.

NOTE

To learn more details about XPath, visit the W3C Web site at http://www.w3.org/tr/xpath.

In Listing 13.14, the first template matches Recipe elements. Whenever a Recipe element is found, the Name element is formatted with the HTML <H2> tag. Next, each ingredient for a particular recipe is formatted with HTML <LI> tags to create a bulleted list.

You can use the Xml control's Document and Transform properties with preloaded XML documents and XSL stylesheets. For example, the page in Listing 13.15 uses the Xml control to format the contents of a DataSet (see Figure 13.7).

Listing 13.15 DataSetTransform.aspx
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Xml" %>

<Script Runat="Server">

Sub Page_Load
  Dim conNorthwind As SqlConnection
  Dim dadProducts As SqlDataAdapter
  Dim dstProducts As DataSet

  conNorthwind = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Northwind" )
  dadProducts = New SqlDataAdapter( "Select * From Products", conNorthwind )
  dstProducts = New DataSet()
  dadProducts.Fill( dstProducts, "Products" )

  xmlProducts.Document = New XmlDataDocument( dstProducts )
  xmlProducts.TransformSource = "TransformProducts.xsl"
End Sub

</Script>

<html>
<head><title>DataSetTransform.aspx</title></head>
<body>

<asp:Xml
  ID="xmlProducts"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 13.7. Transformed XML document.

In Listing 13.15, a DataSet is populated with the contents of the Products table from the Northwind database. An XmlDataDocument is created from the DataSet and assigned to the Document property of the Xml control. The Xml control uses an XSL stylesheet named TransformProducts.xsl to format the list of products in the DataSet.

The TransformProducts.xsl stylesheet is included in Listing 13.16.

Listing 13.16 TransformProducts.xsl
<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:template match="Products">
    <p>
    <table width="100%" border="1"
      cellpadding="10" cellspacing="0">
      <tr>
        <td bgcolor="yellow">
          <b><xsl:value-of select="ProductName" /> </b>
        </td>
      </tr>
      <tr bgcolor="#eeeeee">
        <td>
          <b>Price:</b>
          $<xsl:value-of select="UnitPrice" />
        </td>
      </tr>
    </table>
    </p>
  </xsl:template>
</xsl:stylesheet>

The C# version of this code can be found on the CD-ROM.

The stylesheet in Listing 13.16 formats the products from the Products database table. It contains one template that matches all the Products elements. Each product is displayed as a separate HTML table. Within the HTML table, both the name and price of the product are displayed.


Using the XslTransform Class

Instead of using the ASP.NET Xml control to transform an XML document with an XSL stylesheet, you can use the XslTransform class. The advantage of using the XslTransform class is that it enables you to work with XSL transforms without necessarily displaying the results. For example, you can use the XslTransform class to transform an XML document and save it to the file system.

NOTE

For more information on using the file system from within an ASP.NET page, see Chapter 25, "Working with the File System."

The page in Listing 13.17 uses the XslTransform class to apply the TransformProducts.xsl stylesheet to the Products table in the Northwind database. The results of the transformation are written directly to the Response object's output stream.

Listing 13.17 Xsl.aspx
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Xml" %>
<%@ Import Namespace="System.Xml.Xsl" %>

<Script Runat="Server">

Sub Page_Load
  Dim conNorthwind As SqlConnection
  Dim dadProducts As SqlDataAdapter
  Dim dstProducts As DataSet
  Dim objDataDocument As XmlDataDocument
  Dim objXsl As XslTransform

  conNorthwind = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Northwind" )
  dadProducts = New SqlDataAdapter( "Select * From Products", conNorthwind )
  dstProducts = New DataSet()
  dadProducts.Fill( dstProducts, "Products" )

  objDataDocument = New XmlDataDocument( dstProducts )

  objXsl = New XslTransform
  objXsl.Load( MapPath( "TransformProducts.xsl" ) )
  objXsl.Transform( objDataDocument, Nothing, Response.OutputStream )
End Sub

</Script>

The C# version of this code can be found on the CD-ROM.

In Listing 13.17, a DataSet is populated with the contents of the Products table from the Northwind database. Next, an instance of the XmlDataDocument class is created from the DataSet. The XmlDataDocument is used with the XslTransform class.

Next, an instance of the XslTransform class is created. The XslTransform's Load method retrieves an XSL stylesheet from the file system, and the Transform method is called. The Transform method converts the contents of the XmlDataDocument using the stylesheet and outputs the results to the Response object's output stream.


Using Strongly Typed DataSets

Normally, when you work with a DataSet, you must work with several collections. To retrieve the value of a certain column in a certain row, you must work with the DataSet's Tables and Rows collections. For example, to display the value of the Title column for all the rows of the Titles table, you must write code that looks like this:

Dim drowDataRow As DataRow
For Each drowDataRow in dstDataSet.Tables( "Titles" ).Rows
  Response.Write( drowDataRow( "Title" ) )
Next

This section examines an alternative way to access the information contained in a DataSet. Instead of accessing data in a DataSet by working with collections, you can access the data by using strongly typed methods and properties specified by an XML schema. In this section, you learn how to create a strongly typed DataSet.

The advantage of using a strongly typed DataSet is subtle, and doing so might not always be worth the effort. However, if you create a strongly typed DataSet, you can create more readable code. With a strongly typed DataSet, you can refer to tables and columns by name.

With a strongly typed DataSet, for example, you can display the value of the Title column for all the rows in the Titles table like this:

Dim drowTitleRow As TitlesRow
For Each drowTitleRow in objTitlesDS.Titles
  Response.Write( drowTitleRow.Title )
Next

When you create a typed DataSet, you create a new class that represents the DataSet. In this example, you created a new class named objTitlesDS that represents the DataSet. After you create this class, you can retrieve a reference to the Titles DataTable by using the Titles property. Furthermore, you can refer to the value of the Title column by using the Title property.

Again, the difference here is not earth shattering. A strongly typed DataSet simply provides you with a slightly more intuitive means of referring to the members of a DataSet.

To create a strongly typed DataSet, you must complete each of the following steps:

  • Create an XML schema that represents the structure of the DataSet.

  • Use the XML schema with the Xsd.exe command-line tool to generate the source code for a class that represents the DataSet.

  • Compile the source code for the class and copy the compiled class into your application's /bin directory.

Now, create a strongly typed DataSet that contains the Titles table from the Pubs database table. The first step is to create an XML schema that specifies the structure of the DataSet and Titles table. You could write this schema by hand; however, an easier way is to use the page in Listing 13.18. It contains an ASP.NET page that will automatically generate the proper XML schema for you.

Listing 13.18 GetXmlSchema.aspx
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Xml" %>

<Script Runat="Server">

Const strTableName As String = "Titles"

Sub Page_Load
  Dim conPubs As SqlConnection
  Dim dadDataAdapter As SqlDataAdapter
  Dim dstDataSet As DataSet
  Dim strXmlSchema As String

  conPubs = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Pubs" )
  dadDataAdapter = New SqlDataAdapter( "Select * From " & strTableName, conPubs )
  dstDataSet = New DataSet()
  dstDataSet.DataSetName = strTableName & "DS"
  dadDataAdapter.Fill( dstDataSet, strTableName )

  strXmlSchema = Server.HTMLEncode( dstDataSet.GetXmlSchema() )
  Response.Write( "<pre>" & strXmlSchema & "</pre>" )
End Sub

</Script>

The C# version of this code can be found on the CD-ROM.

When you execute the page in Listing 13.18, the page displays the XML schema for the Titles table in the Pubs database. If you want to display the schema for another table, you can modify the strTableName constant and change the database connection string to refer to the proper database.

When you execute the page in Listing 13.18, the XML schema in Listing 13.19 is displayed.

Listing 13.19 TitlesDS.xsd
<xsd:schema id="TitlesDS" targetNamespace="" xmlns=""
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
  <xsd:element name="TitlesDS" msdata:IsDataSet="true">
    <xsd:complexType>
      <xsd:choice maxOccurs="unbounded">
        <xsd:element name="Titles">
          <xsd:complexType>
            <xsd:sequence>
              <xsd:element name="title_id" type="xsd:string" minOccurs="0" />
              <xsd:element name="title" type="xsd:string" minOccurs="0" />
              <xsd:element name="type" type="xsd:string" minOccurs="0" />
              <xsd:element name="pub_id" type="xsd:string" minOccurs="0" />
              <xsd:element name="price" type="xsd:decimal" minOccurs="0" />
              <xsd:element name="advance" type="xsd:decimal" minOccurs="0" />
              <xsd:element name="royalty" type="xsd:int" minOccurs="0" />
              <xsd:element name="ytd_sales" type="xsd:int" minOccurs="0" />
              <xsd:element name="notes" type="xsd:string" minOccurs="0" />
              <xsd:element name="pubdate" type="xsd:dateTime" minOccurs="0" />
            </xsd:sequence>
          </xsd:complexType>
        </xsd:element>
      </xsd:choice>
    </xsd:complexType>
  </xsd:element>
</xsd:schema>

The C# version of this code can be found on the CD-ROM.

If you look closely at the schema, you notice that the schema's ID is the name of the DataSet (TitlesDS), and the schema includes a Titles element that represents the Titles database table.

Copy the schema generated by the GetXMLSchema.aspx page to a new file and save the file with the name TitlesDS.xsd. You need to use this schema when generating the source code for the strongly typed DataSet class.

The next step is to generate the source code for the class. You use the Schema Definition Tool (Xsd.exe) from the command line. Open a DOS prompt and change to the directory that contains the TitlesDS.xsd file. Next, execute the following statement from the command line:

Xsd.exe /d /l:vb TitlesDS.xsd

This statement generates the source for a new Visual Basic class file named TitlesDS.vb. The /d directive indicates that a DataSet should be generated, and the /l directive specifies the language to use for the class file.

If you are curious, go ahead and open the TitlesDS.vb file in Notepad. The class file contains methods and properties that correspond to the tables and columns contained in the TitlesDS DataSet.

NOTE

You can use the Xsd.exe tool to generate the class in a particular namespace. To do so, use the /n: directive and supply a namespace when generating the source for the class file.

Now that you have the source for the class file, you need to compile it. Execute the following statement from the command line:

vbc /t:library /r:System.dll,System.Data.dll,System.Xml.dll TitlesDS.vb

After you execute this command, a new file named TitlesDS.dll is created.

The final step is to copy the compiled class file to the application /bin directory. If the /bin directory does not exist at the root of your ASP.NET application, you can create it. Copying the compiled class to the /bin directory makes the class visible to all your ASP.NET pages.

The page in Listing 13.20 illustrates how you can use the typed DataSet in an ASP.NET page. The page displays the values of the Title and Notes columns for all the rows in the Titles database table.

Listing 13.20 TypedDataSet.aspx
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
  Dim conPubs As SqlConnection
  Dim dadTitles As SqlDataAdapter
  Dim objTitlesDS As TitlesDS
  Dim objTitleRow As TitlesDS.TitlesRow

  ' Fill the typed dataset
  conPubs = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=Pubs" )
  dadTitles = New SqlDataAdapter( "Select * From Titles", conPubs )
  objTitlesDS = New TitlesDS()
  dadTitles.Fill( objTitlesDS, "Titles" )

  ' Display its titles
  For each objTitleRow in objTitlesDS.Titles
    Response.Write( "<br>" & objTitleRow.Title )
    If Not objTitleRow.IsNotesNull Then
      Response.Write( "<br>" & objTitleRow.Notes )
    End If
    Response.Write( "<hr>" )
  Next
End Sub

</Script>

The C# version of this code can be found on the CD-ROM.

In Listing 13.20, the Fill() method fills the typed DataSet with all the rows from the Titles table. After the rows are added, a For...Each loop displays the values of the Titles and Notes columns for each row.

Notice that the Titles table is returned as a property of the DataSet. Also, notice that each column name is returned as a property of the TitlesRow object. Finally, IsNotesNull is a method of the typed DataSet. This method returns True when the Notes column has the value Null and False otherwise.


Summary

In this chapter, you learned several methods of using XML and XML schemas within an ASP.NET application. In the first section, you learned how to load XML data into a DataSet by using the ReadXML() and ReadXMLSchema() methods.

Next, you were introduced to the XmlDataDocument class. You learned how to create an XmlDataDocument that represents the data from a DataSet as nodes in a tree.

The chapter also discussed two methods of applying XSL transforms to XML data. You learned how to use the ASP.NET Xml control. You also were introduced to the XslTransform class.

Finally, you learned how to build a typed DataSet with an XML schema. You examined how to use a typed DataSet to provide a more intuitive representation of the contents of a DataSet.


Chapter 14. Using ADO.NET to Create a Search Page

IN THIS CHAPTER

  • Using SQL Server Full-Text Search

  • Using the Microsoft Indexing Service

In this chapter, you learn how to build a search page using ADO.NET. By the end of this chapter, you'll understand how you can build a search page similar to the type used by such Web sites as Yahoo! and AltaVista.

In the first part, you examine how to create a search page that enables users to search through data stored in a database table. You learn how to enable users to perform both free-form and Boolean queries.

Next, you learn how to construct a search page that enables users to search through data stored in the file system. You examine how to create a page that works with the Microsoft Indexing Service.


Using SQL Server Full-Text Search

Microsoft SQL Server 7.0 and higher includes the Full-Text Search Service. You can use this service to perform queries that you could not efficiently perform with a standard SQL SELECT statement. For example, the Full-Text Search Service enables you to do the following:

  • Search for particular combinations of words or phrases

  • Search for a word with a particular prefix

  • Search for inflectional variations of verbs and nouns

  • Search for words that appear near each other

  • Search for weighted terms

Imagine that you want to build a job site. In that case, you need to create a page that enables users to search through resumes or job descriptions that contain certain key phrases.

Or, imagine that you are creating an online store. In that case, you need to build a page that enables users to search through product descriptions. The Full-Text Search Service was designed for these types of queries.

Configuring the Full-Text Search Service

Before you can use the SQL Server Full-Text Search Service, you must complete the following three steps:

  1. Install and start the Full-Text Search Service.

  2. Define full-text indexing on a table.

  3. Populate the full-text index.

The Full-Text Search Service is not installed by default when you install Microsoft SQL Server. You must choose the custom installation option to install the Search Service. You can check whether the Search Service is installed on your server by opening the SQL Server Service Manager and looking for a service named Search Service (see Figure 14.1). If you cannot find the Search Service, you need to rerun the Setup program from the SQL Server installation disk.

Figure 14.1. The SQL Server Service Manager.

After the Search Service is installed, you can start the service by opening the SQL Server Service Manager, selecting the Search Service, and clicking the Start/Continue button.

CAUTION

After you define full-text indexing on a table, you cannot change the columns in a table without removing its full-text index. Because populating a full-text index for a large table can take several hours, dropping and re-adding a full-text index to a table can be a very unpleasant experience.

The next step is to define full-text indexing on a table by completing these steps:

  1. Launch the SQL Server Enterprise Manager.

  2. Navigate to a particular database table.

  3. Right-click the name of the table.

  4. Choose Full-Text Index Table.

  5. Select Define Full-Text Indexing on a Table.

Completing these steps launches the SQL Server Full-Text Indexing Wizard, which enables you to select several options:

  • The unique index to use when indexing the table for full-text search. You cannot use the Search Service with tables that do not have a unique index. The Search Service needs a unique index to match the items in the full-text index with the rows in the table. Typically, the unique index is the primary key of the table.

  • The character columns to use when indexing the table for a full-text search. The Full-Text Search Service can index only character columns such as VarChar, Text, and Char. Microsoft SQL Server 2000 also supports the indexing of Image columns.

  • The catalog to use for the full-text index. A full-text index is stored in a catalog. If a catalog doesn't already exist, the wizard provides you with the option of creating one.

  • The schedule to use for indexing the table for a full-text search. The wizard provides you with the option of creating a scheduled job for indexing the table. For the job to execute, the SQL Server Agent Service must be started. (You can start the Agent Service from the SQL Server Service Manager.)

  • Unlike a normal table index, a full-text index is not updated automatically. If you do not populate the index, it will become outdated when new rows are added to the table or existing rows are modified.

  • You can provide any population schedule you want. For example, you can repopulate the index every hour, once a day, or once a month.

  • You are also provided with the option of performing either a full population or incremental population. A full population repopulates the index starting from scratch. An incremental population scans the table for changed or modified rows. An incremental population can be performed much faster than a full population. However, if you want to perform an incremental population efficiently, the table must contain a TimeStamp column.

The final step is to populate the full-text index. When a full-text index is first created, it's empty. To populate the index, you need to populate the catalog that contains the index.

NOTE

Microsoft SQL Server 2000 also supports a feature called Change Tracking. You can use Change Tracking to automatically update the full-text index when rows in a database table are modified. To learn more details, see the SQL Server Books Online.

You can view the number of items currently indexed in a catalog and total size of the catalog by launching the SQL Server Enterprise Manager, clicking the Catalogs folder for a particular database, and double-clicking the name of a catalog (see Figure 14.2).

Figure 14.2. Displaying the number of indexed items.

To populate the catalog, right-click the name of the catalog and select Start Full Population. Depending on the size of the table, populating the catalog can take several hours.

Performing Free Text Queries with Database Data

When most people perform a search at a Web site, they do not perform a Boolean query. Most people do not understand, or care about, Boolean operators such as AND, OR, and NOT. Instead, users typically enter a few keywords in a search box and hope for the best results.

The Full-Text Search Service includes support for free text queries. A free text query can consist of any set of words, any set of phrases, or even a sentence. The Search Service attempts to identify significant phrases in the query and return a matching set of records.

In the following sections, you learn how to create a search page by using the FREETEXT predicate and FREETEXTTABLE function.

Using the FREETEXT Predicate

The FREETEXT predicate accepts two parameters. The first parameter represents the column to search. You can supply either a single column name or the * character to search all columns in the table. (You can search only columns that have been enabled for full-text indexing.) The second parameter represents the search phrase.

The following sample statement searches the Title column in the Titles table for the phrase secret computer:

SELECT Title FROM Titles
WHERE FREETEXT( Title, 'secret computer' )

When you execute this query, the following results are returned:

Onions, Leeks, and Garlic: Cooking Secrets of the Mediterranean
Cooking with Computers: Surreptitious Balance Sheets
Computer Phobic AND Non-Phobic Individuals: Behavior Variations
Secrets of Silicon Valley
You Can Combat Computer Stress!
Straight Talk About Computers
The Psychology of Computer Cooking

Notice that all the titles returned by the query contain some variation of the word secret or computer. For example, the first title contains the word Secrets.

The page in Listing 14.1 illustrates how you can use the FREETEXT predicate in an ASP.NET page (see Figure 14.3).

Listing 14.1 FreeText.aspx
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<Script Runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  Dim conPubs As SqlConnection
  Dim strSearch As String
  Dim cmdSearch As SqlCommand
  Dim dtrSearch As SqlDataReader

  conPubs = New SqlConnection( "Server=Localhost;UID=sa;PWD=secret; Database=Pubs" )
  strSearch = "SELECT Title FROM Titles WHERE FREETEXT( Title, @searchphrase )"
  cmdSearch = New SqlCommand( strSearch, conPubs )
  cmdSearch.Parameters.Add( "@searchphrase", txtSearchPhrase.Text )
  conPUbs.Open
  dtrSearch = cmdSearch.ExecuteReader()
  While dtrSearch.Read
    lblResults.Text &= "<li>" & dtrSearch( "Title" )
  End While
  conPubs.Close
End Sub

</Script>

<html>
<head><title>FreeText.aspx</title></head>
<body>
<form Runat="Server">

<h2>Search Titles:</h2>
<asp:TextBox
  ID="txtSearchPhrase"
  Columns="50"
  Runat="Server" />
<asp:Button
  Text="Search!"
  OnClick="Button_Click"
  Runat="Server" />
<hr>
<asp:Label
  ID="lblResults"
  EnableViewState="False"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 14.3. Using the FREETEXT predicate.

NOTE

You'll need to set up full-text indexing on the Titles table before the page in Listing 14.3 will work. See the earlier section in this chapter, "Configuring the Full-Text Search Service."

The page in Listing 14.1 contains a form with a single TextBox control. If you enter a search phrase in the text box and submit the form, the Button_Click subroutine is executed.

The Button_Click subroutine uses a SqlCommand object to execute a query containing the FREETEXT predicate. The results of the query are displayed in a Label control.

Using the FREETEXTTABLE Function

You can use the FREETEXTTABLE function to perform the same type of free text query that the FREETEXT predicate performs. Like the FREETEXT predicate, the FREETEXTTABLE function searches for a set of words, a phrase, or a sentence. The FREETEXTTABLE function, however, returns a table of matching results.

The advantage of using the FREETEXTTABLE function is that it enables you to display a ranking next to each result. You can display how well each result matched the query.

The FREETEXTTABLE function returns a table with two columns: KEY and RANK. The KEY column represents the unique key of the record associated with the match. The RANK column represents how well the result matched the query. This column returns a value between 0 and 1000, where 1000 represents the best match and 0 represents the worst match.

The FREETEXTTABLE function accepts three parameters: the table to search, the columns to search, and the search phrase. For example, the following query searches the Titles table for all titles that match the search phrase A book about Silicon Valley:

SELECT * FROM
FREETEXTTABLE( Titles, Title, 'A book about Silicon Valley' )

Notice that FREETEXTTABLE is used in this query just like a table name; the function returns a table. This query returns the following results:

KEY    RANK
------ -----------
MC2222 174
PC8888 174

(2 row(s) affected)

This query returned two records. The values in the KEY column match the unique keys of two records in the Titles table. The two records matched the search phrase equally well, with the value 174.

Typically, you use the FREETEXTTABLE function in a query that joins the table returned by the function with the table being searched. For example, the following query joins the table returned by the function to the Titles table:

SELECT RANK, Title FROM Titles,
FREETEXTTABLE( Titles, Title, 'computer cooking' ) searchTable
WHERE [KEY] = Titles.Title_ID
ORDER BY RANK DESC

The preceding statement joins the table returned by the FREETEXTTABLE function to the Titles table by using the common KEY and Title_ID columns. This query returns the following set of records:

RANK        Title
----------- ---------------------------------------------------------------
224         The Psychology of Computer Cooking
2           Cooking with Computers: Surreptitious Balance Sheets
0           Computer Phobic AND Non-Phobic Individuals: Behavior Variations
0           Onions, Leeks, and Garlic: Cooking Secrets of the Mediterranean
0           Straight Talk About Computers
0           You Can Combat Computer Stress!

(6 row(s) affected)

The page in Listing 14.2 demonstrates how you can perform a search by using the FREETEXTTABLE function in an ASP.NET page (see Figure 14.4).

Listing 14.2 FreeTextTable.aspx
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<Script Runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  Dim conPubs As SqlConnection
  Dim strSearch As String
  Dim cmdSearch As SqlCommand
  Dim dtrSearch As SqlDataReader

  conPubs = New SqlConnection( "Server=Localhost;UID=sa;PWD=secret; Database=Pubs" )

  strSearch = "SELECT RANK, Title FROM Titles," & _
    "FREETEXTTABLE( Titles, Title, @searchphrase ) searchTable " & _
    "WHERE [KEY] = Titles.Title_ID " & _
    "ORDER BY RANK DESC "
  cmdSearch = New SqlCommand( strSearch, conPubs )
  cmdSearch.Parameters.Add( "@searchphrase", txtSearchphrase.Text )
  conPubs.Open()
  dtrSearch = cmdSearch.ExecuteReader()
  lblResults.Text = "<table border=1 cellspacing=0 cellpadding=4>"
  While dtrSearch.Read
    lblResults.Text &= "<tr>"
    lblResults.Text &= "<td align=right>&nbsp;"
    lblResults.Text &= ShowRank( dtrSearch( "RANK" ) )
    lblResults.Text &= "</td>"
    lblResults.Text &= "<td>" & dtrSearch( "Title" ) & "</td>"
    lblResults.Text &= "</tr>"
  End While
  lblResults.Text &= "</table>"
  conPubs.Close()
End Sub

Function ShowRank( intRank As Integer ) As String
  Dim intCounter As Integer
  Dim strRank As String

  strRank = "<font color=green>"
  For intCounter = 1 to intRank / 10
    strRank &= "*"
  Next
  strRank &= "</font>"
  Return strRank
End Function

</Script>

<html>
<head><title>FreeTextTable.aspx</title></head>
<body>

<form Runat="Server">

<h2>Search Titles:</h2>
<asp:TextBox
  ID="txtSearchPhrase"
  Columns="50"
  Runat="Server" />
<asp:Button
  Text="Search!"
  OnClick="Button_Click"
  Runat="Server" />
<hr>
<asp:Label
  ID="lblResults"
  EnableViewState="False"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 14.4. Using the FREETEXTTABLE function.

The page in Listing 14.2 displays a search form. When you submit a search phrase, the Button_Click subroutine is executed. This subroutine uses a FULLTEXTTABLE query to retrieve matching results from the Titles table. The results are displayed in a Label control.

The page also includes a function named ShowRank() that converts the value of RANK into a string of * characters. This string is displayed next to each search result.

Performing Boolean Queries with Database Data

The FREETEXT predicate and FREETEXTTABLE function were designed to enable users to execute free-form queries. If you attempt to use Boolean operators in a free text query, the operators are completely ignored.

To perform Boolean queries, you need to use either the CONTAINS predicate or CONTAINSTABLE function. Both the CONTAINS predicate and CONTAINSTABLE function correctly interpret Boolean operators as well as other advanced search options such as proximity operators and weighted search terms.

Using the CONTAINS Predicate

The CONTAINS predicate accepts two parameters: the search column and search phrase. You can list a single column name for the search column or use the * character to represent all columns. You can search only columns that have been enabled for full-text indexing.

The following sample query returns all records from the Titles table that contain the word Computer but not the word Cooking:

SELECT Title FROM Titles
WHERE CONTAINS( Title, 'Computer and not Cooking' )

This query returns the following results:

Computer Phobic AND Non-Phobic Individuals: Behavior Variations
You Can Combat Computer Stress!

However, it does not return the following title from the Titles table:

The Psychology of Computer Cooking

The search phrase must contain a valid query. For example, the following query generates an error:

SELECT Title FROM Titles
WHERE CONTAINS( Title, 'Computers AND' )

Executing this query generates a syntax error. The problem results from the dangling Boolean AND operator. When you execute a CONTAINS query that generates an error with ASP.NET, an empty result set is returned.

The page in Listing 14.3 illustrates the proper way of executing a CONTAINS query in an ASP.NET page (see Figure 14.5).

Listing 14.3 Contains.aspx
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<Script Runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  Dim conPubs As SqlConnection
  Dim strSearch As String
  Dim cmdSearch As SqlCommand
  Dim dtrSearch As SqlDataReader

  conPubs = New SqlConnection( "Server=Localhost;UID=sa;PWD=secret; Database=Pubs" )
  strSearch = "SELECT Title FROM Titles WHERE CONTAINS( title, @searchphrase )"
  cmdSearch = New SqlCommand( strSearch, conPubs  )
  cmdSearch.Parameters.Add( "@searchphrase", txtSearchphrase.Text )
  conPubs.Open()
  dtrSearch = cmdSearch.ExecuteReader()
  While dtrSearch.Read()
    lblResults.Text &= "<li>" & dtrSearch( "Title" )
  End While
  conPubs.Close
End Sub

</Script>

<html>
<head><title>Contains.aspx</title></head>
<body>
<form Runat="Server">

<h2>Search Titles:</h2>
<asp:TextBox
  ID="txtSearchPhrase"
  Columns="50"
  Runat="Server" />
<asp:Button
  Text="Search!"
  OnClick="Button_Click"
  Runat="Server" />
<hr>
<asp:Label
  ID="lblResults"
  EnableViewState="False"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 14.5. Using the CONTAINS predicate.

Using the CONTAINSTABLE Function

The CONTAINSTABLE function is similar to the CONTAINS predicate. However, the CONTAINSTABLE function returns a table. You use this function when you need to display a ranking next to each search result.

The CONTAINSTABLE function returns a table that contains two columns: KEY and RANK. The KEY column represents the unique key associated with each result. The RANK column represents how well each result matches the query. RANK contains a value between 0 and 1000, where higher numbers represent better matches.

The CONTAINSTABLE function accepts three parameters: the table to search, the columns to search, and the search phrase. You can supply a single column name for the column or the * character to search all columns enabled for full-text indexing.

The following query, for example, performs a search on the Title column of the Titles table for the search phrase Cooking AND NOT Computer:

SELECT * FROM
CONTAINSTABLE( Titles, Title, 'Cooking AND NOT Computer' )

This query returns the following results:

KEY    RANK
------ -----------
BU1111 32
TC3218 16

(2 row(s) affected)

The values of the KEY column represent values in the unique Title_ID column in the Titles table. The values of the RANK column represent how well each result matches the query.

Typically, you join the table returned by the CONTAINSTABLE function with the table being searched. For example, the following query joins the table returned by the CONTAINSTABLE function with the Titles table:

SELECT RANK, Title FROM Titles,
CONTAINSTABLE( Titles, Title, 'Cooking AND NOT Computer' ) searchTable
WHERE [KEY] = Titles.Title_ID
ORDER BY RANK DESC

This query returns the following results:

RANK        Title
----------- ---------------------------------------------------------------
32          Cooking with Computers: Surreptitious Balance Sheets
16          Onions, Leeks, and Garlic: Cooking Secrets of the Mediterranean

(2 row(s) affected)

The query returns both the RANK and Title for each search result.

Notice that the query Cooking AND NOT Computer does not exclude the result Cooking with Computers: Surreptitious Balance Sheets. The word Computer does not match the word Computers. In other words, the CONTAINS predicate and CONTAINSTABLE function do not automatically handle pluralization.

You can use prefix terms with the CONTAINSTABLE predicate. To use a prefix term, you must enclose the search term in quotation marks and use * as a wildcard character like this:

SELECT RANK, Title FROM Titles,
CONTAINSTABLE( Titles, Title, 'Cooking AND NOT "Computer*"' ) searchTable
WHERE [KEY] = Titles.Title_ID
ORDER BY RANK DESC

This query excludes titles that contain either the word Computer or the word Computers.

You also can use the CONTAINSTABLE function to match inflectional variations of a word. For example, the following query matches both Cook and Cooking:

SELECT RANK, Title FROM Titles,
CONTAINSTABLE( Titles, Title, 'FormsOf( INFLECTIONAL, Cook )' ) searchTable
WHERE [KEY] = Titles.Title_ID
ORDER BY RANK DESC

This example uses the FormsOf operator to find inflectional variations of the word Cook.

You can also use the CONTAINSTABLE function with a proximity operator. For example, you might want results in which the word Computer appears close to the word Cooking to be ranked higher than results in which the word Computer appears far from the word Cooking. The following query illustrates how you can use the NEAR proximity operator:

SELECT RANK, Title FROM Titles,
CONTAINSTABLE( Titles, Title, 'Cooking NEAR Computers' ) searchTable
WHERE [KEY] = Titles.Title_ID
ORDER BY RANK DESC

The page in Listing 14.4 illustrates how you can use the CONTAINSTABLE function in an ASP.NET page (see Figure 14.6).

Listing 14.4 ContainsTable.aspx
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<Script Runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  Dim conPubs As SqlConnection
  Dim strSearch As String
  Dim cmdSearch As SqlCommand
  Dim dtrSearch As SqlDataReader

  conPubs = New SqlConnection( "Server=Localhost;UID=sa;PWD=secret; Database=Pubs" )
  strSearch = "SELECT RANK, Title FROM Titles," & _
    "CONTAINSTABLE( Titles, Title, @searchphrase ) searchTable " & _
    "WHERE [KEY] = Titles.Title_ID " & _
    "ORDER BY RANK DESC "
  cmdSearch = New SqlCommand( strSearch, conPubs )
  cmdSearch.Parameters.Add( "@searchphrase", txtSearchphrase.Text )
  conPubs.Open()
  dtrSearch = cmdSearch.ExecuteReader()
  lblResults.Text = "<table border=1 cellspacing=0 cellpadding=4>"
  While dtrSearch.Read
    lblResults.Text &= "<tr>"
    lblResults.Text &= "<td align=right>&nbsp;"
    lblResults.Text &= ShowRank( dtrSearch( "RANK" ) )
    lblResults.Text &= "</td>"
    lblResults.Text &= "<td>" & dtrSearch( "Title" ) & "</td>"
    lblResults.Text &= "</tr>"
  End While
  lblResults.Text &= "</table>"
  conPubs.Close
End Sub

Function ShowRank( intRank As Integer ) As String
  Dim intCounter As Integer
  Dim strRank As String
  strRank = "<font color=green>"
  For intCounter = 1 to intRank \ 10
    strRank &= "*"
  Next
  strRank &= "</font>"
  Return strRank
End Function

</Script>

<html>
<head><title>ContainsTable.aspx</title></head>
<body>

<form Runat="Server">

<h2>Search Titles:</h2>
<asp:TextBox
  ID="txtSearchPhrase"
  Columns="50"
  Runat="Server" />
<asp:Button
  Text="Search!"
  OnClick="Button_Click"
  Runat="Server" />
<hr>
<asp:Label
  ID="lblResults"
  EnableViewState="False"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 14.6. Using the CONTAINSTABLE predicate.

Uploading and Indexing Binary Documents

Microsoft SQL Server 2000, but not earlier versions, supports indexing documents stored in an image column. You can use this feature to store and index Microsoft Word documents, PowerPoint presentations, HTML, XML, and text documents.

In the following sections, you learn how to create an ASP.NET application that enables you to upload documents, such as Microsoft Word documents, to a Microsoft SQL Server 2000 database and search through the documents. You could use this application, for example, to create a central document repository for a company intranet.

To create this application, you need to build three ASP.NET pages:

  • DBUpload.aspx— You use this page to upload files to a Microsoft SQL Server 2000 database table.

  • DBUploadSearch.aspx— You use this page to search through binary files stored in a database table.

  • showDBUpload.aspx— You use this file to retrieve and display binary files stored in the database table.

Configuring the Database

Before you do anything else, you need to create a database table named Uploads to contain your documents. You can create this table by executing the SQL CREATE TABLE statement included in Listing 14.5 within Microsoft Query Analyzer.

Listing 14.5 CreateUploadsTable.sql
Create Table Uploads
(
  u_id INT NOT NULL IDENTITY Primary Key,
  u_title Varchar( 200 ),
  u_document Image,
  u_documentType Varchar( 50 ),
  u_entrydate DATETIME Default getDate()
)

The C# version of this code can be found on the CD-ROM.

Next, you need to enable full-text indexing for this table by completing the following steps:

  1. Launch Microsoft SQL Server Enterprise Manager.

  2. Navigate to the Uploads table in your database.

  3. Right-click the name of the table and select Full-Text Index Table and then Define Full-Text Indexing on a Table.

Completing these steps launches the Full-Text Indexing Wizard. After the wizard opens, enter the following options:

  • Select the u_title and u_document columns for full-text indexing. You need to specify the column that contains the document type for the u_document column. Select the u_documentType column for the document type column.

  • Select any full-text catalog for storing your full-text index. If one doesn't already exist, you can create it.

  • Don't select a table population schedule. You'll configure the full-text index to be automatically updated.

After you complete the wizard, you must perform one last configuration step. So that the full-text index is updated automatically as soon as someone uploads a new file to the Uploads database table, you need to enable Change Tracking for the Uploads table. To enable Change Tracking, right-click the Uploads table, choose Full-Text Index Table, and then Change Tracking.

To update the full-text index immediately after changes to the Uploads table, right-click the Uploads table and choose Full-Text Index Table and then Update Index in Background.

Now that you've made these changes, whenever a new document is added to the Upload table's image column, the full-text index is automatically updated.

Creating the Database Upload Page

The first ASP.NET database page that you need to create is DBUpload.aspx. This page enables users to select a file from their hard drive to upload to the database server. The complete listing for the DBUpload.aspx page is included in Listing 14.6 (see Figure 14.7).

Listing 14.6 DBUpload.aspx
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  Dim strFileExtension As String
  Dim strFileType As String
  Dim intFileLen As Integer
  Dim objStream As Stream
  Dim conMyData As SqlConnection
  Dim strInsert As String
  Dim cmdInsert As SqlCommand

  If Not IsNothing( txtFileContents.PostedFile ) Then
    ' Determine File Type
    strFileExtension = RIGHT( txtFileContents.PostedFile.FileName, 4 )
    SELECT Case strFileExtension.ToLower
      CASE ".doc"
        strFileType = "doc"
      CASE ".ppt"
        strFileType = "ppt"
      CASE ".htm"
        strFileType = "htm"
      CASE "html"
        strFileType = "htm"
      CASE ELSE
        strFileType = "txt"
    End SELECT

    ' Grab the contents of uploaded file
    intFileLen = txtFileContents.PostedFile.ContentLength
    Dim arrFile( intFileLen ) As Byte
    objStream = txtFileContents.PostedFile.InputStream
    objStream.Read( arrFile, 0, intFileLen )

    ' Add Uploaded file to database
    conMyData = New SqlConnection( "Server=Localhost;UID=sa;PWD=secret; Database=myData" )
    strInsert = "Insert Uploads ( u_title, u_documentType, u_document ) " & _
      "Values ( @title, @fileType, @document )"
    cmdInsert = New SqlCommand( strInsert, conMyData )
    cmdInsert.Parameters.Add( "@title", txtfileTitle.Text )
    cmdInsert.Parameters.Add( "@fileType", strFileType )
    cmdInsert.Parameters.Add( "@document", arrFile )
    conMyData.Open()
    cmdInsert.ExecuteNonQuery()
    conMyData.Close()
  End If
End Sub

</Script>

<html>
<head><title>DBUpload.aspx</title></head>
<body>

<form
  enctype="multipart/form-data"
  Runat="Server">

<h2>Upload File:</h2>

<b>File Title:</b>
<br>
<asp:TextBox
  ID="txtFileTitle"
  Runat="Server" />
<asp:RequiredFieldValidator
  ControlToValidate="txtFileTitle"
  Text="Required!"
  Runat="Server" />
<p>
<b>File:</b>
<br>
<input
  id="txtFileContents"
  type="file"
  Runat="Server" />
<p>
<asp:Button
  Text="Upload File!"
  OnClick="Button_Click"
  Runat="Server" />

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 14.7. Uploading a document to the database.

The DBUpload.aspx page in Listing 14.6 contains a form with a file upload button. For example, you can select a Microsoft Word document from your local hard drive, submit the form, and the file is added to the Uploads database table.

The uploaded file is added to the database table in the Button_Click subroutine. The uploaded file is converted into a byte array and then added to the Upload table's u_document column. Because Change Tracking is enabled on the Uploads table, any document you upload is immediately indexed.

Creating the Database Search Page

The DBUploadSearch.aspx page enables users to search through all the files that have been uploaded to the database server. This page enables users to perform free-form queries. The complete code for the DBUploadSearch.aspx file is contained in Listing 14.7 (see Figure 14.8).

Listing 14.7 DBUploadSearch.aspx
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<Script Runat="Server">

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 RANK, u_id, u_title FROM Uploads," & _
    "FREETEXTTABLE( Uploads, *, @searchphrase ) searchTable " & _
    "WHERE [KEY] = uploads.u_ID " & _
    "ORDER BY RANK DESC "
  cmdSearch = New SqlCommand( strSearch, conMyData )
  cmdSearch.Parameters.Add( "@searchphrase", txtSearchphrase.Text )
  conMyData.Open()
  dtrSearch = cmdSearch.ExecuteReader()
  lblResults.Text = "<table border=1 cellspacing=0 cellpadding=4>"
  While dtrSearch.Read
    lblResults.Text &= "<tr>"
    lblResults.Text &= "<td align=right>"
    lblResults.Text &= dtrSearch( "RANK" ) / 10
    lblResults.Text &= "%</td>"
    lblResults.Text &= "<td><a href="""
    lblResults.Text &= String.Format( "ShowDBUpload.aspx?id={0}", dtrSearch( "u_id" ) )
    lblResults.Text &= """>" & dtrSearch( "u_title" )
    lblResults.Text &= "</a></td>"
    lblResults.Text &= "</tr>"
  End While
  lblResults.Text &= "</table>"
  conMyData.Close
End Sub

</Script>

<html>
<head><title>DBUploadSearch.aspx</title></head>
<body>

<form Runat="Server">

<h2>Search Uploads:</h2>
<asp:TextBox
  ID="txtSearchPhrase"
  Columns="50"
  Runat="Server" />
<asp:Button
  Text="Search!"
  OnClick="Button_Click"
  Runat="Server" />
<hr>
<asp:Label
  ID="lblResults"
  EnableViewState="False"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 14.8. Searching uploaded documents.

The DBUploadSearch.aspx page contained in Listing 14.7 contains a search form. When you submit a search phrase, a query is constructed by using the FREETEXTTABLE function. This query is executed against all the free-text indexed columns in the Uploads database table. The results of the query are displayed in a Label control.

Each search result is displayed as a hypertext link. If you click a link, you go to the ShowDBUpload.aspx page. When you link to the ShowDBUpload.aspx page, a query string variable named ID representing the unique ID of the document that you selected is passed.

Creating the Show Database Upload Page

The ShowDBUpload.aspx page displays a particular document from the Uploads database table. The complete code for this page is contained in Listing 14.8.

Listing 14.8 ShowDBUpload.aspx
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
  Dim intItemID As Integer
  Dim conMyData As SqlConnection
  Dim strSelect As String
  Dim cmdSelect As SqlCommand
  Dim dtrSearch As SqlDataReader

  intItemID = Request.Params( "id" )
  conMyData = New SqlConnection( "Server=Localhost;UID=sa;PWD=secret; Database=myData" )
  strSelect = "SELECT u_documentType, u_document From Uploads " & _
    "WHERE u_id=@itemID"
  cmdSelect = New SqlCommand( strSelect, conMyData )
  cmdSelect.Parameters.Add( "@itemID", intItemID )
  conMyData.Open()
    dtrSearch = cmdSelect.ExecuteReader()
    If dtrSearch.Read
      ' Set Content Type
      Response.ClearHeaders()
      Select Case dtrSearch( "u_documentType" )
        CASE "doc"
          Response.ContentType = "application/msword"
        CASE "ppt"
          Response.ContentType = "application/ppt"
        CASE "txt"
          Response.ContentType = "text/plain"
      End Select
      Response.BinaryWrite( dtrSearch( "u_document" ) )
    End IF
    dtrSearch.Close
   conMyData.Close
End Sub

</Script>

The C# version of this code can be found on the CD-ROM.

The ShowDBUpload.aspx page contained in Listing 14.8 contains a single Page_Load subroutine. Within the subroutine, the value of the ID query string parameter is retrieved and assigned to a local variable named intItemID. Next, the database record matching the ID is retrieved from the Uploads table.

The content type of the page is set with the ContentType property of the Response object. For example, if the document being retrieved from the database is a Microsoft Word document, the ContentType property is set to the value application/msword.

The contents of the image column are output with the BinaryWrite method of the Response object. Because the image column contains binary data, not just text data, you need to use the BinaryWrite method rather than the normal Write method.

CAUTION

Netscape and Internet Explorer behave differently when you open a document with the ShowDBUpload.aspx page. For example, if you use this page to view a Microsoft Word document with Internet Explorer, the Word document opens automatically without any user interaction. If you open a Microsoft Word document with Netscape, on the other hand, you are asked whether you want to open the file or save the file to disk.

Opening PowerPoint presentations can be especially tricky. Netscape displays a dialog box that enables you to launch PowerPoint. Internet Explorer displays the raw contents of the PowerPoint presentation in a text file.


Using the Microsoft Indexing Service

In the first part of this chapter, you examined how to search documents stored in a database table. For the remainder of this chapter, you examine how to search through static documents stored on the file system. You learn how to use the Microsoft Indexing Service with an ASP.NET page.

In the following sections, you learn how to create both a search page that enables you to perform free-form queries and a search page that enables you to perform Boolean queries. You also learn how to work with document properties.

Configuring the Microsoft Indexing Service

Microsoft Windows 2000 includes the Microsoft Indexing Service as a component of the operating system. In fact, if you have ever searched for a file by clicking Start, Search, For Files or Folders, you have already used the Indexing Service.

Before you can use the service in an ASP.NET page, you must verify that the Indexing Service has been started. To do so, follow these steps:

  1. Go to Start, Program Files, Administrative Tools, Computer Management.

  2. Expand the Services and Applications node.

  3. Select Indexing Service.

  4. Click the Start Indexing button (the VCR Run button).

After you start the Indexing Service, it runs continuously in the background. The service automatically scans the directories selected for indexing for new or changed files.

The indexes are stored in catalogs. Windows 2000 includes two default catalogs: System and Web. You use the Web catalog to index all your pages for your Web applications.

You can index any directory accessible to your Web server. You can enable indexing for a directory within the Internet Services Manager. To do this, launch the Internet Services Manager, open the property sheet for a directory, select the Directory tab, and check the option Index This Resource.

After you enable indexing for a directory, you must be patient. It might take several minutes for the Indexing Service to start scanning the directory. If you are impatient, you can force the service to scan a directory within Computer Management by right-clicking the name of the directory, selecting All Tasks, and then selecting Rescan.

The Indexing Service has two important configuration options. First, you can specify the level of performance for the Indexing Service. To do so, you need to temporarily stop the service by following these steps:

  1. Within Computer Management, select Indexing Service.

  2. Click the Stop Indexing button (the stop VCR button).

  3. Right-click Indexing Service and select All Tasks and then Tune Performance.

You are provided with the option of making the server a dedicated Indexing Service server. If you are less ambitious, you can select Used Often, Used Occasionally, Never Used, or a custom level. After you select a performance level, remember to restart the Indexing Service by clicking the Start Indexing button.

You also have the option of generating abstracts for each document when the document is indexed. This option is useful for displaying a brief summary of a document next to each search result in a search page. The disadvantage of generating abstracts is that it requires more work for the Indexing Service and consumes more hard drive space.

To automatically generate abstracts, follow these steps:

  1. Within Computer Management, open the property sheet for the Indexing Service (click the hand holding the sheet of paper).

  2. Check the option labeled Generate Abstracts.

After this option is enabled, the Indexing Service generates and stores a brief summary of each document in the catalog for the index.

Connecting to Microsoft Indexing Service

You can communicate with the Microsoft Indexing Service by using the Microsoft OLE DB Provider for Indexing Service. The name of the provider is MSIDXS. For example, the following connection string opens a connection to Microsoft Indexing Service:

Dim con As new OleDbConnection( "Provider=MSIDXS;Data Source=Web")

Notice that this statement opens a particular catalog: the Web catalog. Specifying a particular catalog is optional.

After you open a connection to index server, you can execute queries like the following using either an OleDbCommand or OleDbDataAdapter object:

Select FileName From Scope() Where FreeText( 'ASP.NET')

This particular query retrieves a list of files that contain the word ASP.NET from indexed files on your server's hard drive.

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:

SELECT FileName, Size, DocAuthor
   FROM SCOPE()
   WHERE FREETEXT('ASP.NET')
   ORDER BY RANK DESC

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.

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

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 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 RANK, FileName, Characterization
   FROM SCOPE()
   WHERE FREETEXT( 'How do you use ASP.NET?')
   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.

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:

SELECT RANK, FileName, Characterization
   FROM SCOPE( 'SHALLOW TRAVERSAL OF ""/"" ')
   WHERE FREETEXT('How do you use ASP.NET?')
   ORDER BY RANK DESC

This query performs a search of the wwwroot directory. Notice the special phrase SHALLOW TRAVERSAL OF. This phrase causes the search to be performed only against the contents of the wwwroot directory and not any subdirectories. If you also want to search subdirectories, you can use the phrase DEEP TRAVERSAL OF.

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

Listing 14.9 FileFreeText.aspx
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.OleDb" %>
<Script Runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  Dim conMyData As OleDbConnection
  Dim strSearch As String
  Dim cmdSearch As OleDbCommand
  Dim dtrSearch As OleDbDataReader

  conMyData = New OleDbConnection( "Provider=MSIDXS;Data Source=Web" )
  strSearch = "SELECT RANK, FileName, VPath, Characterization " & _
    "FROM SCOPE() " & _
    "WHERE FREETEXT( '" & txtSearchPhrase.Text & _
    "') " & _
    "ORDER BY RANK DESC"
  cmdSearch = New OleDbCommand( strSearch, conMyData )
  conMyData.Open()
  Try
    dtrSearch = cmdSearch.ExecuteReader()
    lblResults.Text = "<ul>"
    While dtrSearch.Read
      lblResults.Text &= "<li> (" & dtrSearch( "RANK" ) / 10 & "%) "
      lblResults.Text &= "<a href=""" & dtrSearch( "VPath" ).ToString()
      lblResults.Text &= """>"
      lblResults.Text &= dtrSearch( "FileName" ) & "</a><br>"
      lblResults.Text &= dtrSearch( "Characterization" )
      lblResults.Text &= "<p>"
    End While
  Catch exc As Exception
    lblResults.Text = "Please rephrase your query"
  End Try
  conMyData.Close()
End Sub

</Script>

<html>
<head><title>FileFreeText.aspx</title></head>
<body>

<form Runat="Server">

<h2>File Free Text Search:</h2>
<asp:TextBox
  ID="txtSearchPhrase"
  Columns="50"
  Runat="Server" />
<asp:Button
  Text="Search!"
  OnClick="Button_Click"
  Runat="Server" />
<hr>
<asp:Label
  ID="lblResults"
  EnableViewState="False"
  Width="400px"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 14.9. Performing a free text query with the file system.

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. 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 CONTAINS function also supports several advanced text matching features. For example, you can perform proximity and weighted matches. You can also use the CONTAINS function to match both singular and plural forms of a word.

The following sample query returns the names of documents that contain the word apple but not the word green:

SELECT FileName, Characterization
   FROM SCOPE()
   WHERE CONTAINS( 'apple AND NOT green')
   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).

Listing 14.10 FileContains.aspx
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.OleDb" %>
<Script Runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  Dim conMyData As OleDbConnection
  Dim strSearch As String
  Dim cmdSearch As OleDbCommand
  Dim dtrSearch As OleDbDataReader

  conMyData = New OleDbConnection( "Provider=MSIDXS;Data Source=Web" )
  strSearch = "SELECT RANK, FileName, VPath, Characterization " & _
    "FROM SCOPE() " & _
    "WHERE CONTAINS( '" & txtSearchPhrase.Text & "') " & _
    "ORDER BY RANK DESC"
  cmdSearch = New OleDbCommand( strSearch, conMyData )
  conMyData.Open()
  Try
    dtrSearch = cmdSearch.ExecuteReader()
    lblResults.Text = "<ul>"
    While dtrSearch.Read
      lblResults.Text &= "<li> (" & dtrSearch( "RANK" ) / 10 & "%) "
      lblResults.Text &= "<a href=""" & dtrSearch( "VPath" ).ToString()
      lblResults.Text &= """>"
      lblResults.Text &= dtrSearch( "FileName" ) & "</a><br>"
      lblResults.Text &= dtrSearch( "Characterization" )
      lblResults.Text &= "<p>"
    End While
  Catch exc As Exception
    lblResults.Text = "Please rephrase your query"
  End Try
  conMyData.Close()
End Sub

</Script>

<html>
<head><title>FileContains.aspx</title></head>
<body>
<form Runat="Server">

<h2>File Contains Search:</h2>
<asp:TextBox
  ID="txtSearchPhrase"
  Columns="50"
  Runat="Server" />
<asp:Button
  Text="Search!"
  OnClick="Button_Click"
  Runat="Server" />
<hr>
<asp:Label
  ID="lblResults"
  EnableViewState="False"
  Width="400px"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 14.10. Performing a contains query with the file system.

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 FileName, Characterization
   FROM SCOPE()
   WHERE CONTAINS('Apple')
   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).

NOTE

The LIKE operator enables you to do wildcard matches. Microsoft Indexing Service also supports regular expression matches through the MATCHES predicate.

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.


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


Part IV: Working with ASP.NET Applications

IN THIS PART

CHAPTER 15 Creating ASP.NET Applications

CHAPTER 16 Tracking User Sessions

CHAPTER 17 Caching ASP.NET Applications

CHAPTER 18 Application Tracing and Error Handling


Chapter 15. Creating ASP.NET Applications

IN THIS CHAPTER

  • Overview of ASP.NET Applications

  • Using Application State

  • Using the Web.Config File

  • Using HTTP Handlers and Modules

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

You also examine how to configure an application with the Web.Config and Machine.Config configuration files. You examine the standard sections of the Web.Config file so that you understand how to add new sections of your own.

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:

  • 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 applicationwide 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

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.

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

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.

CAUTION

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.

Listing 15.1 BadPageCounter.aspx
<Script Runat="Server">

Sub Page_Load
  Application( "PageCounter" ) += 1
  lblCount.Text = Application( "pageCounter" )
End Sub

</Script>

<html>
<head><title>BadPageCounter.aspx</title></head>
<body>

This page has been requested:
<asp:Label
  ID="lblCount"
  Runat="Server" />
times!

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

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
<Script Runat="Server">

Sub Page_Load
  Application.Lock
  Application( "PageCounter" ) += 1
  lblCount.Text = Application( "pageCounter" )
  Application.Unlock
End Sub

</Script>

<html>
<head><title>GoodPageCounter.aspx</title></head>
<body>

This page has been requested:
<asp:Label
  ID="lblCount"
  Runat="Server" />
times!

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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 )

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 applicationwide events and declare applicationwide objects.

CAUTION

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 before authenticating a user.

  • Application_AuthorizeRequest— Raised before authorizing a user.

  • Application_BeginRequest— Raised by every request to the server.

  • Application_End— Raised immediately before the end of all application instances.

  • Application_EndRequest— Raised at the end of every request to the server.

  • Application_Error— Raised 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 immediately before the end of a single application instance.

  • Init— Raised immediately after each application instance is created. This event might occur multiple times.

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.

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

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
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Application_Start
  Dim conNorthwind As SqlConnection
  Dim strSelect As String
  Dim dadProducts As SqlDataAdapter
  Dim dstProducts As DataSet

  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

</Script>

The C# version of this code can be found on the CD-ROM.

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

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

Listing 15.4 AppInit/Global.asax
<%@ Import Namespace="System.Data" %>

<Script Runat="Server">

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

  objRan = New Random()
  drowSelectedAd = dtblBannerAds.Rows( _
    objRan.Next( dtblBannerAds.Rows.Count ) )
  strPageFooter = "<hr><a href=" & drowSelectedAd( "NavigateUrl" )
  strPageFooter &= "><img src=" & drowSelectedAd( "ImageUrl" )
  strPageFooter &= "></a>"
  Response.Write( strPageFooter )
End Sub
</Script>

The C# version of this code can be found on the CD-ROM.

Figure 15.3. Displaying random banner advertisement.

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

Listing 15.5 AppVanity/Global.asax
<Script Runat="Server">

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

</Script>

The C# version of this code can be found on the CD-ROM.

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

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

Listing 15.6 AppVanity/Custom/Default.aspx
<Script Runat="Server">

Dim strUsername As String

Sub Page_Load
  strUsername = Request.Params( "username" )
End Sub

</Script>

<html>
<head><title>Welcome!</title></head>
<body>

Hello <b><%=strUsername%></b> and welcome to your Web site!

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

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
<badlinks>
  <link>
    <badlink>/products.aspx</badlink>
    <goodlink>/default.aspx</goodlink>
  </link>
  <link>
    <badlink>/services.aspx</badlink>
    <goodlink>/default.aspx</goodlink>
  </link>
</badlinks>

The C# version of this code can be found on the CD-ROM.

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
<%@ Import Namespace="System.Data" %>

<Script Runat="Server">

Sub Application_BeginRequest
  Dim dtblBadLinks As DataTable
  Dim strThisUrl As String
  Dim strSelect As String
  Dim arrMatches() As DataRow
  Dim strGoodLink As String

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

</Script>

The C# version of this code can be found on the CD-ROM.

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.

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.

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, human-readable XML files that you can open and modify with any text editor.

The ASP.NET framework uses a hierarchical configuration system. You can use Web.Config 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.

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.

NOTE

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.

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.

  • 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 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 request validation, page buffering, session state, or view state.

  • customErrors— This section specifies how error information should be displayed. To learn more information, see Chapter 18.

  • authentication— This section specifies information for authenticating users. For more information, see Chapter 19, "Using Forms-Based Authentication," and Chapter 20, "Using Windows-Based Authentication."

  • 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 section is used for sharing a standard encryption key across machines in a Web farm. For more information, see Chapter 19.

  • trust— This 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 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."

  • 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 section configures the process model settings on Internet Information Server. For more information, see Chapter 18.

  • 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 lists the values that you can use with the ClientTarget 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
<configuration>
  <system.web>
    <compilation debug="true" />
  </system.web>
</configuration>

The C# version of this code can be found on the CD-ROM.

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.

CAUTION

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
<configuration>
  <location path="NoViewState">
  <system.web>
    <pages enableViewState="false" />
  </system.web>
  </location>
</configuration>

The C# version of this code can be found on the CD-ROM.

The configuration settings in Listing 15.10 apply to a directory named NoViewState. They do not modify the settings for the current directory.

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 <location> 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
<configuration>
  <system.web>
    <authentication mode="Forms" />
  </system.web>

  <location path="secret.aspx">
  <system.web>
    <authorization>
      <deny users="*" />
    </authorization>
  </system.web>
  </location>
</configuration>

The C# version of this code can be found on the CD-ROM.

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 <location> 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 <location> 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 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
<configuration>
  <location allowOverride="false" path="application1">
  <system.web>
    <identity userName="app1" password="secret" />
  </system.web>
  </location>
  <location allowOverride="false" path="application2">
  <system.web>
    <identity userName="app2" password="secret" />
  </system.web>
  </location>
</configuration>

The C# version of this code can be found on the CD-ROM.

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 /ConfigLock/Application1 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.

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

Listing 15.13 ConfigCustom/Web.Config
<configuration>
  <appSettings>
    <add key="conString"
     value="Server=localhost;UID=sa;PWD=secret;Database=Northwind" />
  </appSettings>
</configuration>

The C# version of this code can be found on the CD-ROM.

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
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

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

</Script>

<html>
<head><title>Default.aspx</title></head>
<body>

<asp:DataGrid
  ID="dgrdProducts"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Notice that the value of conString is retrieved from the Web.Config file with the following single line of code:

strConString = ConfigurationSettings.AppSettings( "conString" )


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:

http://yourSite.com/products/product1.aspx

It will retrieve information for that product from the Products database table.

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( "<h2>Product Name:</h2>" )
      objContext.Response.Write( dtrProducts( "ProductName" ) )
      objContext.Response.Write( "<h2>Product Price:</h2>" )
      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

  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 C# version of this code can be found on the CD-ROM.

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.

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
<configuration>
  <system.web>
    <httpHandlers>
      <add verb="*" path="*"
      type="ProductsHandler,ProductsHandler" />
    </httpHandlers>
  </system.web>
</configuration>

The C# version of this code can be found on the CD-ROM.

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:

<add verb="*" path="product*.*"
type="ProductsHandler,ProductsHandler" />

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.

  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.

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

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 C# version of this code can be found on the CD-ROM.

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.

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
<configuration>
  <system.web>
    <httpModules>
    <add name="AuthModule"
     type="myModules.AuthModule,AuthModule" />
    </httpModules>
  </system.web>
</configuration>

The C# version of this code can be found on the CD-ROM.

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 use the Web.Config file to configure both WhosOnModule and WhosOnHandler.

  • 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 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
<configuration>
  <system.web>
    <httpModules>
    <add name="WhosOnModule"
     type="WhosOn.WhosOnModule,WhosOnModule" />
    </httpModules>
    <httpHandlers>
      <add verb="*" path="*.axd"
      type="WhosOn.WhosOnHandler,WhosOnHandler" />
    </httpHandlers>
  </system.web>
  <appSettings>
    <add key="whoson" value="5" />
  </appSettings>
</configuration>

The C# version of this code can be found on the CD-ROM.

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.

Creating the WhosOn Module

The WhosOnModule, contained in Listing 15.20, grabs statistics for each request and adds them to the application cache.

Listing 15.20 WhosOnModule.vb
Imports System
Imports System.Web
Imports System.Collections
Imports System.Configuration
Imports 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( _
    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 TimeStamp As DateTime
Public BrowserType As String
Public UserHostName As String
Public 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

The C# version of this code can be found on the CD-ROM.

The WhosOn module is triggered by the Application_BeginRequest event. When this event fires, the OnEnter subroutine is executed.

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

  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( "<table border=1 cellpadding=4>" )
  objContext.Response.Write( "<tr><td colspan=4 bgcolor=orange>" )
  objContext.Response.Write( "<b>Who's On</b>" )
  objContext.Response.Write( "</td></tr>" )
  objContext.Response.Write( "<tr colspan=4 bgcolor=#eeeeee>" )
  objContext.Response.Write( "<th>Timestamp</th>" )
  objContext.Response.Write( "<th>Browser Type</th>" )
  objContext.Response.Write( "<th>Remote Address</th>" )
  objContext.Response.Write( "<th>Referrer</th>" )
  objContext.Response.Write( "</td></tr>" )
  For each objStatsEntry in colPageStats
    objContext.Response.Write( "<tr>" )
    objContext.Response.Write( "<td>" & objStatsEntry.TimeStamp & "&nbsp;</td>" )
    objContext.Response.Write( "<td>" & objStatsEntry.BrowserType & "&nbsp;</td>" )
    objContext.Response.Write( "<td>" & objStatsEntry.UserHostName & "&nbsp;</td>" )
    objContext.Response.Write( "<td>" & objStatsEntry.Referrer & "&nbsp;</td>" )
  Next
  objContext.Response.Write( "</table>" )
  End If
  End Sub

  ReadOnly Property IsReusable() As Boolean _
  Implements IHttpHandler.IsReusable
    Get
      Return True
    End Get
  End Property

End Class

End Namespace

The C# version of this code can be found on the CD-ROM.

The WhosOn handler contains one long subroutine named ProcessRequest, which retrieves the item from the Cache object associated with the current page. If this item exists, it is a Queue collection. Next, the subroutine iterates through the contents of the Queue collection, displaying each statistics entry.

Before you can use the WhosOn handler, you need to compile the file in Listing 15.21. You can compile the handler by executing the following statement from the command line:

vbc /t:library /r:System.dll,System.Web.dll,WhosOnModule.dll WhosOnHandler.vb

Notice that you must reference the WhosOnModule.dll assembly because the StatsEntry class is defined there.

After you compile the handler, you need to copy the WhosOnHandler.dll file to your application's /bin directory.

You can test the WhosOn application by requesting any page from your Web site, for example:

http://localhost/default.aspx

Next, request the page using the extension .axd, rather than .aspx:

http://localhost/default.axd

Statistics for the default.aspx page are displayed.


Summary

In this chapter, you learned how to work with ASP.NET applications. In the first part, you learned how to create items that persist through the lifetime of an application by using application state.

Next, you learned how to use the Global.asax file to handle applicationwide events. For example, you learned how to use the Global.asax file with the RewritePath() method to create vanity URLs and automatically fix bad links at your Web site.

You also examined the various configuration sections in the Web.Config and Machine.Config files. You learned how to lock down settings in the Web.Config file. You also saw how to add your own custom configuration information to this file.

Finally, you learned how to work with HTTP handlers and modules. You examined how to use handlers to handle requests for pages located at a particular path. You also learned how to create modules that participate in every page request.


Chapter 16. Tracking User Sessions

IN THIS CHAPTER

  • Using Browser Cookies

  • Using Session State

  • Using Cookieless Sessions

In this chapter, you learn how to track and associate information with users as they move from page to page at your Web site. You learn how to take advantage of three mechanisms supported by the ASP.NET framework for tracking user sessions.

First, you learn how to create and read browser cookies, which can be used to store small bits of information on a user's browser. You learn how to create both session and persistent cookies.

The bulk of the chapter is devoted to the topic of session state. When you store information in session state, the information is automatically associated with a particular user. You learn how the ASP.NET framework supports storing session information in process, out of process, and in a database table.

Finally, you examine the subject of cookieless sessions. A cookieless session, as the name implies, provides you with all the benefits of session state without the reliance on browser cookies.


Using Browser Cookies

The Hypertext Transfer Protocol (HTTP) is a stateless protocol. HTTP itself does not provide a Web server with any means of detecting whether two requests originate from the same browser. From the point of view of a Web server, every request for a page is from a new user. This fact is inconvenient for someone developing a Web site. If you want to associate a shopping cart with a user as he or she navigates from page to page, or you need to enable a user to complete forms that span multiple pages, you need to perform some extra work.

One way to overcome the statelessness of the Web is to take advantage of browser cookies. Netscape introduced cookies into the world with the first version of its browser. Since then, the World Wide Web Consortium (W3C) has endorsed a cookie standard. Most browsers now can use cookies.

Cookies are little pieces of information that a server stores on a browser. According to the original Netscape specification, a cookie cannot contain more than 4KB (the size refers to the combined size of the cookie's name and value).

Certain cookies are temporary; others are persistent. The temporary cookies, called session cookies, exist only in the browser's memory. When the browser is shut down, any session cookies added to the browser are lost.

Persistent cookies, on the other hand, can survive for months or even years. Browsers that support cookies maintain one or more special files. These files, called cookie files on Windows machines and magic cookie files on the Macintosh, store data from Web sites.

NOTE

Several Web sites have valuable information on cookies. To view the original Netscape specification for cookies, see this site:

http://home.netscape.com/newsref/std/cookie_spec.html

To view the W3C's Reference specification on cookies, visit this site:

http://www.w3.org

Finally, for general information on cookies, go to the following:

http://www.cookiecentral.com

How Cookies Work

Cookies are passed back and forth between a browser and server through HTTP headers. The server first creates a cookie by using the Set-Cookie header in a response. Subsequent requests from the browser return this cookie in the Cookie header.

Suppose that you want to create a cookie named UserName that contains the name of a visitor to your Web site. To create this cookie, the server would send a header like this:

Set-Cookie: UserName=Bill+Gates; path=/; domain=superexpert.com;
expires=Tuesday, 01-Jan-05 00:00:01 GMT

This header instructs the browser to add an entry to its cookie file. The browser adds the cookie named UserName with the value Bill Gates. Notice that the value of the cookie is URL-encoded.

Furthermore, the header informs the browser that this cookie should be returned to the server regardless of the path used in the request. If the path attribute were set to another value such as /private, the cookie would be returned only in requests to this path.

The domain attribute further restricts where the cookie can be sent by the browser. In this example, the cookie can be sent only to the superexpert.com Web site. The cookie will never be sent to www.yahoo.com or any other Web site on the Internet.

Finally, the expires attribute specifies when the cookie should expire. The header in the example tells the browser to store the cookie until the first second of January 1, 2005. Actually, the cookie will probably expire much earlier than that. When a cookie file becomes too large, the browser automatically starts removing cookies.

After the server creates a cookie, the browser returns the cookie in every request it makes to the Web site. The browser sends the cookie in a header that looks like this:

Cookie: Username: Bill+Gates

Creating and Reading Session Cookies

You can create and read session cookies by using the Cookies property of the Response object and Request class. The Cookies property represents a collection of cookies (an instance of the HttpCookieCollection class).

The following sample statements create a new session cookie named myCookie:

Dim objCookie As New HttpCookie( "myCookie", "Hello!" )
Response.Cookies.Add( objCookie )

The first statement creates an individual cookie named myCookie with the value "Hello!". The second statement adds the new cookie to the Response object's cookie collection.

The preceding statements create a session cookie. The cookie is added to the browser's memory but not recorded to a file. When the user shuts down the browser, the cookie is wiped from memory.

To read an existing cookie, you access the cookies collection of the Request object. For example, the following statement displays the value of a cookie named myCookie:

Response.Write( Request.Cookies( "myCookie" ).Value )

The Value property returns the value of the cookie as a string.

NOTE

A cookie can contain only a string value. If you want to store an integer value in a cookie, such as a user ID, you need to convert the value to a string.

You can display all the cookies sent by a browser by iterating through the Request object's cookies collection. Here's how you would display them:

Dim strKey As String

For each strKey in Request.Cookies
  Response.Write( "<li>" & strKey & "=" & _
    Request.Cookies( strKey ).Value )
Next

This For...Next loop iterates through all the items in the Request object's cookies collection. The name value of each cookie is displayed in a bulleted list.

The page in Listing 16.1 illustrates how you can add and read a cookie within an ASP.NET page (see Figure 16.1).

Listing 16.1 CreateCookie.aspx
<Script Runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  Dim objCookie As HttpCookie

  objCookie = New HttpCookie( txtCookieName.Text, txtCookieValue.Text )
  Response.Cookies.Add( objCookie )
End Sub

Sub Page_PreRender( s As Object, e As EventArgs )
  Dim strKey As String

  For each strKey in Request.Cookies
  lblCookies.Text &= "<li>" & strKey & "=" & _
    Request.Cookies( strKey ).Value
  Next
End Sub

</Script>

<html>
<head><title>CreateCookie.aspx</title></head>
<body>
<form Runat="Server">

<b>Cookie Name:</b>
<br>
<asp:TextBox
  ID="txtCookieName"
  Runat="Server" />
<p>
<b>Cookie Value:</b>
<br>
<asp:TextBox
  ID="txtCookieValue"
  Runat="Server" />
<p>
<asp:Button
  Text="Add Cookie!"
  OnClick="Button_Click"
  Runat="Server" />

<h2>Existing Cookies:</h2>
<asp:Label
  ID="lblCookies"
  EnableViewState="False"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 16.1. Creating browser 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.

Creating and Reading Persistent Cookies

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.

CAUTION

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.

CAUTION

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 )

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 expiration date for a persistent cookie.

  • HasKeys— A 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 value of the cookie.

  • Values— A NameValueCollection 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.

Finally, notice that two of the properties enable you to work with cookie dictionaries. A cookie dictionary is a single cookie that stores multiple key and value pairs. The HasKeys property enables you to detect whether a cookie is a cookie dictionary. The Values property enables you to read the keys and values from a cookie dictionary.

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( "<li>" & strItem & _
      "=" & objCookie.Values( strItem ) )
  Next
End If

Examining Limitations of Cookies

Cookies are fine for storing a small amount of string information on a user's browser. However, they have some significant limitations. First, an individual cookie can contain a very limited amount of information (no more than 4KB). This means, for example, that you should not attempt to store a user's shopping cart directly in a cookie.

Another limitation is that you cannot store anything but a string in a cookie. This means that you cannot store such complex objects as HashTables or DataSets in a cookie.

You cannot get around these limitations. Typically, you use a cookie to store a user ID on a browser. After you add a user ID to a browser, you can track the user as he or she moves from page to page. Because you can track the user, you can associate objects stored on the server, such as a shopping cart, with the proper user. In other words, you can use cookies purely as an identification mechanism. After you identify a user with a cookie, you can associate objects stored on the server with that user. These objects can be stored in the server's memory or a database table.

The ASP.NET framework includes built-in support for using cookies in this way. You can automatically track a user with a cookie and associate resources on the server with the user by taking advantage of session state. In the next section of this chapter, "Using Session State," you learn how to take advantage of session state.

Another significant limitation of cookies is that they are browser-dependent. For various reasons, cookies do not work with all browsers. In some cases, users have disabled cookies on their browsers in response to security or privacy worries. In other cases, a user's cookie file has become corrupted. Finally, cookies do not always work well with company proxy servers.

The general warning is that you are taking a risk whenever you rely on cookies. Your Web pages will not work for a certain percentage of users. Whenever possible, you should try to avoid using cookies for crucial functions.

NOTE

The decision to use cookies is similar to the decision to use other advanced browser features, such as Java or JavaScript. Several large Web sites do, in fact, rely on cookies. For example, to visit certain areas of the Yahoo! and Microsoft Web sites, you must have cookies enabled on your browser.

In the section "Using Cookieless Sessions" later in this chapter, you learn how to take advantage of a method of retaining state without cookies. The ASP.NET framework includes a tricky method of tracking users as they move from page to page that does not rely on cookies.


Using Session State

The ASP.NET framework includes built-in support for automatically tracking and associating resources on the server with users. You can associate information with users as they move from page to page at your Web site by taking advantage of session state.

By default, when a user first requests a page from an ASP.NET Web site, the Web site automatically adds a session cookie to the user's browser. This cookie, named the _ASP.NET_SessionID cookie, tracks the user for the remainder of his or her visit to the Web site.

By using session state, you can associate any type of information with the user that you please. For example, you can use session state to display the user's name on each page he or she requests. Or you can use session state to associate a shopping cart with the user as he or she browses from page to page. You can also use session state to enable the user to complete HTML forms that span multiple pages.

In the following sections, you learn how to take advantage of the built-in session state facility included in the ASP.NET framework. You learn how to store objects in session state and how to manage session state in process, with a Windows service, and with a database table.

Adding Items to Session State

You can store any object in session state by using a statement like the following:

Session( "myItem" ) = "Hello!"

This statements adds a new item, named myItem, to session state with the value "Hello!".

After you add an item to session state, you can retrieve the item on any page that a user visits. The following statement retrieves myItem from session state and displays its value:

Response.Write( Session( "myItem" ) )

You should be aware of three points when using session state. First, any items you add to session state remain there for the duration of a user's visit to your Web site. Normally, the items continue to be associated with the user until the user shuts down his or her browser, the item is explicitly removed from session state, or the user does not request a page for more than 20 minutes.

Second, items added to session state are added relative to a particular user. If Fred requests a page that adds an item named myItem to session state with the value "Hello!" and Jane requests a page that adds an item named myItem to session state with the value "Goodbye!", no conflict occurs. Separate copies of each item added to session state are created for each user.

Finally, you can add any object to session state. Because objects stored in session state are stored on the server, session objects are not subject to the same size limitations as cookies. For example, you can add a DataSet to session state like this:

Session( "myDataSet" ) = dstDataSet

After you add the DataSet to session state, you can retrieve the same DataSet in whatever pages the user requests.

CAUTION

Almost any object can be added to session state. The one requirement is that the object support serialization.

Be aware, however, that a separate copy of each item added to session state is created for each user who requests the page. If you place a DataSet with 400 records into session state in a page, and 500 users request the page, you have 500 copies of that DataSet in memory.

Removing Items from Session State

You can remove an item from session state by using the Remove() or RemoveAll() method. The Remove() method removes a particular item for the current user. For example, to remove an item named myItem associated with the user making the current request, you would use the following statement:

Session.Remove( "myItem" )

The RemoveAll method removes all the items stored in session state for a particular user:

Session.RemoveAll

Starting a User Session

A user session starts when a user requests the first page from a Web site. When the first page is requested, the Web server adds the ASP.NET_SessionID cookie to the user's browser.

You can detect the start of a new session by using the NewSession property. When a new session starts, the NewSession property returns the value True:

Response.Write( Session.NewSession )

Each user session is assigned a unique session ID. You can retrieve the value of a session ID like this:

Response.Write( Session.SessionID )

A session ID is guaranteed to be unique among all current user sessions. However, a session ID is not guaranteed to be unique over time; it might be recycled for a new session in the future.

Ending a User Session

When does a user session end? Remember that HTTP is a stateless protocol. A Web server has no means of detecting when a user leaves a Web site. Instead, the Web server detects that a certain period of time has passed without the user requesting a page. At that point, the Web server assumes that the user has left, and it removes all items in session state associated with that user.

By default, a session times out when a user has not requested a page for more than 20 minutes. If the same user requests a page after a 20-minute period has elapsed, that person is treated as a new user.

You might want to change the session timeout period. Setting a smaller timeout period makes your Web site more scalable because the server can reclaim memory devoted to individual session instances faster. On the other hand, you might want to set a longer timeout period if you expect that users might need to devote more than 20 minutes to a page.

You can modify the default timeout period for a session in several ways. To set a new session timeout period, modify the Timeout property of the Session object like this:

Session.Timeout = 10

This statement sets the session timeout period to 10 minutes. The new session timeout value applies to the remainder of the user's visit to the Web site.

You also can modify the session timeout period in the Web.Config file. For example, placing the following Web.Config file in the root directory of your application changes the default session timeout period to 60 minutes:

<configuration>
  <system.web>
    <sessionState timeout="60" />
  </system.web>
</configuration>

You also have the option of explicitly ending a user session. You can do so by calling the Abandon method like this:

Session.Abandon

If a user requests a new page after the Abandon method is called, he or she is treated as a new user. The user is assigned a new session ID and all the items in session state previously associated with that user are removed from memory. The Abandon method is often used with sign-out pages.

CAUTION

Calling the Abandon method does not end the user session until the current page finishes processing. You can still retrieve items from session state after the Abandon method is called. For example, the following statements display "Hello!" twice:

Session( "myItem" ) = "Hello!"
Response.Write( Session( "myItem" ) )
Session.Abandon
Response.Write( Session( "myItem" ) )

However, the items will be gone from memory when the user makes a new page request.

Handling Session Events

You can capture two events related to session state: Session_Start and Session_End. The Session_Start event is raised when a user requests the first page from a Web site. The Session_End event is triggered when the user session ends.

You can capture both of these events by adding subroutines to the Global.asax file. For example, suppose that you want to create a page that displays the current number of active user sessions at your Web site. You could do so by using the Global.asax file in Listing 16.2 (this page can be found in the SessionStart subdirectory).

NOTE

The Global.asax file must be located in your application's root directory. For more information on the Global.asax file, see Chapter 15, "Creating ASP.NET Applications."

Listing 16.2 SessionStart/Global.asax
<Script Runat="Server">

Sub Session_Start()
  If Application( "SessionCount" ) Is Nothing Then
    Application( "SessionCount" ) = 0
  End If
  Application( "SessionCount" ) += 1
End Sub

Sub Session_End()
  Application( "SessionCount" ) -= 1
End Sub

</Script>

The C# version of this code can be found on the CD-ROM.

Two subroutines are declared in Listing 16.2. The first subroutine, Session_Start, executes at the start of each new user session. This subroutine increments an item stored in application state by one.

The second subroutine, Session_End, executes whenever a user session ends. This subroutine decrements an item stored in application state by one.

CAUTION

Modifying the Global.asax file automatically drops all items from session state. If you modify the Global.asax file in a production Web site, all user sessions are terminated. (This warning applies when session state is stored in process.)

The page in Listing 16.3 displays the current number of user sessions by displaying the value of SessionCount (see Figure 16.2).

Listing 16.3 SessionStart/SessionCount.aspx
<Script Runat="Server">

Sub Page_Load
  lblSessionCount.Text = Application( "SessionCount" )
End Sub

</Script>

<html>
<head><title>SessionCount.aspx</title></head>
<body>

Current Sessions:
<asp:Label
  ID="lblSessionCount"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 16.2. Displaying number of current users.

You also can use the Session_Start subroutine to automatically redirect users to a particular page when a user session begins. You might want to force all users who visit your Web site to go to the Default.aspx page before visiting any other page. The Global.asax file in Listing 16.4 automatically redirects new users to the Default.aspx page. (This page can be found in the SessionRedirect subdirectory.)

Listing 16.4 SessionRedirect/Global.asax
<Script Runat="Server">

Sub Session_Start
  If Instr( Request.Path.ToLower, "/default.aspx" ) = 0 Then
    Response.Redirect( "Default.aspx" )
  End If
End Sub

</Script>

The C# version of this code can be found on the CD-ROM.

If the first page a user requests is not the Default.aspx page, he or she is automatically redirected to this page. Because the Session_Start event is triggered only once per user session, this subroutine executes only once when the user makes the first page request.

Storing Session State in Process

By default, session state is managed in process. All items added to session state are stored in the same process as the Web server.

The advantage of storing session state in process is performance. You can store and retrieve items much faster in session state when you do not need to marshal the data across separate processes.

ASP Classic Note

In Classic ASP, session state was always managed in process.

Storing session state in process does, however, have some significant disadvantages:

  • If, for whatever reason, the server crashes, you lose all data stored in session state. You lose session data when an ASP.NET application is reloaded for any reason. For example, modifying the Global.asax file drops all active user sessions.

  • If session state is stored in process, session state cannot be shared across multiple servers. Storing session state in process limits the scalability of your Web site because you cannot configure multiple servers to handle requests. In other words, storing session state in process is incompatible with Web farms.

ASP.NET provides you with the option of managing session state out of process. In the following sections, you learn two methods of storing session data in a separate process: First, you learn how to manage session state with a separate Windows service. Second, you learn how to manage a session state with a database.

Storing Session State in a Windows Service

Instead of storing session data in process, you can store the data in a separate Windows service. To do so, you need to complete the following two steps:

  • Start the ASP.NET Windows service.

  • Modify the Web.Config file to set the sessionstate mode to the value stateserver and specify the location of the State service.

You can start the ASP.NET Windows service by going to Start, Programs, Administrative Tools, Services. Find the ASP.NET State service and click the Start Service button (the VCR play button).

You should modify the settings for the service so that it starts automatically when Windows starts. To do so, double-click the name of the service and set Startup type to the value Automatic (see Figure 16.3).

Figure 16.3. Automatically starting the ASP.NET State service.

Alternatively, you can start the ASP.NET State service by executing the following statement from a command prompt:

net start aspnet_state

After you start the service, you need to modify the Web.Config file for your application to use the service. The Web.Config file in Listing 16.5 contains the proper settings. (This file can be found in the SessionService subdirectory.)

Listing 16.5 SessionService/Web.Config
<configuration>
  <system.web>
    <sessionState
       mode="StateServer"
       stateConnectionString="tcpip=127.0.0.1:42424" />
  </system.web>
</configuration>

The C# version of this code can be found on the CD-ROM.

In the Web.Config file in Listing 16.5, the mode attribute is set to the value StateServer (by default, mode is set to the value InProc). The stateConnectionString attribute is assigned information about the location of the service. In this example, the State service is assumed to be running on the same machine as the Web server on port 42424.

You could run the ASP.NET State service on a separate server by supplying the IP address of the server. If you supply this address, any information stored in session state is not lost even if your Web server goes down.

You also can use the same State service with multiple Web servers. For example, you can build a Web farm in which all the servers share the same state information stored on a separate server.

After you enable the ASP.NET State service, you can store and retrieve items from session state just as you normally would. However, when you store items in session state with the State service enabled, the items are stored in a separate process.

To test that items are actually being stored in a separate process, add an item to session state and restart the Web service by launching the Internet Services Manager, right-clicking the name of your server, and clicking Restart IIS. Restarting the Web service should have no effect on the items stored in the State service.

A test page (named TestSession.aspx) is included in the SessionService subdirectory.

Storing Session State in a Database Table

You also can manage session state out of process by storing session data in a Microsoft SQL Server database. The advantage of this approach is that you can cluster multiple database servers so that if one server fails, another database server can take over the state management.

Storing session state in a SQL Server database requires the completion of the following two steps:

  • Create the necessary objects in SQL Server to manage the session data by executing the InstallSqlState.sql batch file.

  • Modify the Web.Config file to set the sessionState mode to the value SQLServer and specify the connection information for the SQL Server database.

To complete the first step, launch the Microsoft SQL Server Query Analyzer and log in to SQL Server by using the sa account.

Next, open the InstallSqlState.sql batch file. You can find this file at the following location:

WINNT\Microsoft.NET\Framework\[Framework Version]\InstallSqlState.sql

After you load the InstallSqlState.sql batch file, click the Execute Query button. Executing this batch file creates a new database named ASPState on your database server. The batch file also creates several other objects, including tables to store the state data and stored procedures to add and retrieve the data.

NOTE

The InstallSqlState.sql batch file adds a new job to SQL Server. This job clears away expired sessions. For the job to execute, the SQL Server Agent service must be running.

After the batch file is finished executing, you need to restart the SQL Server service. Launch the SQL Server Service Manager, and stop and start the SQL Server service.

NOTE

If you want to remove the objects added to SQL Server by the InstallSqlState.sql batch file, you can execute the UninstallSqlState.sql batch file from Query Analyzer.

The final step is to create a new Web.Config file that configures session state to store data in the database. A Web.Config file with the necessary settings is included in Listing 16.6. (This file is located in the SQLState subdirectory.)

Listing 16.6 SQLState/Web.Config
<configuration>
  <system.web>
    <sessionState
       mode="SQLServer"
       sqlConnectionString="Server=127.0.0.1;UID=sa;PWD=secret" />
  </system.web>
</configuration>

The C# version of this code can be found on the CD-ROM.

In the Web.Config file in Listing 16.6, the mode attribute is set to the value SQLServer. A valid SQL Server connection string is also supplied. In this example, the SQL connection string points to the local server and passes the credentials for the sa account. Notice that you do not specify a database in the connection string.

After you create the Web.Config file, all data added to session state is stored on the database. You can test it by using Query Analyzer. The session data is stored in the tempdb database in a table named AspStateTempSessions. Each row in this table represents the data stored for one user session.

Because the session data is stored in a database table, you can analyze the session data using standard SQL queries. For example, if you want to retrieve a count of the number of active user sessions, you can execute the following query against the tempdb database:

SELECT COUNT( SessionID )
FROM AspStateTempSessions
WHERE Expires > GetDate()

This query returns a count of the session IDs in the AspStateTempSession table where the session hasn't expired.

Disabling Session State

By default, session state is enabled for all pages in an application. You can improve the performance of your Web application by disabling session state for pages when you don't need to use it.

To disable session state for a particular page, modify the value of the EnableSessionState attribute in the page directive like this:

<%@ Page EnableSessionState="False" %>

If you need to retrieve objects from session state but don't need to add objects to it, you can set the EnableSessionState attribute to ReadOnly like this:

<%@ Page EnableSessionState="ReadOnly %>

You also can disable session state for all the pages in an application by adding the following Web.Config file to the root directory of the application:

<configuration>
  <system.web>
    <sessionState
       mode="Off" />
   </system.web>
</configuration>

The Web.Config file takes precedence over the page directive for particular pages. So, if you disable session state with the Web.Config file, you cannot selectively enable session state with the page directive.


Using Cookieless Sessions

As noted in the first section of this chapter, using cookies can be risky. For various reasons, cookies do not work with a certain percentage of browsers used to request pages from your Web site.

By default, session state relies on cookies. The session state facility uses the ASP.NET_SessionID cookie to track users as they move from page to page. If the cookie cannot be added to a user's browser, every request made by the user starts a new user session. Any session data associated with that user is lost when a new page is requested.

The ASP.NET framework includes an option to enable cookieless sessions. Cookieless sessions enable you to take advantage of session state without relying on browser cookies.

Cookieless sessions are implemented with a clever trick. When a user makes the first request to a Web site with cookieless sessions enabled, the URL used for the request is automatically modified to include the user's session ID. For example, if a user makes a request for http://mysite.com/mypage.aspx, the request is automatically modified to http://mysite.com/(nd4vqe2fnbmnwi451fwvda45)/mypage.aspx. The part of the URL that appears in parentheses is the session ID for the current user.

After the session ID is embedded in the URL of the first page request, the session ID will continue to be associated with the user throughout his or her visit to the Web site. The session ID is passed as part of the base URL whenever the user clicks a relative link or submits a form.

Because the session ID is automatically passed from one page to another, the user can be tracked without relying on a cookie. So, by using cookieless sessions, you get all the advantages of session state without the worries about browser incompatibility.

Enabling Cookieless Sessions

You enable cookieless sessions by modifying a single attribute in the Web.Config file. A Web.Config file with the proper settings is included in Listing 16.7 (this file can be found in the Cookieless subdirectory).

Listing 16.7 Cookieless/Web.Config
<configuration>
  <system.web>
    <sessionState
       cookieless="true" />
  </system.web>
</configuration>

The C# version of this code can be found on the CD-ROM.

In the Web.Config file in Listing 16.7, the cookieless attribute is set to the value true. This is the only thing you need to do to enable cookieless sessions.

Cookieless sessions are fully compatible with managing state in process, in a Windows service, or in a database table. There's nothing wrong with enabling cookieless sessions and storing the session data in a database table.

Examining Limitations of Cookieless Sessions

One very significant limitation is imposed on you when you use cookieless sessions. If you choose to use cookieless sessions, you cannot use absolute URLs when linking between pages. You must design your Web site in such a way that every link uses a URL that is relative to the current page. For example, all the following links work fine with a cookieless session:

<a href="mypage.aspx">Click Here</a>
<a href="mydir/mypage.aspx">Click Here</a>
<a href="http://mypage.aspx">Click Here</a>

However, the following links do not work with a cookieless session:

<a href="/mypage.aspx">Click Here</a>
<a href="http://mysite.com/mypage.aspx">Click Here</a>

Clicking either of these two links starts a new user session, and all the items stored in session state are lost. Because the links include the full path to the page, the session ID is not added.

The same limitation applies to using forms or the Redirect statement. When specifying paths, you must always specify the path relative to the current page.

NOTE

Bookmarking a page actually works quite well with cookieless sessions. If you bookmark a page and return to the page after the session expires, you don't receive an error. However, all session data is lost. This works no differently than normal user sessions.


Summary

In this chapter, you learned how to take advantage of three mechanisms for associating information with users as they move from page to page at your Web site. In the first part, you learned how to create and read both session and persistent cookies.

Next, you learned how to store information in session state. You examined how session data can be stored in process, in a Windows service, or with a database table. You also learned how session state can be disabled for a particular page or an entire Web application.

Finally, you learned about cookieless sessions and how to use cookieless sessions to gain all the benefits of session state without any of the drawbacks of relying on cookies.


Chapter 17. Caching ASP.NET Applications

IN THIS CHAPTER

  • Using Page Output Caching

  • Using Page Fragment Caching

  • Using Page Data Caching

In this chapter, you learn how to cache the contents of an ASP.NET page and arbitrary data in an ASP.NET application. You learn about the three different caching mechanisms supported by the ASP.NET Framework.

First, you learn how to improve the performance of an ASP.NET page by taking advantage of page output caching, which enables you to cache the contents of an entire dynamically generated page on your server. You can cache a page for an absolute period of time or implement a sliding expiration policy.

Next, you learn how to use page fragment caching, which enables you to cache particular areas of a page. For example, you can use page fragment caching to cache a page's menu bar, but not a list of current stock prices.

Finally, you examine how to use data caching in an ASP.NET application. You can use data caching to cache DataSets and other types of data. After the data is cached, you can use the same data in multiple ASP.NET pages.


Using Page Output Caching

You can use page output caching to dramatically improve the performance of your Web site. Instead of executing a page each time the page is requested, you can cache the output of the page and send the cached copy to satisfy browser requests.

Imagine, for example, that your site includes a page that displays product information retrieved from a database table. By default, every time someone requests the product page, the page must be executed and the information must be retrieved from the database. If you enable page output caching, however, the page is executed only once, and the information is retrieved from the database only once. This means less work both for your Web application and for your database server.

To enable output caching for a page, include the following page directive in the page:

<%@ OutputCache Duration="60" VaryByParam="none" %>

This directive causes the output of the page to be cached for 60 seconds.

The page in Listing 17.1, for example, displays the current time. The page also includes an OutputCache directive that caches the output of the page for 10 seconds. If you repeatedly refresh the page, you'll notice that the time is updated only once every 10 seconds.

Listing 17.1 TimeCache.aspx
<%@ OutputCache Duration="10" VaryByParam="none" %>
<html>
<head><title>TimeCache.aspx</title></head>
<body>

At the tone, the time will be:
<%= Now() %>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

You can also cache the output of a page that retrieves data from a database. For example, Listing 17.2 contains a product page that displays information on a particular product retrieved from the Northwind database. This page is cached for 5 minutes.

Listing 17.2 ProductPage.aspx
<%@ OutputCache Duration="300" VaryByParam="none" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">
Sub Page_Load
  Dim conNorthwind As SqlConnection
  Dim strSelect As String
  Dim cmdSelect As SqlCommand
  Dim dtrProducts As SqlDataReader

  conNorthwind = New SqlConnection( "Server=Localhost;UID=sa;PWD=secret;Database=Northwind" )
  strselect = "Select * From Products Where ProductID=1"
  cmdSelect = New SqlCommand( strSelect, conNorthwind )
  conNorthwind.Open()
    dgrdProducts.DataSource = cmdSelect.ExecuteReader( CommandBehavior.SingleRow )
    dgrdProducts.DataBind()
  conNorthwind.Close
End Sub
</Script>
<html>
<head><title>ProductPage.aspx</title></head>
<body>

<asp:DataGrid
  ID="dgrdProducts"
  CellPadding="10"
  Runat="Server" />
<hr>
<small>Last Updated: <%=Now%></small>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

The OutputCache directive at the top of the page in Listing 17.2 results in the page being cached for 5 minutes. The page also is regenerated if its source is modified.

CAUTION

You are not guaranteed that items will remain in the page output cache. If system resources get low, the ASP.NET Framework starts dumping items from the cache.

Varying Cache Contents by Parameter

The OutputCache directive has a second attribute that indicates how a page should be cached when a request for a page contains parameters (either query string parameters or form field parameters). This attribute can accept either a semicolon-delimited list of parameters or one of the following special values:

  • none— Indicates that different cached versions of a page should not be created when the page is requested with different parameters.

  • *— Indicates that different cached versions of a page should be created whenever the page is requested with different parameters.

The VaryByParam attribute is particularly useful when you need to display different content on a page depending on the value of a query string parameter.

For example, in the preceding section, you used the OutputCache page directive to cache the output of a page that retrieves product information from a database table. The page works fine as long as you want to display information for only one product. Typically, however, you want to create a page that displays information on different products depending on the value of a query string parameter passed to the page.

Consider the page in Listing 17.3. This page displays information on different products depending on the value of the PID query string variable.

Listing 17.3 ProductPageByParam.aspx
<%@ OutputCache Duration="300" VaryByParam="none" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">
Sub Page_Load( s As Object, e As EventArgs )
  Dim strProductID As String
  Dim conNorthwind As SqlConnection
  Dim strSelect As String
  Dim cmdSelect As SqlCommand
  Dim dtrProducts As SqlDataReader

  strProductID = Request.Params( "PID" )
  If strProductID = Nothing Then
    strProductID = "1"
  End If
  conNorthwind = New SqlConnection( "Server=Localhost;UID=sa;PWD=secret;Database=Northwind" )
  strSelect = "Select * From Products Where ProductID=" & strProductID
  cmdSelect = New SqlCommand( strSelect, conNorthwind )
  conNorthwind.Open
    dgrdProducts.DataSource = cmdSelect.ExecuteReader()
    dgrdProducts.DataBind()
  conNorthwind.Close
End Sub
</Script>
<html>
<head><title>ProductPageByParam.aspx</title></head>
<body>

<asp:DataGrid
  ID="dgrdProducts"
  CellPadding="10"
  Runat="Server" />
<hr>
<small>Last Updated: <%=Now%></small>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

The value of the PID query string variable is grabbed in the Page_Load subroutine and assigned to a variable named strProductID. If PID doesn't have a value, the strProductID variable defaults to the value 1. You use the strProductID variable to retrieve information on a specific product in the Products database table.

In Listing 17.3, the VaryByParam attribute is assigned the value none. If you request the page in Listing 17.3 without including a PID in the query string, the page displays the information for the product with a product ID of 1. The OutputCache directive causes the page to be cached for 300 seconds.

If you subsequently request the page by supplying a PID, you'll still receive the same cached page as before. Regardless of the PID that you supply, the page will continue to display the content for the product with the PID of 1.

Since the VaryByParam attribute is assigned the value none, any query string parameter passed to the page is ignored. This isn't good since you want to display different product information with the page depending on the value of the PID parameter.

There are two ways around this problem. You can indicate that you want a different cached version of the page whenever different parameters are passed to the page by declaring the OutputCache directive like this:

<%@ OutputCache Duration="300" VaryByParam="*" %>

This directive indicates that different cached versions of the page should be created whenever a different parameter is passed to the page. So both of the following requests would generate different cached pages:

http://Localhost/ProductPageByParm.aspx?PID=4

http://Localhost/ProductPageByParm.aspx?username=bob

<%@ OutputCache Duration="300" VaryByParam="PID" %>

This directive stores different cached versions of a page depending on the value of the PID query string parameter. If you request the page with a PID of 1, and then request the page with a PID of 4, different cached versions of the page are stored.

The modified product page is contained in Listing 17.4.

Listing 17.4 ProductPageByParamCache.aspx
<%@ OutputCache Duration="300" VaryByParam="PID" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">
Sub Page_Load( s As Object, e As EventArgs )
  Dim strProductID As String
  Dim conNorthwind As SqlConnection
  Dim strSelect As String
  Dim cmdSelect As SqlCommand
  Dim dtrProducts As SqlDataReader

  strProductID = Request.Params( "PID" )
  If strProductID = Nothing Then
    strProductID = "1"
  End If
  conNorthwind = New SqlConnection( "Server=Localhost;UID=sa;PWD=secret;Database=Northwind" )
  strSelect = "Select * From Products Where ProductID=" & strProductID
  cmdSelect = New SqlCommand( strSelect, conNorthwind )
  conNorthwind.Open
    dgrdProducts.DataSource = cmdSelect.ExecuteReader()
    dgrdProducts.DataBind()
  conNorthwind.Close
End Sub
</Script>
<html>
<head><title>ProductPageByParamCache.aspx</title></head>
<body>

<asp:DataGrid
  ID="dgrdProducts"
  CellPadding="10"
  Runat="Server" />
<hr>
<small>Last Updated: <%=Now%></small>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

You can list more than one parameter in the VaryByParam attribute. You simply need to separate each parameter listed with a semicolon. For example, the following OutputCache directive creates different cached copies of a page depending on the values of the PID and ProductCategory parameters:

<%@ OutputCache Duration="20" VaryByParam="PID;ProductCategory" %>

Be aware, however, that each cached copy of a page eats away at memory. So, the more versions of a page that you cache, the more server resources are consumed.

Varying Cache Contents by Header

One of the benefits of using the controls in the ASP.NET framework is that many of them can detect the type of browser being used to request a page and display different content appropriate to the capabilities of the browser. However, by default, page output caching defeats a control's capability to display different content for different browsers. Because page output caching stores the output of a page, the very same page is sent to every browser that requests it.

Fortunately, you can find an easy way around this problem. The OutputCache page directive supports a VaryByHeader attribute. You can use this attribute to cache different versions of a page depending on the type of browser that requests the page. Here's how you would use this directive:

<%@ OutputCache Duration="20" VaryByParam="*" VaryByHeader="User-Agent"%>

The User-Agent header identifies the type of browser being used to request the page. Different types of browsers—such as Netscape, Internet Explorer, and Opera—transmit different User-Agent headers when they request a page. Normally, the User-Agent header also includes such information as the version of the browser and type of platform on which the browser is running.

The page in Listing 17.5 illustrates how you use the VaryByHeader attribute. This page displays different content depending on whether it is requested by a Netscape browser or some other type of browser. When the page is requested by a Netscape browser, the page displays blinking text. Otherwise, the page displays the text in a red font (see Figure 17.1).

Listing 17.5 CacheByHeader.aspx
<%@ OutputCache Duration="300" VaryByParam="*" VaryByHeader="User-Agent"%>

<Script Runat="Server">

Dim strBuyOurProducts As String

Sub Page_Load
  If Request.Browser.Browser = "Netscape" Then
    strBuyOurProducts = "<Blink>Buy Our Products!</Blink>"
  Else
    strBuyOurProducts = "<font color=red>Buy Our Products!</font>"
  End If
End Sub
</Script>

<html>
<head><title>CacheByHeader.aspx</title></head>
<body>

<h1><%=strBuyOurProducts %></h1>

<hr>
<small>Last Updated: <%=Now%></small>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 17.1. Using the CacheByHeader attribute.

If you launch both Netscape and Internet Explorer browsers on your computer and request the page in Listing 17.5, different content is displayed. Note that different copies of the page are cached not only when different types of browsers request the page, but also when different versions of the same browser request the page. Any variation in the User-Agent header results in a different version of the page being cached.

NOTE

See the next section for a way to make the output cache less sensitive to minor differences in browser types.

You also can use the VaryByHeader attribute to create different cached versions of a page depending on other headers, such as Referrer, Host, and Accept-Language headers.

You can use the page in Listing 17.6 to view all the headers that you can use from the ServerVariables collection (see Figure 17.2).

Listing 17.6 ServerVariables.aspx
<Script Runat="Server">

Sub Page_Load
  Dim strHeader As String

  For each strHeader in Request.ServerVariables
    lblOutput.Text &= "<li>" & strHeader & "=" & Request( strHeader )
  Next
End Sub

</Script>

<html>
<head><title>ServerVariables.aspx</title></head>
<body>

<asp:Label
  ID="lblOutput"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 17.2. The ServerVariables collection.

Varying the Cache Contents by a Custom String

The OutputCache directive supports an additional attribute named VaryByCustom. This attribute enables you to control the sensitivity of page output caching.

In the preceding section, you used the VaryByHeader attribute with the User-Agent header to cache different versions of a page depending on the browser. One problem with this approach is that the User-Agent header not only varies among different types of browsers, but also typically contains additional information, such as information about the version of the browser and computer platform on which the browser is running.

If you want to make broader distinctions among browsers, you can use the VaryByCustom attribute like this:

<%@ OutputCache Duration="60" VaryByParam="*" VaryByCustom="Browser" %>

When you use the value Browser with the VaryByCustom attribute, only the browser name and major version information are taken into account when caching the output of a page. So, the same page would be cached for Internet Explorer 5.0 and Internet Explorer 5.5.

You also can supply a custom string to the VaryByCustom attribute. If you supply a custom string, you are responsible for creating a function in the Global.asax file that controls how pages are cached.

NOTE

To learn more information about the Global.asax file, see Chapter 15, "Creating ASP.NET Applications."

Suppose that you want to cache different versions of a page depending on whether the browser requesting the page supports VBScript. To do so, you need to assign a custom string to the VaryByCustom attribute. Listing 17.7 illustrates the proper method of assigning a string to the VaryByCustom attribute in the OutputCache directive. (This file is located in the VaryByCustom subdirectory.)

Listing 17.7 VaryByCustom/VaryByCustom.aspx
<%@ OutputCache Duration="300" VaryByParam="*" VaryByCustom="VBScript" %>

<Script Runat="Server">

Sub Page_Load
  lblMessage.Text = Request.Browser.VBScript
End Sub

</Script>

<html>
<head><title>VaryByCustom.aspx</title></head>
<body>

<asp:Label
  ID="lblMessage"
  Runat="Server" />

<hr>
Page generated: <%=DateTime.Now %>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Next, you need to add a new function named GetVaryByCustomString to the Global.asax file. This function is included in the Global.asax file in Listing 17.8.

Listing 17.8 VaryByCustom/Global.asax
<Script Runat="Server">

Overrides Function GetVaryByCustomString( _
  objContext As HttpContext, _
  strCustom As String ) As String

  Return objContext.Request.Browser.VBScript
End Function

</Script>

The C# version of this code can be found on the CD-ROM.

CAUTION

The Global.asax file must be located in the application root directory; it doesn't work in a subdirectory.

The GetVaryByCustomString function has two parameters: the current context and the custom string passed by the VaryByCustom attribute. The function returns a string indicating how a page should be cached. In the case of Listing 17.8, the function returns the value True when a browser supports VBScript and False otherwise. The function therefore creates two different cached versions of the page.

NOTE

To determine whether a browser supports VBScript, you can use the HttpBrowserCapabilities class. You can use this class to detect many other features of a browser, such as its name, its major and minor version number, whether it supports Java applets, and so on.

Setting the Cache Location

By default, when you enable page output caching, the page is cached on both the server and client. The page is cached in the server's memory, and the proper headers are sent to the browser requesting the page to cause the browser to create a cached copy.

You can control the exact location where the caching occurs by adding the Location attribute to the OutputCache page directive. The Location attribute can have any one of the following values from the OutputCacheLocation enumeration:

  • Any— This is the default value. The page should be cached at any location, including the server, any downstream servers, and the browser.

  • Client— The output of the page should be cached only on the browser.

  • Downstream— The output of the page should be cached only on a downstream server.

  • None— No page caching should be performed.

  • Server— The page should be cached only on the server.

If you want to cache the output of a page only on the browser and not on the server, for example, you would use the following OutputCache directive:

<%@ OutputCache Duration="600" VaryByParam="*" Location="Client" %>

Be aware, however, that browser caching is browser-dependent.

Working with the HttpCachePolicy Class

Behind the scenes, page output caching is handled by the HttpCachePolicy class. If you want more fine-grained control over the caching behavior of a page, you can directly call the methods of this class.

The two most important methods of this class are the SetExpires() and SetCacheability() methods. The SetExpires() method indicates an absolute date and time when a page expires, and the SetCacheability() method indicates how the page should be cached. The SetCacheability() method accepts any of the following values from the HttpCacheability enumeration:

  • NoCache— Prevents any page caching with the Cache-Control:no-cache header.

  • Private— Indicates that the page should not be cached by proxy servers, but can be cached by browsers (this is the default value).

  • Public— Indicates that the page can be cached by proxy servers and browsers.

  • Server— Indicates that the page should only be cached on the server.

For example, the page in Listing 17.9 uses the methods of the HttpCachePolicy class to cache a page on the server until 12/25/2005. (Most likely, the page will be dumped from the cache much earlier than that.)

Listing 17.9 HttpCachePolicy.aspx
<Script Runat="Server">

Sub Page_Load
  Response.Cache.SetExpires( #12/25/2005# )
  Response.Cache.SetCacheability( HttpCacheability.Server )
End Sub

</Script>

<html>
<head><title>HttpCachePolicy.aspx</title></head>
<body>

At the tone, the time will be:
<%=Now() %>

</body>
</html>

The C# version of this code can be found on the CD-ROM.


Using Page Fragment Caching

In several situations, caching part of a page makes more sense than caching the whole page. For example, if you were randomly displaying banner advertisements on a Web page, you would not want to cache the part that contains the banner advertisements because caching the page would result in the same banner advertisement being displayed each time the page is requested.

Page fragment caching is implemented with user controls. You define different areas of a page by creating separate user controls for each area of the page. Within each user control, you can use the OutputCache directive to indicate how the output of the control is cached.

NOTE

To learn more details about user controls, see Chapter 5, "Creating Custom Controls with User Controls."

Imagine that the home page for your Web site contains randomly selected banner advertisements that are displayed at the top and bottom of the page. Furthermore, imagine that the body of the page displays a list of products from the Products database table. In that case, you would want to cache the area of the page that displays the list of products, but not the areas of the page that display the banner advertisements.

The page in Listing 17.10 illustrates how you can cache the body of the page, but not the banner advertisements displayed at the top and bottom (see Figure 17.3).

Listing 17.10 Home.aspx
<%@ Register TagPrefix="myControls" TagName="PageContents"
SRC="PageContents.ascx" %>

<html>
<head><title>Home.aspx</title></head>
<body bgcolor="#eeeeee">

<asp:AdRotator
  ID="myTopAd"
  KeywordFilter="Top"
  AdvertisementFile="AdFile.xml"
  Runat="Server" />

<myControls:PageContents
  CategoryID="2"
  Runat="Server"/>

<asp:AdRotator
  ID="myBottomAd"
  Align="Center"
  KeywordFilter="Bottom"
  AdvertisementFile="AdFile.xml"
  Runat="Server" />

<hr>
( page generated: <%=Now()%> )

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 17.3. Partial page caching.

The body of the page in Listing 17.10 contains a user control named PageContents. This user control, contained in Listing 17.11, displays a list of products for a particular category.

Listing 17.11 PageContents.ascx
<%@ OutputCache Duration="60" VaryByParam="*" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Public CategoryID As Integer

Sub Page_Load
  Dim conNorthwind As SqlConnection
  Dim strSelect As String
  Dim cmdSelect As SqlCommand

  conNorthwind = New SqlConnection( "Server=Localhost;UID=sa;PWD=secret;Database=Northwind" )
  strSelect = "Select * From Products Where CategoryID=@CategoryID"
  cmdSelect = New SqlCommand( strSelect, conNorthwind )
  cmdSelect.Parameters.Add( "@CategoryID", CategoryID )
  conNorthwind.Open()
    dgrdProducts.DataSource = cmdSelect.ExecuteReader()
    dgrdProducts.DataBind()
  conNorthwind.Close()
End Sub
</Script>

<p>
<h1>Welcome to Our Web Site!</h1>
Here is a list of our current products:
<p>
<asp:DataGrid
  ID="dgrdProducts"
  Runat="Server" />

( Control generated: <%=Now%> )
<p>

The C# version of this code can be found on the CD-ROM.

The user control in Listing 17.11 displays the text Welcome to Our Web Site! and a DataGrid that contains a list of products.

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.

NOTE

What happens if you use the same cached user control in multiple pages? By default, the output of each user control is cached separately, If you want to share the same cached output across multiple user controls in order to preserve memory, then you can use the Shared attribute with the OutputCache directive like this:

<%@ OutputCache Duration="60" VaryByParam="*" Shared="true" %>

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

Listing 17.12 Menu.ascx
<%@ OutputCache Duration="300" VaryByParam="menuID" %>
<Script Runat="Server">

Dim strMenuID As String

Sub Page_Load
  strMenuID = Request.Params( "menuID" )
  If strMenuID = Nothing Then
    strMenuID = "0"
  End If
End Sub
</Script>

<table border="2" bordercolor="black"
  cellpadding="5" cellspacing="0"
  bgcolor="#eeeeee">
<tr>
  <td>
  <% If strMenuID = "0" Then %>
  <b>Our Company</b>
  <% Else %>
  <a href="ourcompany.aspx?menuID=0">Our Company</a>
  <% End If %>
  </td>
  <td>
  <% If strMenuID = "1" Then %>
  <b>Our Services</b>
  <% Else %>
  <a href="ourservices.aspx?menuID=1">Our Services</a>
  <% End If %>
  </td>
  <td>
  <% If strMenuID = "2" Then %>
  <b>Our Products</b>
  <% Else %>
  <a href="ourproducts.aspx?menuID=2">Our Products</a>
  <% End If %>
  </td>
</tr>
</table>
(menu generated: <%=Now() %> )

The C# version of this code can be found on the CD-ROM.

Figure 17.4. Using VaryByParam with partial page caching.

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.

Listing 17.13 OurCompany.aspx
<%@ Register TagPrefix="myControls" TagName="Menu"
SRC="menu.ascx" %>

<html>
<head><title>OurCompany.aspx</title></head>
<body bgcolor="lightblue">

<myControls:Menu
  Runat="Server" />

<h1>Our Company</h1>
<asp:Panel
  width="400"
  style="padding: 10px"
  BorderStyle="solid"
  BackColor="White"
  Runat="Server">
Our company specializes in creating large scale Web sites
for commercial clients. Visit the different areas of
our Web site to learn more!
</asp:Panel>

<hr>
( page generated: <%=now() %> )

</body>
</html>

The C# version of this code can be found on the CD-ROM.

The page in Listing 17.13 registers the user control by using the Register page directive. The control is declared in the body of the page.

Examining the Limitations of Page Fragment Caching

You should be aware that you are significantly limited on how you can use a user control that contains an OutputCache directive. If you enable caching for a user control, you can no longer programmatically access the control in its containing page.

For example, suppose you declare a cached user control like this:

<myControls:PageContents
  ID="ctlContents"
  Runat="Server"/>

Since the control is cached, you cannot programmatically set a property of the user control in a page like this:

ctlContents.Message = "Hello!"

You also should not attempt to use data-binding syntax with a cached user control. For example, never attempt to declare a cached user control like this:

<myControls:PageContents
  CategoryID='<%# CategoryID %>'
  Runat="Server"/>

This declaration of the cached user control generates an error message.

Nothing is wrong, however, with declaratively setting a property of a cached user control like this:

<myControls:PageContents
  CategoryID="2"
  Runat="Server"/>

In this case, the value of the CategoryID property is set declaratively.


Using Page Data Caching

In the previous sections of this chapter, you learned how to cache the entire contents of a page and a particular area of a page. However, in certain situations, caching the data displayed on a page makes more sense than caching the page itself.

Imagine that you are creating an intranet application that displays an employee directory for your company. Imagine that you need to display employee information on multiple pages. On one page, you might display a list of employee last names. On another page, you might want to display detailed information about a particular employee.

In this situation, you can store all the employee information in a database table and cache a DataSet that contains the database table in memory. On each page in which you need to display employee information, you can retrieve the information from the cached DataSet.

Caching database records can significantly improve the performance of your Web site. Instead of retrieving employee information from a database table every time you need to display the information on a page, you can retrieve the information directly from memory.

You can cache items in memory by taking advantage of the Cache object, which is an instance of the Cache class. Each ASP.NET application has a single Cache object that remains valid until the application is restarted.

ASP CLASSIC NOTE

When building pages with Classic ASP, developers often resorted to caching data in the Application object. This object still exists in ASP.NET, and you can still use it to cache data. However, the Cache object provides you with more flexibility than the Application object.

The Cache object enables you, for example, to specify expiration policies and dependencies for cached items. Furthermore, the Cache object supports scavenging. When you add items to the Application object, the item remains in memory until explicitly removed or the application is restarted. The Cache object, on the other hand, automatically dumps items from the cache when memory resources get low.

In the following sections, you learn different methods of adding and removing items from the Cache object.

Adding Items to the Cache

You can add any item to the Cache object by assigning a value to its Item property. For example, the following statement adds a new item named myItem with the value "Hello World!" to the Cache object:

Cache.Item( "myItem" ) = "Hello World!"

Because Item is the default property of the Cache object, you can also add an item to the cache like this:

Cache( "myItem" ) = "Hello World!"

You can add more complex objects to the cache, such as DataSets, DataViews, or collections. For example, the following statements add a HashTable to the Cache object:

Dim colHashTable As HashTable

colStates = New HashTable

colStates.Add( "CA", "California" )
colStates.Add( "WA", "Washington" )
Cache( "States" ) = colStates

After you add an item to the cache, you can retrieve it like this:

myItem = Cache( "myItem" )

You need to understand that when you add items to the Cache object, the items are not guaranteed to remain there. The Cache object supports scavenging; it automatically removes low-priority or infrequently used items from the cache when system resources become low.

NOTE

See the section "Setting Cache Item Priorities" later in this chapter for more information on how to control when a cached item expires.

When you retrieve items from the cache, therefore, you should always check to see whether the item still exists before you do anything with it. If the item has been removed from the Cache object, attempting to retrieve the item from the cache returns the value Nothing.

Typically, when you retrieve items from the cache, you use the following logic:

myItem = Cache( "myItem" )
If myItem Is Nothing Then
  ... Recreate item and assign to Cache
End If

In other words, after you retrieve an item from the cache, you should check whether the item is Nothing. If it is Nothing, you need to reassign the item to the cache.

If you want to explicitly remove items from the cache, you can use the Cache object's Remove method. For example, the following statement removes the item named myItem from the cache:

Cache.Remove( "myItem" )

The Cache object contains more than just the information you have added. The object also is used internally to cache system information between requests. You can view all the current items in the cache by using the page in Listing 17.14 (see Figure 17.5).

Listing 17.14 ShowCached.aspx
<Script Runat="Server">

Sub Page_Load
  Dim objItem As DictionaryEntry

  For each objItem in Cache
    lblContents.Text &= "<li>" & objItem.Key
    lblContents.Text &= "=" & objItem.Value.ToString
  Next
End Sub
</Script>

<html>
<head><title>ShowCached.aspx</title></head>
<body>

<h2>Cached Items</h2>

<asp:Label
  ID="lblContents"
  EnableViewState="False"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 17.5. List of cached items.

In the Page_Load subroutine in Listing 17.14, a FOR...EACH loop displays the key and value for each item in the Cache object. The items are displayed in a Label control.

Adding Cache File Dependencies

When you add an item to the cache, you can associate the item with a file. If the file changes, the item is automatically removed from the cache.

To create a file dependency, use the Insert method of the Cache object like this:

Cache.Insert( "myItem", "Hello!", _
  New CacheDependency( MapPath( "myFile.txt" ) ) )

This statement adds an item named myItem with the value Hello! to the cache. It also creates a file dependency between the item and a file named myFile.txt. If myFile.txt is modified, myItem is automatically dumped from the cache.

You also can create a file dependency between a cached item and multiple files. For example, the following statements make the cached item named myItem dependent on three files named File1.txt, File2.txt, and File3.txt:

Dim myFiles() As String = _
    { MapPath( "File1.txt" ), _
      MapPath( "File2.txt" ), _
      MapPath( "File3.txt" ) }

Cache.Insert( "myItem", "Hello", _
  New CacheDependency( myFiles ) )

A string array containing a list of three files is passed to the constructor of the CacheDependency class to create the file dependencies. If any of the three files are modified, myItem is dropped from the cache.

NOTE

The MapPath method of the Page class translates a virtual path, such as /myFile.txt, into a physical path, such as c:\inetpub\wwwroot\myFile.txt.

Creating file dependencies is useful when you're working with custom configuration files. Imagine, for example, that your application depends on a custom XML file for configuration information. You use the XML file to specify format information for your pages, such as the background color and font size.

To improve the performance of your application, you can cache the XML file in the Cache object. However, if someone updates the XML configuration file, you want the cached version of the configuration file to be automatically updated as well. You can add this functionality by creating a file dependency between the cached XML file and the XML file on the hard drive.

The page in Listing 17.15 illustrates how you can create a file dependency between a cached item named FormatInfo and an XML file named FormatInfo.xml.

Listing 17.15 FileDependency.aspx
<%@ Import Namespace="System.Data" %>
<Script Runat="Server">

Dim strBgcolor As String
Dim strFontface As String
Dim strFontsize As String

Sub Page_Load
  Dim dstFormat As DataSet
  Dim drowFormat As DataRow

  dstFormat = Cache( "FormatInfo" )
  If dstFormat Is Nothing Then
    dstFormat = GetFormatInfo()
    Cache.Insert( "FormatInfo", dstFormat, _
      New CacheDependency( MapPath( "FormatInfo.xml" ) ) )
  End If
  drowFormat = dstFormat.Tables( 0 ).Rows( 0 )
  strBgcolor = drowFormat( "bgcolor" ).Trim
  strFontface = drowFormat( "fontface" ).Trim
  strFontsize = drowFormat( "fontsize" ).Trim
End Sub

Function GetFormatInfo() As DataSet
  Dim dstFormat As DataSet

  dstFormat = New DataSet()
  dstFormat.ReadXML( MapPath( "FormatInfo.xml" ) )
  Return dstFormat
End Function

</Script>

<html>
<head>
<basefont
  size="<%=strFontsize%>"
  face="<%=strFontface%>">
<title>FileDependency.aspx</title>
</head>
<body bgcolor="<%=strBgcolor%>">

Hello, welcome to our Web site!

</body>
</html>

The C# version of this code can be found on the CD-ROM.

In the Page_Load subroutine in Listing 17.15, an item named FormatInfo is retrieved from the Cache object. The FormatInfo item represents the FormatInfo.xml file as a DataSet.

The background color, base font face, and base font size of the page are assigned values from the FormatInfo DataSet.

If the FormatInfo item does not exist in the cache, the GetFormatInfo() function is called to retrieve the FormatInfo.xml file from the hard drive and assign it to the cache. When the FormatInfo item is added to the cache, it's added with a file dependency on the FormatInfo.xml file, which is included in Listing 17.16.

Listing 17.16 FormatInfo.xml
<format>
  <bgcolor>
  Green
  </bgcolor>
  <fontsize>
  6
  </fontsize>
  <fontface>
  Arial
  </fontface>
</format>

The C# version of this code can be found on the CD-ROM.

If you modify the FormatInfo.xml file, the FormatInfo DataSet is automatically updated in the cache. For example, if you change the bgcolor element in the FormatInfo.xml file from Green to Red, the background color of the page is automatically updated.

Adding Cache Trigger Dependencies

It would be nice if you could update an item in the cache whenever a change is made to a record in a database table. That way, you could cache a database table in memory and dramatically improve the performance of your application without worrying about the data becoming outdated. Unfortunately, however, the Cache object does not directly support this type of dependency. To make an item in the cache dependent on a database table, you need to do a little work.

Microsoft SQL Server includes support for a special type of stored procedure called a trigger. A trigger is a stored procedure that executes whenever an update, delete, or insert statement is executed against a table or view. In other words, you can associate a trigger with a table in such a way that it fires whenever a change is made to the contents of the table.

Microsoft SQL Server also includes an extended stored procedure named xp_cmdshell that executes a string as an operating system command. You can use the xp_cmdshell stored procedure to write a file to a directory.

To create an item in the cache that is dependent on a database table, therefore, you can create a trigger that writes a file to a directory. You can then use a normal file cache dependency to update the item in the cache.

To illustrate how this scenario works, you can add a DataSet that represents the Products table to the cache. Whenever a change to the Products table is made, the Products table is automatically updated.

The first thing you need to do is create the trigger. The proper trigger is contained in Listing 17.17.

Listing 17.17 UpdateCache.sql
CREATE TRIGGER UpdateCache
ON Products
FOR UPDATE, DELETE, INSERT
AS
DECLARE @cmd Varchar( 200 )
SELECT @cmd = 'echo ' + Cast( getDATE() As Varchar( 50 ) ) +
  ' > c:\	leChange.txt'
EXEC master..xp_cmdshell @cmd, no_output

The C# version of this code can be found on the CD-ROM.

The UpdateCache trigger is fired when an update, delete, or insert statement is executed against the Products table. The trigger uses the xp_cmdshell stored procedure to write a file, named tableChange.txt, to the file system. The file contains the current date and time.

Next, you need to create an ASP.NET page that adds the Products database table to the Cache object. The page in Listing 17.18 adds a DataSet representing the Products table to the cache.

Listing 17.18 DatabaseDependency.aspx
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
  Dim dstProducts As DataSet

  dstProducts = Cache( "productsDS" )
  If dstProducts Is Nothing Then
    dstProducts = GetProducts()
    Cache.Insert( "Products", dstProducts, _
      New CacheDependency( "c:\	leChange.txt" ) )
  End If
  dgrdProducts.DataSource = dstProducts
  dgrdProducts.DataBind()
End Sub

Function GetProducts() As DataSet
  Dim conNorthwind As SqlConnection
  Dim strSelect As String
  Dim dadProducts As SqlDataAdapter
  Dim dstProducts As DataSet

  conNorthwind = New SqlConnection( "Server=Localhost;UID=sa;PWD=secret;Database=Northwind" )
  strSelect = "Select TOP 20 * From Products ORDER BY ProductID"
  dadProducts = New SqlDataAdapter( strSelect, conNorthwind )
  dstProducts = New DataSet()
  dadProducts.Fill( dstProducts, "ProductsDS" )
  Return dstProducts
End Function

</Script>

<html>
<head><title>DatabaseDependency.aspx</title>
</head>
<body>

<asp:DataGrid
  ID="dgrdProducts"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

When you request the page in Listing 17.18, a DataSet representing the Products database table is retrieved from the cache. If the item doesn't exist in the cache, the DataSet is created and added to the cache with a file dependency on the tableChange.txt file.

The Products database table remains cached in memory until a change is made to the underlying Products database table. If the Products table is modified, the UpdateCache trigger executes and writes a file to the hard drive. This file, in turn, causes the current cached copy of the Products table to be dropped and a new copy of the table to be added to the cache.

Adding Cache Key Dependencies

You can make one cached item dependent on another cached item by creating a key dependency. For example, imagine that you have two database tables cached in memory. One table contains a list of products, and the other table contains a list of product categories. If you make a change to the cached product categories table, you also want to update the cached copy of the products table. You can do so by creating a key dependency.

To create a key dependency between one cached item and another, you use the Cache object's Insert method like this:

Dim arrKeyDepends() As String = { "item2" }

Cache.Insert( "item1", "hello!", _
  New CacheDependency( Nothing, arrKeyDepends ) )

This statement makes item1 dependent on item2. If item2 is modified, item1 is dumped from the Cache object. The first statement creates a string array of key names. In the second statement, this array is passed to the CacheDependency constructor to create the dependencies.

You can make one cached item dependent on multiple cached items. To do so, simply list multiple cached item keys in the array:

Dim arrKeyDepends() As String = { "item2", "item3", "item4" }

Cache.Insert( "item1", "hello!", _
  New CacheDependency( Nothing, arrKeyDepends ) )

These statements make item1 dependent on item2, item3, and item4. If any of these cached items are modified, item1 is automatically deleted from the cache.

The page in Listing 17.19 illustrates how to make one cached item dependent on another.

Listing 17.19 KeyDependency.aspx
<Script Runat="Server">

Sub UpdateItem1( s As Object, e As EventArgs )
  Cache( "item1" ) = txtNewValue.Text
End Sub

Sub UpdateItem2( s As Object, e As EventArgs )
  Dim arrKeyDepends() As String = { "item1" }

  Cache.Insert( "item2", txtNewValue.Text, _
    New CacheDependency( Nothing, arrKeyDepends ) )
End Sub

</Script>

<html>
<head><title>KeyDependency.aspx</title></head>
<body>
<form Runat="Server">

<asp:TextBox
  ID="txtNewValue"
  Runat="Server" />
<p>
<asp:Button
  Text="Update Item1"
  OnClick="UpdateItem1"
  Runat="Server" />
<asp:Button
  Text="Update Item2"
  OnClick="UpdateItem2"
  Runat="Server" />

<hr>

Item1 = <%=Cache( "item1" )%>
<br>
Item2 = <%=Cache( "item2" )%>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

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
<Script Runat="Server">

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

</Script>

<html>
<head><title>AbsoluteExpiration.aspx</title></head>
<body>

Data last cached:
<asp:Label
  ID="lblMessage"
  Font-Bold="True"
  ForeColor="Blue"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

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

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

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
<Script Runat="Server">

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
</Script>

<html>
<head><title>DataSlide.aspx</title></head>
<body>

Data last cached:
<asp:Label
  ID="lblMessage"
  Font-Bold="True"
  ForeColor="Blue"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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 value is Normal.

  • High— Items with this priority are the least likely to be removed from the cache.

  • Low— Items with this priority are the most likely to be removed from the cache.

  • Normal— These items have the Normal priority.

  • NotRemovable— Items with this priority should never be removed from the cache.

To insert an item into the cache with a High priority, for example, you would use the following statement:

Cache.Insert( "myItem", _
              "Hello", _
               Nothing, _
               Cache.NoAbsoluteExpiration, _
               Cache.NoSlidingExpiration, _
               CacheItemPriority.High, _
               Nothing )

This statement provides a hint to the Cache object to weigh this cache item higher than other cache items.

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:

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

Figure 17.6. Using a cache callback method.

Listing 17.22 CacheCallback.aspx
<Script Runat="Server">

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 )
    Cache.Insert( "myItem", _
                   txtNewValue.Text, _
                   Nothing, _
                   Now.AddSeconds( 10 ), _
                   Cache.NoSlidingExpiration, _
                   CacheItemPriority.High, _
                   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

</Script>

<html>
<head><title>CacheCallback.aspx</title></head>
<body>
<form Runat="Server">

<h2>Cache Log</h2>
<asp:DataGrid
  ID="dgrdCacheLog"
  CellPadding="8"
  Runat="Server" />
<p>
<asp:TextBox
  id="txtNewValue"
  Runat="Server" />
<p>
<asp:Button
  id="btnAddCache"
  Text="Add To Cache!"
  OnClick="btnAddCache_Click"
  Runat="Server" />

<asp:Button
  id="btnRemoveCache"
  Text="Remove From Cache!"
  OnClick="btnRemoveCache_Click"
  Runat="Server" />

<asp:Button
  Text="Refresh Page!"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

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:

  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.

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

  • Tracing and Monitoring Your Application

  • Logging Events

  • Using the Debugger

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 read 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
<configuration>
  <appSettings>
    <add key="myKey" value="myValue"/>
</configuration>

The C# version of this code can be found on the CD-ROM.

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

Figure 18.1. Receiving a configuration error.

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

Listing 18.2 Parser.aspx
<script Runat="Server">

Sub Page_Load
  lblMessage.Text = "hello!"
End Sub

<html>
<head><title>Parser.aspx</title></head>
<body>

<asp:Label
  ID="lblMessage"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 18.2. Receiving a parser error.

The problem with the page in Listing 18.2 is that it does not include a closing </Script> tag that corresponds with the opening <Script> 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).

Listing 18.3 Compilation.aspx
<Script Runat="Server">

Sub Page_Load
  lblMessage.Value = "hello!"
End Sub

</Script>

<html>
<head><title>Compilation.aspx</title></head>
<body>

<asp:Label
  ID="lblMessage"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 18.3. Receiving a compilation error.

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.

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

Listing 18.4 Runtime.aspx
<Script Runat="Server">

Sub Page_Load
  Dim txtTextBox As TextBox

  txtTextBox.Text = "hello!"
End Sub

</Script>

<html>
<head><title>Runtime.aspx</title></head>
<body>

<asp:Label
  ID="lblMessage"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 18.4. Receiving a runtime error.

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.

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
<configuration>
  <system.web>
    <customErrors mode="Off" />
  </system.web>
</configuration>

The C# version of this code can be found on the CD-ROM.

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.

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.

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.

CAUTION

Never enable debug mode for a production Web site. Placing an application in debug mode results in a severe performance penalty.

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
<%@ Page Debug="True" %>

<Script Runat="Server">

Sub Page_Load
  Dim txtTextBox As TextBox

  txtTextBox.Text = "hello!"
End Sub

</Script>

<html>
<head><title>Debug.aspx</title></head>
<body>

<h2>This page has an error!</h2>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

Figure 18.5. Page with debug mode enabled.

Figure 18.6. Page with debug mode disabled.

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
<configuration>
  <system.web>
    <customErrors mode="Off" />
    <compilation debug="true" />
  </system.web>
</configuration>

The C# version of this code can be found on the CD-ROM.

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 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
[View full width]
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

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

</Script>

<html>
<head><title>TryCatch.aspx</title></head>
<body>

<asp:DataGrid
  ID="dgrdProducts"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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 email 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 a string that represents the error message

  • Source— Returns 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
[View full width]
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

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 objException As Exception
    Response.Write( "We're sorry, we are experiencing technical problems..." )
    Response.Write( "<hr>" )
    Response.Write( "<li> Message: " & objException.Message )
    Response.Write( "<li> Source: " & objException.Source )
    Response.Write( "<li> Stack Trace: " & objException.StackTrace )
    Response.Write( "<li> Target Site: " & objException.TargetSite.Name )
  End Try
End Sub

</Script>

<html>
<head><title>CatchException.aspx</title></head>
<body>

<asp:DataGrid
  ID="dgrdProducts"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

The Exception object represents a generic exception. You also can use the CATCH statement with particular exceptions. You might want to do so if you need to gather more specific information on the cause of an error. For example, the page in Listing 18.10 uses the CATCH statement with the SqlException class.

Listing 18.10 SqlException.aspx
[View full width]
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

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 BadTable", conNorthwind )
  Try
  conNorthwind.Open()
    dgrdProducts.DataSource = cmdSelect.ExecuteReader
    dgrdProducts.DataBind()
  conNorthwind.Close
  Catch objException As SqlException
    Dim objError As SqlError
    For each objError in objException.Errors
      Response.Write( "<li>" & objError.Message )
    Next
  End Try
End Sub

</Script>

<html>
<head><title>SqlException.aspx</title></head>
<body>

<asp:DataGrid
  ID="dgrdProducts"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

In the CATCH statement in Listing 18.10, an instance of the SqlException class is caught. Next, detailed error information is displayed for the SQL Exception by looping through the SQL Exception's Errors collection.

NOTE

The .NET Framework SDK Documentation lists the particular exceptions raised by the properties and methods of each class.

The TRY...CATCH statement includes an optional FINALLY clause. Any statements contained in the FINALLY clause are guaranteed to execute whether or not an exception has occurred. For example, in the following statements, the FINALLY clause closes the database connection, regardless of whether an error occurred:

Try
  dgrdProducts.DataSource = cmdSelect.ExecuteReader()
  dgrdProducts.DataBind()
Catch objException As Exception
  Response.Write( "Could Not Execute Statement!" )
Finally
  conNorthwind.Close
End Try
Catching Unhandled Exceptions in a Page

You can catch and handle any error that occurs in a page by creating a Page_Error subroutine, which is executed whenever an unhandled exception occurs.

The page in Listing 18.11, for example, uses the Page_Error subroutine to display a quick apology, display the error message, and clear the exception that raised the error.

Listing 18.11 PageError.aspx
<Script Runat="Server">

Sub Page_Error
  Response.Write( "Sorry, there was an error:" )
  Response.Write( "<p>" )
  Response.Write( Server.GetLastError.Message )
  Server.ClearError()
End Sub

Sub Page_Load
  Dim txtTextBox As TextBox

  txtTextBox.Text = "Hello World!"
End Sub

</Script>

<html>
<head><title>PageError.aspx</title></head>
<body>

<h2>This page contains an error!</h2>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

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.

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.

Listing 18.12 CustomErrors/Web.Config
<configuration>
  <system.web>
    <customErrors mode="On" defaultRedirect="AppError.aspx">
      <error statusCode="404" redirect="NotFound.aspx" />
      <error statusCode="500" redirect="AppError.aspx" />
    </customErrors>
  </system.web>
</configuration>

The C# version of this code can be found on the CD-ROM.

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 Listing 18.13 illustrates how you can use the aspxerrorpath query string variable to display a simple error message.

Listing 18.13 NotFound.aspx
<Script Runat="Server">

Sub Page_Load
  lblMessage.Text = Request.Params( "aspxerrorpath" )
  lblMessage.Text &= " does not exist!"
End Sub

</Script>

<html>
<head><title>NotFound.aspx</title></head>
<body>

<asp:Label
  ID="lblMessage"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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
<%@ Page ErrorPage="AppError.aspx" %>

<Script Runat="Server">

Sub Page_Load
  Dim txtTextBox As TextBox

  txtTextBox.Text = "Hello"
End Sub

</Script>

<html>
<head><title>ErrorPage.aspx</title></head>
<body>

<h2>This page contains an error!</h2>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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

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.

The Global.asax file in Listing 18.15, for example, contains an Application_Error subroutine that automatically emails the Webmaster of the Web site whenever an error occurs in a page.

Listing 18.15 GlobalError/Global.asax
<%@ Import Namespace="System.Web.Mail" %>
<Script Runat="Server">

Sub Application_Error
  Dim objMail As New MailMessage

  objMail.From = "website@yourdomain.com"
  objMail.To = "Webmaster@yourdomain.com"
  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

</Script>

The C# version of this code can be found on the CD-ROM.

If you add the file in Listing 18.15 to your application root directory, an email is sent to Webmaster@yourdomain.com whenever an error occurs in any page within the application.

NOTE

To learn more details about sending email in an ASP.NET application, see Chapter 26, "Sending Email 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:

<%@ Page Trace="True" %>

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:

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

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

Figure 18.7. Page with tracing enabled.

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

Listing 18.16 Trace.aspx
<%@ Page Trace="True" %>

<Script Runat="Server">

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

</Script>

<html>
<head><title>Trace.aspx</title></html>
<body>

<form Runat="Server">

<h2>Enter your Favorite Color</h2>

<b>Favorite Color:<b>
<br>
<asp:TextBox
  ID="txtFavColor"
  Runat="Server" />
<asp:Button
  Text="Click Here!"
  OnClick="Button_Click"
  Runat="Server" />
<p>
<asp:Label
  ID="lblMessage"
  Runat="Server" />

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 18.8. Page with tracing enabled.

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
<%@ Page Trace="True" %>

<Script Runat="Server">

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
    For Each strItem in colArrayList
      strTraceMessage &= strItem & ","
    Next
    Trace.Warn( strTraceMessage )
  End If

  dropColors.DataSource = colArrayList
  dropColors.DataBind
End Sub

</Script>

<html>
<head><title>TraceIsEnabled.aspx</title></html>
<body>

<form Runat="Server">

<h2>Enter your Favorite Color</h2>

<b>Favorite Color:<b>
<asp:DropDownList
  ID="dropColors"
  Runat="Server" />

</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

Listing 18.18 TraceCategories.aspx
<%@ Page Trace="True" TraceMode="SortByCategory" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">
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
</Script>

<html>
<head><title>TraceCategories.aspx</title></head>
<body>
<form Runat="Server">

<h2>Choose an Author</h2>

<b>Author Last Name:<b>
<asp:DropDownList
  ID="dropAuthors"
  Runat="Server" />
<asp:Button
  Text="Select!"
  OnClick="Button_Click"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

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 SortByCategory. Because you want to group trace messages by category, you set TraceMode 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.

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.

Listing 18.19 TraceException.aspx
<%@ Page Trace="True" %>

<Script Runat="Server">

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

</Script>

<html>
<head><title>TraceException.aspx</title></head>
<body>

<h2>This page contains an error!</h2>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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 first 10 requests to your application after application tracing is enabled. 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
<configuration>
  <system.web>
  <trace
    enabled="true"
    requestLimit="50"
    pageOutput="false"
    traceMode="SortByTime"
    localOnly="true" />
  </system.web>
</configuration>

The C# version of this code can be found on the CD-ROM.

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 value that indicates the order in which trace messages are displayed. Possible values are SortByTime and SortByCategory; the default value is SortByTime.

  • localOnly— A Boolean value that indicates whether trace messages should be displayed only on the local computer, not remote computers. The default value is true.

CAUTION

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

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

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:

<processModel
  enable="true"
  timeout="24:00:00" />

You can test processModel configuration settings by using the page in Listing 18.21.

CAUTION

The page in Listing 18.21 is designed to crash the ASP.NET process by consuming too much memory.

Listing 18.21 ProcessModel.aspx
<%@ Import Namespace="System.IO" %>

<Script Runat="Server">

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

</Script>
<html>
<head><title>ProcessModel.aspx</title></head>
<body>

Memory Consumed:
<asp:Label
  ID="lblMemory"
  Runat="Server" />
bytes
<p>
Process ID:
<asp:Label
  ID="lblProcessID"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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

Listing 18.22 ProcessHistory.aspx
<Script Runat="Server">

Sub Page_Load
  dgrdHistory.DataSource = ProcessModelInfo.GetHistory( 10 )
  dgrdHistory.Databind()
End Sub

</Script>

<html>
<head><title>ProcessHistory.aspx</title></head>
<body>

<asp:DataGrid
  ID="dgrdHistory"
  CellPadding="15"
  AutoGenerateColumns="False"
  Runat="Server">
<Columns>
  <asp:BoundColumn
    HeaderText="ProcessID"
    DataField="ProcessID"/>
  <asp:BoundColumn
    HeaderText="Age"
    DataField="Age"/>
  <asp:BoundColumn
    HeaderText="PeakMemoryUsed"
    DataField="PeakMemoryUsed"/>
  <asp:BoundColumn
    HeaderText="RequestCount"
    DataField="RequestCount"/>
  <asp:BoundColumn
    HeaderText="ShutdownReason"
    DataField="ShutdownReason"/>
  <asp:BoundColumn
    HeaderText="StartTime"
    DataField="StartTime"/>
  <asp:BoundColumn
    HeaderText="Status"
    DataField="Status"/>
</Columns>
</asp:DataGrid>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 18.10. Viewing process history.

    [ Team LiB ]


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. The EventLog class includes a WriteEntry() method that you can use to write a new event to the event log. When you write a new entry to the event log, you write the entry to a particular log using a particular event source.

The event log can be the Application, System, or Security event log. Or, the event log can be a custom log. When writing entries from an ASP.NET page, you will normally want to use the Application event log since there are fewer security issues when writing to this log.

The event source is the source of the log entry. You can name your event source anything that you please. For example, when writing to an event log from an ASP.NET application, you might want to create an event source named My ASP.NET App.

Event sources must be unique for a event log. When you create a new event source, you are adding an entry to the registry. Since writing to the registry requires special security permissions, you need to create your event source outside of your ASP.NET application. There is a special command-line utility included on the CD that accompanies this book named CreateEventSource.exe that will allow you to create a new event source. You'll need to run this utility to create a new event source before you can write entries to the event log (the source for the tool is also include on the CD).

For example, if you use the CreateEventSource.exe utility to create a new event source named My ASP.NET App, then you can use the page in Listing 18.23, which contains a form that enables you to insert a new entry into the Application event log.

Listing 18.23 AddEventLog.aspx
<%@ Import Namespace="System.Diagnostics" %>

<Script Runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  Dim objLog As EventLog

  objLog = New EventLog
  objLog.Source = "My ASP.NET App"
  objLog.WriteEntry( txtLogEntry.Text, EventLogEntryType.Information )
End Sub

</Script>

<html>
<head><title>AddEventLog.aspx</title></head>
<body>

<form Runat="Server">
<asp:TextBox
  ID="txtLogEntry"
  Runat="Server" />
<asp:Button
  Text="Add Event!"
  OnClick="Button_Click"
  Runat="Server" />
</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

The Button_Click subroutine in Listing 18.23 adds a new event to the Application event log. First, 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 unhandled application errors to the Application event log. (You can find this page in the LogError directory on the CD that accompanies this book.)

Listing 18.24 LogError/Global.asax
<%@ Import Namespace="System.Diagnostics" %>

<Script Runat="Server">

Sub Application_Error
  Dim strErrorText As String
  Dim objLog As EventLog

  strErrorText = "ERROR IN " & Request.Path
  strErrorText &= vbNewline & vbNewline & "ERROR:"
  strErrorText &= Server.GetLastError.InnerException.Message
  strErrorText &= vbNewline & vbNewline & "STACK TRACE:"
  strErrorText &= Server.GetLastError.InnerException.StackTrace

  objLog = New EventLog
  objLog.Source = "My ASP.NET App"
  objLog.WriteEntry( strErrorText, EventLogEntryType.Error )
End Sub

</Script>

The C# version of this code can be found on the CD-ROM.

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 event log. The CD that accompanies this book includes a page named ErrorPage.aspx in the LogError folder. If you request this page, you'll receive an error and a new entry will be added to the Application 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 Application event log.

Listing 18.25 DisplayEventLog.aspx
<%@ Import Namespace="System.Diagnostics" %>

<Script Runat="Server">

Sub Page_Load
  Dim objLog As EventLog

  objLog = New EventLog
  objLog.Log = "Application"
  dgrdLog.DataSource = objLog.Entries
  dgrdLog.DataBind()
End Sub
</Script>

<html>
<head><title>DisplayEventLog.aspx</title></head>
<body>

<asp:DataGrid
  ID="dgrdLog"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

In Listing 18.25, all the entries from the Application event log are retrieved with the Entries property of the EventLog class. The log entries are displayed in a DataGrid control.

NOTE

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 System event log, you would set the Log property like this:

myLog.Log = "System"

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.

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.

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

  • 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 Total— The total number of requests since the service was started

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

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

Listing 18.26 ReadCounters.aspx
<%@ Import Namespace="System.Diagnostics" %>

<Script Runat="Server">

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

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
</Script>

<html>
<head><title>ReadCounters.aspx</title></head>
<body>

<h2>ASP.NET Application Performance Counters</h2>

<form Runat="Server">

<b>Instances</b>
<br>
<asp:ListBox
  ID="lstInstances"
  AutoPostback="True"
  OnSelectedIndexChanged="selectInstance"
  Runat="Server" />
<p>
<b>Counters</b>
<br>
<asp:ListBox
  ID="lstCounters"
  Visible="False"
  AutoPostback="True"
  OnSelectedIndexChanged="selectCounter"
  Runat="Server" />
<p>
<b>Counter Value:</b>
<asp:Label
  ID="lblValue"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 18.11. Displaying performance counters.

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.

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.

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.


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.

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.27 in the root directory of your application.

Listing 18.27 DebugMode/Web.Config
<configuration>
  <system.web>
    <compilation debug="true" />
  </system.web>
</configuration>

The C# version of this code can be found on the CD-ROM.

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.

CAUTION

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.

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.

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.

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.

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


Part V: Securing ASP.NET Applications

IN THIS PART

CHAPTER 19 Using Forms-Based Authentication

CHAPTER 20 Using Windows-Based Authentication

CHAPTER 21 Encrypting Data over the Network


Chapter 19. Using Forms-Based Authentication

IN THIS CHAPTER

  • Working with Forms Authentication

  • Working with Passport Authentication

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.

  • FormsIdentity— This class represents the identity of the user authenticated with Forms authentication.

  • FormsAuthenticationModule— This 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.

CAUTION

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.

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.

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
<configuration>
  <system.web>
    <authentication mode="Forms" />
  </system.web>
</configuration>

The C# version of this code can be found on the CD-ROM.

In Listing 19.1, the authentication mode is set to Forms. Creating this file enables Forms authentication for the entire application.

CAUTION

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
<configuration>
  <system.web>
    <authorization>
      <deny users="?" />
    </authorization>
  </system.web>
</configuration>

The C# version of this code can be found on the CD-ROM.

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.

CAUTION

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
<configuration>
  <system.web>
    <authorization>
      <allow users="?" />
    </authorization>
  </system.web>
</configuration>

The C# version of this code can be found on the CD-ROM.

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

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

Listing 19.4 FormsSimple\Login.aspx
<Script Runat="Server">

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

</Script>

<html>
<head><title>Login.aspx</title></head>
<body>
<form Runat="Server">

<h2>Please Login:</h2>

<asp:Label
  ID="lblMessage"
  ForeColor="Red"
  Font-Bold="True"
  Runat="Server" />

<p>
<b>Username:</b>
<br>
<asp:TextBox
  ID="txtUsername"
  Runat="Server" />
<asp:RequiredFieldValidator
  ControlToValidate="txtUsername"
  Text="Required!"
  Runat="Server" />

<p>
<b>Password:</b>
<br>
<asp:TextBox
  ID="txtPassword"
  Runat="Server" />
<asp:RequiredFieldValidator
  ControlToValidate="txtPassword"
  Text="Required!"
  Runat="Server" />

<p>
<asp:CheckBox
  ID="chkRemember"
  Runat="Server"/>
Remember me with a cookie?

<p>
<asp:Button
  Text="Login!"
  OnClick="Button_Click"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 19.1. A simple login page.

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.

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.

CAUTION

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.

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 path used for the cookie. By default, this attribute has the value /.

  • protection— The 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 protection 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.

The Web.Config file in Listing 19.5 illustrates how you can set the forms attributes.

Listing 19.5 FormsAttributes\Web.Config
<configuration>
  <system.web>
    <authentication mode="Forms">
      <forms
        name=".MyCookie"
        loginUrl="/login/mylogin.aspx"
        protection="All"
        timeout="80"
        path="/"/>
    </authentication>
  </system.web>
</configuration>

The C# version of this code can be found on the CD-ROM.

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
<configuration>
  <system.web>
    <authorization>
      <deny users="?" />
    </authorization>
  </system.web>
</configuration>

The C# version of this code can be found on the CD-ROM.

The authorization section can contain either <deny> elements, which deny access for particular users, or <allow> 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
<configuration>
  <system.web>
    <authorization>
      <deny users="?" />
      <deny users="James,Mark" />
    </authorization>
  </system.web>
</configuration>

The C# version of this code can be found on the CD-ROM.

In Listing 19.7, the <deny> 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.

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
<configuration>
  <system.web>
    <authorization>
      <allow verbs="POST" users="James,Mark" />
      <deny verbs="POST" users="*" />
      <allow verbs="GET" users="*" />
    </authorization>
  </system.web>
</configuration>

The C# version of this code can be found on the CD-ROM.

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 returns the value Forms

  • IsAuthenticated— Indicates whether the user was authenticated

  • Name— Indicates the name of an authenticated user

  • Ticket— Specifies the cookie Authentication Ticket associated with the current user

NOTE

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.

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 data that you can include in the Authentication Ticket.

  • Version— An 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.)

Listing 19.9 FormsIdentity\FormsIdentity.aspx
<%@ debug="True" %>
<Script Runat="Server">

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

</Script>

<html>
<head><title>FormsIdentity.aspx</title>
<body>

<asp:Label
  ID="lblName"
  Font-Size="18pt"
  Font-Bold="True"
  Runat="Server" />
<p>
Expiration:
<asp:Label
  ID="lblExpiration"
  Runat="Server" />
<p>
Expired:
<asp:Label
  ID="lblExpired"
  Runat="Server" />
<p>
IsPersistent:
<asp:Label
  ID="lblIsPersistent"
  Runat="Server" />
<p>
IssueDate:
<asp:Label
  ID="lblIssueDate"
  Runat="Server" />
<p>
UserData:
<asp:Label
  ID="lblUserData"
  Runat="Server" />
<p>
Version:
<asp:Label
  ID="lblVersion"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 19.2. Displaying user information.

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
<Script Runat="Server">

Sub Page_Load
  FormsAuthentication.SignOut()
End Sub

</Script>

<html>
<head><title>SignOut.aspx</title></head>
<body>

<h2>Goodbye!</h2>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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
<configuration>
  <system.web>
    <authentication mode="Forms">
    <forms>
      <credentials passwordFormat="Clear" >
        <user name="Sam" password="Secret"/>
        <user name="Fred" password="Secret"/>
      </credentials>
    </forms>
    </authentication>
    <authorization>
      <deny users="?" />
    </authorization>
  </system.web>
</configuration>

The C# version of this code can be found on the CD-ROM.

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.

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
<Script Runat="Server">

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

</Script>

<html>
<head><title>Login.aspx</title></head>
<body>
<form Runat="Server">

<h2>Please Login:</h2>

<asp:Label
  ID="lblMessage"
  ForeColor="Red"
  Font-Bold="True"
  Runat="Server" />

<p>
<b>Username:</b>
<br>
<asp:TextBox
  ID="txtUsername"
  Runat="Server" />
<asp:RequiredFieldValidator
  ControlToValidate="txtUsername"
  Text="Required!"
  Runat="Server" />

<p>
<b>Password:</b>
<br>
<asp:TextBox
  ID="txtPassword"
  Runat="Server" />
<asp:RequiredFieldValidator
  ControlToValidate="txtPassword"
  Text="Required!"
  Runat="Server" />

<p>
<asp:Button
  Text="Login!"
  OnClick="Button_Click"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

Encrypting Passwords 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:

<credentials passwordFormat="Clear" >
  <user name="Sam" password="Secret"/>
  <user name="Fred" password="Secret"/>
</credentials>

Instead, you can store the credentials like this:

<credentials passwordFormat="SHA1" >
  <user name="Sam"
    password="9AA4F59F833D419EF8FF2B44567645A282BCD6F0"/>
  <user name="Fred"
    password="9AA4F59F833D419EF8FF2B44567645A282BCD6F0"/>
</credentials>

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
[View full width]
<Script Runat="Server">

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

</Script>

<html>
<head><title>ComputeHash.Aspx</title></head>
<body>
<form Runat="Server">

<h2>Compute Hash Value:</h2>

<b>Password:</b>
<br>
<asp:TextBox
  ID="txtPassword"
  Runat="Server" />
<p>
<b>Algorithm:</b>
<br>
<asp:ListBox
  ID="lstAlgorithm"
  Runat="Server">
  <asp:ListItem Text="SHA1" />
  <asp:ListItem Text="MD5" />
</asp:ListBox>

<p>
<asp:Button
  Text="Compute!"
  OnClick="Button_Click"
  Runat="Server" />

<hr>
Hash Value:
<br>
<asp:Label
  ID="lblHash"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

NOTE

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.

  • 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
<configuration>
  <system.web>
    <authentication mode="Forms" />
    <authorization>
      <deny users="?" />
    </authorization>
  </system.web>
</configuration>

The C# version of this code can be found on the CD-ROM.

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
<Passwords>
  <user>
    <name>Sam</name>
    <password>Secret</password>
  </user>
  <user>
    <name>Fred</name>
    <password>Secret</password>
  </user>
</Passwords>

The C# version of this code can be found on the CD-ROM.

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
<%@ Import Namespace="System.Data" %>

<Script Runat="Server">

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

  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

</Script>

<html>
<head><title>Login.aspx</title></head>
<body>
<form Runat="Server">

<h2>Please Login:</h2>

<asp:Label
  ID="lblMessage"
  ForeColor="Red"
  Font-Bold="True"
  Runat="Server" />
<p>
<b>Username:</b>
<br>
<asp:TextBox
  ID="txtUsername"
  Runat="Server" />
<asp:RequiredFieldValidator
  ControlToValidate="txtUsername"
  Text="Required!"
  Runat="Server" />
<p>
<b>Password:</b>
<br>
<asp:TextBox
  ID="txtPassword"
  Runat="Server" />
<asp:RequiredFieldValidator
  ControlToValidate="txtPassword"
  Text="Required!"
  Runat="Server" />
<p>
<asp:Button
  Text="Login!"
  OnClick="Button_Click"
  Runat="Server" />
<hr>
<asp:HyperLink
  ID="lnkRegister"
  Text="Click Here To Register!"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

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 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
<%@ Import Namespace="System.Data" %>

<Script Runat="Server">

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

  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

</Script>

<html>
<head><title>Register.aspx</title></head>
<body>
<form Runat="Server">

<h2>Please Register</h2>

<asp:Label
  ID="lblMessage"
  ForeColor="Red"
  Font-Bold="True"
  EnableViewState="False"
  Runat="Server" />
<p>
<b>Username:</b>
<br>
<asp:TextBox
  ID="txtUsername"
  Runat="Server" />
<asp:RequiredFieldValidator
  ControlToValidate="txtUsername"
  Text="Required!"
  Runat="Server" />
<p>
<b>Password:</b>
<br>
<asp:TextBox
  ID="txtPassword"
  Runat="Server" />
<asp:RequiredFieldValidator
  ControlToValidate="txtPassword"
  Text="Required!"
  Runat="Server" />
<p>
<asp:Button
  Text="Register!"
  OnClick="Button_Click"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

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 page validates usernames and passwords by checking them against the contents of the UserList database table.

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

  • Register.aspx— This ASP.NET page enables new users to register usernames and passwords by adding them to the UserList table.

  • DBRegister stored procedure— This SQL Server stored procedure is used by the Register.aspx page to add a new username and password to the UserList table.

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
<configuration>
  <system.web>
    <authentication mode="Forms" />
    <authorization>
      <deny users="?" />
    </authorization>
  </system.web>
</configuration>

The C# version of this code can be found on the CD-ROM.

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

The C# version of this code can be found on the CD-ROM.

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
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

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

  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

</Script>

<html>
<head><title>Login.aspx</title></head>
<body>
<form Runat="Server">

<h2>Please Login:</h2>

<asp:Label
  ID="lblMessage"
  ForeColor="Red"
  Font-Bold="True"
  Runat="Server" />
<p>
<b>Username:</b>
<br>
<asp:TextBox
  ID="txtUsername"
  Runat="Server" />
<asp:RequiredFieldValidator
  ControlToValidate="txtUsername"
  Text="Required!"
  Runat="Server" />
<p>
<b>Password:</b>
<br>
<asp:TextBox
  ID="txtPassword"
  Runat="Server" />
<asp:RequiredFieldValidator
  ControlToValidate="txtPassword"
  Text="Required!"
  Runat="Server" />
<p>
<asp:Button
  Text="Login!"
  OnClick="Button_Click"
  Runat="Server" />
<hr>
<asp:HyperLink
  ID="lnkRegister"
  Text="Click Here To Register!"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

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 C# version of this code can be found on the CD-ROM.

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.

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

Listing 19.22 FormsDB\Register\Register.aspx
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

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

</Script>

<html>
<head><title>Register.aspx</title></head>
<body>
<form Runat="Server">

<h2>Please Register</h2>

<asp:Label
  ID="lblMessage"
  ForeColor="Red"
  Font-Bold="True"
  EnableViewState="False"
  Runat="Server" />
<p>
<b>Username:</b>
<br>
<asp:TextBox
  ID="txtUsername"
  Runat="Server" />
<asp:RequiredFieldValidator
  ControlToValidate="txtUsername"
  Text="Required!"
  Runat="Server" />
<p>
<b>Password:</b>
<br>
<asp:TextBox
  ID="txtPassword"
  Runat="Server" />
<asp:RequiredFieldValidator
  ControlToValidate="txtPassword"
  Text="Required!"
  Runat="Server" />
<p>
<asp:Button
  Text="Register!"
  OnClick="Button_Click"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

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
ELSE
  INSERT UserList (
    u_username,
    u_password
    ) VALUES (
    @username,
    @password
    )
  RETURN @@IDENTITY

The C# version of this code can be found on the CD-ROM.

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.

Listing 19.24 FormsRoles\Roles.xml
<roles>
  <user
    name="Bob"
    roles="Sales" />
  <user
    name="Jane"
    roles="Supervisor,Sales" />
</roles>

The C# version of this code can be found on the CD-ROM.

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
<%@ Import Namespace="System.Security.Principal" %>
<%@ Import Namespace="System.Xml" %>

<Script Runat="Server">

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

  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

Function GetRoles() As XmlDocument
  Dim objRoles As XmlDocument

  objRoles = Context.Cache( "Roles" )
  If objRoles Is Nothing Then
    objRoles = New XmlDocument
    objRoles.Load( Server.MapPath( "Roles.xml" ) )
    Context.Cache.Insert( _
      "Roles", _
      objRoles, _
      New CacheDependency( Server.MapPath( "Roles.xml" ) ) )
  End If
  Return objRoles
End Function

</Script>

The C# version of this code can be found on the CD-ROM.

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
<html>
<head><title>Default.aspx</title></head>
<body>

<h1>Welcome <%=User.Identity.Name%></h1>

<%
If User.IsInRole( "Supervisor" ) Then
  Response.Write( "You have supervisor permissions!" )
Else
  Response.Write( "You don't have supervisor permissions!" )
End If
%>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

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

CAUTION

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.

You can explicitly create a new Authentication Ticket by passing the following parameters to the FormsAuthenticationTicket constructor:

  • 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 original date the cookie was issued.

  • expiration— The date the cookie expires.

  • isPersistent— A Boolean value indicating whether the cookie is a session or persistent cookie.

  • userData— A string that can contain any data you please.

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
<configuration>
  <system.web>
    <authentication mode="Forms" />
    <authorization>
      <deny users="?" />
    </authorization>
  </system.web>
</configuration>

The C# version of this code can be found on the CD-ROM.

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
<Script Runat="Server">

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, _
    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

</Script>

<html>
<head><title>Login.aspx</title></head>
<body>
<form Runat="Server">

<b>Username:</b>
<br>
<asp:TextBox
  ID="txtUsername"
  Runat="Server" />
<p>
<b>User Data:</b>
<br>
<asp:TextBox
  ID="txtUserdata"
  Runat="Server" />
<p>
<asp:button
  Text="Add Ticket!"
  OnClick="Button_Click"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

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
<Script Runat="Server">

Sub Page_Load
  Dim objFormsID As FormsIdentity

  objFormsID = User.Identity
  lblName.Text = objFormsID.Ticket.Name
  lblUserData.Text = objFormsID.Ticket.UserData
End Sub
</Script>
<html>
<head><title>Default.aspx</title></head>
<body>

<b>Name:</b>
<asp:Label
  ID="lblName"
  Runat="Server" />
<p>
<b>User Data:</b>
<asp:Label
  ID="lblUserData"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

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:

<machineKey
  validationKey="AutoGenerate, IsolateApps"
  decryptionKey="AutoGenerate, IsolateApps"
  validation="SHA1"/>

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.

NOTE

The IsolateApps modifier was introduced with version 1.1 of the ASP.NET Framework. It causes different keys to be generated for different applications running on the same server. If you need to share the same keys across multiple ASP.NET applications, then you need to remove the IsolateApps modifier.


Working with Passport 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.

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.

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. The current subscription fee is $10,000 a year plus a $1,500 fee for URL. For details, see the www.passport.com/business Web site.

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.

Listing 19.30 Passport\Web.Config
<configuration>
  <system.web>
    <authentication mode="Passport">
      <passport redirectUrl="/Login.aspx" />
    </authentication>
  </system.web>
</configuration>

The C# version of this code can be found on the CD-ROM.

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
<configuration>
  <system.web>
    <authorization>
      <deny users="?" />
    </authorization>
  </system.web>
</configuration>

The C# version of this code can be found on the CD-ROM.

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.

CAUTION

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.

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

Listing 19.32 Passport\Default.aspx
<Script Runat="Server">

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
</Script>

<html>
<head><title>Default.aspx</title></head>
<body>

<asp:PlaceHolder
  ID="plhPassport"
  Runat="Server" />
<hr>

<h2>Welcome to our Web site!</h2>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 19.3. Passport sign-in button.

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.

CAUTION

The Page_Load subroutine in Listing 19.32 contains the following code:

If objPassportID.GetFromNetworkServer Then
  Response.Redirect( Request.Path )
End If

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.

The LogoTag2() method displays a sign-in button until you sign in. After you sign in, the method displays a sign-out button.

CAUTION

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

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
<Script Runat="Server">

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

</Script>

<html>
<head><title>Login.aspx</title></head>
<body>

<h2>Please Login:</h2>

You have requested a restricted resource.
To continue, please login by clicking
the following button:
<p>
<asp:PlaceHolder
  ID="plhPassport"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

Listing 19.34 Passport\News.aspx
<Script Runat="Server">

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
</Script>

<html>
<head><title>News.aspx</title></head>
<body>

<asp:PlaceHolder
  ID="plhPassport"
  Runat="Server" />
<hr>

<asp:Panel
  ID="pnlAuth"
  Visible="False"
  Runat="Server">

  <h2>Customized News</h2>
  News customized only for you...

</asp:Panel>

<asp:Panel
  ID="pnlAnon"
  Visible="False"
  Runat="Server">

  <h2>Anonymous News</h2>
  News for anonymous users...

</asp:Panel>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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 email 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 an integer that indicates the precision of the Birthdate 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

    4 = User is under 13

    5 = User is minor over 13

  • Birthdate— Returns either the user's birth year or full birth date in the format MM/DD/YYYY. The default value is 0.

  • City— Indicates a GeoID value that represents the user's city.

  • Country— Contains a two-character ISO 3166 country code.

  • Flags— Contains an integer bitmask. If the first bit is set (00000000 00000000 00000000 00000001), the email address of the user has been verified.

  • Gender— Specifies the value M (Male), F (Female), or U (Unknown).

  • Lang_Preference— Indicates the LCID value for the user's preferred language.

  • MemberIDHigh— Specifies the upper portion of the 64-bit Passport Unique ID (PUID) associated with each user.

  • 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 the user's preferred email address.

  • PostalCode— Specifies the postal code (both for the U.S. and other countries).

  • ProfileVersion— Returns the value 1 currently.

  • Region— Indicates a GeoID that represents the user's geographical region.

  • Wallet— Returns 1 when the user has created an online wallet and 0 otherwise.

NOTE

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.

Listing 19.35 Passport\UserInfo.aspx
<Script Runat="Server">

Sub Page_Load
  Dim objID As PassportIdentity

  objID = User.Identity
  If objID.IsAuthenticated Then
    lblInfo.Text &= "<li>PUID: " & objID.Name
    lblInfo.Text &= "<li>Accessibility: " & objID( "Accessibility" )
    lblInfo.Text &= "<li>BDay_precision: " & objID( "BDay_precision" )
    lblInfo.Text &= "<li>Birthdate: " & objID( "Birthdate" )
    lblInfo.Text &= "<li>City: " & objID( "City" )
    lblInfo.Text &= "<li>Country: " & objID( "Country" )
    lblInfo.Text &= "<li>Gender: " & objID( "Gender" )
    lblInfo.Text &= "<li>Lang_Preference: " & objID( "Lang_Preference" )
    lblInfo.Text &= "<li>MemberIDHigh: " & objID( "MemberIDHigh" )
    lblInfo.Text &= "<li>MemberIDLow: " & objID( "MemberIDLow" )
    lblInfo.Text &= "<li>Nickname: " & objID( "Nickname" )
    lblInfo.Text &= "<li>PreferredEmail: " & objID( "PreferredEmail" )
    lblInfo.Text &= "<li>PostalCode: " & objID( "PostalCode" )
    lblInfo.Text &= "<li>ProfileVersion: " & objID( "ProfileVersion" )
    lblInfo.Text &= "<li>Region: " & objID( "Region" )
    lblInfo.Text &= "<li>Wallet: " & objID( "Wallet" )
  End If
End Sub

</Script>

<html>
<head><title>UserInfo.aspx</title></head>
<body>

<h2>User Data:</h2>
<asp:Label
  ID="lblInfo"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

In the Page_Load subroutine in Listing 19.35, an instance of the PassportIdentity class is created. If the user has been authenticated, several profile attributes are retrieved and displayed in a Label control.

NOTE

You also can create ASP.NET pages that enable users to update their profile information. For example, if a user has not entered a nickname or preferred email address into his or her profile, you can ask the user to update the profile. To learn more details, see the Microsoft Passport SDK documentation.


Summary

In this chapter, you examined two methods of authenticating users. In the first part, you learned how to configure Forms authentication and require users to log in before they can access particular directories. You also learned how to use Forms authentication with the Web.Config file, an XML file, and a database table.

Next, you learned how to take advantage of Microsoft Passport authentication. You learned how to create sign-in and sign out buttons and retrieve profile information from authenticated Passport users.


Chapter 20. Using Windows-Based Authentication

IN THIS CHAPTER

  • Working with Windows Authentication

  • Impersonating User Accounts

  • Setting Security Policies

In this chapter, you learn how to use ASP.NET with Windows security and authenticate users by using Windows user accounts and groups.

In the first section, you learn how to configure both Internet Information Server and ASP.NET to use Windows authentication. You examine how to use Windows authentication to control access to particular resources on your Web site.

Next, you examine the topic of impersonation. You learn how to configure impersonation so that ASP.NET pages will execute with the permissions of the user making the request. You also learn how to execute ASP.NET pages under a particular Windows user account regardless of the user making the request.

Finally, this chapter provides a brief overview of security policies. You learn how to implement different security policies for your ASP.NET applications.


Working with Windows Authentication

When should you use Windows authentication instead of other forms of authentication such as Forms or Microsoft Passport authentication?

If you are developing a company intranet, you can use Windows authentication with existing Windows user accounts and groups. For example, your organization might already have Windows user accounts configured for each employee.

You can use Windows-based authentication to control access to sensitive information. For example, you might want users in the Windows Managers group to have access to directories containing employee resumes and job performance reviews. You can use Windows-based authentication to prevent employees in other Windows groups, such as the Sales group, from accessing these sensitive documents.

Windows-based authentication is not a good security option to use, however, when you're building a public user registration and password system. When Windows authentication is enabled, a valid Windows user account must be configured for each user who accesses a restricted page. The process of adding new Windows user accounts cannot be easily automated.

You therefore should use Windows authentication when you have a fixed number of users with existing Windows user accounts and an existing system of group membership. Don't use Windows authentication when you're building a public user registration system—use Forms authentication instead.

Configuring Internet Information Server Security

Before you can use Windows authentication, you must first configure Internet Information Server (IIS). When a user requests a page that requires authorization, the user must be authenticated through IIS.

The version of IIS included with Windows 2000 Server, IIS 5.0, supports three methods of authentication:

  • Basic Authentication— Compatible with all browsers, firewalls, and proxy servers. However, usernames and passwords are transmitted across the network in plain text.

  • Integrated Windows Authentication— Compatible with Internet Explorer (version 2.0 or higher). Usernames and passwords are not transmitted across the network in plain text. This form of authentication is not compatible with all firewalls and proxy servers.

  • Digest Authentication— Compatible with Internet Explorer (version 2.0 or higher). Usernames and passwords are not transmitted across the network in plain text. This form of authentication is compatible with all firewalls and proxy servers. However, it requires you to save Windows usernames and passwords in a plain-text file on the domain server.

Typically, you enable either Basic Authentication or Integrated Windows Authentication. If your application must be compatible with Netscape browsers, you have no choice but to use Basic Authentication. If you can mandate that all users must use Microsoft Internet Explorer and you are not using a firewall or proxy server, you can use Integrated Windows Authentication.

NOTE

You can use the Secure Sockets Layer (SSL) with Basic Authentication to encrypt usernames and passwords as they are transmitted across the network. For more information, see Chapter 21 "Encrypting Data over the Network."

To enable one of these authentication methods for a directory or file, follow these steps:

  1. Launch the Internet Services Manager.

  2. Open the property sheet for a directory or file (by clicking the button of the hand holding the piece of paper).

  3. Choose the tab labeled Directory Security or File Security.

  4. In the section labeled Anonymous Access and Authentication, click Edit.

  5. Enable one or more authentication methods.

After you enable a method of authentication, you can force users to log in to your Web site through IIS in two ways. First, you can disable anonymous access to a file or directory. Follow the same steps just described for enabling authentication and click to remove the check from the Anonymous Access option.

Alternatively, you can modify the NTFS permissions on a file or directory. If an anonymous user does not have permission to access a file, the password dialog box is displayed even when anonymous access is enabled. You learn how to configure Windows file permissions in the next section.

Configuring Microsoft Windows Security

Windows NT and Windows 2000 implement security at the file system level. Every directory and file has an associated set of permissions. These permissions are defined relative to Windows users and groups.

CAUTION

To set permissions on individual files, you must be using NTFS. Using NTFS is strongly recommended if you plan to use Windows authentication.

Windows 2000 includes several default users and groups. For example, the Administrator user account has the broadest set of possible permissions. The Administrator account is a member of the Administrators group.

When configuring Windows authentication with ASP.NET, you'll need to work with the following users and groups:

  • ASPNET— The user account for the ASP.NET worker process.

  • IUSER_MachineName— The user account used by Internet Information Server for anonymous access. For example, if your Web server is named myServer, all anonymous users of your Web site access the server through the IUSER_myServer user account.

  • Users— This group, by default, includes the ASPNET user account

  • Authenticated Users— Operating system group. All authenticated users are automatically part of the Authenticated Users group. This group corresponds to the Everyone group account in Windows NT 4.0.

At a minimum, in order to enable anonymous access to the files in a directory, you'll need to grant Read & Execute permissions to two user accounts: the ASPNET and IUSER_MachineName user accounts. The ASPNET user account represents the ASP.NET worker process and the IUSER_MachineName account represents an anonymous Internet Information Server user.

NOTE

You can use IIS to change the account associated with anonymous users from the IUSER_MachineName account to any other account. You can modify the anonymous user account within IIS by opening the property sheet for a directory, selecting the Directory Security tab, and clicking the Edit button under Anonymous Access and Authentication Control.

Since both the ASPNET and IUSER_MachineName user accounts are part of the Users group, the Authenticated Users group, and the Everyone group, you also can enable anonymous access for a directory by simply granting Read & Execute permissions to one of these groups. For example, you can enable anonymous access by granting Read & Execute permissions on a directory to the Users group.

NOTE

There is a special problem that you encounter when executing ASP.NET on a primary or backup domain controller. Since a primary or backup domain controller cannot include non-administrative local accounts, on these machines the ASP.NET worker process must be configured to execute under the local system account. See the later section in this chapter, "Executing ASP.NET Pages under a User Account," for more information.

If you want to prevent anonymous access to a directory, then you can simply remove the IUSER_MachineName user account from that directory. For example, imagine that you want to enable only the Administrator account to access the files in a directory. Follow these steps:

  1. Right-click the directory and select Properties.

  2. Choose the Security tab.

  3. Uncheck the checkbox labeled Allow inheritable permissions from parent to propagate to this object.

  4. Remove every user and group except the ASPNET and Administrator users.

  5. Click Advanced to open the Access Control Settings dialog box.

  6. Check the checkbox labeled Reset permissions on all child objects and enable propagation of inheritable permissions.

  7. Click OK to close the Access Control Settings dialog box.

  8. Click OK to close the Properties dialog box.

After you complete these steps, if you request any file in that directory with your Web browser, a Login dialog box is displayed. You are forced to log in with a user account that has access to the directory (in this case, the Administrator user account).

You can provide any user or group with permissions to access any directory or file in your Web site. Simply add the user or group by using the Security tab associated with the file or directory.

Configuring Windows Authentication

Windows authentication is enabled by default in the Machine.Config file. This setting is automatically inherited by all ASP.NET applications running on the same server.

If you have modified the Machine.Config file—for example, you've set the default authentication method to Forms authentication—you can explicitly enable Windows authentication for an application by adding the Web.Config file in Listing 20.1 to the application root directory.

Listing 20.1 EnableWindows\Web.Config
<configuration>
  <system.web>
    <authentication mode="Windows" />
  </system.web>
</configuration>

The C# version of this code can be found on the CD-ROM.

You cannot add the file in Listing 20.1 to any directory beneath an application's root directory. In other words, it must be located in either the wwwroot directory or the root directory of a virtual directory.

Configuring Windows Authorization

After enabling Windows authentication, you can provide authorization to particular users and groups to access particular directories and files. Again, you do so by using the Web.Config file.

You can add a Web.Config file that includes an authorization section to any directory (not only the application root directory). Within the authorization section, you can list the users and groups who have access to files in that directory.

The authorization section can contain two elements: deny and allow. You can use these elements to allow or deny a list of Windows users or groups access to the files in the current directory. For example, the Web.Config file in Listing 20.2 explicitly allows access to anyone in the Sales group, except for Jane.

Listing 20.2 NoJane\Web.Config
<configuration>
  <system.web>
    <authorization>
      <allow roles="YourDomain\Sales" />
      <deny users="YourDomain\Jane" />
      <deny users="*" />
    </authorization>
  </system.web>
</configuration>

The C# version of this code can be found on the CD-ROM.

The authorization section uses a "first match" algorithm to allow or deny users and groups access to a page. Since the Sales role is allowed access before everyone is denied access, anyone in the Sales role is given authorization to access any page in the directory.

Notice that the domain must be specified when listing user accounts and groups. If the user account or group is a local machine account, the domain is the name of your server.

NOTE

When using Basic Authentication, you don't need to enter the domain when entering your username as long as it is the default domain for your application. You can specify the default domain associated with an application with IIS. Set this option within the Authentication Methods dialog box.

You can use two special wildcard characters in the authorization section of the Web.Config file. The * wildcard character represents all users, and the ? wildcard character represents all unauthenticated users. For example, if you want to force all users to log in before accessing a file in a directory, you can use the Web.Config file in Listing 20.3.

Listing 20.3 NoAnon\Web.Config
<configuration>
  <system.web>
    <authorization>
      <deny users="?" />
    </authorization>
  </system.web>
</configuration>

The C# version of this code can be found on the CD-ROM.

Now you're ready to tackle a more complicated example. Say that you need to create two directories named Managers and Sales. You want to enable only users in the Managers group to access the files in the Managers directory. However, you want to enable members of both the Managers and Sales groups to access all the files in the Sales directory.

NOTE

The files discussed in this section are located in the SimpleAuth directory on the CD that accompanies this book.

Imagine that you have two user accounts, one named Jane and one named George. The Jane user account is a member of the Managers group, and the George user account is a member of the Sales group.

NOTE

As long as the Everyone group has access to the files in a Web application, you do not need to explicitly add either the Jane or George user accounts to the file permissions for the application.

By default, both Jane and George have access to every page in the application. To prevent George from accessing any file in the Managers folder, you need to add the Web.Config file in Listing 20.4 to the Managers folder.

Listing 20.4 Managers\Web.Config
<configuration>
  <system.web>
    <authorization>
      <allow users="YourDomain\Jane" />
      <deny users="*" />
    </authorization>
  </system.web>
</configuration>

The C# version of this code can be found on the CD-ROM.

The Web.Config file in Listing 20.4 explicitly allows Jane to access files in the current directory and explicitly denies everyone else access. Because George is not granted permissions to access files in the directory, he cannot access any files.

To enable both Jane and George access to all files in the Sales directory, you can use the Web.Config file in Listing 20.5.

Listing 20.5 Sales\Web.Config
<configuration>
  <system.web>
    <authorization>
      <allow users="YourDomain\Jane, YourDomain\George" />
      <deny users="*" />
    </authorization>
  </system.web>
</configuration>

The C# version of this code can be found on the CD-ROM.

The Web.Config file in Listing 20.5 explicitly provides both Jane and George access to the files in the current directory.

Configuring Custom Roles

Instead of using Windows groups for your roles, you can associate user accounts with custom roles. By using custom roles, you can invent any roles you want.

To create custom user roles, you need to add a WindowsAuthentication_Authenticate subroutine in the Global.asax file. This subroutine is triggered whenever a user is authenticated using Windows authentication.

NOTE

All the files in this section are located in the CustomRoles directory on the CD that accompanies this book.

The Global.asax file in Listing 20.6 uses the WindowsAuthentication_Authenticate subroutine to associate custom roles with authenticated users.

Listing 20.6 CustomRoles\Global.asax
<%@ Import Namespace="System.Security.Principal" %>
<Script Runat="Server">

Dim colRoles As HashTable

Overrides Sub Init()
  colRoles = New HashTable()

  Dim arrJaneRoles As String() = _
    { "Supervisors", "Engineers" }
  colRoles( "yourdomain\jane" ) = arrJaneRoles
  Dim arrGeorgeRoles As String() = _
    { "Engineers" }
  colRoles( "yourdomain\george" ) = arrGeorgeRoles
End Sub

Sub WindowsAuthentication_Authenticate( _
  s As Object, _
  e As WindowsAuthenticationEventArgs )

  e.User = New GenericPrincipal( _
    e.Identity, _
    colRoles( e.Identity.Name.ToLower() ) )
End Sub

</Script>

The C# version of this code can be found on the CD-ROM.

In the Init() subroutine in the Global.asax file in Listing 20.6, custom roles are associated with the Jane and George user accounts. Jane is assigned to the custom roles Supervisors and Engineers. George is assigned to the custom role Engineers.

The WindowsAuthentication_Authenticate subroutine is triggered whenever a user is authenticated with Windows authentication. Within this subroutine, a new instance of the GenericPrincipal class is created. This class associates the authenticated user account with the custom roles defined in the Init() subroutine.

After you place the Global.asax file in Listing 20.6 in your application's root directory, you can refer to the custom roles in any Web.Config files contained in the application. When Jane is authenticated, she automatically becomes a member of the custom Supervisors and Engineers roles. When George is authenticated, he automatically becomes a member of the Engineers role.

Now, imagine that you have a directory named Supervisors that contains information that should be viewed only by members of the custom Supervisors role. You can restrict access to the files in this directory by using the Web.Config file in Listing 20.7.

Listing 20.7 CustomRoles\Supervisors\Web.Config
<configuration>
  <system.web>
    <authorization>
      <allow roles="Supervisors" />
      <deny users="*" />
    </authorization>
  </system.web>
</configuration>

The C# version of this code can be found on the CD-ROM.

The Web.Config file in Listing 20.7 denies access to everyone except users in the Supervisors role.

You can use custom roles to develop very complex role-based security systems. For example, you can store the custom roles in a database table and retrieve the table in the Global.asax Init() subroutine.

Retrieving User Information

You can use two classes to retrieve information on users who have been authenticated by using Windows authentication: WindowsPrincipal and WindowsIdentity.

You can use the WindowsPrincipal class to detect the roles to which an authenticated user belongs. For example, to check whether the current user is part of the Sales role, you can use the following statements:

If User.IsInRole( "Sales" ) Then
  Response.Write( "Welcome Sales Person!" )
Else
  Response.Write( "Go away!" )
End If

The IsInRole method works both with roles that correspond to Windows groups and custom roles that you have created. You can use the IsInRole method to display different content to users depending on their role. For example, you might want to enable members of the Supervisors role to delete items in a database table, but not members of the Editors role.

You also can retrieve information on authenticated Windows users by using the WindowsIdentity class, which has the following properties:

  • AuthenticationType— Specifies the type of authentication used; for example, Basic or NTLM (for Windows Integrated Authentication)

  • IsAnonymous— Returns True when the current user is anonymous (the anonymous account)

  • IsAuthenticated— Returns True when the current user has been authenticated

  • IsGuest— Returns True when the current user has been authenticated with the Guest account

  • IsSystem— Returns True when current user has been authenticated with the System account

  • Name— Returns the name of the current user

  • Token— Returns the handle of the access token associated with the current thread

To display the user account associated with the current user, for example, you would use the following statement:

Response.Write( User.Identity.Name )

This statement displays the full user account name including the domain.

To retrieve the other properties, such as IsAnonymous or IsGuest, you need to use the CType function to explicitly cast the user's Identity as a WindowsIdentity like this:

Response.Write( CType( User.Identity, _
  System.Security.Principal.WindowsIdentity ).IsAnonymous )

This statement displays the value True before the user logs in, and False afterward.


Impersonating User Accounts

When you requested pages with Classic Active Server Pages, the page executed with the permissions of the user making the request. In other words, in Classic Active Server Pages, each request impersonated a user account. For example, if you requested a page while logged in under the Administrator user account, the requested Active Server Page executed with the permissions of the Administrator account. If you requested an Active Server Page anonymously, the Active Server Page executed under the IUSER_MachineName account.

By default, ASP.NET does not impersonate the user making a request. An ASP.NET page executes with the permissions of the ASPNET account. The ASPNET account was added to your server when you installed the .NET Framework.

This means that an anonymous user has the same permissions as the ASPNET account. If you grant the ASPNET account sufficient permissions to delete all the files on your hard drive, then an anonymous user can request a page that deletes all the files on your hard drive.

NOTE

A fresh install of the Windows 2003 operating system will use the NETWORK SERVICE account instead of the ASPNET account. If you configure IIS 6.0 (The version of IIS included with Windows 2003) to use IIS 5.0 Isolation mode, then Windows 2003 will use the ASPNET account.

You can modify this default behavior by enabling client impersonation. By default, this option is disabled in the Machine.Config file. You can modify the impersonation setting in the Machine.Config file, or you can override this setting in a Web.Config file located in an application root directory. For example, the Web.Config file in Listing 20.8 enables impersonation.

Listing 20.8 Impersonate\Web.Config
<configuration>
  <system.web>
    <identity impersonate="true" />
  </system.web>
</configuration>

The C# version of this code can be found on the CD-ROM.

NOTE

All the files in this section are located in the Impersonate directory on the CD that accompanies this book.

You can test the difference between enabling and disabling impersonation by using the file in Listing 20.9.

Listing 20.9 ReadDirectory.aspx
<%@ Import Namespace="System.IO" %>

<%
Dim objDir As New DirectoryInfo( MapPath( "testDir" ) )
Dim objItem As FileInfo

For Each objItem in objDir.GetFiles( "*.*" )
  Response.Write( "<li>" & objItem.Name )
Next
%>

The C# version of this code can be found on the CD-ROM.

The ASP.NET page in Listing 20.9 displays all the files in a subdirectory named testDir. Imagine that the testDir directory can be accessed only by the ASPNET account. In that case, if you enable impersonation, you receive an error if you attempt to execute the ReadDirectory.aspx page. On the other hand, if you disable impersonation, the page executes just fine.

CAUTION

If you enable impersonation, the account being impersonated must have read and write permissions on (at least) the following two directories:

\WINNT\Microsoft.NET\Framework\[version]\Temporary ASP.NET Files\

\WINNT\Assembly

The first directory contains compiled ASP.NET pages, and the second directory contains the global assembly cache.

Executing ASP.NET Pages under a User Account

As I discussed in the preceding section, by default, if request impersonation is disabled, an ASP.NET page executes using the ASPNET account. If you prefer, you can execute ASP.NET pages under a different user account by changing the account assigned to the ASP.NET process or by changing the account assigned to an individual ASP.NET application.

In order to change the ASP.NET worker process, you need to modify the Machine.Config file (located at /WINNT/Microsoft.NET/Framework/[version]/Config). Open the Machine.Config file in Notepad and find the processModel section.

The processModel section includes the userName and password attributes. These attributes determine the account under which the ASP.NET worker process executes. By default, userName is assigned the value machine and password is assigned the value AutoGenerate. The special value machine indicates that the ASPNET user account is used for the ASP.NET worker process. You can also use the special value SYSTEM to indicate that the local system account should be used for the ASP.NET process.

The advantage of using the local system account for ASP.NET is that the local system account is very powerful. If you use local system for ASP.NET, then you never have to worry about granting such permissions as write permissions on a directory. The disadvantage, however, is evil code could wreak havoc on your server. There is nothing to prevent an ASP.NET page running under the security context of the local system account from deleting all the files from your hard drive.

NOTE

The Beta versions of ASP.NET all ran under the local system account by default. At the last moment, before releasing the final version of ASP.NET, Microsoft switched the default account to the ASPNET account.

Changing the account associated with the ASP.NET process modifies the account used by all ASP.NET applications on a machine. However, you might need to modify the security account associated with a particular application.

You might want to execute pages under a different account if you are hosting multiple ASP.NET applications on your Web server and you do not want one application to have access to the files in another application. You can configure the applications to run under distinct user accounts.

NOTE

You can lock down configuration settings in the Web.Config file. You need to do so when hosting untrusted applications to prevent users from modifying their security settings. For more information, see Chapter 15, "Creating ASP.NET Applications."

You specify a particular account for executing ASP.NET pages by modifying either the Machine.Config file or an application root Web.Config file. For example, the Web.Config file in Listing 20.10 would cause all ASP.NET pages to execute under the George user account. (This file is located in the ASPNETAccount directory on the CD that accompanies this book.)

Listing 20.10 Web.Config
<configuration>
  <system.web>
    <identity
      impersonate="true"
      userName="YourDomain\George"
      password="secret" />
  </system.web>
</configuration>

The C# version of this code can be found on the CD-ROM.

The value of userName and password must correspond to a valid Windows user account. This Web.Config file causes ASP.NET pages to execute under the George user account regardless of the identity of the client who requests the page.

CAUTION

If you execute ASP.NET pages under a particular account, the account must have read and write permissions on the following two directories:

\WINNT\Microsoft.NET\Framework\[version]\Temporary ASP.NET Files\

\WINNT\Assembly

The first directory contains compiled ASP.NET pages, and the second directory contains the global assembly cache.

You can display the account that an ASP.NET page is executing by requesting the page in Listing 20.11.

Listing 20.11 ShowAccount.aspx
<Script Runat="Server">

Sub Page_Load()
  lblAccount.Text = System.Security.Principal.WindowsIdentity.GetCurrent().Name
End Sub

</Script>

<asp:Label id="lblAccount" Runat="Server" />

The C# version of this code can be found on the CD-ROM.


Setting Security Policies

In this section, you learn how to set different security policies for your ASP.NET applications. If you look in the same directory as your Machine.Config file (\WINNT\Microsoft.NET\Framework\[version]\CONFIG), you see a number of other files that end with the extension .config. These configuration files define different security policies. For example, the web_lowtrust.config file defines the security policy for an ASP.NET application that is provided with a low-trust security policy. You can open any of these human-readable XML files in Notepad.

A security policy file contains three main sections: a list of security classes, a list of named permission sets, and a list of code groups.

A security policy is built from one or more code groups. Code groups specify membership conditions for code. If the code contained in an assembly matches the membership conditions of a code group, the security policy of the code group applies to the code contained in the assembly. You can find the classes that define code groups in the System.Security.Policy namespace.

Each code group is associated with a named permission set, which contains a set of permissions and a name and description of the permission set. You can create custom permission sets or use the built-in permission sets.

The permissions that you can use with permission sets are located in the System.Security.Permissions namespace. An example of a permission is the FileIOPermission class, which controls how you access files and directories on the hard drive. For example, you can use this permission to grant Read and Write permissions on files. There are also permissions for controlling access to environmental and registry variables.

You can apply a security policy to an ASP.NET application by modifying the trust level of the application in either the Machine.Config file or the root Web.Config file for the application. For example, if you want to apply a low-trust security policy, you can configure the trust section like this:

<trust level="Low" originUrl="" />
        <securityPolicy>
            <trustLevel name="Full" policyFile="internal" />
            <trustLevel name="High" policyFile="webhightrust.config" />
            <trustLevel name="Low"  policyFile="weblowtrust.config" />
            <trustLevel name="None" policyFile="webnotrust.config" />
        </securityPolicy>


Summary

In this chapter, you learned how to configure ASP.NET applications to use Windows security. In the first section, you learned how to use Windows authentication with Windows users and groups to password-protect files in a directory. You also learned how to create custom roles and associate these roles with existing user accounts.

You also examined the topic of impersonation. You learned how to enable impersonation so that pages execute with the permissions of the user requesting the page. You also learned how to execute all the pages in an ASP.NET application under the security context of a particular Windows user account.

Finally, the chapter provided a brief overview of security policies. You learned how to apply different security policies to an ASP.NET application.


Chapter 21. Encrypting Data over the Network

IN THIS CHAPTER

  • Using the Secure Sockets Layer

  • Configuring Your Server to Use SSL

  • Using SSL in ASP.NET Pages

  • Using .NET Encryption Classes

If you need to send or receive sensitive information at your Web site—such as credit card numbers, business plans, or legal documents—you need to encrypt the information. In this chapter, you examine methods for encrypting data before it is sent across an unsecure network such as the Internet.

First, you learn how to configure the Secure Sockets Layer (SSL) for your Web site. I discuss how you can use SSL to create a secure channel of communication and cover some techniques for using SSL in your ASP.NET pages.

Next, I discuss the classes in the .NET framework that provide cryptographic services. You learn how to use hash algorithms and work with both symmetric and asymmetric (public key) cryptographic algorithms.


Using the Secure Sockets Layer

The Secure Sockets Layer (SSL) is a protocol, originally developed by Netscape, for transmitting information securely across an unsecure network. SSL is the only existing method for sending private information across the Internet that works with the majority of current browsers. SSL provides a technical solution to three distinct security problems: encryption, authentication, and data integrity.

Encryption

When you enter information into an HTML form and submit it at a Web site, the information is transmitted from your browser to the Web site's server. As the information travels across the Internet, it typically passes through several intermediate connections. In theory, the data entered into the form can be intercepted and read.

The problem of interception is analogous to the situation a general faces when he must send a message containing secret plans across enemy territory. As the messenger travels across the unknown territory, he could be captured, and the enemy could steal and read the secret plans.

The proper solution, for both the general and for the person entering information into the HTML form, is to encrypt the message before it is sent across hostile territory. Even if the message is captured, the privacy of the information is protected—unless, of course, the secret code is cracked.

SSL encrypts information as it passes back and forth between a Web server and Web browser. The information is encoded using a publicly known encryption algorithm and a secret session encryption key. The number of bits in the session key determines the strength of the encryption.

When you installed Internet Information Server, by default, you installed a version of IIS that supports a 40-bit session encryption key. However, you have the option of upgrading IIS to use a stronger 128-bit session encryption key. Although messages encrypted with the 40-bit key have been cracked, messages encrypted with the 128-bit key are considered unbreakable with current technology.

Why not always use the 128-bit key? There are two reasons. First, communicating using a 128-bit key can be significantly slower than using the 40-bit key. The longer the key, the more work the server and browser must perform to encrypt and decrypt the messages passed back and forth.

Furthermore, until recently, legal restrictions were placed on using the longer 128-bit key. In the past, the United States government classified 128-bit SSL as munitions. It therefore was illegal, with certain exceptions, to export any program that supports this stronger encryption outside the United States or Canada. This restriction applies to both Web servers and Web browsers.

Recently, the government has relaxed its position. However, many users still have browsers that do not support 128-bit SSL, especially outside the United States.

Normally, if you install a 128-bit session key on your Web server, the Web server automatically negotiates the highest level of encryption to use for securing communication. If someone opens a connection using a browser with a 40-bit key, your server automatically uses this level of encryption. However, you also can configure IIS to reject browsers that do not support the stronger 128-bit key.

Authentication

If you visit a Web site that appears to be Amazon.com in every way, you might feel safe providing your credit card information to buy a book. However, a clever thief could create a Web site that is indistinguishable from Amazon.com and steal your credit card information.

To return to the example of the general sending a message across enemy territory, imagine that the enemy decides to impersonate the intended recipient of the secret plans. The general and imposter decide on a secret code, and the messenger delivers the message encoded with the secret code. However, the messenger has delivered the secret plans to the imposter.

To prevent one Web site from pretending to be another, you can use SSL to authenticate a Web site. When you install SSL on your Web server, you must install a server certificate. This certificate is used to verify your Web site's identity in much the same way as your driver's license or passport is used to verify your personal identity. A server certificate contains information about your organization, your Web site, and the issuer of the certificate.

To work as a digital ID, a server certificate must be signed by a certificate authority. A certificate authority acts as a trusted third party that verifies the identity of a Web site for its users. Whenever you open a page using SSL, the information from the server certificate is included. For example, using Internet Explorer, you can view the certificate information for the home page of the Microsoft site. Enter http://www.microsoft.com into the address bar of your browser and choose File, then Properties, then click the Certificates button (see Figure 21.1).

Figure 21.1. Viewing certificate information.

Instead of using a third-party certificate authority, you also can issue and sign your own certificates using Microsoft Certificate Services (included with Windows 2000). In other words, you can be your own certificate authority. Being your own certificate authority is valuable when you need to authenticate multiple computers in your organization to members of your organization. However, if your Web site is public, you should use a third-party certificate authority such as Verisign because a server certificate is only as trustworthy as its issuer.

SSL version 3.0 also supports client certificates. Client certificates work in exactly the same way as server certificates except that they are used to authenticate Web browsers rather than Web servers. Both Microsoft Internet Explorer (version 3.0 and higher) and Netscape Navigator (version 3.0 and higher) support client certificates. You can get a client certificate from a certificate authority, or you can use Microsoft Certificate Services to issue your own.

Data Integrity

Imagine that a malicious individual decides to alter a message as it is transmitted across the Internet. This individual does not read the message or prevent the message from being transmitted. The message is simply vandalized.

To return to the example of the general, suppose that the messenger successfully delivers the general's secret plans to the intended recipient. Without the messenger's knowledge, however, the secret plans were switched while the messenger was crossing the enemy terrain. The wrong plans have been delivered.

SSL protects the integrity of data as it crosses the Internet. When messages are transmitted with SSL, they include a message authentication code (MAC). This code detects whether a message has been altered. In other words, when you use SSL, you know that the message received is the same as the message sent.

How Secure Is SSL?

How safe is SSL? Can you use SSL to safely transmit credit card information or private business documents across the Internet? All the major commercial Web sites on the Internet that accept credit card information currently use SSL. For example, Amazon.com has accepted credit card information from more than 4.5 million customers using SSL.

The real answer is that you do not have much choice. If you want to convey private information across the Internet without forcing your Web site's users to download special programs such as Wallets, ActiveX components, or Java applets, you must use SSL. SSL is the only method of sending private information that is supported by the majority of browsers.


Configuring Your Server to Use SSL

Configuring your server to use SSL is a straightforward process. However, if you are planning to use a server certificate from a third-party certificate authority, you should be warned that it can be a long process. Receiving a server certificate can take several weeks. This process also can be expensive. Verisign, for example, currently charges $349 for a server certificate, and you must pay $249 each additional year the certificate is renewed.

Three main steps are involved in installing SSL. First, you must generate a certificate request file and an encryption key pair file using the Web Server Certificate Wizard. Next, you must apply for a server certificate at a third-party certificate authority by providing it with your certificate request file. Finally, after you receive your server certificate, you must install it by using the Web Server Certificate Wizard.

Generating a Certificate Request File

To create a certificate request file—also called a certificate signing request (CSR)—open the Web Server Certificate Wizard by completing the following steps:

  1. Launch the Internet Services Manager.

  2. Open the property sheet for your Web site (by clicking the icon with the hand holding a piece of paper).

  3. Select the Directory Security tab.

  4. Click the Server Certificate button in the section labeled Secure Communications.

The Web Server Certificate Wizard guides you through the task of creating the certificate request file (see Figure 21.2).

Figure 21.2. The Web Server Certificate Wizard.

To create the certificate request file, supply the following information:

  • Name— You can supply any name here. This name is used to identify the key.

  • Bit length— You can choose the value 512 or 1040. The greater the bit length, the more work the server must perform.

  • Organization— The name of the owner of your domain name. Usually, the organization is the name of your company.

  • Organizational unit— The name of your department or business unit.

  • Common name— Your fully qualified domain name; for example, www.superexpert.com. You should not include the protocol (http://).

  • Country/Region— The two-letter ISO country code for your country; for example, US for the United States or CA for Canada.

  • State/province— The full name of your state or province; for example, Washington.

  • City/locality— The name of your city or town; for example, Seattle.

  • Certificate Request File Name— When you complete the wizard, your certificate request file is stored on your hard drive with this name.

After you supply this information to the wizard, a certificate request file is saved to your hard drive. At this point, the certificate request file has been generated, but the certificate has not been installed. If you're curious, here's an example of a certificate request file:

-----BEGIN NEW CERTIFICATE REQUEST-----
MIIChjCCAjACAQAwdjEUMBIGA1UEAxMLc3VwZXJleHBlcnQxFDASBgNVBAsTC1N1
cGVyZXhwZXJ0MRQwEgYDVQQKEwtTdXBlcmV4cGVydDEQMA4GA1UEBxMHU2VhdHRs
ZTETMBEGA1UECBMKV2FzaGsafsdsbjELMAkGA1UEBhMCVVMwXDANBgkqhkiG9w0B
AQEFAANLADBIAkEAyeavOog01j1aPdoEi6dO1qKB6WLie0Ilz/Yr1NioPmRRNxw8
7QbgGoVcaDNmxCBWFE9UzCasffsMHZL9GCey3QIDAQABoIIBUzAaBgorBgEEAYI3
DQIDMQwWCjUuMC4yMTk1LjIwNQYKKwYBBAGCNwIBDjEnMCUwDgYDVR0PAQH/BAQD
AgTwMBMGA1UdJQQMMAoGCCsGAQUFBwMBMIH9BgorBgEEAYI3DQICMYHuMIHrAgEB
HloATQBpAGMAcgBvAHMAbwBmAHQAIABSAFMAQQAgAFMAQwBoAGEAbgBuAGUAbAAg
AEMAcgB5AHAAdABvAGcAcgBhAHAAaABpAGMAIABQAHIAbwB2AGkAZABlAHIDgYkA
0jwwllPCwtmzxrLJ/2/rpGCvHrqzYzASmxr2ltdVP4OJogQKKcWQz5vkwdEPmEY2
3Ivam+3jSC5oZ6+I54thisdfszNLyHZ5lZK11nalKu/dN6hbwBhBemxUoi4NpIFf
dw6MIxm1bmlcLFxaI4jtJ7UDIg+pMMiMraSAo4zAaBMAAAAAAAAAADANBgkqhkiG
9w0BAQUFAANBAL2Y6L96BpQMWayt0LzHtTjRGf+dNDHUFFNtWWB3iVwztCdJsvFa
luqFigBWYWLubHjOp+0MKg18p62BG5tVfoI=
-----END NEW CERTIFICATE REQUEST-----

Applying for a Server Certificate

After you generate a certificate request file, you can apply for a server certificate from a certificate authority. These three are the more popular ones:

  • Verisign Inc. (http://www.verisign.com)

  • Thawte Consulting (http://www.thawte.com)

  • GTE CyberTrust Solutions (http://www.cybertrust.gte.com)

To apply for a Verisign server certificate, for example, go to http://www.verisign.com and choose Secure Server ID. You need to provide Verisign with identifying information about your organization, such as your Dun and Bradstreet DUNS number, your articles of incorporation, or your business license. After you provide this information, you can submit your certificate request file through an online form. After your information is verified, you receive an e-mail message that contains instructions for downloading your new server certificate.

Installing Your Server Certificate

The last step in preparing your server to use SSL is to actually install the server certificate. To do so, launch the Web Server Certificate Wizard once again and choose the option labeled Process the Pending Request and Install the Certificate. Then open the server certificate file from your hard drive. The server certificate should now be installed on your server.

NOTE

If you must transfer your certificate to a new server, you can use the Web Server Certificate Wizard to create a backup copy of your certificate. You can then load the certificate on the new server by launching the Web Server Certificate Wizard and selecting the option labeled Import a Certificate from a Key Manager Backup File. The new server must have exactly the same Internet domain name as the original server (the IP address can be different).


Using SSL in ASP.NET Pages

After you configure your server to use SSL, you can request any page from your Web site using a secure connection. To retrieve a Web page using SSL, use an address of the form https://www.yourdomain.com/page.aspx rather than the normal http://www.yourdomain.com/page.aspx. This form works for any page at your Web site.

You can force users to use SSL when requesting pages from your Web site. To do so, open the property sheet for a directory or a single page within the Internet Services Manager and choose the Directory Security or File Security tab. Next, click the Edit button under Secure Communications and choose Require Secure Channel When Accessing This Resource. If you want to require 128-bit SSL, click the Encryption Settings button and choose Require 128-Bit Encryption.

When you're requesting credit card information, it is a good idea to provide both a secure and an unsecure version of the form for collecting the information. Even though SSL is as old as Netscape 1.0, strangely enough, some browsers still do not support it. To enable users to choose between the two versions of the same page, simply provide two different links:

<a href="https://www.yourdomain.com/pay.aspx">Pay Now (secure version)</a>
<a href="http://www.yourdomain.com/pay.aspx">Pay Now (unsecure version)</a>

Within the pay.aspx page itself, you can detect whether the page is being requested using SSL. If a user is requesting a page unsecurely, you might want to provide a warning. The page in Listing 21.1 contains a subroutine that detects and reports the security status of an ASP.NET page (see Figure 21.3).

Listing 21.1 IsSecureConnection.aspx
<Script Runat="Server">

Sub Page_Load
  If Request.IsSecureConnection Then
    lblMessage.Text = "You are using a secure connection"
    lblMessage.Text &= "<br><b>Issuer:</b>"
    lblMessage.Text &= Request.ServerVariables( "CERT_SERVER_ISSUER" )
    lblMessage.Text &= "<br><b>Subject</b>"
    lblMessage.Text &= Request.ServerVariables( "CERT_SERVER_SUBJECT" )
    lblMessage.Text &= "<br><b>Encryption Key Size:</b>"
    lblMessage.Text &= Request.ServerVariables( "CERT_KEYSIZE" )
    lblMessage.Text &= "<br><b>Certificate Key Size:</b>"
    lblMessage.Text &= Request.ServerVariables( "CERT_SECRETKEYSIZE" )
    lblMessage.ForeColor = System.Drawing.Color.Green
  Else
    lblMessage.Text = "You are not using a secure connection"
    lblMessage.ForeColor = System.Drawing.Color.Red
  End If
End Sub

</Script>

<html>
<head><title>IsSecureConnection.aspx</title></head>
<body>

<asp:Label
  ID="lblMessage"
  Runat="Server" />

<p>

Please enter your credit card information, your social security number,
your yearly income, and your favorite color.

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 21.3. Reporting security status.

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 system. You also can use these classes to automatically add digital signatures to documents.

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.

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

Listing 21.2 Hash.aspx
<%@ Import Namespace="System.Security.Cryptography" %>

<Script Runat="Server">

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

</Script>

<html>
<head><title>Hash.aspx</title></head>
<body>

<form Runat="Server">

<h2>Generate MD5 Hash</h2>

<b>Enter text to hash:</b>
<br>
<asp:TextBox
  id="txtInput"
  TextMode="Multiline"
  Columns="50"
  Rows="10"
  Runat="Server" />
<p>
<asp:Button
  Text="Compute Hash!"
  OnClick="Button_Click"
  Runat="Server" />
<p>
<b>Hash value:</b>
<br>
<asp:Label
  ID="lblOutput"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 21.4. Computing a 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.

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 email, store the information on the file system, or add the information to a database table.

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
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.Security.Cryptography" %>

<Script Runat="Server">

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( _
    MapPath( "secret.txt" ), _
    FileMode.Create, _
    FileAccess.Write )
  objCryptoStream = New CryptoStream( _
    objFileStream, _
    objEncryptor, _
    CryptoStreamMode.Write )
  objCryptoStream.Write( arrInput, 0, arrInput.Length )
  objCryptoStream.Close()
End Sub

</Script>

<html>
<head><title>SymmetricWrite.aspx</title></head>
<body>

<form Runat="Server">

<h2>DES Encryption</h2>

<b>Enter text to encrypt:</b>
<br>
<asp:TextBox
  id="txtInput"
  TextMode="Multiline"
  Columns="50"
  Rows="10"
  Runat="Server" />
<p>
<asp:Button
  Text="Encrypt!"
  OnClick="Button_Click"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

Figure 21.5. Encrypting a file.

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.

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 SymmetricRead.aspx
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.Security.Cryptography" %>

<Script Runat="Server">

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

  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

</Script>

<html>
<head><title>SymmetricRead.aspx</title></head>
<body>

<h2>DES Decryption</h2>

<b>Decrypted Text:</b>
<p>
<asp:Label
  ID="lblMessage"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 21.6. Decrypting a file.

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.

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( "<h2>Private Key:</h2>" )
Response.Write( Server.HTMLEncode( objDSA.ToXmlString( True ) ) )
Response.Write( "<h2>Public Key:</h2>" )
Response.Write( Server.HTMLEncode( objDSA.ToXmlString( False ) ) )

Notice that you use the ToXmlString method to display both the private and public keys. You pass the value True to display the private key and the value False to display the public key.

The preceding statements generate output similar to the following:

Private Key:
<DSAKeyValue><P>1AYChM6ITZ1sFQDtDd8rfDNAoeWdIttEpgY53xArTjl1RrZ7LD887IQFRmY
CqCjgA0EoToR8elnIbAsr+IGN9h4dRnbKaCk/LupXj1bxJqQbMlqXyfLehxeshRCgajFqAOmMW6
SSlNNdJU8DY8gzPUzSar57UhpbDur1p2MHwlc=</P><Q>5RdZjO/s6pJMQEOnzThKX0/EJp8=</
Q><G>sQNgfhRtZ5GGufpdPgb5zYAP0go4egcNhGibQbIV4OBgqP2jiBLURjnxSHNs5GKbvUGT2t
5Sr2DP8+704Tv+osjNtOsG324Txgdx3Li0wsqguyBu2stlIPI2FxPuTcasLqPmFuWhBHexFT2lg
4w4eVCVH1jlaCZ7lmrI1ux2w2c=</G><Y>FlAgexkYEnPIM06dnGmE5s/mj/7M9V1ohQY+ZH80d
LTOWCobBt7T16ToR0dUpcIHDrn7kha5Bj4YnT6pjAr55VtclBu3td8m6M3Ymm8MqLQXTVj9aWem
nrHVX+/hMNXOnBGsaf/Lrvjw37TERKocTzJcV9Jv2tsaklC9nQeSx3I=</Y><J>7O1xQHFufGn5
X592ZJ90kuRA/RzxZltsiULtfuNaBu95uNaAYHKOajkBJQP8/U4jzoYvPOTYw9Z37r8BLt8Vt8Q
p64IkmlqHMwlFVZhYU/XNwuXfrSdUPxwl6s+eZHe4GKr4H1vz49wTaGvq</J><Seed>b3M+ieba
mm7xgDEZmUJ2QNB1elg=</Seed><PgenCounter>ARM=</PgenCounter><X>iJMgNkrX+Wj4pV
eSc1VPV2fOBPg=</X></DSAKeyValue>
Public Key:
<DSAKeyValue><P>1AYChM6ITZ1sFQDtDd8rfDNAoeWdIttEpgY53xArTjl1RrZ7LD887IQFRmY
CqCjgA0EoToR8elnIbAsr+IGN9h4dRnbKaCk/LupXj1bxJqQbMlqXyfLehxeshRCgajFqAOmMW6
SSlNNdJU8DY8gzPUzSar57UhpbDur1p2MHwlc=</P><Q>5RdZjO/s6pJMQEOnzThKX0/EJp8=</
Q><G>sQNgfhRtZ5GGufpdPgb5zYAP0go4egcNhGibQbIV4OBgqP2jiBLURjnxSHNs5GKbvUGT2t
5Sr2DP8+704Tv+osjNtOsG324Txgdx3Li0wsqguyBu2stlIPI2FxPuTcasLqPmFuWhBHexFT2lg
4w4eVCVH1jlaCZ7lmrI1ux2w2c=</G><Y>FlAgexkYEnPIM06dnGmE5s/mj/7M9V1ohQY+ZH80d
LTOWCobBt7T16ToR0dUpcIHDrn7kha5Bj4YnT6pjAr55VtclBu3td8m6M3Ymm8MqLQXTVj9aWem
nrHVX+/hMNXOnBGsaf/Lrvjw37TERKocTzJcV9Jv2tsaklC9nQeSx3I=</Y><J>7O1xQHFufGn5
X592ZJ90kuRA/RzxZltsiULtfuNaBu95uNaAYHKOajkBJQP8/U4jzoYvPOTYw9Z37r8BLt8Vt8Q
p64IkmlqHMwlFVZhYU/XNwuXfrSdUPxwl6s+eZHe4GKr4H1vz49wTaGvq</J><Seed>b3M+ieba
mm7xgDEZmUJ2QNB1elg=</Seed><PgenCounter>ARM=</PgenCounter></DSAKeyValue>

If you look closely at the private and public keys, you notice that they differ in a final <X> element, which represents the private key.

You can use the SignData method to generate a digital signature for a message. This method accepts one parameter: a byte array representing the message to be signed. The SignHash method returns a byte array that represents the digital signature for the message.

The VerifyData method performs the opposite operation. You can use this method to verify that a message matches its digital signature. It accepts two parameters: a byte array representing the data to verify and a byte array that represents the digital signature. VerifyData returns the value True when the message matches the signature and False otherwise.

The page in Listing 21.5, for example, generates a digital signature for any text that you enter into an HTML form (see Figure 21.7).

Listing 21.5 AsymmetricWrite.aspx
<%@ Import Namespace="System.Security.Cryptography" %>

<Script Runat="Server">

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 arrInput As Byte()
  Dim objDSA As DSACryptoServiceProvider
  Dim strPublicKey As String
  Dim arrDigitalSignature As Byte()

  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

</Script>

<html>
<head><title>AsymmetricWrite.aspx</title></head>
<body>

<H2>Create Digital Signature</h2>

<form Runat="Server">

<asp:TextBox
  ID="txtInput"
  TextMode="Multiline"
  Columns="60"
  Rows="3"
  Runat="Server" />
<p>
<asp:Button
  Text="Create Signature!"
  OnClick="Button_Click"
  Runat="Server" />
<p>
Digital Signature:
<br>
<asp:TextBox
  ID="txtSignature"
  TextMode="Multiline"
  Columns="60"
  Rows="4"
  Runat="Server" />
<p>
Public Key:
<br>
<asp:TextBox
  ID="txtPublicKey"
  TextMode="Multiline"
  Columns="60"
  Rows="10"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 21.7. Generating a digital signature.

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

Listing 21.6 AsymmetricRead.aspx
<%@ Import Namespace="System.Globalization" %>
<%@ Import Namespace="System.Security.Cryptography" %>

<Script Runat="Server">

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

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

</Script>

<html>
<head><title>AsymmetricRead.aspx</title></head>
<body>

<H2>Verify Digital Signature</h2>

<form Runat="Server">
Message:
<br>
<asp:TextBox
  ID="txtInput"
  TextMode="Multiline"
  Columns="60"
  Rows="3"
  Runat="Server" />
<p>
Digital Signature:
<br>
<asp:TextBox
  ID="txtSignature"
  TextMode="Multiline"
  Columns="60"
  Rows="4"
  Runat="Server" />
<p>
Public Key:
<br>
<asp:TextBox
  ID="txtPublicKey"
  TextMode="Multiline"
  Columns="60"
  Rows="10"
  Runat="Server" />
<p>
<asp:Button
  Text="Verify Signature!"
  OnClick="Button_Click"
  Runat="Server" />
<p>
Verified?
<br>
<asp:Label
  ID="lblVerify"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 21.8. Verifying a digital signature.

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 for your Web site and provided an overview of the features of SSL. You also learned how to install SSL on your Web server.

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.


Part VI: Building ASP.NET Web Services

IN THIS PART

CHAPTER 22 Creating an XML Web Service

CHAPTER 23 Advanced XML Web Services


Chapter 22. Creating an XML Web Service

IN THIS CHAPTER

  • Overview of XML Web Services

  • Creating a Simple XML Web Service

  • Testing an XML Web Service from a Browser

  • Accessing an XML Web Service Through a Proxy Class

  • Transmitting Complex Data in an XML Web Service

  • Examining XML Web Service and Web Site Interaction

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

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.

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.

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

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.

Listing 22.1 TemperatureService.asmx
<%@ WebService Class="TemperatureService" %>

Imports System
Imports System.Web.Services

Public Class TemperatureService : Inherits WebService

  <WebMethod()> Public Function ToCelsius( TF As Double ) As Double
    Return ( 5/9 ) * ( TF - 32 )
  End Function

  <WebMethod()> Public Function ToFahrenheit( TC As Double ) As Double
    Return ( 9/5 ) * Tc + 32
  End Function

End Class

The C# version of this code can be found on the CD-ROM.

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.

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:

<WebMethod()> Public Function ToCelsius( TF As Double ) As Double

The <WebMethod()> 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:

  • BufferResponse— 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.

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

  • EnableSession— 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.

  • MessageName— This property can be used to create an alias for a Web method 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:

[View full width]

<WebMethod(Description:="Converts Fahrenheit to Celsius")> 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— Specifies a name for the Web service (by default, the class name is used).

  • Namespace— 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 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:

[View full width]

<WebService(Description := "Temperature Service", Namespace := "http://www.yourdomain.com
  /webservices")>_

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

  <WebMethod()> Public Function ToCelsius( TF As Double ) As Double
    Return ( 5/9 ) * ( TF - 32 )
  End Function

  <WebMethod()> Public Function ToFahrenheit( TC As Double ) As Double
    Return ( 9/5 ) * Tc + 32
  End Function

End Class

The C# version of this code can be found on the CD-ROM.

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.

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.

The final step is to create the .asmx file that references the compiled class. The new .asmx file contains only a single line. The complete code for the file is shown in Listing 22.3.

Listing 22.3 CompiledTemperatureService.asmx
<%@ WebService Class="CompiledTemperatureService" %>

The C# version of this code can be found on the CD-ROM.

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.

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 for two protocols: SOAP and HTTP-Post. These are two of the standard wire formats for transmitting Web service requests and responses. When exposing a Web service to the outside world, you normally use SOAP. However, you can test your Web service on the local server by using the HTTP-Post protocol (see the next section).

NOTE

ASP.NET version 1.0 enabled you to expose a Web service using SOAP, HTTP-Post, and HTTP-Get. In ASP.NET 1,1, by default, a Web service can only be accessed using SOAP (you can use HTTP-Post only when requesting a Web service from the local machine). The HTTP-Get and HTTP-Post protocols are disabled for security reasons. If you want to enable HTTP-Get and HTTP-Post from remote computers, then you need to add the following Web.Config file to your application:

<configuration>
  <system.web>
    <webServices>
    <protocols>
      <add name="HttpPost" />
      <add name="HttpGet" />
    </protocols>
    </webServices>
  </system.web>
</configuration>

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:

<a href="/Services/TemperatureService.asmx/ToCelsius?TF=32">Convert</a>

Alternatively, you could type the following URL directly into the address bar of your Web browser:

http://localhost/Services/TemperatureService.asmx/ToCelsius?TF=32

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:

<?xml version="1.0" encoding="utf-8" ?>
  <double xmlns="http://tempuri.org/">0</double>

Because 0 degrees Celsius is equivalent to 32 degrees Fahrenheit, the ToCelsius method returns the value 0.

CAUTION

Web service method names are case sensitive. So, attempting to invoke the TOCELSIUS method would generate an error.

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
<form method="post"
  action="/Services/TemperatureService.asmx/ToCelsius">
<input name="TF" value="32">
<input type="submit" value="Convert!">
</form>

The C# version of this code can be found on the CD-ROM.

Posting the form in Listing 22.4 would invoke the ToCelsius method of the TemperatureService Web service. The following XML document would be returned:

<?xml version="1.0" encoding="utf-8" ?>
  <double xmlns="http://tempuri.org/">0</double>

You can test a Web service using HTTP-Post 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).

Figure 22.2. Invoking a Web service method.

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.

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

[View full width]

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap
  /envelope/">
  <soap:Body>
    <ToCelsius xmlns="http://tempuri.org/">
      <TF>32</TF>
    </ToCelsius>
  </soap:Body>
</soap:Envelope>

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:

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <ToCelsiusResponse xmlns="http://tempuri.org/">
      <ToCelsiusResult>0</ToCelsiusResult>
    </ToCelsiusResponse>
  </soap:Body>
</soap:Envelope>

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.

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.

NOTE

If you install Visual Studio .NET, then you cannot execute the command-line tools from the default command prompt (the proper path environmental variable is not set). Instead, you need to execute the compiler by opening the Visual Studio .NET Command Prompt located at Start, Programs, Microsoft Visual Studio .NET, Visual Studio .NET Tools.

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.

CAUTION

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 then, by default, the proxy class works only on the same computer as the Web service.

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

After the class is compiled, you need to copy it to your ASP.NET application's /bin directory so that it is visible to your ASP.NET pages. If the /bin directory does not exist, you can create it.

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 22.5 ConvertTemperature.aspx
<Script Runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  Dim objTemp As New TemperatureService
  lblCelsius.Text = objTemp.ToCelsius( txtFahrenheit.Text )
End Sub

</Script>

<html>
<head><title>ConvertTemperature.aspx</title></head>
<body>

<form Runat="Server">

Fahrenheit:
<asp:TextBox
  ID="txtFahrenheit"
  Runat="Server" />

<asp:Button
  Text="Convert!"
  OnClick="Button_Click"
  Runat="Server" />

<hr>

<asp:Label
  ID="lblCelsius"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 22.3. The ConvertTemperature.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.

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

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.

You can use any of the following options with Wsdl.exe:

  • /nologo:— Prevents the banner text for the Wsdl.exe tool from being displayed.

  • /language: or /l:— By default, classes are generated using the C# language. You can specify CS for C#, VB for Visual Basic, VJS for J#, or JS for JScript.

  • /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:— Indicates the filename for the proxy class.

  • /protocol:— Indicates the protocol to use with the proxy class. By default, a proxy class uses the SOAP protocol. You can specify SOAP, SOAP12, HttpGet, or HttpPost.

  • /username: 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:— Specifies the URL of a proxy server to use for HTTP requests (defaults to Internet Explorer settings).

  • /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: or /urlkey:— Specifies the Url property for the Web service proxy class from a key in the <appSettings> section of the web.config file.

  • /appsettingbaseurl: or /baseurl:— Specifies the base URL for the Web service proxy class from a key in the <appSettings> section of the web.config file.

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— X509CertificateCollection of client-side certificates.

  • CookieContainer— A CookieContainer that contains the cookies associated with the proxy class.

  • Credentials— A class implementing ICredentials (such as NetworkCredential) that can be used for Basic, Digest, NTLM, or Kerberos authentication.

  • PreAuthenticate— A Boolean value indicating whether a WWW-authenticate header is sent with each request (default value is False).

  • Proxy— A class implementing IWebProxy that contains information for making requests through a firewall.

  • RequestEncoding— The character encoding used for proxy requests.

  • Timeout— An integer value in milliseconds that specifies when a proxy request times out (default is -1 which represents infinity).

  • UserAgent— 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.

Listing 22.6 NewsService.asmx
<%@ WebService Class="NewsService" %>

Imports System
Imports System.Web.Services

<WebService( Namespace:="http://yourdomain.com/webservices" )> _
Public Class NewsService : Inherits WebService

<WebMethod( CacheDuration:=30 )>  Public Function GetNews() As String()
  Dim arrNews( 2 ) 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 C# version of this code can be found on the CD-ROM.

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

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
<Script Runat="Server">

Sub Page_Load
  Dim objNewsService As NewsService
  Dim arrNews As String()

  objNewsService = New NewsService()
  arrNews = objNewsService.GetNews()
  rptNews.DataSource = arrNews
  rptNews.DataBind
End Sub

</Script>

<html>
<head><title>DisplayNews.aspx</title></head>
<body>

<h2>The lastest news:</h2>
<asp:Repeater
  ID="rptNews"
  Runat="Server">
  <ItemTemplate>
  <li> <%# Container.DataItem %>
  </ItemTemplate>
</asp:Repeater>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

In Listing 22.7, an instance of the NewsService proxy class is created in the Page_Load subroutine. The GetNews() method retrieves a list of current news items. These news items are assigned to an array that is bound to a Repeater control, which formats each news item (see Figure 22.4).

Figure 22.4. The DisplayNews.aspx page.

XML Web Services and Classes

You can pass a class that contains public properties and fields back and forth to a Web service. This capability is useful when you need to work with structured data.

In the preceding section, for example, you developed a Web service that returns a list of news items. Each news item was represented by a single string in an array. However, say you want to return multiple properties for each item. Instead of simply returning the text for each one, you might also want to return the date on which it was posted and a URL associated with it for retrieving additional information.

You can improve on the NewsService Web service by using an array of classes rather than an array of strings. Listing 22.8 contains the class definition for a NewsItem.

Listing 22.8 NewsItem.vb
Namespace myComponents

Public Class NewsItem
  Public Headline As String
  Public Posted As Date
  Public URL As String
End Class

End Namespace

The C# version of this code can be found on the CD-ROM.

Before you can use the NewsItem class in Listing 22.8, you'll need to compile it:

vbc /t:library NewsItem.vb

Next, you'll need to copy the compiled NewsItem.dll file to your application's /bin directory.

The page in Listing 22.9 contains the new and improved Web service.

Listing 22.9 BetterNewsService.asmx
<%@ WebService Class="BetterNewsService" %>

Imports System
Imports System.Web.Services
Imports myComponents

<WebService( Namespace:="http://yourdomain.com/webservices" )> _
Public Class BetterNewsService : Inherits WebService

<WebMethod( CacheDuration:=30 )> Public Function GetNews() As NewsItem()
  Dim arrNews( 2 ) As NewsItem

  arrNews( 0 ) = New NewsItem
  arrNews( 0 ).Headline = "Visit superexpert.com for ASP.NET News!"
  arrNews( 0 ).Posted = #12/12/2002#
  arrNews( 0 ).URL = "http://www.superexpert.com"

  arrNews( 1 ) = New NewsItem
  arrNews( 1 ).Headline = "Visit ASPWorkshops for ASP.NET Training!"
  arrNews( 1 ).Posted = #2/1/2002#
  arrNews( 1 ).URL = "http://www.AspWorkshops.com"

  arrNews( 2 ) = New NewsItem
  arrNews( 2 ).Headline = "Visit superexpertControls.com for ASP.NET Controls!"
  arrNews( 2 ).Posted = #2/14/2002#
  arrNews( 2 ).URL = "http://www.superexpertControls.com"

  Return arrNews
End Function

End Class

The C# version of this code can be found on the CD-ROM.

In Listing 22.9, the namespace for the NewsItem class that you created in Listing 22.8 is imported with the Imports statement. When the GetNews method is invoked, the method returns an array of NewsItem objects. The three NewsItem objects are declared within the method.

After you create the Web service in Listing 22.9, you can build a proxy class that enables you to use the Web service in your applications. First, you need to generate the source code for the proxy class like this:

Wsdl /l:vb http://www.YourSite.com/BetterNewsService.asmx?WSDL

Next, you need to compile the proxy class as follows:

vbc /t:library /r:System.dll,System.Web.Services.dll,System.XML.dll BetterNewsService.vb

After you copy the compiled proxy class, BetterNewsService.dll, to your ASP.NET application's /bin directory, you can start using the class in your ASP.NET pages. The page in Listing 22.10 uses the proxy class to display a list of news items.

Listing 22.10 DisplayBetterNews.aspx
<Script Runat="Server">

Sub Page_Load
  Dim objNewsService As BetterNewsService
  Dim arrNews As NewsItem()

  objNewsService = New BetterNewsService()
  arrNews = objNewsService.GetNews()
  rptNews.DataSource = arrNews
  rptNews.DataBind()
End Sub

</Script>

<html>
<head><title>DisplayBetterNews.aspx</title></head>
<body>

<h2>The lastest news:</h2>
<asp:Repeater
  ID="rptNews"
  Runat="Server">
  <itemTemplate>
  <li> <%# Container.DataItem.Posted %>
  <br> <%# Container.DataItem.Headline %>
  <br> <%# Container.DataItem.URL %>
  </itemTemplate>
</asp:Repeater>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

In Listing 22.10, an instance of the BetterNewsService proxy class is initialized in the Page_Load subroutine. An array of NewsItem objects is created to contain the news items retrieved by the GetNews method. This array is bound to a Repeater control to format and display each news item (see Figure 22.5).

Figure 22.5. The DisplayBetterNews.aspx page.

The NewsItem class is automatically created for you when you create the proxy class for the BetterNewsService. If you are curious, you can verify that the class was created by opening the BetterNewsService.vb file in Notepad. Because the class is created automatically for you, you don't have to worry about distributing the class definitions used with your Web service.

Web Services and DataSets

You can pass DataSets to and from a Web service when you need to work with sets of database records. For example, imagine that you are a product wholesaler and you want to distribute a list of your products to multiple retail Web sites. You can create a Web service method that returns a DataSet.

The Web service in Listing 22.11, for example, retrieves a list of products from the Products table in the Northwind database.

Listing 22.11 ProductsService.asmx
<%@ WebService Class="ProductsService" %>

Imports System
Imports System.Web.Services
Imports System.Data
Imports System.Data.SqlClient

<WebService( Namespace:="http://yourdomain.com/webservices" )> _
Public Class ProductsService : Inherits WebService

<WebMethod()> Public Function GetProducts( CategoryName As String ) As DataSet
  Dim strSelect As String
  Dim conNorthwind As SQLConnection
  Dim dadNorthwind As SQLDataAdapter
  Dim dstProducts As DataSet

  strSelect = "SELECT ProductName, UnitPrice, UnitsInStock, CategoryName " & _
    "FROM Products, Categories WHERE Products.CategoryID = " & _
    "Categories.CategoryID AND CategoryName = @CategoryName"

  conNorthwind = New SQLConnection( "Server=localhost;UID=sa;PWD=secret; Database=Northwind" )
  dadNorthwind = New SQLDataAdapter( strSelect, conNorthwind )
  dadNorthwind.SelectCommand.Parameters.Add( "@CategoryName", CategoryName )
  dstProducts = New DataSet
  dadNorthwind.Fill( dstProducts, "Products" )

  Return dstProducts
End Function

End Class

The C# version of this code can be found on the CD-ROM.

In Listing 22.11, the GetProducts method accepts a single parameter: the name of a category. The method opens a connection to the Northwind database, retrieves a list of products that match the category name, and returns the results as a DataSet.

After you create the Web service, you can generate a proxy class for the service by executing the Wsdl.exe tool from the command line like this:

Wsdl /l:vb http://www.SomeSite.com/ProductsService.asmx?WSDL

Next, you need to compile the proxy class by executing the Visual Basic command-line compiler like this:

[View full width]

vbc /t:library /r:System.dll,System.Web.Services.dll,System.XML.dll, System.Data.dll
   ProductsService.vb

Finally, you need to copy the compiled proxy class, ProductsService.dll, to your ASP.NET application's /bin directory.

After you create the ProductsService proxy class, you can use it in an ASP.NET page. The page in Listing 22.12 retrieves a list of products from the ProductsService Web service.

Listing 22.12 DisplayProducts.aspx
<Script Runat="Server">

Sub Page_Load
  Dim objProductsService As ProductsService

  objProductsService = New ProductsService()
  dgrdProducts.DataSource = objProductsService.getProducts( "Seafood" )
  dgrdProducts.DataBind()
End Sub

</Script>

<html>
<head><title>DisplayProducts.aspx</title></head>
<body>

<h2>Available Products:</h2>

<asp:DataGrid
  ID="dgrdProducts"
  Runat="Server"/>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

In the Page_Load subroutine in Listing 22.12, an instance of the ProductsService proxy class is initialized. Next, the GetProducts method is called to retrieve a list of Seafood products. The DataSet returned by the method is bound to a DataGrid control and displayed (see Figure 22.6).

Figure 22.6. The DisplayProducts.aspx page.

Web Services and Binary Files

You can pass and retrieve binary files to and from a Web service. For example, you could create a Web service that works with music files, program files, or images.

The Web service in Listing 22.13 generates an image from a string of text.

Listing 22.13 ImageService.asmx
<%@ WebService Class="ImageService" %>

Imports System
Imports System.Web.Services
Imports System.Drawing
Imports System.Drawing.Imaging
Imports System.IO

<WebService( Namespace:="http://yourdomain.com/webservices" )> _
Public Class ImageService : Inherits WebService

<WebMethod()> Public Function GetTextImage( strText As String ) As Byte()
  Dim objBitmap As Bitmap
  Dim objGraphics As Graphics
  Dim objFont As Font
  Dim strmBuffer As MemoryStream

  objBitmap = New Bitmap( 300, 50 )
  objGraphics = Graphics.FromImage( objBitmap )
  objFont = New Font( "Impact", 24 )
  objGraphics.DrawString( strText, objFont, Brushes.Red, 1, 1 )

  strmBuffer = New MemoryStream()
  objBitmap.Save( strmBuffer, ImageFormat.Gif )
  Return strmBuffer.ToArray()
End Function

End Class

The C# version of this code can be found on the CD-ROM.

The Web service in Listing 22.13 contains a method named GetTextImage. When you pass a string of text to this method, it generates an image that displays the text.

The image is represented by a bitmap. You cannot return a bitmap directly from a method in a Web service. In Listing 22.13, the bitmap is first converted into a byte array by using the ToArray method of the MemoryStream class. The Web method returns the byte array.

NOTE

When the image is retrieved from the Web service, the image is automatically Base64 encoded when sent across the wire and Base64 decoded when received. To avoid the overhead of Base64 encoding binary data with a Web service, you can take advantage of the Direct Internet Message Encapsulation (DIME) protocol. DIME enables you to attach binary data to a Web service in much the same way as you can attach a file to an email message. To learn more about DIME, search for Web Services Enhancements at the Microsoft MSDN Web site (http://msdn.microsoft.com).

Before you can use this Web service in an application, you must first create a proxy class. The first step is to generate the source for the class by using the Wsdl.exe tool like this:

Wsdl /l:vb http://www.YourSite.com/ImageService.asmx?wsdl

Next, you need to compile the proxy class like this:

vbc /t:library /r:System.dll,System.Web.Services.dll,System.XML.dll ImageService.vb

Finally, you need to copy the compiled proxy class, ImageService.dll, to the ASP.NET application's /bin directory.

After you create the proxy class, you can use it in an ASP.NET page. The page in Listing 22.14 displays the image generated by the Web service.

Listing 22.14 DisplayImage.aspx
<%@ Page ContentType="image/gif" %>
<%@ Import Namespace="System.Drawing" %>
<%@ Import Namespace="System.Drawing.Imaging" %>
<%@ Import Namespace="System.IO" %>

<Script Runat="Server">
Sub Page_Load
  Dim objImageService As ImageService
  Dim objBitmap As Bitmap
  Dim strmBuffer As MemoryStream

  objImageService = New ImageService
  strmBuffer = New MemoryStream( objImageService.GetTextImage( "Hello World!" ) )
  objBitmap = New Bitmap( strmBuffer )
  objBitmap.Save( Response.OutputStream, ImageFormat.Gif )
End Sub
</Script>

The C# version of this code can be found on the CD-ROM.

The first line in Listing 22.14 contains a page directive that sets the content type of the page. Because this page displays a GIF image, the ContentType attribute is set to the value image/gif.

An instance of the ImageService proxy class is created in the Page_Load subroutine. The GetTextImage method is called with the string Hello World! and it returns a byte array that represents the image returned by the Web service.

The byte array is converted into a memory stream by using the constructor for the MemoryStream class. Finally, a bitmap is created from the memory stream and saved to the output stream of the Response object. The end result is that the GIF image is displayed (see Figure 22.7).

Figure 22.7. The DisplayImage.aspx page.

    [ Team LiB ]


Examining XML Web Service and Web Site Interaction

An ASP.NET Web service and a Web site can happily coexist in the same application space. In particular, a Web service and Web site can share the same application and session state. In the following sections, you learn how to use application and session state with Web services.

XML Web Services and Application State

A Web site and Web service can share the same application state. You can use application state as a mechanism to exchange information between a Web site and a Web service.

For example, the Web service in Listing 22.15 can be used as a simple counter. Every time you call the GetAppCounter method, an application item named Counter is incremented by one. Because Counter is maintained in application state, the value of the Counter item is not lost between requests to the Web service.

NOTE

To learn more details about application state, see Chapter 15, "Creating ASP.NET Applications."

Listing 22.15 ApplicationService.asmx
<%@ WebService Class="ApplicationService" %>

Imports System
Imports System.Web.Services

<WebService( Namespace:="http://yourdomain.com/webservices" )> _
Public Class ApplicationService : Inherits WebService

<WebMethod()>  Public Function GetAppCounter() As Integer
  Application( "Counter" ) = CINT( Application( "Counter" ) ) + 1
  Return CINT( Application( "Counter" ) )
End Function

End Class

The C# version of this code can be found on the CD-ROM.

After you create the ApplicationService Web service in Listing 22.15, you can use it in an ASP.NET page by creating a proxy class. First, you must use the Wsdl.exe tool to create the source file for the proxy class like this:

Wsdl /l:vb http://www.SomeSite.com/ApplicationService.asmx?wsdl

Next, you need to compile the proxy class like this:

vbc /t:library /r:System.dll,System.Web.Services.dll,System.XML.dll ApplicationService.vb

After you copy the compiled proxy class to the application's /bin directory, you can use the class in an ASP.NET page. The page in Listing 22.16 uses the ApplicationService Web service to track the number of times the page has been requested.

Listing 22.16 ApplicationCounter.aspx
<Script Runat="Server">
Sub Page_Load
  Dim objApplicationService As ApplicationService

  objApplicationService = New ApplicationService
  lblCounter.Text = objApplicationService.GetAppCounter()
End Sub
</Script>

<html>
<head><title>ApplicationCounter.aspx</title></head>
<body>

This page has been requested

<asp:Label
  ID="lblCounter"
  Runat="Server" />

times!

</body>
</html>

The C# version of this code can be found on the CD-ROM.

In Listing 22.16, an instance of the ApplicationService proxy class is initialized in the Page_Load subroutine. Next, the GetAppCounter method is called to get the current value of the counter stored in application state. The value returned by the method is assigned to a Label control.

XML Web Services and Session State

Using session state with Web services is a little trickier than using application state.

By default, session state is disabled for Web service methods for performance reasons (maintaining session state requires some extra work on the part of the server). However, if you want, you can modify the EnableSession property of the WebMethod attribute to enable session state for a particular method.

NOTE

To learn more information about session state, see Chapter 16, "Tracking User Sessions."

The page in Listing 22.17, for example, takes advantage of session state to save the value of a counter between requests to the Web service.

Listing 22.17 SessionService.asmx
<%@ WebService Class="SessionService" %>

Imports System
Imports System.Web.Services

<WebService( Namespace:="http://yourdomain.com/webservices" )> _
Public Class SessionService : Inherits WebService

<WebMethod( EnableSession:=True )> Public Function GetSessionCounter() As Integer
  Session( "Counter" ) = CINT( Session( "Counter" ) ) + 1
  Return CINT( Session( "Counter" ) )
End Function

<WebMethod( EnableSession:=True )> Public Function GetSessionID() As String
  Return Session.SessionID
End Function

End Class

The C# version of this code can be found on the CD-ROM.

Notice that the EnableSession property is set to the value True within the WebMethod attribute for both the GetSessionCounter() and GetSessionID() methods. If you neglect to enable this property and you attempt to access an item from session state, you receive a Null Reference error.

After you create the SessionState Web service, you can test it by opening the SessionService.asmx page in your browser. If you call the GetSessionCounter() method, the session counter should increment as you expect. If you call the GetSessionID() method, the same session ID should be displayed each time the method is called.

The Web service will work fine when you call its methods from a Web browser. You'll encounter problems, however, when you call its methods from a proxy class. Go ahead and try it.

Generate a proxy class for the SessionState Web service by executing the following statement from a command prompt:

wsdl /l:vb http://www.SomeSite.com/SessionService.asmx?wsdl

Next, compile the source code for the proxy class like this:

vbc /t:library /r:System.dll,System.Web.Services.dll,System.XML.dll SessionService.vb

Finally, copy the compiled class, SessionService.dll, to your application's /bin directory.

The page in Listing 22.18 calls the new SessionService proxy to display the value of the session counter and session ID.

Listing 22.18 SessionCounterBad.aspx
<Script Runat="Server">
Sub Page_Load
  Dim objSessionService As SessionService

  objSessionService = New SessionService
  lblSessionCounter.Text = objSessionService.GetSessionCounter()
  lblSessionID.Text = objSessionService.GetSessionID()
End Sub
</Script>

<html>
<head><title>SessionCounterBad.aspx</title></head>
<body>

<h2>This page does not work!</h2>

Your session ID is

<asp:Label
  ID="lblSessionID"
  Runat="Server" />

<p>

This page has been requested by you

<asp:Label
  ID="lblSessionCounter"
  Runat="Server" />

times!

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

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 you want your proxy class to be able to take advantage of session state, you have to do some extra work. You 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
<%@ Import Namespace="System.Net" %>

<Script Runat="Server">
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
</Script>

<html>
<head><title>SessionCounterGood.aspx</title></head>
<body>

<h2>This page works!</h2>

Your session ID is

<asp:Label
  ID="lblSessionID"
  Runat="Server" />

<p>

This page has been requested by you

<asp:Label
  ID="lblSessionCounter"
  Runat="Server" />

times!

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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

Figure 22.8. The SessionCounterGood.aspx page.

    [ Team LiB ]


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

  • Securing an XML Web Service

  • Using HTML Pattern Matching

In the preceding chapter you learned 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, "Additional Samples and 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/downloads/samples/internet/behaviors/library/webservice/default.asp

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.

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

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.

Creating a Simple Page with a WebService Behavior

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
<%@ WebService Class="TemperatureService" %>

Imports System
Imports System.Web.Services

<WebService( Namespace:="http://yourdomain.com/webservices" )> _
Public Class TemperatureService : Inherits WebService

  <WebMethod()> Public Function ToCelsius( TF As Double ) As Double
    Return ( 5/9 ) * ( TF - 32 )
  End Function

  <WebMethod()> Public Function ToFahrenheit( TC As Double ) As Double
    Return ( 9/5 ) * TC + 32
End Function

End Class

The C# version of this code can be found on the CD-ROM.

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
<SCRIPT language="JavaScript">

var intCallID = 0;

function Init()
{
  Service.useService("/webservices/TemperatureService.asmx?WSDL", "TemperatureService");
}

function Service_Result()
{
  lblCelsius.innerText = event.result.value;
}

function Button_Click()
{
  intCallID = Service.TemperatureService.callService( "ToCelsius", txtFahrenheit.value );
}
</SCRIPT>

<html>
<head><title>BehaviorSimple.aspx</title></head>
<body onload="Init()">

<div
  id="Service"
  style="behavior:url(webservice.htc)"
  onresult="Service_Result()"></div>

Fahrenheit:
<input
  id="txtFahrenheit">

<input
  Type="Button"
  Value="Convert!"
  OnClick="Button_Click()">

<hr>

<span id="lblCelsius"></span>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

Figure 23.1. Using the WebService behavior with the Temperature Service.

The WebService behavior is attached to the page with the following tag:

<div
  id="Service"
  style="behavior:url(webservice.htc)"
  onresult="Service_Result()"></div>

The behavior is attached to an HTML <div> 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.

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 Init() function (this function is called by the page's load event). The Init() function contains the following statement:

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

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 <div> 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 <span> tag named lblCelsius.

Using a WebService Behavior Callback Function

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
[View full width]
<SCRIPT language="JavaScript">

var intCallID = 0;

function Init()
{
  Service.useService("TemperatureService.asmx?WSDL","TemperatureService");
}

function ToCelsius_Result( result )
{
  lblCelsius.innerText = result.value;
}

function Button_Click()
{
  intCallID = Service.TemperatureService.callService( ToCelsius_Result, "ToCelsius",
   txtFahrenheit.value );
}
</SCRIPT>

<html>
<head><title>BehaviorCallback.aspx</title></head>
<body onload="Init()">

<div
  id="Service"
  style="behavior:url(webservice.htc)"></div>

Fahrenheit:
<input
  id="txtFahrenheit">

<input
  Type="Button"
  Value="Convert!"
  OnClick="Button_Click()">

<hr>

<span id="lblCelsius"></span>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

In Listing 23.3, you call the ToCelsius method by using the following statement:

[View full width]

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 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 raw contents of the Simple Object Access Protocol (SOAP) packet returned by the Web service method call

  • string— A human-readable error message

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.

Listing 23.4 BehaviorErrors.aspx
<SCRIPT language="JavaScript">

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 );
}
</SCRIPT>

<html>
<head><title>BehaviorErrors.aspx</title></head>
<body onload="Init()">

<div
  id="Service"
  style="behavior:url(webservice.htc)"
  onresult="Service_Result()"></div>

Fahrenheit:
<input
  id="txtFahrenheit">

<input
  Type="Button"
  Value="Convert!"
  OnClick="Button_Click()">

<hr>

<span id="lblCelsius"></span>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 23.2. Using the error property and errorDetail object.

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.

Listing 23.5 BehaviorRefresh.aspx
<SCRIPT language="JavaScript">

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;
  }
  else
  {
  divFeatured.innerText = event.result.value;
  }
}
</SCRIPT>

<html>
<head><title>BehaviorRefresh.aspx</title></head>
<body onload="Init()">

<table width="600">
<tr>
  <td valign="top">
  <h2>Welcome to this Web Site!</h2>
  Browse this Web site to get great deals on the latest books!
  </td>
  <td>
  <div
    id="Service"
    style="behavior:url(webservice.htc)"
    onresult="Service_Result()"></div>

  <div
    id="divFeatured"
    style="width:200px;padding:10px;border: 1px solid darkgreen">
  </div>
  </td>
</tr>
</table>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

The page in Listing 23.5 uses the JavaScript setInterval method to execute a function named GetNewFeatured() every five seconds (5000 milliseconds). The GetNewFeatured() function retrieves a book description from a Web service named FeaturedService. When the Web service method call is complete, the Service_Result function executes and displays the book description within a <div> tag (see Figure 23.3).

Figure 23.3. Partial page update with WebService behavior.

The code for the FeaturedService Web service is contained in Listing 23.6.

Listing 23.6 FeaturedService.asmx
<%@ WebService Class="FeaturedService" %>

Imports System
Imports System.Web.Services
Imports System.Data
Imports System.Data.SqlClient

<WebService( Namespace:="http://yourdomain.com/webservices" )> _
Public Class FeaturedService : Inherits WebService

<WebMethod()> Public Function GetFeatured() As String
  Dim strSelect As String
  Dim conPubs As SqlConnection
  Dim dadPubs As SqlDataAdapter
  Dim dstTitles As DataSet
  Dim drowTitle As DataRow
  Dim objRanNum As Random
  Dim intRanNum As Integer

  strSelect = "SELECT Title, isNull( Notes, '' ) Notes From Titles"
  conPubs = New SqlConnection( "Server=localhost;UID=sa;PWD=secret; Database=Pubs" )
  dadPubs = New SqlDataAdapter( strSelect, conPubs )
  dstTitles = New DataSet
  dadPubs.Fill( dstTitles, "Titles" )
  objRanNum = New Random
  intRanNum = objRanNum.Next( 0, dstTitles.Tables( "Titles" ).Rows.Count )
  drowTitle = dstTitles.Tables( "Titles" ).Rows( intRanNum )
  Return drowTitle( "Title" ) & " - " & drowTitle( "Notes" )
End Function

End Class

The C# version of this code can be found on the CD-ROM.

The FeaturedService Web service in Listing 23.6 randomly returns information on one book title from the Titles database table. The GetFeatured() method returns a string value that represents the featured title.

Using the WebService Behavior to Retrieve Database Data

Unfortunately, you cannot use the WebService behavior to represent a DataSet or DataTable from an XML Web service. This means that you cannot directly access database data through the WebService behavior.

NOTE

The raw contents of a SOAP packet can be retrieved from the raw property of the result object. In theory, you could parse the raw contents of a SOAP packet that contains a DataSet.

One way to get around this limitation is to convert a DataTable into an array before returning it with a Web service method. This is the approach taken in the page contained in Listing 23.7 and the Web service contained in Listing 23.8 (see Figure 23.4).

Listing 23.7 BehaviorDataTable.aspx
[View full width]
<SCRIPT language="JavaScript">

var intCallID = 0;

function Init()
{
  Service.useService("ProductsService.asmx?WSDL","ProductsService");
  intCallID = Service.ProductsService.callService( Categories_Result, "GetCategories" );
}

function Categories_Result( result )
{
  var Categories = new Array();

  Categories = result.value;
  for (var intCounter = 0;intCounter < Categories.length;intCounter++ )
  {
    var optOption = new Option( Categories[ intCounter ] );
    frmProducts.dropCategories.options[ frmProducts.dropCategories.options.length ] =
   optOption;
}
}

function Categories_Change( newCategory )
{
  var CategoryName = "";
  CategoryName = newCategory.options[ newCategory.selectedIndex ].text;
  lblCategory.innerText = CategoryName;
  intCallID = Service.ProductsService.callService( Products_Result, "GetProducts",
   CategoryName );
}

function Products_Result( result )
{
  var Products = new Array();

  Products = result.value;
  frmProducts.lstProducts.options.length = 0;

  for (var intCounter = 0;intCounter < Products.length;intCounter++ )
  {
    var optOption = new Option( Products[ intCounter ] );
    frmProducts.lstProducts.options[ frmProducts.lstProducts.options.length ] = optOption;
  }
  frmProducts.lstProducts.style.display = "block";
}
</SCRIPT>

<html>
<head><title>BehaviorDataTable.aspx</title></head>
<body onload="Init()">

<div
  id="Service"
  style="behavior:url(webservice.htc)"></div>

<form id="frmProducts">

<select
  name="dropCategories"
  onchange="Categories_Change( this )">
  <option>Select Product Category</option>
</select>

<p>
<span id="lblCategory"></span>
<br>
<select
  name="lstProducts"
  size="10"
  style="display:none">
</select>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 23.4. Retrieving database data by using the WebService behavior.

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
<%@ WebService Class="ProductsService" %>

Imports System
Imports System.Web.Services
Imports System.Data
Imports System.Data.SqlClient
Imports System.Collections

<WebService( Namespace:="http://yourdomain.com/webservices" )> _
Public Class ProductsService : Inherits WebService

<WebMethod()> 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
  conNorthwind.Close()
  Return colCategories.ToArray( GetType( System.String ) )
End Function

<WebMethod()> 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

The C# version of this code can be found on the CD-ROM.

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 service. So, to identify them, you have to be creative. This section explores one approach to securing an XML Web service.

NOTE

There are a number of security specifications currently in the works that will provide standard ways to pass user credentials and encrypt Web service transactions. For example, the WS-Security specification adds a <Security> element to the SOAP header so that there is a standard way to pass security credentials when accessing a Web service. To learn more about WS-Security and related specifications, visit msdn.microsoft.com/WebServices.

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.

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

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.

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.

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 )

The C# version of this code can be found on the CD-ROM.

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  NOT NULL ,
  session_expiration datetime NOT NULL ,
  session_userID int NOT NULL ,
  session_username varchar(20) NOT NULL ,
  session_role int NOT NULL )

The C# version of this code can be found on the CD-ROM.

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
[View full width]
<WebMethod()> Public Function Login( username As String, password As String ) As ServiceTicket

  Dim conMyData As SqlConnection
  Dim cmdCheckPassword As SqlCommand
  Dim parmWork As SqlParameter
  Dim intUserID As Integer
  Dim intRole As Integer
  Dim objServiceTicket As ServiceTicket
  Dim 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

  ' 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

  ' 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()

  ' 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 C# version of this code can be found on the CD-ROM.

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 PROCEDURE statement for the CheckPassword procedure is contained in Listing 23.12.

Listing 23.12 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

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 C# version of this code can be found on the CD-ROM.

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.

The stored procedure also generates the expiration time for the session key. The DataAdd() function returns a time that is 30 minutes in the future.

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.

Listing 23.13 The ServiceTicket Class
Public Class ServiceTicket
  Public IsAuthenticated As Boolean
  Public SessionKey As String
  Public Expiration As DateTime
End Class

The C# version of this code can be found on the CD-ROM.

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

The C# version of this code can be found on the CD-ROM.

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
<WebMethod(), SoapHeader( "AuthenticationHeader" )> _
Public Function GetLuckyNumber As Integer
  If Authenticate( AuthenticationHeader ) Then
    Return 7
  End If
End Function

The C# version of this code can be found on the CD-ROM.

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

  ' 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

The C# version of this code can be found on the CD-ROM.

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
  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 C# version of this code can be found on the CD-ROM.

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
[View full width]
<%@ WebService Class="SecureService" debug="True"%>

Imports System
Imports System.Web.Services
Imports System.Web.Services.Protocols
Imports System.Data
Imports System.Data.SqlClient

<WebService( Namespace:="http://yourdomain.com/webservices" )> _
Public Class SecureService : Inherits WebService

Public AuthenticationHeader As AuthHeader

<WebMethod()> Public Function Login( username As String, password As String ) As ServiceTicket

  Dim conMyData As SqlConnection
  Dim cmdCheckPassword As SqlCommand
  Dim parmWork As SqlParameter
  Dim intUserID As Integer
  Dim intRole As Integer
  Dim objServiceTicket As ServiceTicket
  Dim 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

  ' 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

  ' 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()

  ' 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

<WebMethod(), SoapHeader( "AuthenticationHeader" )> _
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

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 AuthHeader:Inherits SoapHeader
  Public SessionKey As String
End Class

Public Class ServiceTicket
  Public IsAuthenticated As Boolean
  Public SessionKey As String
  Public Expiration As DateTime
End Class

The C# version of this code can be found on the CD-ROM.

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.

Listing 23.19 TestSecureService.aspx
<%@ Import Namespace="Services" %>

<Script runat="Server">

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

</Script>

<html>
<head><title>TestSecureService.aspx</title></head>
<body>

<asp:Label
  id="lblLuckyNumber"
  EnableViewState="False"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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:

objAuthHeader = New AuthHeader
objAuthHeader.SessionKey = objServiceTicket.SessionKey
objSecureService.AuthHeaderValue = objAuthHeader
lblLuckyNumber.Text = objSecureService.GetLuckyNumber()

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.

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 the operations that can be performed with messages.

  • Bindings— Associates the operations listed in PortTypes with a particular message format and protocol.

  • Services— Associates a binding with a particular location (URL).

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
<?xml version="1.0"?>
<definitions

  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/">

  <types/>

  <message name="MessageNameHttpGetIn" />
  <message name="MessageNameHttpGetOut" />

  <portType name="PortTypeNameHttpGet">
    <operation name="OperationName">
      <input message="s0:MessageNameHttpGetIn"/>
      <output message="s0:MessageNameHttpGetOut"/>
    </operation>
  </portType>

  <binding name="BindingNameHttpGet"
    type="s0:PortTypeNameHttpGet">
    <http:binding verb="GET"/>
    <operation name="OperationName">
      <http:operation location="Relative Url"/>
      <input>
        <http:urlEncoded/>
      </input>
      <output>
         <tm:text>
          <tm:match
              name='PropertyName'
              pattern='pattern' />
         </tm:text>
      </output>
    </operation>
  </binding>
  <service name="ServiceName">
    <port
      name="PortTypeNameHttpGet"
      binding="s0:BindingNameHttpGet">
      <http:address location="Url" />
    </port>
  </service>
</definitions>

The C# version of this code can be found on the CD-ROM.

The file in Listing 23.20 contains a template for a Web service that has a single method with no parameters. When you call the method, any text matching the regular expression pattern described by the <match> element can be retrieved.

Keep in mind that this is a simple WSDL template. You can create HTML pattern matching services that match multiple elements in a page and also create services with multiple input and output parameters.

Specifying the Regular Expression Pattern

The real work of pattern matching is performed in the <match> element in Listing 23.20. This element has the following attributes:

  • Capture— An integer that represents the index of a match within a grouping

  • Group— An integer that represents the index of a particular capturing group

  • IgnoreCase— A Boolean value that specifies whether a case-sensitive match is performed

  • Matches— An attribute that returns a MimeTextMatchCollection, which represents all the regular expression matches

  • Name— A string that represents the name of the property that exposes the matches

  • Pattern— A string that represents a regular expression pattern

  • Repeats— An integer that represents the number of matches to perform

  • Type— A string that is used when a match contains submatches

NOTE

The <match> element is represented by the MimeTextMatch class in the .NET Framework.

Creating a Simple HTML Pattern Matching Service

Now you can start with a simple HTML pattern matching service called the TitlesService. This service will enable you to retrieve a list of book titles and prices from an HTML page.

A sample of the target page exposed by the Web service is displayed in Figure 23.5. This page is included on the CD-ROM with the name Titles.aspx. The Titles.aspx page displays different books from the Titles table, depending on the value of a query string variable named type.

Figure 23.5. Target page for HTML pattern matching.

The first step in creating the TitlesService is creating the necessary WSDL document. To do so, you can use the WSDL document in Listing 23.21.

Listing 23.21 Titles.Wsdl
<?xml version="1.0"?>
<definitions

  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/">

  <types/>

  <message name="GetTitlesHttpGetIn" />
  <message name="GetTitlesHttpGetOut" />

  <portType name="BookTitlesHttpGet">
    <operation name="GetTitles">
      <input message="s0:GetTitlesHttpGetIn"/>
      <output message="s0:GetTitlesHttpGetOut"/>
    </operation>
  </portType>

  <binding name="BookTitlesHttpGet"
    type="s0:BookTitlesHttpGet">
    <http:binding verb="GET"/>
    <operation name="GetTitles">
      <http:operation location="/Titles.aspx?type=business"/>
      <input>
        <http:urlEncoded/>
      </input>
      <output>
         <tm:text>
          <tm:match
              name='Titles'
              pattern='&lt;li&gt;(.*?)- \$'
              ignoreCase='true'
              repeats='100' />
          <tm:match
              name='Prices'
              pattern='\$([\d|\.]+)'
              ignoreCase='true'
              repeats='100' />
         </tm:text>
      </output>
    </operation>
  </binding>
  <service name="TitlesService">
    <port
      name="BookTitlesHttpGet"
      binding="s0:BookTitlesHttpGet">
      <http:address location="http://localhost" />
    </port>
  </service>
</definitions>

The C# version of this code can be found on the CD-ROM.

The WSDL file in Listing 23.21 documents the public interface of a Web service named TitlesService that has one method named GetTitles. It contains two match elements: one for book titles and one for book prices.

NOTE

The WSDL file in Listing 23.21 describes an XML Web service located at http://localhost/. Normally, you would specify the actual domain of the Web service.

The regular expression for book titles looks like this:

&lt;li&gt;(.*?)- \$

The regular expression for book prices looks like this:

\$([\d|\.]+)

The Web service works with an HTML page located on the local host with the path /Titles.aspx?type=business. It always retrieves a page that displays a list of business titles.

You can build a proxy class from the WSDL file in Listing 23.21 with the following statements executed from a command prompt:

wsdl /l:vb Titles.wsdl
vbc /t:library /r:System.dll,System.Web.Services.dll,System.Xml.dll TitlesService.vb

These statements generate a new proxy class named TitlesService.dll. Before you can use this proxy class, you need to copy it to your application /bin directory.

You then can use the page in Listing 23.22 to test the TitlesService Web service. The page displays the book titles and prices in a slightly different format than the format on the Titles.aspx page (compare Figure 23.5 with Figure 23.6).

Listing 23.22 TestTitlesService.aspx
<Script runat="Server">

Sub Page_Load
  Dim objTitlesService As TitlesService
  Dim objMatches As GetTitlesMatches
  Dim intCounter As Integer

  objTitlesService = New TitlesService
  objTitlesService.Timeout = 10000
  objMatches = objTitlesService.GetTitles()

  For intCounter = 0 To objMatches.Titles.Length - 1
    lblTitles.Text &= "<p>TITLE: " & objMatches.Titles( intCounter )
    lblTitles.Text &= "<br>PRICE: " & objMatches.Prices( intCounter )
  Next
End Sub

</Script>

<html>
<head><title>TestTitlesService.aspx</title></head>
<body>

<h2>Scraped Titles</h2>

<asp:Label
  id="lblTitles"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 23.6. Results of screen scraping.

In the Page_Load subroutine in Listing 23.22, an instance of the TitlesService proxy class is created. Next, the GetTitles() method is called to retrieve an array of titles and prices. Finally, all the titles and prices are displayed within a For...Next loop.

Using Input Parameters with HTML Pattern Matching

One limitation of the HTML pattern matching service that you created earlier is that it always passes the same query string variable (type=business). Therefore, it always retrieves a list of business titles. However, you might want to pass different input parameters and retrieve different types of books. To do so, you need to modify the WSDL document. A modified WSDL document is contained in Listing 23.23.

Listing 23.23 TitlesInput.Wsdl
<?xml version="1.0"?>
<definitions

  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/">

  <types/>

  <message name="GetTitlesInputHttpGetIn">
    <part name="type" type="s:string" />
  </message>
  <message name="GetTitlesInputHttpGetOut" />

  <portType name="BookTitlesHttpGet">
    <operation name="GetTitlesInput">
      <input message="s0:GetTitlesInputHttpGetIn"/>
      <output message="s0:GetTitlesInputHttpGetOut"/>
    </operation>
  </portType>

  <binding name="BookTitlesHttpGet"
    type="s0:BookTitlesHttpGet">
    <http:binding verb="GET"/>
    <operation name="GetTitlesInput">
      <http:operation location="/Titles.aspx"/>
      <input>
        <http:urlEncoded/>
      </input>
      <output>
         <tm:text>
          <tm:match
              name='Titles'
              pattern='&lt;li&gt;(.*?)- \$'
              ignoreCase='true'
              repeats='100' />
          <tm:match
              name='Prices'
              pattern='\$([\d|\.]+)'
              ignoreCase='true'
              repeats='100' />
         </tm:text>
      </output>
    </operation>
  </binding>
  <service name="TitlesServiceInput">
    <port
      name="BookTitlesHttpGet"
      binding="s0:BookTitlesHttpGet">
      <http:address location="http://localhost/webservices" />
    </port>
  </service>
</definitions>

The C# version of this code can be found on the CD-ROM.

Notice that the first message in the file in Listing 23.23 contains a <part> element, which represents an input parameter named type. The type parameter is passed to Titles.aspx when the Web service is called.

To create the proxy class for the Web service, you need to execute the following statements:

wsdl /l:vb TitlesInput.wsdl
vbc /t:library /r:System.dll,System.Web.Services.dll,System.Xml.dll TitlesServiceInput.vb

Finally, you need to copy the compiled proxy class, TitleServiceInput.dll, to the application /bin directory.

You can use the page in Listing 23.24 to test the new Web service.

Listing 23.24 TestTitlesServiceInput.aspx
<Script runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  Dim objTitlesService As TitlesServiceInput
  Dim objMatches As GetTitlesInputMatches
  Dim intCounter As Integer

  objTitlesService = New TitlesServiceInput
  objTitlesService.Timeout = 10000
  objMatches = objTitlesService.GetTitlesInput( txtBookType.Text )

  For intCounter = 0 To objMatches.Titles.Length - 1
    lblTitles.Text &= "<p>TITLE: " & objMatches.Titles( intCounter )
    lblTitles.Text &= "<br>PRICE: " & objMatches.Prices( intCounter )
  Next
End Sub

</Script>

<html>
<head><title>TestTitlesServiceInput.aspx</title></head>
<body>

<h2>Scraped Titles</h2>

<form runat="Server">

<asp:TextBox
  id="txtBookType"
  Runat="Server" />

<asp:Button
  Text="Go!"
  OnClick="Button_Click"
  Runat="Server" />
<p>

<asp:Label
  id="lblTitles"
  EnableViewState="False"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

The page in Listing 23.24 contains a form that enables you to enter a book type. When you enter a type and click Go!, only books of that type are retrieved through the Web service and displayed (see Figure 23.7).

Figure 23.7. Using an input parameter with HTML pattern matching.

Building the Six Degrees Web Service

Before leaving the subject of building HTML pattern-matching Web services, I want to provide you with a more complicated example. In this section, you'll build the Six Degrees Web service.

The Six Degrees Web service enables you to enter any Web address into an HTML form. After you enter an address, the Web service retrieves all the links on the page you indicated. The Web service chooses one of the links and follows it to a new page. This process continues until the Web service cannot find a new link or makes 25 hops.

Figure 23.8 illustrates what might happen when you enter the address for the home page of the Microsoft Web site.

Figure 23.8. Using the Six Degrees Web Service.

The WSDL document for the Six Degrees Web service is contained in Listing 23.25.

Listing 23.25 SixDegrees.Wsdl
<?xml version="1.0"?>
<definitions
  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/">

  <types/>

  <message name="GetLinksHttpGetIn" />
  <message name="GetLinksHttpGetOut" />
  <portType name="SixDegreesHttpGet">
    <operation name="GetLinks">
      <input message="s0:GetLinksHttpGetIn"/>
      <output message="s0:GetLinksHttpGetOut"/>
    </operation>
  </portType>
  <binding name="SixDegreesHttpGet" type="s0:SixDegreesHttpGet">
    <http:binding verb="GET"/>
    <operation name="GetLinks">
      <http:operation location="/"/>
      <input>
        <http:urlEncoded/>
      </input>
      <output>
         <tm:text>
          <tm:match name='Links'
            pattern='"(http://.*?)["|/]'
            repeats="10" />
         </tm:text>
      </output>
    </operation>
  </binding>
  <service name="SixDegrees">
    <port name="SixDegreesHttpGet" binding="s0:SixDegreesHttpGet">
      <http:address location="http://localhost" />
    </port>
  </service>
</definitions>

The C# version of this code can be found on the CD-ROM.

The WSDL document in Listing 23.25 uses the regular expression pattern "(http://.*?)["|/] to match an Internet address. (Notice that this regular expression matches only the home page of a Web site.)

You can use the following statements to build a proxy class from the WSDL file:

wsdl.exe /l:vb SixDegrees.wsdl
vbc /t:library /r:System.dll,System.Web.Services.dll,System.xml.dll SixDegrees.vb

After you execute these statements, you need to copy the compiled proxy class, SixDegrees.dll, to your application's /bin directory.

The ASP.NET page in Listing 23.26 uses the Six Degrees Web service to follow Web site links.

Listing 23.26 DisplaySixDegrees.aspx
<Script Runat="Server">

Dim objSixDegrees As New SixDegrees
Dim objMatches As New GetLinksMatches
Dim objRandom As New Random
Dim colHistory As New ArrayList

Sub Button_Click( s As Object, e As EventArgs )
  objSixDegrees.UserAgent = "SixDegrees"
  objSixDegrees.Timeout = 5000
  objSixDegrees.AllowAutoRedirect = True
  GetNextLink( txtUrl.Text )
  dgrdHistory.DataSource = colHistory
  dgrdHistory.DataBind()
End Sub

Sub GetNextLink( strCurrentLink )
  Dim strNextLink As String

  colHistory.Add( strCurrentLink )
  objSixDegrees.Url = strCurrentLink

  Try
    objMatches = objSixDegrees.GetLinks()
  Catch ex As Exception
    colHistory.Add( ex.Message )
  End Try

  If Not ( objMatches.Links Is Nothing ) And colHistory.Count < 25 Then
    If objRandom.Next( 2 ) = 1 Then
      Array.Reverse( objMatches.Links )
    End If

    For each strNextLink in objMatches.Links
      If strNextLink.ToLower() <> strCurrentLink.ToLower() Then
        GetNextLink( strNextLink )
        Exit For
      End If
    Next
  End If
End Sub

</Script>

<html>
<head><title>DisplaySixDegrees.aspx</title></head>
<body>

<h2>Six Degrees of Separation</h2>

<form runat="Server">

<asp:TextBox
  id="txtUrl"
  Columns="40"
  Text="http://"
  Runat="Server" />

<asp:Button
  Text="Go!"
  OnClick="Button_Click"
  Runat="Server"/>

</form>
<p>
<asp:DataList
  id="dgrdHistory"
  CellPadding="6"
  Gridlines="Both"
  AlternatingItemStyle-BackColor="lightblue"
  Runat="Server">
  <ItemTemplate>
    <%# Container.ItemIndex %> -
    <asp:HyperLink
      Text='<%# Container.DataItem %>'
      NavigateUrl='<%# Container.DataItem %>'
      Runat="Server" />
  </ItemTemplate>
</asp:DataList>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

The page in Listing 23.26 recursively calls a subroutine named GetNextLink that uses the Six Degrees Web service to get a list of all the links in a Web page. The subroutine chooses one of the links and calls itself, once again passing the link to the new Web site. The list of links retrieved by leaping from Web site to Web site is displayed in a DataList control.


Summary

This chapter examined three advanced XML Web service topics. The first section discussed the WebService behavior. You learned how to use the WebService behavior to directly access a Web service through Internet Explorer and how the behavior can be used to dynamically refresh content in a page without reloading the whole page.

Next, you learned one method of securing an XML Web service. You built a Web service that uses a custom SOAP header for authentication information. The Web service requires the presence of a session key in the authentication header to access its methods.

Finally, you learned how to build an HTML pattern-matching Web service and use HTML pattern matching to expose any Web site (or text document) as an XML Web service.


Part VII: Leveraging the .NET Framework

IN THIS PART

CHAPTER 24 Working with Collections and Strings

CHAPTER 25 Working with the File System

CHAPTER 26 Sending Email and Accessing the Network

CHAPTER 27 Creating Graphics On-the-Fly with GDI+


Chapter 24. Working with Collections and Strings

IN THIS CHAPTER

  • Working with Collections

  • Working with Strings

  • Working with Regular Expressions

In this chapter, you learn how to use collections, manipulate strings, and work with regular expressions. The classes discussed in this chapter are not specific to the ASP.NET framework; they are part of the overall programming infrastructure provided by the .NET framework.

The first topic is collections. You can use collections as a general mechanism for storing items. You learn how to use them to store lists of items and lists of key and value pairs.

You also learn how to work with strings in your application. You examine methods of formatting strings, manipulating strings, and using advanced methods for working with large strings.

Finally, I provide an overview of regular expressions. You can use regular expressions to perform advanced pattern-matching operations in your ASP.NET pages.


Working with Collections

You've been using collections throughout this book to store objects. Many of the standard ASP.NET controls, such as DropDownList and DataGrid, use collections internally to store the items they display. In the following sections, you look at the properties and methods of the different types of collections in detail.

Most of the collection classes inhabit the System.Collections namespace, which includes several types of collections. Some of these collections are generally useful, whereas some were designed for more specialized functions.

I discuss three types of collections in these sections:

  • ArrayLists

  • HashTables

  • SortedLists

Using the ArrayList Collection

An ArrayList enables you to store lists of objects (any type of object, even other ArrayLists). Using an ArrayList is similar to using an array, except that an ArrayList does not have any fixed upper bound. An ArrayList also has a number of useful properties and functions that it does not share with an array.

You can create a new ArrayList by using the following statement:

colArrayList = New ArrayList()

This statement creates an ArrayList that can initially store 16 objects. If you attempt to store more than 16 objects, the capacity of the ArrayList is automatically doubled. The capacity keeps doubling to handle the number of elements added.

If you know the number of elements that an ArrayList will store before you create the ArrayList, you can get the (ever so slight) performance benefit by declaring the ArrayList with a particular starting capacity. For example, if you know that an ArrayList will be used to store 100 items, you can declare it like this:

colArrayList = New ArrayList( 100 )

This ArrayList is declared to handle 100 elements. If you add a 101st element, the capacity of the ArrayList automatically doubles to 200.

You can determine the number of elements in an ArrayList at any time by using the Count property. You can determine the current capacity of an ArrayList at any time by using the Capacity property. For example, the following statements display the numbers 0 and 16:

Dim colArrayList As ArrayList

colArrayList = New ArrayList
Response.Write( colArrayList.Count & "," & colArrayList.Capacity )

The numbers 0 and 16 are displayed because they are the initial values of the Count and Capacity properties.

If you are working with a really huge ArrayList, you can trim its capacity back to its current size by using the TrimToSize method as follows:

colArrayList.TrimToSize()

The TrimToSize method changes the capacity of the ArrayList to match its current number of items.

Finally, if you want to clear all the objects out of an ArrayList and start over again, you can use the Clear method like this:

colArrayList.Clear()

The Clear method gets rid of all the current items in an ArrayList. However, it does not reset the capacity. To clear all the items and reset the capacity, you must call both the Clear and TrimToSize methods.

Adding Items to an ArrayList

You can add individual items to an ArrayList by using the Add method. For example, the following statement adds the string "Hello World!" to an ArrayList:

colArrayList.Add( "Hello World!" )

Remember that an ArrayList can contain any type of object. So, you can add integers, TextBox controls, or any weird object that you discover in the .NET framework.

When you add an object to an ArrayList, it's added to the end as the last element. You can return the index of an item that you add to an ArrayList like this:

intItemIndex = colArrayList.Add( "Hello World!" )

If this were the first item added to the ArrayList, intItemIndex would return 0; if it were the second, intItemIndex would return 1; and so on.

If you need to add multiple objects to an ArrayList at once, you can use the AddRange method. For example, the following statement adds all the items in an ArrayList named colAnotherArrayList to an ArrayList named colArrayList:

colArrayList.AddRange( colAnotherArrayList )

You also can add multiple duplicate items to an ArrayList by using the Repeat method:

colArrayList = ArrayList.Repeat( "Hello World!", 100 )

The Repeat method returns an ArrayList that contains a value repeated a certain number of times. In this case, the ArrayList would contain 100 items with the value "Hello World!".

If you need to insert an object into an ArrayList at a particular position, you can use the Insert method. For example, the following statement inserts the string "Hello World!" at the fourth index position:

colArrayList.Insert( 4, "Hello World!" )

The InsertRange method also corresponds to the AddRange method. You can use the InsertRange method, as shown here, to add multiple objects starting at a certain position:

colArrayList.InsertRange( 4, colAnotherArrayList )

This statement adds the contents of an ArrayList named colAnotherArrayList to an ArrayList named colArrayList starting at the fourth index position.

NOTE

You can use the AddRange and InsertRange methods to add other types of objects to an ArrayList as well. You can use these methods with any object that supports the ICollection interface.

You might be disappointed that you can add only single items to an ArrayList. The Add method accepts only one argument. However, this disappointment is not warranted. Remember that an ArrayList can contain any type of object. If you want to add multiple items to an ArrayList at a time, use a class with the ArrayList.

The page in Listing 24.1, for example, contains a class named Products. This class has both ProductName and Price properties.

Listing 24.1 MultiArrayList.aspx
<Script Runat="Server">

Public Class Product
  Public ProductName As String
  Public Price As Decimal

  Public Sub New( ProductName As String, Price As Decimal )
    MyBase.New()
    Me.ProductName = ProductName
    Me.Price = Price
  End Sub
End Class

Sub Page_Load
  Dim colArrayList As ArrayList

  colArrayList = New ArrayList
  colArrayList.Add( New Product( "eggs", 2.10 ) )
  colArrayList.Add( New Product( "milk", 3.24 ) )

  dlstDataList.DataSource = colArrayList
  dlstDataList.DataBind()
End Sub
</Script>

<html>
<head><title>MultiArrayList.aspx</title></head>
<body>

<asp:DataList
  ID="dlstDataList"
  Runat="Server">
<itemTemplate>
  <%# Container.DataItem.ProductName %>
  - <%# String.Format( "{0:c}", Container.DataItem.Price ) %>
</itemTemplate>
</asp:DataList>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

In the Page_Load subroutine in Listing 24.1, an ArrayList named colArrayList is declared. Two instances of the Product class are added to the ArrayList. Finally, the ArrayList is bound to a DataList control, and the two products are displayed.

Removing Items from an ArrayList

The most straightforward method of removing an item from an ArrayList is to use the Remove method. For example, the following statement removes the item "Hello World!" from an ArrayList:

colArrayList.Remove( "Hello World!" )

If an ArrayList contains duplicate items, the first item is removed. Beware that the Remove method performs an exact comparison, so it is case sensitive. If you attempt to remove a nonexistent item, nothing horrible happens. The whole ArrayList is searched for the item, and the Remove method gives up without generating an error.

If you need to remove an ArrayList item with a certain index, you can use the RemoveAt method like this:

colArrayList.RemoveAt( 4 )

This statement removes the item with an index of 4. If the ArrayList doesn't contain an item at index position 4, an error is generated.

If you want to remove a range of items in an ArrayList, use the RemoveRange method. For example, the following statement removes three items (items 2 through 4), starting at index position 2:

colArrayList.RemoveRange( 2, 3 )

Finally, if you want to clear all the items from an ArrayList, use the Clear method like this:

colArrayList.Clear()

Remember to call the TrimToSize method after calling Clear if you want to reduce the capacity of the ArrayList to the minimum (16 items).

Iterating Through the Items in an ArrayList

If you need to loop through the contents of an ArrayList, displaying each item, you can use either a For...Each or For...Next loop.

The following For...Each loop, for example, displays all the elements in an ArrayList named colArrayList:

Dim strItem As String

For Each strItem in colArrayList
  Response.Write( strItem )
Next

You can do the same thing with a For...Next loop:

Dim intCounter As Integer

for intCounter = 0 To colArrayList.Count - 1
  Response.Write( colArrayList( intCounter ) )
Next

In both cases, you assume that the ArrayList contains strings. If the ArrayList contains items with different types of data or you are not sure about the type of data contained in the ArrayList, you can loop through it by using an Object variable like this:

Dim objItem As Object

For Each objItem in colArrayList
  Response.Write( objItem.ToString )
Next

An Object type can accept any type of value. So, regardless of whether an ArrayList contains integers, strings, classes, or whatever, you can assign the items to an Object type.

Sorting Items in an ArrayList

You can sort the items in an ArrayList by using the Sort method. For example, the page in Listing 24.2 sorts all the elements in an ArrayList named colShoppingList.

Listing 24.2 SortArrayList.aspx
<%
Dim colShoppingList As ArrayList
Dim strItem As String

colShoppingList = New ArrayList
colShoppingList.Add( "eggs" )
colShoppingList.Add( "milk" )
colShoppingList.Add( "beer" )

colShoppingList.Sort

For Each strItem in colShoppingList
  Response.Write( "<li>" & strItem )
Next
%>

The C# version of this code can be found on the CD-ROM.

In Listing 24.2, three items are added to the ArrayList: eggs, milk, and beer. When the Sort method is called, the items are sorted in alphabetical order: beer, eggs, milk.

You reverse the order in which items in an ArrayList are sorted by calling the Reverse method. This method reverses the order of all the elements in an ArrayList. For example, the page in Listing 24.3 sorts a list of random numbers and reverses the results.

Listing 24.3 ReverseArrayList.aspx
<%
Dim colArrayList As ArrayList
Dim objRanNumber As Random
Dim intCounter As Integer
Dim intItem As Integer

colArrayList = New ArrayList
objRanNumber = New Random
For intCounter = 1 to 10
  colArrayList.Add( objRanNumber.Next( 100 ) )
Next

colArrayList.Sort
colArrayList.Reverse

For Each intItem in colArrayList
  Response.Write( "<li>" & intItem.ToString() )
Next
%>

The C# version of this code can be found on the CD-ROM.

Ten random numbers are assigned to the ArrayList in this listing. Next, the ArrayList is sorted, and the elements are reversed by calling the Reverse method.

Searching for Items in an ArrayList

You could find an element in an ArrayList by looping through all its elements until a match is found. However, this is a notoriously slow search algorithm. If an ArrayList contains thousands of elements, then, potentially, thousands of elements must be checked before a match is found.

You can perform this type of slow, linear search on an ArrayList by using the Contains method. For example, the page in Listing 24.4 uses the Contains method to find the number 53 in an ArrayList.

Listing 24.4 ContainsArrayList.aspx
<%
Dim colArrayList As ArrayList
Dim intCounter As Integer
Dim objRanNumber As Random

colArrayList = New ArrayList
objRanNumber = New Random
For intCounter = 1 to 1000
  colArrayList.Add( objRanNumber.Next( 1000 ) )
Next

If colArrayList.Contains( 53 ) Then
  Response.Write( "Found 53!" )
Else
  Response.Write( "Couldn't Find 53!" )
End If
%>

The C# version of this code can be found on the CD-ROM.

In Listing 24.4, 1,000 random numbers are added to the ArrayList. The Contains method determines whether the number 53 is contained in it.

Using the Contains method can be slow when you're working with large ArrayLists. Fortunately, an ArrayList supports binary searches.

If you want to perform a binary search, the ArrayList must be sorted. A binary search takes advantage of the fact that the elements in an array occur in a certain order so that a match can be found more quickly.

CAUTION

A binary search can return incorrect results if an ArrayList is not sorted.

The page in Listing 24.5 illustrates how you can use the BinarySearch method.

Listing 24.5 BinarySearchArrayList.aspx
<%
Dim colArrayList As ArrayList
Dim objranNumber As Random
Dim intCounter As Integer
Dim intItemIndex As Integer

colArrayList = New ArrayList
objRanNumber = New Random
For intCounter = 1 to 1000
  colArrayList.Add( objRanNumber.Next( 1000 ) )
Next

colArrayList.Sort()

intItemIndex = colArrayList.BinarySearch( 53 )
If intItemIndex > 0 Then
  Response.Write( "Found 53!" )
Else
  Response.Write( "Couldn't Find 53!" )
End If
%>

The C# version of this code can be found on the CD-ROM.

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.

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.

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:

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

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( "<li>" & objItem.Key & "=" & objItem.Value )
Next

This For...Each loop displays the key and value for each item in the HashTable named colHashTable. (Notice that each item in a HashTable is actually an instance of the DictionaryEntry class.)

You also can use a For...Each loop to iterate through the Keys collection of a HashTable like this:

Dim strItem As String

For Each strItem in colHashTable.Keys
  Response.Write( "<li>" & strItem )
Next

The Keys collection represents all the keys in a HashTable. I'm assuming that all the keys are strings. If the Keys collection contains items of another type, such as integers, you need to declare a variable with the appropriate type.

You also can loop through all the values in a HashTable by using the Values collection:

Dim strItem As String

For Each strItem in colHashTable.Values
  Response.Write( "<li>" & strItem )
Next

Again, I'm assuming that all the values are strings. If the items in the Values collection are a different type, you need to declare a variable with the appropriate type.

Searching for Items in a HashTable

The HashTable collection was designed to enable you to quickly look up a key and retrieve the key's associated value.

The simplest way to retrieve a certain value from a HashTable that has a certain key is to use the following:

Response.Write( colHashTable( "WA" ) )

This statement displays the value for the "WA" key. (The comparison is case sensitive.)

If you simply want to check whether a certain key exists in a HashTable, you can use the Contains method like this:

If colHashTable.Contains( "WA" ) Then
  Response.Write( "Found WA!" )
Else
  Response.Write( "Couldn't Find WA!" )
End If

The Contains method returns True if the key exists and False otherwise. (Again, the Contains method is case sensitive.)

NOTE

If you want to perform comparisons that are not case sensitive with a HashTable, you need to declare it like this:

colHashTable = New HashTable( _
  New CaseInsensitiveHashCodeProvider, _
  New CaseInsensitiveComparer )

This statement initializes the HashTable with a hash code provider that enables hash code comparisons that are not case sensitive. It also uses the CaseInsensitiveComparer to compare values.

HashTables are very good at finding keys. They are slower at finding values, however. You can find a particular value by using the ContainsValue method:

If colHashTable.ContainsValue( "Washington" ) Then
  Response.Write( "Found Washington!" )
Else
  Response.Write( "Couldn't Find Washington!" )
End If

The ContainsValue method must perform a linear search on the elements of the HashTable. So, the more elements in a HashTable, the slower the search.

Using the SortedList Collection

HashTables are great for quickly looking up a key and finding an associated value. However, they are not useful for displaying items in a certain order. You have no control over the order in which a HashTable internally represents its items.

A SortedList collection, on the other hand, enables you to control exactly how items are sorted. You can create a new instance of the SortedList collection like this:

colSortedList = New SortedList()

You also have the option of declaring a SortedList with an initial capacity like this:

colSortedList = New SortedList( 100 )

This statement creates a new SortedList with an initial capacity of 100 elements. If you add more than 100 elements, the SortedList automatically doubles in size to handle the new elements.

Adding Items to a SortedList

Like a HashTable, a SortedList contains key and value pairs. You can add a new key and value pair to a SortedList by using the Add method like this:

colSortedList.Add( "Bob", "(415) 455-9090" )

This statement adds a new key, "Bob", and a new value, "(415) 455-9090", to a SortedList named colSortedList.

Alternatively, you can add a new item to a SortedList like this:

colSortedList( "Bob" ) = "(415) 455-9090"

A SortedList cannot contain duplicate keys. So, if you try to execute this statement twice, an error is generated. There is nothing wrong, however, with adding duplicate values to a SortedList.

Whenever you add new items to a SortedList, they are automatically added in the right order. For example, imagine that you add the following three items:

colSortedList.Add( "eggs", 2.39 )
colSortedList.Add( "beer", 8.16  )
colSortedList.Add( "milk", 4.87 )

The SortedList automatically rearranges the items in the following order: beer, eggs, milk. Notice that the items are sorted in order of the keys, and not the values.

Removing Items from a SortedList

You can remove an item from a SortedList by using its key or index position to refer to the item. For example, the following statement removes an item with the key "eggs":

colSortedList.Remove( "eggs" )

You also can remove an item according to its index position in the list by using the RemoveAt method like this:

colSortedList.RemoveAt( 1 )

Remember, when you're using the RemoveAt method, the items in a SortedList are automatically ordered. So, an item's index position has nothing to do with the order in which you added the item.

If you want to clear away all the items in a SortedList, you can use the Clear method like this:

colSortedList.Clear()

This statement removes all the items from a SortedList named colSortedList.

Clearing a SortedList does not automatically reduce the capacity of the list. To reduce the capacity of a SortedList to its initial capacity (16 items), you need to call the TrimToSize method after calling the Clear method like this:

colSortedList.Clear()
colSortedList.TrimToSize()
Iterating Through the Items in a SortedList

Several methods can be used to loop through the contents of a SortedList. For example, you can display all the elements by using a For...Each loop like this:

Dim objItem As DictionaryEntry

For each objItem in colSortedList
  Response.Write( "<li>" & objItem.Key & "=" & objItem.Value )
Next

Notice that each entry in a SortedList is actually an instance of the DictionaryEntry class. The Key and Value properties are used to display the key and value for each item in the SortedList.

You also can use the Keys property to display each key in a SortedList like this:

Dim strItem As String

For each strItem in colSortedList.Keys
  Response.Write( "<li>" & strItem )
Next

A Value property also enables you to display all the values in a SortedList:

Dim strItem As String

For each strItem in colSortedList.Values
  Response.Write( "<li>" & strItem )
Next

I'm assuming here that the SortedList contains strings. You need to use the correct data type for the items contained in the SortedList (for example, Integer, Decimal, or Object).

Finding Items in a SortedList

The most straightforward way of displaying the value of an item with a certain key in a SortedList is to use a statement like this:

Response.Write( colSortedList( "eggs" ) )

If the SortedList contains a list of grocery store items and their prices, this statement displays the price of eggs.

If you want to check whether a SortedList contains a particular key, you can use the Contains method like this:

If colSortedList.Contains( "eggs" ) Then
  Response.Write( "We sell eggs!" )
Else
  Response.Write( "Nope, no eggs!" )
End If

The Contains method returns True if the key exists and False otherwise.

NOTE

By default, a SortedList is case sensitive. So, the key "eggs" is treated differently than the key "eGGs". If you want to perform comparisons that are not case sensitive, you need to initialize the SortedList collection like this:

colSortedList = New SortedList( New CaseInsensitiveComparer )

This statement initializes the SortedList with CaseInsensitiveComparer.

The Contains method uses a fast binary search to find a matching key. The process of finding a matching value is slower because a linear search must be performed. You can check whether a SortedList contains a particular value by using the ContainsValue method as follows:

If colSortedList.ContainsValue( 2.39 ) Then
  Response.Write( "Found It!" )
Else
  Response.Write( "Couldn't Find It!" )
End If

Here, the ContainsValue method finds an item with the value 2.39. If an item is found, the ContainsValue method returns True; otherwise, it returns False.


Working with Strings

In the following sections, you examine in detail the methods and properties that the .NET framework provides for working with strings.

First, you look at various methods for formatting strings. You learn how to format currencies, dates, and times and control exactly how a string is displayed.

Next, you examine the methods and properties of the String class itself for manipulating the contents of your strings.

Finally, you learn about the StringBuilder class and how to exploit this class to work efficiently with large strings.

Formatting Strings

The String class includes a Format method that you can use to format one or more arguments. For example, in the following code, the Format method formats the output of a string variable named strUsername:

Dim strUsername As String

strUsername = "aspguy"
Response.Write( String.Format( "Your username is {0}", strUsername ) )

When these statements are executed, the following string is output:

Your username is aspguy

Two parameters are passed to the Format method. The first parameter represents a format string. In this case, the format string is "Your username is {0}". The {0} characters represent a placeholder. The second parameter passed to the Format method is substituted for this placeholder.

You can list up to three arguments with the Format method, as shown here:

Dim strSentence, strPerson, strPlace, strThing As String

strPerson = "Bill"
strPlace = "the store"
strThing = "some eggs"

strSentence = String.Format( "{0} went to {1} and bought {2}", strPerson, strPlace, strThing )
Response.Write( strSentence )

When the preceding statements are executed, the following sentence is output:

Bill went to the store and bought some eggs

You also can specify an optional minimum width used when displaying the output of each placeholder. You do so by adding a comma, followed by a number specifying a width, like this:

Response.Write( String.Format( "<pre>{0,10}</pre>", "eggs" ) )

This format string displays the argument with a minimum of 10 spaces. The HTML <pre> tag is included here because HTML normally ignores spaces. The <pre> tag forces spaces to be interpreted literally.

By default, when you specify a minimum width, the argument is right-aligned. If you prefer to left-align the output, use a negative value for the width like this:

Response.Write( String.Format( "<pre>{0,-10}</pre>", "eggs" ) )

This statement pads the output with a minimum of 10 spaces and left-aligns the argument represented by the placeholder.

NOTE

If you need to pass more than three arguments to the Format method, you can pass an array of arguments as the second parameter.

Formatting Numbers

The .NET framework includes several format specifiers that you can use to format the output of numbers. You can use these specifiers with the Format method discussed in the preceding section or with the ToString method.

The following list describes the format specifiers used for formatting numbers:

  • D or d— Formats a number as a decimal number; for example, 3200

  • E or e— Formats a number as an exponential number using scientific notation; for example, 3.200100e+004

  • F or f— Formats a number as a fixed-point number; for example, 32001.00

  • G or g— Formats a number as a decimal number or as an exponential number (represents the number in the shortest possible way); for example, 32001

  • N or n— Formats a number using number formatting; for example, 32,001.00

  • H or h— Formats a number as a hexadecimal number; for example, 7d01

The page in Listing 24.6 demonstrates how the different specifiers work with the ToString method to format both an Integer and Decimal number.

Listing 24.6 FormatNumber.aspx
<%
Dim intNum As Integer
Dim decNum As Decimal

intNum = 32001
decNum = 32001.9099

' Decimal Format
Response.Write( intNum.ToString( "d" ) )
' => Displays 32001

' Exponential Format
Response.Write( intNum.ToString( "e" ) )
' => Displays 3.200100e+004

Response.Write( decNum.ToString( "e" ) )
' => Displays 3.200191e+004

' Fixed-Point Format
Response.Write( intNum.ToString( "f" ) )
' => Displays 32001.00

Response.Write( decNum.ToString( "f" ) )
' => Displays 32001.91

 ' General Format
Response.Write( intNum.ToString( "g" ) )
' => Displays 32001

Response.Write( decNum.ToString( "g" ) )
' => Displays 32001.9099

' Number Format
Response.Write( intNum.ToString( "n" ) )
' => Displays 32,001.00

Response.Write( decNum.ToString( "n" ) )
' => Displays 32,001.91

' HexaDecimal Format
Response.Write( intNum.ToString( "x" ) )
' => Displays 7d01
%>

The C# version of this code can be found on the CD-ROM.

You also can use any of these format specifiers with the Format method discussed in the preceding section. The following statement uses the number specifier:

Response.Write( String.Format( "Quantity: {0:n}", 12300.00 ) )

The n that follows the placeholder indicates that the number should be formatted using the number specifier. This statement outputs the following:

Quantity: 12,300.00

When displaying numbers, you also can indicate the number of digits to display by listing a number immediately after the format specifier. For example, the following statement displays an integer value with extra leading zeros:

Response.Write( 6.ToString( "d10" ) )
' => Displays 0000000006

When you append a number to any of the other number specifiers, the number represents the number of digits to display after the decimal point, as shown here:

Dim intNum As Integer

intNum = 32
Response.Write( intNum.ToString( "n10" ) )
' => Displays 32.0000000000

NOTE

If you need very precise control over the formatting of numbers, you can use picture format strings. To learn more details, see the .NET Framework Software Development Kit Documentation.

Formatting Currency

The .NET framework includes a special format specifier for formatting currency amounts. You can use either the C or c format specifier with the Format or ToString method to display a number as currency.

The following statements illustrate how to format a decimal number with the ToString method:

Dim decPrice As Decimal

decPrice = 1233.99
Response.Write( decPrice.ToString( "c" ) )

This Response.Write statement outputs the following string:

$1,233.99

You also can use the currency format specifier with the Format method like this:

Response.Write( String.Format( "Price: {0:c}", 2321.65 ) )

This statement outputs the following:

Price: $2,321.65

By default, two decimal places are displayed. If you want to display a different number of decimal places, you can append a number to the currency specifier like this:

Dim decPrice As Decimal

decPrice = 2321.65
Response.Write( decPrice.ToString( "c0" ) )
' => Displays $2,322

Response.Write( String.Format( "Price: {0:c5}", 2321.65 ) )
' => Displays $2,321.65000

The ToString method displays the price with no decimal places (notice that the price is automatically rounded up). The Format method displays the price with five decimal places.

Formatting Dates and Times

The .NET framework includes the following format specifiers for displaying dates and times:

  • d— Displays a short date; for example, 2/13/2001

  • D— Displays a long date; for example, Tuesday, February 13, 2001

  • f— Displays a long date and short time; for example, Tuesday, February 13, 2001 3:40 PM

  • F— Displays a long date and long time; for example, Tuesday, February 13, 2001 3:41:04 PM

  • g— Displays a short date and short time; for example, 2/13/2001 3:42 PM

  • G— Displays a short date and long time; for example, 2/13/2001 3:42:52 PM

  • M or m— Displays month and day; for example, February 13

  • R or r— Displays in RFC 1123 format; for example, Tue, 13 Feb 2001 15:44:52 GMT

  • s— Displays in ISO 8601 format; for example, 2001-02-13T15:45:55

  • t— Displays a short time; for example, 3:47 PM

  • T— Displays a long time; for example, 3:47:37 PM

  • u— Displays in ISO 8601 format using universal time; for example, 2001-02-13 15:48:41Z

  • U— Displays a date and time using universal time; for example, Tuesday, February 13, 2001 11:49:51 PM

  • Y or y— Displays a month and year; for example, February, 2001

The page in Listing 24.7 illustrates how you can use the date and time specifiers with the ToString method.

Listing 24.7 FormatDateTime.aspx
<%
Dim dtmDate As DateTime

dtmDate = DateTime.Now()

' Short Date
Response.Write( dtmDate.ToString( "d" ) )
' => Displays 2/13/2001

' Long Date
Response.Write( dtmDate.ToString( "D" ) )
' => Displays Tuesday, February 13, 2001

 ' Long Date and Short Time
Response.Write( dtmDate.ToString( "f" ) )
' => Displays Tuesday, February 13, 2001 3:40 PM

' Long Date and Long Time
Response.Write( dtmDate.ToString( "F" ) )
' => Displays Tuesday, February 13, 2001 3:41:04 PM

' Short Date and Short Time
Response.Write( dtmDate.ToString( "g" ) )
' => Displays 2/13/2001 3:42 PM

' Short Date and Long Time
Response.Write( dtmDate.ToString( "G" ) )
' => Displays 2/13/2001 3:42:52 PM

' Month and Day
Response.Write( dtmDate.ToString( "M" ) )
' => Displays February 13

' RFC 1123
Response.Write( dtmDate.ToString( "R" ) )
' => Displays Tue, 13 Feb 2001 15:44:52 GMT

' ISO 8601
Response.Write( dtmDate.ToString( "s" ) )
' => Displays 2001-02-13T15:45:55

' Short Time
Response.Write( dtmDate.ToString( "t" ) )
' => Displays 3:47 PM

' Long Time
Response.Write( dtmDate.ToString( "T" ) )
' => Displays 3:47:37 PM

' Universal ISO 8601
Response.Write( dtmDate.ToString( "u" ) )
' => Displays 2001-02-13 15:48:41Z

' Universal Time
Response.Write( dtmDate.ToString( "U" ) )
' => Displays Tuesday, February 13, 2001 11:49:51 PM

' Month and Year
Response.Write( dtmDate.ToString( "Y" ) )
' => Displays February, 2001
%>

The C# version of this code can be found on the CD-ROM.

You also can use the date and time format specifiers with the Format method. For example, the following statement displays the current date using a long date:

Response.Write( String.Format( "Today is {0:D}", DateTime.Now ) )

This statement displays output similar to the following:

Today is Tuesday, February 13, 2003
Formatting Strings for Different Cultures

Different cultures format numbers, dates, and times using different conventions. For example, in the United States, the currency amount of 9999.99 is formatted like this:

$9,999.99

In Germany, on the other hand, this same currency amount is formatted like this:

9.999,99 DM

Imagine that you decide to sell products from your Web site internationally. If you decide to do so, you need a way to format currency amounts for different countries.

The .NET framework includes classes for formatting strings for different cultures. These classes inhabit the System.Globalization namespace. The most important of these classes is CultureInfo.

You can use the CultureInfo class with the ToString method to format a string for a particular culture. For example, the following statements display a currency amount using the culture information for Germany:

<%@ Import Namespace="System.Globalization" %>
<%
Dim objCulture As CultureInfo
Dim decPrice As Decimal

decPrice = 9999.99
objCulture = New CultureInfo( "de-DE" )

Response.Write( decPrice.ToString( "c", objCulture ) )
%>

Notice that the System.Globalization namespace needs to be imported at the top of the page. You must import the namespace before you can use the CultureInfo class.

When the CultureInfo class is initialized, it is initialized with the CultureInfo name for Germany (de-DE). The CultureInfo class is then used with the ToString method to display a currency amount using a format appropriate to Germany.

NOTE

A list of all the CultureInfo names is included with the documentation for the CultureInfo class in the .NET Framework SDK Documentation.

The page in Listing 24.8 displays the list of CultureInfo names supported by your server. It also displays a currency amount, numeric amount, and date-time value formatted according to the conventions of each culture (see Figure 24.1).

Listing 24.8 CultureInfo.aspx
<%@ Import Namespace="System.Globalization" %>

<Script Runat="Server">

Dim decPrice As Decimal = 9999.99
Dim decQuantity As Decimal = 9999.99

Sub Page_Load
  dgrdCultures.DataSource = CultureInfo.GetCultures( CultureTypes.InstalledWin32Cultures )
  dgrdCultures.DataBind()
End Sub

</Script>
<html>
<head><title>CultureInfo.aspx</title></head>
<body>

<asp:DataGrid
  ID="dgrdCultures"
  AutoGenerateColumns="False"
  CellPadding="4"
  HeaderStyle-Font-Bold="True"
  HeaderStyle-BackColor="Turquoise"
  AlternatingItemStyle-BackColor="AliceBlue"
  Runat="Server">
<Columns>
  <asp:BoundColumn
    HeaderText="EnglishName"
    DataField="EnglishName" />
  <asp:BoundColumn
    HeaderText="Name"
    DataField="Name" />
  <asp:TemplateColumn HeaderText="Currency Format">
    <itemTemplate>
    <%# decPrice.ToString( "c", Container.DataItem ) %>
    </itemTemplate>
  </asp:TemplateColumn>
  <asp:TemplateColumn HeaderText="Number Format">
    <itemTemplate>
    <%# decQuantity.ToString( "n", Container.DataItem ) %>
    </itemTemplate>
  </asp:TemplateColumn>
  <asp:TemplateColumn HeaderText="Date/Time Format">
    <itemTemplate>
    <%# DateTime.Now.ToString( "F", Container.DataItem ) %>
    </itemTemplate>
  </asp:TemplateColumn>
</Columns>
</asp:DataGrid>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 24.1. Displaying culture information.

In the Page_Load subroutine in Listing 24.8, the GetCultures method retrieves a list of all the installed cultures on the current server. This list is bound to a DataGrid control.

The DataGrid control" displays the English name of each item in the list of cultures, the abbreviated name, and templates that display a currency amount, number, and date formatted for each culture.

Using String Methods and Properties

The String class contains a rich set of properties and methods that you can use to manipulate the contents of a string. The following sections provide an overview of these properties and methods.

NOTE

The Visual Basic language itself includes a number of methods for manipulating strings. For more information see the Visual Basic Language and Run-Time Reference in the .NET Framework SDK Documentation.

Comparing Two Strings

The easiest way to compare two strings is to use the equality operator like this:

If "Aardvark" = "Turtle" Then
  Response.Write( "They Match!" )
Else
  Response.Write( "They Don't Match!" )
End If

If the string Aardvark matches the string Turtle, the message They Match! is displayed.

If you want to make life more difficult for yourself, you also can compare strings by using the Compare method like this:

If String.Compare( "Aardvark", "Turtle" ) = 0 Then
  Response.Write( "They Match!" )
Else
  Response.Write( "They Don't Match!" )
End If

If the two strings compared by the Compare method match, the method returns the value 0. This example accomplishes the same thing as the previous example, with a little more work.

There are a couple of advantages to using the Compare method. The first, and probably most significant advantage, is that it enables you to perform comparisons that are not case sensitive. The Compare method enables you to specify a third, Boolean argument, as shown here, that indicates whether to perform a case-sensitive comparison:

If String.Compare( "AARDVARK", "aardvark", True ) = 0 Then
  Response.Write( "They Match!" )
Else
  Response.Write( "They Don't Match!" )
End If

In this example, the Compare method reports that the two strings match, even though one string is uppercase and one string is lowercase. The third parameter to the Compare method, True, indicates that a comparison that is not case sensitive should be performed.

The second advantage of the Compare method is that it enables you to compare whether one string is greater or less than another. Greater than and less than mean alphabetically greater than and less than. If the first string passed to the Compare method would appear earlier in a dictionary than the second string, the Compare method returns the value -1. If the strings match, the Compare method returns 0. Finally, if the first string would appear later in a dictionary than the second string, Compare returns the value 1.

Removing Characters from a String

When users enter information into HTML forms, they often add extra spaces. These extra spaces can cause problems. For example, if a user accidentally enters additional spaces when entering a password, the password might not be correctly matched.

To get around this problem, you should always be careful to trim away extraneous whitespace. The String class includes three Trim methods that perform this very function:

  • Trim

  • TrimStart

  • TrimEnd

By default, all three methods remove whitespace from a string. The Trim method removes any whitespace at the beginning and end of a string, the TrimStart method removes any whitespace at the beginning of a string, and the TrimEnd method removes any whitespace at the end of a string.

NOTE

Whitespace includes carriage returns, spaces, and tabs. You can determine whether any character is considered to be whitespace by using the IsWhiteSpace method of the Char class.

The following statements, for example, trim all the spaces and the carriage return from a string named strMessage:

Dim strString As String = " Hello!  " & vbNewline
Response.Write( strString.Trim() )

You also can use the Trim methods to remove characters other than whitespace. All three methods accept an array of characters as a parameter. The array contains the characters to trim away.

The following statement, for example, trims away all (lowercase) vowels from the beginning of the string:

Dim arrVowels() As Char = {"a","e","i","o","u"}
Dim strString As String = "aardvark"

Response.Write( strString.TrimStart( arrVowels ) )

You also can use the Remove method to remove characters from a string. This method removes a certain number of characters starting at a certain position in a string. For example, the following call to the Remove method removes characters starting at index position 2 and continuing for three characters:

Dim strString As String = "aardvark"
Response.Write( strString.Remove( 2, 3 ) )

After the Remove method is called, this statement outputs the value aaark.

Finding Characters in a String

You can search for a particular string within a string by using the IndexOf method, which returns the index position of the target string. For example, the following statements check whether aardvark appears in a string:

Dim strString As String

strString = "The slow moving aardvark"
If strString.IndexOf( "aardvark" ) > 0  Then
  Response.Write( "Found aardvark!" )
Else
  Response.Write( "No aardvark found!" )
End If

You can use the IndexOfAny method with an array of characters. The following statement returns the position of the first lowercase vowel in the string:

Dim arrVowels() As Char = {"a","e","i","o","u"}
Dim strString As String

strString = "The slow moving aardvark"
Response.Write( strString.IndexOfAny( arrVowels  ) )

The IndexOfAny method in this statement returns the value 2 because the letter e appears as the third character in the string (the IndexOfAny method is zero-based).

A variation of the IndexOf method, called LastIndexOf, enables you to find the last matching string in a string. (There is also a LastIndexOfAny method that works with an array of characters.)

Dim strString As String

strString = "The slow moving aardvark"
Response.Write( strString.LastIndexOf( "ar" ) )

In this example, the LastIndexOf method returns the value 21 because the last occurrence of the string ar appears 21 characters from the beginning of the string.

Two other methods of the String class should be mentioned in this context: StartsWith and EndsWith. These methods enable you to match a string at the beginning and end of a string:

Dim strString As String

strString = "http://www.superexpert.com"
If strString.StartsWith( "http://" ) and strString.EndsWith( ".com" ) Then
  Response.Write( "Thanks for entering your company URL!" )
End If

Here, the StartsWith and EndsWith methods check whether a string starts and ends with particular characters.

Modifying a String

The String class includes several methods for modifying a string. For example, you can use the Replace method to substitute new strings for parts of strings:

Dim strString As String

strString = "The slow-moving aardvark"
strString = strString.Replace( "aardvark", "turtle" )
Response.Write( strString )

When you execute these statements, the phrase The slow-moving turtle is displayed.

You can insert a new string into an existing string by using the Insert method as follows:

Dim strString As String

strString = "The slow-moving aardvark"
strString = strString.Insert( 16, "but happy " )
Response.Write( strString )

When the Insert method is executed in this example, the phrase The slow-moving but happy aardvark is displayed.

You also can extract a substring from a string by using the Substring method like this:

Dim strString As String

strString = "The slow-moving aardvark"
strString = strString.Substring( 4, 11 )
Response.Write( strString )

The Substring method in this example extracts the phrase slow-moving. The Substring method grabs the string starting at index position 4 with a length of 11 characters.

You also can use the ToUpper and ToLower methods to modify the case of a string. The following example demonstrates both methods:

Dim strString As String

strString = "The slow-moving aardvark"
Response.Write( strString.ToUpper )
Response.Write( strString.ToLower )

The ToUpper method returns THE SLOW-MOVING AARDVARK and the ToLower method returns the slow-moving aardvark.

Splitting and Joining Strings

You can split a string into multiple little strings by using the Split method. For example, the following statement splits a sentence into individual words and displays each word in a bulleted list:

Dim strString As String
Dim arrArray() As String
Dim strItem As String

strString = "The slow-moving aardvark"
arrArray = strString.Split()
For Each strItem in arrArray
  Response.Write( "<li>" & strItem )
Next

By default, the Split method splits a string wherever whitespace occurs (spaces, carriage returns, tabs, and so on). The method returns an array in which each element represents one of the resulting strings.

You also can pass a character, or array of characters, to the Split method. For example, the following statement splits a sentence wherever the letter v occurs:

Dim strString As String
Dim arrArray() As String
Dim strItem As String

strString = "The slow-moving aardvark"
arrArray = strString.Split( "v" )
For Each strItem in arrArray
  Response.Write( "<li>" & strItem )
Next

After the Split method is called, the following items are displayed:

  • The slow-mo

  • ing aard

  • ark

You can perform the opposite operation by using the Join method, which joins together all the elements in an array into a single string like this:

Dim arrArray() As String = {"The","slow-moving","aardvark"}
Response.Write( String.Join( " ", arrArray ) )

The first statement creates a three-element array. The second statement joins all the elements together, using a space as a separator, and displays The slow-moving aardvark.

Using the StringBuilder Class

If you need to work with large strings—resumes, product descriptions, the text of the Declaration of Independence—you should consider using the methods and properties of the StringBuilder class. This class, unlike the String class, can more efficiently manage memory when representing a string.

You can create a new instance of the StringBuilder class from an existing string like this:

Dim objStringBuilder As StringBuilder
objStringBuilder = New StringBuilder( "The slow-moving aardvark" )

When you create a new instance of the StringBuilder class, it is created with a certain capacity that represents the number of characters the StringBuilder can contain.

By default, a StringBuilder has a capacity of 16 characters. However, the capacity automatically grows to handle the number of characters contained in the class. If you know that you'll be working with a large string, you can specify the capacity when you initialize the StringBuilder like this:

Dim objStringBuilder As StringBuilder
objStringBuilder = New StringBuilder( "The slow-moving aardvark", 1000 )

This StringBuilder is created with an initial capacity of 1,000 characters.

If you're working with truly huge strings, you might want to check whether the server has enough memory to handle the size of the strings. You can attempt to initialize a string with a particular capacity by using the EnsureCapacity method like this:

Dim objStringBuilder As StringBuilder
objStringBuilder = New StringBuilder( "The slow-moving aardvark" )
objStringBuilder.EnsureCapacity( 99999 )

This example attempts to create a StringBuilder with a capacity of 99,999 characters, which is the minimum capacity. Calling the EnsureCapacity method might actually reserve more characters than the number requested.

Appending Strings to a StringBuilder

After you create an instance of a StringBuilder, you can add characters to it by using the Append method. For example, the following example initializes a StringBuilder with the text The slow-moving aardvark and appends the text and the happy turtle and bought some eggs to the end of the StringBuilder:

Dim objStringBuilder As StringBuilder

objStringBuilder = New StringBuilder( "The slow-moving aardvark" )
objStringBuilder.Append( " and the happy turtle" )
objStringBuilder.Append( " bought some eggs" )

Response.Write( objStringBuilder.ToString() )

You also can add a formatted string to a StringBuilder. The following example uses a For...Next loop to append 10 strings to the StringBuilder. Each string is formatted with the format string "<p> counter = {0:n}":

Dim objStringBuilder As StringBuilder
Dim intCounter As Integer

objStringBuilder = New StringBuilder()
For intCounter = 1 to 10
  objStringBuilder.AppendFormat( "<p> counter = {0:n}", intCounter )
Next
Response.Write( objStringBuilder.ToString() )

This example outputs the following:

counter = 1.00
counter = 2.00
counter = 3.00
counter = 4.00
counter = 5.00
counter = 6.00
counter = 7.00
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.

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:

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:

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( "<p>{0} is a digit", chrChar ) )
  End If
Next

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

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

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

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

objRegEx = New RegEx( "b[iao]t" )
objMatch = objRegEx.Match( "The bat flew in the window" )

Response.Write( "<p>Success: " & objMatch.Success )
Response.Write( "<p>Index: " & objMatch.Index )
Response.Write( "<p>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

objRegEx = New RegEx( "[aeiou]" )
objMatchCollection = objRegEx.Matches( "The bat flew in the window" )
For Each objMatch in objMatchCollection
  Response.Write( "<p>Success: " & objMatch.Success )
  Response.Write( "<p>Index: " & objMatch.Index )
  Response.Write( "<p>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).

Listing 24.9 TestRegEx.aspx
<Script Runat="Server">

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

</Script>

<html>
<head><title>TestRegEx.aspx</title></head>
<body>
<form Runat="Server">

<b>Regular Expression:</b>
<br>
<asp:TextBox
  ID="txtRegularExpression"
  Columns="80"
  Runat="Server" />
<p>
<b>Text:</b>
<br>
<asp:TextBox
  ID="txtMatchExpression"
  TextMode="MultiLine"
  Columns="80"
  Rows="3"
  Runat="Server" />
<p>
<asp:Button
  Text="Test RegEx!"
  OnClick="Button_Click"
  Runat="Server" />
<hr>
<asp:DataGrid
  ID="dgrdMatches"
  CellPadding="4"
  HeaderStyle-BackColor="LightBlue"
  HeaderStyle-Font-Bold="True"
  AutoGenerateColumns="False"
  Runat="Server">
<Columns>
  <asp:BoundColumn
    HeaderText="Success"
    DataField="success" />
  <asp:BoundColumn
    HeaderText="Index"
    DataField="Index" />
  <asp:BoundColumn
    HeaderText="Length"
    DataField="Length" />
  <asp:TemplateColumn>
    <HeaderTemplate>
      Value
    </HeaderTemplate>
    <ItemTemplate>
      <%# Server.HtmlEncode( Container.DataItem.Value ) %>
    </ItemTemplate>
  </asp:TemplateColumn>
</Columns>
</asp:DataGrid>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 24.2. Testing regular expressions.

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

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:

<font size="[1-9]">

This regular expression would match <font size="3"> and <font size="5">, but not <font size="33">. 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.

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:

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

  • \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 <br> 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, "<br>" ) )

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 the beginning of a string (even in multiline mode)

  • \Z— Matches the end of a string or the last character before \n at the end of a string (even in multiline mode)

  • \z— Matches the end of a string

  • \G— Matches the beginning of the current search

  • \b— Matches a word boundary

  • \B— Matches nonword boundaries

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.

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:

\.

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 <p> or <br> tag in a string. To match either sequence of characters, you need to use the alternation metacharacter | like this:

<p>|<br>

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 exactly n times

  • {n,}— Matches at least n times

  • {n,m}— Matches 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

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 very slow-moving aardvark" and "The slow-moving aardvark".

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 <i>quantifier</i> can be <big>greedy</big>.

You might expect the regular expression to match the tags <i>, </i>, <big>, and </big>. Unfortunately, the * quantifier gets too ambitious and matches the whole expression <i>quantifier</i> can be <big>greedy</big>. The * quantifier matches the i that follows the first <, but then it also matches the > and every other character that follows until it reaches the final > at the end of </big>.

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:

<.*?>

The ? forces the * quantifier to be nongreedy. This regular expression correctly matches <i>, </i>, <big>, and </big>. 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} quantifier

  • {n,}?— Nongreedy {n,} quantifier

  • {n,m}?— Nongreedy {n,m} 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 slow-moving aardvark 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.

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

Instead of using numbered backreferences to refer to capture groups, you can name the capture groups. For example, the following regular expression matches any two repeating characters in a string using a named backreference:

(?<myLetter>\w)\k<myLetter>

The construct (?<myLetter..) creates a capture group with a name. In this case, the name is myLetter. The construct \k<myLetter> is a named backreference to the capture group.

Suppose that you want to match any two words in a row such as the the. You can match repeating words by using the following regular expression:

\b(\w+)\s+\1\b

This regular expression matches any expression that starts on a word boundary, contains one or more numerals or letters, is followed by at least one whitespace character, and contains the same sequence of numerals or letters followed by a word boundary. In other words, it matches the same word twice in a row.

You can rewrite this regular expression to use a named capture group like this:

\b(?<repeat>\w+)\s+\k<repeat>\b

This regular expression refers to the group of letters captured in the parentheses with the name repeat.

Using Replacement Patterns

A replacement pattern is the string that you supply to the RegEx Replace method when you want to replace expressions in a string. For example, suppose that you want to replace every instance of the word very in a string with the word extremely. You can accomplish this task by using the following code:

Dim objRegEx As RegEx
Dim strString As String

strString = "The very slow-moving aardvark."

objRegEx = New RegEx( "very" )
Response.Write( objRegEx.Replace( strString, "extremely" ) )

You can refer to capture groups in a replacement pattern. In the preceding section, you learned how to refer to capture groups by using both numbered backreference expressions, such as \1 and \2 and named backreference expressions, such as \k<repeat> or \k<myLetter>.

Within a replacement pattern, you must refer to a capture group with a slightly different syntax. In a replacement pattern, you refer to a capture group by number using $1, $2, and so on, and by name using ${repeat}, ${myLetter}, and so on.

Suppose that you want to replace every instance of the HTML <h#> tag with the HTML <font> tag. If the tag <h1> appears in the string, you want to replace it with <font size=1>; if the tag <h2> appears in a string, you want to replace it with <font size=2>; and so on. You can do so by using the following code:

Dim objRegEx As RegEx
Dim strString As String

strString = "<h4>Grocery List</h4>" & _
  "<h3>eggs</h3>" & _
  "<h3>milk</h3>" & _
  "<h3>pancake mix</h3>"

objRegEx = New RegEx( "<h(\d)>(.*?)</h\1>" )
Response.Write( objRegEx.Replace( strString, "<font size=$1>$2</font>" ) )

In this example, the RegEx class is initialized with the following regular expression:

<h(\d)>(.*?)</h\1>

This regular expression matches an opening and closing HTML header tag. It contains two capture groups. The first capture group contains a single digit for the size of the heading.

The second capture group contains the text that appears between the opening and closing header tag.

This regular expression is used with the following replacement pattern:

<font size=$1>$2</font>

In this replacement pattern, the $1 refers to the first capture group in the regular expression, and the $2 refers to the second capture group. So, the $1 is replaced with the size of the header, and the $2 is replaced with the original contents of the header tag.

When all is said and done, the string

<h4>Grocery List</h4>
<h3>eggs</h3>
<h3>milk</h3>
<h3>pancake mix</h3>

is replaced with the string

<font size=4>Grocery List</font>
<font size=3>eggs</font>
<font size=3>milk</font>
<font size=3>pancake mix</font>

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)

  • ExplicitCapture— Captures only explicitly named or numbered groups (uses the option n within a regular expression)

  • Compiled— Compiles the regular expression for faster performance but slower initialization (uses the option c within a regular expression)

  • SingleLine— Indicates single line mode (uses the option s within a regular expression)

  • IgnorePatternWhitespace— Eliminates unescaped whitespace characters and comments (uses the option x within a regular expression)

  • RightToLeft— Searches from right to left (uses the option r within a regular expression)

All these options have the value False by default. You can enable any of them in two ways. First, you can enable one or more options for a regular expression as a whole when you declare it like this:

Dim objRegEx As RegEx

objRegEx = New RegEx( "match me!", RegexOptions.IgnoreCase )

The first parameter here is the regular expression, and the second parameter contains the IgnoreCase option.

If you need to specify multiple options, use the Or operator like this:

Dim objRegEx As RegEx

objRegEx = New RegEx( "match me!", RegexOptions.IgnoreCase Or RegexOptions.Singleline )

You also can set options within the regular expression itself. This capability is useful when you want the option to apply to only part of a regular expression:

(?i:aardvark)

This regular expression enables matches that are not case sensitive with the i option. You can list multiple options and disable particular options like this:

(?im-r:aardvark)

This regular expression enables matches that are not case sensitive and multiline mode and disables right-to-left matching.

The most useful of the regular expression options is IgnoreCase. For example, the following regular expression matches Aardvark, aardvark, and AArDVark:

(?i:aardvark)

The Multiline option is also useful. It affects how the beginning-of-string metacharacter (^) and end-of-string metacharacter ($) are interpreted. By default, the ^ metacharacter matches only the very beginning of a string, even if the string contains multiple lines of text. If you enable the Multiline option, the ^ and $ metacharacters match the beginning and end of each line of text.

Consider this text:

The aardvark went to the store.
The aardvark bought some eggs.

If you apply the regular expression ^The to this text when not in Multiline mode, only the first The in the text is matched. However, if you apply the regular expression in Multiline mode, both instances of The are matched.

Another regular expression option can be used for Singleline mode. This can be confusing because Singleline mode is not the opposite of Multiline mode. In fact, you can enable both Singleline and Multiline modes for the same regular expression at the same time.

The Singleline option affects how the dot metacharacter (.) is interpreted. Normally, a dot matches all characters except a newline. In Singleline mode, however, a dot also matches a newline character.

Let's return to the previous text for another example:

The aardvark went to the store.
The aardvark bought some eggs.

By default, if you apply the regular expression .+ to this text, the regular expression returns the following two matches: The aardvark went to the store. and The _aardvark bought some eggs.. Two matches are found because, by default, the dot character automatically stops when it encounters a newline character.

In Singleline mode, in contrast, the regular expression .+ returns only a single match: The aardvark went to the store. The aardvark bought some eggs.. A single match is returned because, in Singleline mode, the dot keeps going past the newline character.


Summary

This chapter provided an overview of collections, strings, and regular expressions. First, you learned how to work with the ArrayList, HashTable, and SortedList collections and use them to represent lists of items and lists of key and value pairs.

Next, you examined various methods of working with strings. You learned how to format strings by using the Format and ToString methods and reviewed methods and properties for working with strings included in the .NET framework. You also learned how to use the StringBuilder class to efficiently manipulate the contents of large strings.

Finally, you examined the topic of regular expressions. You learned how to use the most important regular expression classes and use regular expression metacharacters to perform advanced pattern matching and pattern replacement.


Chapter 25. Working with the File System

IN THIS CHAPTER

  • Security and the File System

  • Using Files and Directories

  • Using Serialization

In this chapter, you'll learn how to work with the file system within an ASP.NET page. First, you'll learn how to perform standard file manipulation tasks, such as creating and reading files. You'll also learn how to display the contents of a directory and work with path information.

Next, you'll be provided with an overview of serialization. You'll learn how to serialize objects, such as ArrayLists, to persistent storage mechanisms, such as the file system or a database table. You'll learn how to work with both the BinaryFormatter and the XmlSerializer.


Security and the File System

Many of the code samples in this chapter write or access files in a directory. Before you can access files in a directory from within an ASP.NET page, you need to check whether the necessary permissions have been granted for the directory.

By default, the ASP.NET process executes in the security context of the ASPNET user account. If this account does not have permissions to write a file to a directory, then you'll receive an access exception when you attempt to write a file. To avoid an access exception, you'll need to grant write permissions to either the ASPNET user account or the Users group (By default, the Users group includes the ASPNET user account).

To grant the necessary permissions for writing to a directory to either the ASPNET user account or Users group, follow these steps:

  • Right-click the directory

  • Select Properties

  • Select the tab labeled Security

  • Add either the Users group or the ASPNET user account to the directory

  • Make sure that either the Users group or the ASPNET user account has Write permissions enabled

  • Click OK

For more information on the ASPNET user account see Chapter 20, "Using Windows-Based Authentication."


Using Files and Directories

All the classes for working with files and directories can be found in the System.IO namespace. The classes in this namespace can be roughly divided into three groups: classes for working with directories, classes for working with files, and classes for working with streams.

There are two main classes for working with directories:

  • Directory— Contains shared methods for creating, moving, and retrieving the contents of directories. Because the methods of this class are shared methods, you don't need to instantiate the class before using any of its methods.

  • DirectoryInfo— Contains instance methods for creating, moving, and retrieving the contents of directories. Because the methods of this class are instance methods, you must instantiate the class before using any of its methods.

There are also two main classes for working with files:

  • File— Contains shared methods for working with files. Because the methods of this class are shared methods, you don't need to instantiate the class before using any of its methods.

  • FileInfo— Contains instance methods for working with files. Because the methods of this class are instance methods, you must instantiate the class before using any of its methods.

The System.IO namespace includes several classes for working with file streams:

  • FileStream— Represents the base stream for reading and writing to files.

  • StreamReader— Reads characters from a text file.

  • StreamWriter— Writes characters to a text file.

  • BinaryReader— Reads data from a binary file.

  • BinaryWriter— Writes data to a binary file.

Finally, the System.IO namespace contains the following useful utility class:

  • Path— Contains methods for manipulating a file or directory path.

In the following sections, you'll learn how to use these classes to read and write to a file, work with directories, and work with file system paths.

Creating and Reading Text Files

The general approach to reading or writing to a text file is to use one of the shared methods of the File class to return an appropriate stream. When writing to a file, you'll use an instance of the StreamWriter class. When reading from a file, you'll use an instance of the StreamReader class.

Writing to a Text File

The easiest method to write to a file is to call the CreateText method of the File class to return a StreamWriter. After you have created a StreamWriter, you can call its Write method to write text to a file.

For example, the ASP.NET page in Listing 25.1 takes whatever text you enter into the TextBox control and saves it to a file named myFile.txt.

Listing 25.1 WriteText.aspx
<%@ Import Namespace="System.IO" %>

<Script Runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  Dim objStreamWriter As StreamWriter

  objStreamWriter = File.CreateText( MapPath( "myFile.txt" ) )
  objStreamWriter.WriteLine( txtInput.Text )
  objStreamWriter.Close
End Sub

</Script>

<html>
<head><title>WriteText.aspx</title></head>
<body>
<form runat="Server">

<h1>Write to a Text File:</h1>
<br>
<asp:TextBox
  ID="txtInput"
  Runat="Server" />

<asp:Button
  Text="Write!"
  OnClick="Button_Click"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

In Listing 25.1, a text file is created in the Button_Click subroutine. An instance of the StreamWriter class is returned by the CreateText method. The WriteLine method is called to write text to a file named myFile.txt. Finally, calling the Close method closes the StreamWriter.

The page in Listing 25.1 overwrites any existing file when you submit new text. If you want to append text to a file, then you need to use the AppendText method instead of the CreateText method. The page in Listing 25.2 illustrates how to use the AppendText method in an ASP.NET page.

Listing 25.2 AppendText.aspx
<%@ Import Namespace="System.IO" %>

<Script Runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  Dim objStreamWriter As StreamWriter

  objStreamWriter = File.AppendText( MapPath( "myFile.txt" ) )
  objStreamWriter.WriteLine( txtInput.Text )
  objStreamWriter.Close
End Sub

</Script>

<html>
<head><title>AppendText.aspx</title></head>
<body>
<form runat="Server">

<h1>Append to a Text File:</h1>
<br>
<asp:TextBox
  ID="txtInput"
  Runat="Server" />

<asp:Button
  Text="Append Text!"
  OnClick="Button_Click"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

The page in Listing 25.2 is almost exactly the same as the page in Listing 25.1. However, when you submit text with the page in Listing 25.2, the text is appended to the file instead of overwriting the file.

Reading from a Text File

The easiest method of reading from a text file is to call the OpenText method of the File class to return a StreamReader. After you have created a StreamReader, you can call the ReadLine method to retrieve each line of text from the stream.

For example, the page in Listing 25.3 reads every line of text from a file named myFile.txt.

Listing 25.3 OpenText.aspx
<%@ Import Namespace="System.IO" %>

<Script Runat="Server">

Sub Page_Load
  Dim objStreamReader As StreamReader
  Dim strInput As String

  If File.Exists( MapPath( "myFile.txt" ) ) Then
    objStreamReader = File.OpenText( MapPath( "myFile.txt" ) )
    strInput = objStreamReader.ReadLine()
  While strInput <> Nothing
    lblContents.Text &= "<li>" & strInput
    strInput = objStreamReader.ReadLine()
  End While
  objStreamReader.Close
  Else
    lblContents.Text = "myFile.txt does not exist!"
  End If
End Sub

</Script>

<html>
<head><title>OpenText.aspx</title></head>
<body>

<h1>Contents of myFile.txt</h1>

<asp:Label
  ID="lblContents"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

In Listing 25.3, every line of text is read from a file named myFile.txt and displayed in a Label control. Each line is retrieved by calling the ReadLine method within a WHILE...WEND loop. When the end of the file is reached, the ReadLine method returns the value Nothing.

Notice that the Exists method is called to check whether the myFile.txt file exists before the file is opened. The Exists method returns True when a file with the specified path exists, and False otherwise.

Creating and Reading Binary Files

Many types of files stored on your computer file system, such as GIF and JPEG images, are not stored as text files. To work with these types of files, you'll need to use the classes for working with binary files.

The general method for working with binary files is to create an instance of the FileStream class to represent a file stream around the binary file. After you have created a FileStream, you can use the BinaryWrite method to write to the file, and the BinaryRead method to read from the file.

Writing to a Binary File

The first step in writing to a binary file is to create an instance of the FileStream class. The FileStream class has several constructors. In the simplest case, you can construct a new instance of the FileStream class by providing the path to a binary file and a FileMode like this:

objFileStream = New FileStream( MapPath( "myFile.data" ), FileMode.Create )

This statement creates a new binary file named myFile.data. If the file doesn't exist, it creates it. If the file already exists, it overwrites it.

The FileMode enumeration has the following values:

  • Append— Adds new data to an existing file or creates a new file.

  • Create— Overwrites an existing file or creates a new file.

  • CreateNew— Creates a new file. If the file already exists, an error is thrown.

  • Open— Opens a file for reading.

  • OpenOrCreate— Opens a file for reading. If the file doesn't exist, it's created.

  • Truncate— Opens a file and removes any existing content.

After you have an instance of the FileStream class, you can use the instance to initialize a BinaryWriter like this:

objBinaryWriter = New BinaryWriter( objFileStream )

You can use the Write method of the BinaryWriter class to write to a binary file. There are several overloaded versions of the Write method. For example, you can use the Write method to write Integers, Boolean values, Chars, or Bytes to a binary file.

NOTE

For a complete list of the overloaded methods of the Write method, see the .NET Framework SDK Documentation.

The page in Listing 25.4 writes the numbers 0 to 50 to a binary file named myFile.data.

Listing 25.4 BinaryWriter.aspx
<%@ Import Namespace="System.IO" %>

<%
Dim objFileStream As FileStream
Dim objBinaryWriter As BinaryWriter
Dim intCounter As Integer

objFileStream = New FileStream( MapPath( "myFile.data" ), FileMode.Create )
objBinaryWriter = New BinaryWriter( objFileStream )

For intCounter = 0 To 50
  objBinaryWriter.Write( intCounter )
Next

objBinaryWriter.Close
%>

Created binary file!

The C# version of this code can be found on the CD-ROM.

If you execute the page in Listing 25.4 and open the myFile.data file in Notepad, you'll get an unreadable garble of text. The numbers have been stored as integers, not as strings.

NOTE

Visual Basic Integers are mapped to the System.Int32 data type in the .NET Framework.

Reading from a Binary File

To read from a binary file, you must create an instance of both the FileStream class and BinaryReader class. You use the FileStream class to initialize the BinaryReader class.

The BinaryReader class contains several methods for reading a binary file. For example, if you want to read Visual Basic Integers from a binary file, then you use the ReadInt32 method; if you want to read bytes, then you use the ReadByte method; if you want to read Boolean values, then you use the ReadBoolean method.

The page in Listing 25.5 opens a binary file named myFile.data that contains Integer values and displays all the file's contents.

Listing 25.5 BinaryReader.aspx
<%@ Import Namespace="System.IO" %>

<%
Dim objFileStream As FileStream
Dim objBinaryReader As BinaryReader
Dim intCounter As Integer

objFileStream = New FileStream( MapPath( "myFile.data" ), FileMode.Open )
objBinaryReader = New BinaryReader( objFileStream )

For intCounter = 0 to 50
  Response.Write( "<li>" & objBinaryReader.ReadInt32() )
Next

objBinaryReader.Close
%>

All Done!

The C# version of this code can be found on the CD-ROM.

Displaying the Contents of a Directory

If you want to retrieve a list of items in a directory, you can use either the Directory or DirectoryInfo class. The Directory class contains shared methods for working with directories and the DirectoryInfo class contains instance methods.

For example, the Directory class contains the following three methods:

  • GetDirectories— Returns a string array of directory names.

  • GetFiles— Returns a string array of filenames.

  • GetFileSystemEntries— Returns a string array of directory and filenames.

The ASP.NET page in Listing 25.6 returns a list of all files in a directory named myDir (see Figure 25.1).

Listing 25.6 GetFiles.aspx
<%@ Import Namespace="System.IO" %>

<Script runat="Server">

Sub Page_Load
  dgrdFiles.Datasource = Directory.GetFiles( "c:\myDir" )
  dgrdFiles.DataBind()
End Sub

</Script>

<html>
<head><title>GetFiles.aspx</title></head>
<body>

<asp:DataGrid
  ID="dgrdFiles"
  CellPadding="10"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 25.1. Displaying the contents of a directory.

You also can pass a pattern to the GetFiles, GetDirectories, or GetFileSystemEntries methods. For example, if you want to return a list of only those files with the extension .aspx, then you would call the GetFiles method like this:

Directory.GetFiles( "c:\myDir", "*.aspx" )

The pattern is not case sensitive. So both *.txt and *.TXT match any text files.

Retrieving Path Information

The System.IO namespace includes a useful utility class named the Path class. This class has the following methods:

  • ChangeExtension— Enables you to modify a file extension.

  • Combine— Enables you to join two file paths.

  • GetDirectoryName— Returns the directory part of a complete file path.

  • GetExtension— Returns a file extension from a file path.

  • GetFileName— Returns the filename from a file path.

  • GetFileNameWithoutExtension— Returns the filename without the file extension from a file path.

  • GetFullPath— Expands the supplied file path to a fully qualified file path.

  • GetPathRoot— Gets the root of the current path (for example, "c:\").

  • GetTempFileName— Creates a uniquely named file and returns the name of the new file.

  • GetTempPath— Returns the path to the server's temporary directory.

  • HasExtension— Returns True when a file path contains a file extension, and False otherwise.

  • IsPathRooter— Returns True when a file path contains a root (for example, "c:\") and False otherwise.

The page in Listing 25.7 illustrates how to use several of these methods.

Listing 25.7 Path.aspx
<%@ Import Namespace="System.IO" %>

<%
Dim strPath As String

' Get a Temporary File
strPath = Path.GetTempFileName
Response.Write( "<li>Created TempFile " & strPath )

' Retrieve File Name
Response.Write( "<li>The name of the file is " )
Response.Write( Path.GetFileName( strPath ) )

' Retrieve Directory Name
Response.Write( "<li>The name of the directory is " )
Response.Write( Path.GetDirectoryName( strPath ) )

' Retrieve File Extension
Response.Write( "<li>The file has the extension " )
Response.Write( Path.GetExtension( strPath ) )
%>

The C# version of this code can be found on the CD-ROM.

In the page in Listing 25.7, a new file is created with the GetTempFileName method. Next, the filename, the directory path, and the file extension are retrieved and displayed (see Figure 25.2).

Figure 25.2. Using methods of the Path class.

    [ Team LiB ]


Using Serialization

Serialization is the process of converting an object, or group of objects, into a form that can be persisted. For example, you can serialize an object to the file system or a database table. After an object has been saved to a persistent medium, you can deserialize the object to recover the object.

NOTE

When I think of serialization, I tend to think of the scene in The Empire Strikes Back when Han Solo is frozen into a block of graphite. In the next movie, Return of the Jedi, Han Solo is unfrozen without any loss of state.

Serialization enables you to take a snapshot of an object's state at any moment in time. When you serialize an object, you also serialize the values of its properties. For example, if you serialize an ArrayList to disk, then both the ArrayList itself and all its elements are saved to disk.

The .NET Framework includes the following classes for serializing and deserializing objects:

  • BinaryFormatter— Enables you to serialize and deserialize objects to a binary format.

  • SoapFormatter— Enables you to serialize and deserialize objects to a SOAP format.

  • XMLSerializer— Enables you to serialize and deserialize objects to an XML format.

In the following sections, you'll learn how to use the BinaryFormatter and XMLSerializer classes to serialize objects, such as ArrayLists and custom classes to files and databases.

NOTE

I won't be covering the SoapFormatter directly. However, the SoapFormatter works in a similar fashion to the BinaryFormatter.

Using Binary Serialization

The BinaryFormatter enables you to serialize an object or group of objects into a binary format. The BinaryFormatter class has two main methods: Serialize and Deserialize. To serialize an object, you pass an instance of a stream and the object to the Serialize method. To Deserialize an object, you pass an instance of a stream to the Deserialize method.

You can use the BinaryFormatter to serialize many, but not all, classes in the .NET Framework. For example, you can serialize ArrayLists, DataSets, and Arrays but not other objects, such as DataReaders or TextBox controls. To serialize a class, the class must have the Serializable attribute or implement the ISerializable interface.

The BinaryFormatter differs from the XMLSerializer class in two important aspects. First, when you use the BinaryFormatter to serialize an object, both the private and public members of the class are serialized. The BinaryFormatter captures the complete internal state of an object. The XMLSerializer, on the other hand, only captures the state of public members of a class.

Second, unlike the XMLSerializer, you cannot read the output of the BinaryFormatter. The BinaryFormatter serializes an object into a binary format. The advantage of a binary format is that it is relatively compact. You bloat files with XML tags when you serialize an object with the XMLSerializer.

Serializing an ArrayList

Let's start with a simple example of serialization. This example serializes an ArrayList to the file system. The page in Listing 25.8 takes an ArrayList and serializes it to a file named myArrayList.data.

Listing 25.8 SerializeArrayList.aspx
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.Runtime.Serialization.Formatters.Binary" %>

<%
Dim colArrayList As ArrayList
Dim objFileStream As FileStream
Dim objBinaryFormatter As BinaryFormatter

colArrayList = New ArrayList()
colArrayList.Add( "Milk" )
colArrayList.Add( "Oranges" )
colArrayList.Add( "Beer" )

objFileStream = New FileStream( MapPath( "myArrayList.data" ), FileMode.Create )
objBinaryFormatter = New BinaryFormatter
objBinaryFormatter.Serialize( objFileStream, colArrayList )
objFileStream.Close()
%>

All Done!

The C# version of this code can be found on the CD-ROM.

In Listing 25.8, an ArrayList is created with three elements: Milk, Oranges, and Beer. Next, a FileStream is initialized around a file named myArrayList.data.

The ArrayList is serialized with the BinaryFormatter. An instance of the BinaryFormatter is created and the Serialize method of the BinaryFormatter is called.

If you execute the page in Listing 25.8, you'll end up with a new file on your hard drive named myArrayList.data that contains a snapshot of the colArrayList ArrayList.

The page in Listing 25.9 illustrates how you can use the Deserialize method to reconstitute the ArrayList from the file.

Listing 25.9 DeserializeArrayList.aspx
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.Runtime.Serialization.Formatters.Binary" %>

<%
Dim colArrayList As ArrayList
Dim objFileStream As FileStream
Dim objBinaryFormatter As BinaryFormatter
Dim strItem As String

objFileStream = New FileStream( MapPath( "myArrayList.data" ), FileMode.Open )
objBinaryFormatter = New BinaryFormatter
colArrayList = CType( objBinaryFormatter.Deserialize( objFileStream ), ArrayList )
objFileStream.Close()

For Each strItem In colArrayList
  Response.Write( "<li>" & strItem )
Next

%>
All Done!

The C# version of this code can be found on the CD-ROM.

In Listing 25.9, a FileStream is initialized for reading from the myArrayList.data file. Next, an instance of the BinaryFormatter is created and its Deserialize method is called to re-create the ArrayList.

Notice that the CType function must be used to explicitly specify the type of object being created from the Deserialize method. In this case, you need to explicitly cast the deserialized object as an ArrayList.

Finally, the page iterates through all the elements of the ArrayList and displays the value of each element.

Serializing a Class

This section tackles a more complicated example of serialization—serializing a custom class to a database table.

This custom class represents user information. The class is contained in Listing 25.10.

Listing 25.10 UserInfo.vb
Imports System

Namespace myComponents

<serializable()> Public Class UserInfo
  Public Username As String
  Public Password As String
  Public Email As String
End Class

End Namespace

The C# version of this code can be found on the CD-ROM.

The UserInfo class in Listing 25.10 contains three public properties: Username, Password, and Email. Notice that the class is declared with the <serializable()> attribute. Without this attribute, you could not serialize the class.

Before you can use the UserInfo class, you'll need to compile it. Execute the following statement from the command line:

vbc /t:Library UserInfo.vb

Next, copy the compiled class, UserInfo.dll, to your application /bin directory. Placing the UserInfo.dll file in your /bin directory exposes the class to your ASP.NET application.

Next, you need to create the Microsoft SQL Server database table where you will store the serialized version of the UserInfo class. You'll serialize the class to an Image column. The SQL CREATE TABLE statement in Listing 25.11 creates the necessary database table when executed from Query Analyzer.

Listing 25.11 CreateUserList.sql
CREATE TABLE UserList (
  user_id INT NOT NULL IDENTITY,
  username VARCHAR( 50 ),
  userinfo Image )

The C# version of this code can be found on the CD-ROM.

Now that you have the UserInfo class and a database table in which to store it, you can create a page that serializes the UserInfo class. The page in Listing 25.12 enables you to assign values to the properties of the UserInfo class through a form and serialize it to the UserList database table.

NOTE

The SerializeClass.aspx page opens a connection to a database named myData. You'll need to modify the database connection string to open a connection to the particular database where you created the UserList table.

Listing 25.12 SerializeClass.aspx
<%@ Import Namespace="myComponents" %>
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.Runtime.Serialization.Formatters.Binary" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<Script runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  Dim objUserInfo As UserInfo
  Dim objMemoryStream As MemoryStream
  Dim objBinaryFormatter As BinaryFormatter
  Dim conMyData As SqlConnection
  Dim cmdInsert As SqlCommand

  objUserInfo = New UserInfo
  objUserInfo.Username = txtUsername.Text
  objUserInfo.Password = txtPassword.Text
  objUserInfo.Email = txtEmail.Text

  objMemoryStream = New MemoryStream
  objBinaryFormatter = New BinaryFormatter
  objBinaryFormatter.Serialize( objMemoryStream, objUserInfo )

  conMyData = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=myData" )
cmdInsert = New SqlCommand( _
    "Insert Userlist ( username, userinfo ) Values ( @username, @userinfo )", _
    conMyData )
  cmdInsert.Parameters.Add( "@username", txtUsername.Text )
  cmdInsert.Parameters.Add( "@userinfo", objMemoryStream.ToArray() )
  conMyData.Open()
    cmdInsert.ExecuteNonQuery()
  conMyData.Close()
End Sub
</Script>

<html>
<head><title>SerializeClass.aspx</title></head>
<body>
<form runat="Server">

Username:
<br><asp:TextBox
  id="txtUsername"
  runat="Server" />
<p>
Password:
<br><asp:TextBox
  id="txtPassword"
  runat="Server" />
<p>
Email:
<br><asp:TextBox
  id="txtEmail"
  runat="Server" />
<p>
<asp:Button
  Text="Submit!"
  OnClick="Button_Click"
  runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

When you submit the form in Listing 25.12, the Button_Click subroutine is executed. This subroutine creates an instance of the UserInfo class and assigns the values from the form fields to the properties of the class.

NOTE

A MemoryStream is similar to a FileStream. However, it does not represent a file on the disk drive. A MemoryStream represents a block of memory.

Next, a BinaryFormatter is created and the UserInfo class is serialized to a MemoryStream. Finally, a SQL Insert statement is executed to add the serialized UserInfo class to the Userlist database table.

NOTE

The ASP.NET Framework uses the same method discussed in this section when storing session state in a database table. All the objects stored in session state are serialized and stored to a temporary database table named ASPStateTempSessions.

The page in Listing 25.13 performs the opposite operation. It deserializes the UserList class from the database table and displays the values of its properties.

Listing 25.13 DeserializeClass.aspx
<%@ Import Namespace="myComponents" %>
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.Runtime.Serialization.Formatters.Binary" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<Script runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  Dim objUserInfo As UserInfo
  Dim objMemoryStream As MemoryStream
  Dim objBinaryFormatter As BinaryFormatter
  Dim conMyData As SqlConnection
  Dim cmdSelect As SqlCommand
  Dim dtrUserInfo As SqlDataReader
  Dim arrByte As Byte()

  conMyData = New SqlConnection( "Server=localhost;UID=sa;PWD=secret;Database=myData" )
cmdSelect = New SqlCommand( _
    "SELECT username, userInfo FROM Userlist WHERE username=@username", _
    conMyData )
  cmdSelect.Parameters.Add( "@username", txtUsername.Text )
  conMyData.Open()
    dtrUserInfo = cmdSelect.ExecuteReader()
    If dtrUserInfo.Read Then
      arrByte = dtrUserInfo( "userinfo" )
      objMemoryStream = New MemoryStream( arrByte )
      objBinaryFormatter = New BinaryFormatter
      objUserInfo = CType( _
        objBinaryFormatter.Deserialize( objMemoryStream ), _
        UserInfo )
      txtPassword.Text = objUserInfo.Password
      txtEmail.Text = objUserInfo.Email
    End If
  conMyData.Close()
End Sub

</Script>

<html>
<head><title>DeserializeClass.aspx</title></head>
<body>
<form runat="Server">

Username:
<br><asp:TextBox
  id="txtUsername"
  runat="Server" />
<p>
<asp:Button
  Text="Retrieve!"
  OnClick="Button_Click"
  runat="Server" />
<hr>
Password:
<br><asp:Label
  id="txtPassword"
  EnableViewState="False"
  runat="Server" />
<p>
Email:
<br><asp:Label
  id="txtEmail"
  EnableViewState="False"
  runat="Server" />
<p>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

The page in Listing 25.13 contains a form that enables you to enter a username. If you submit a username, the Button_Click subroutine is executed.

The Button_Click subroutine retrieves the UserInfo field that matches the username entered into the form from the Userlist database table. Next, the UserInfo field is converted into a MemoryStream. The BinaryFormatter is then used to deserialize the UserInfo field.

Finally, the Password and Email properties of the UserInfo class are read and assigned to two Label controls. The UserInfo class has been fully recovered from the database table.

CAUTION

If you declare a class in one ASP.NET page, and copy and paste the code for the class to another ASP.NET page, the two classes are not the same class. To serialize and deserialize a class, you must declare the class in a single, separate file.

Using XML Serialization

The XmlSerializer enables you to serialize and deserialize an object, or group of objects, into an XML format. The XMLSerializer is different from the BinaryFormatter in two important respects.

First, the XmlSerializer only captures the public members of a class. The states of any private members, such as private properties, are not persisted.

Second, because the XmlSerializer serializes a class into an XML format, you can read the output of the XmlSerializer. In other words, the end result of XML serialization is a human-readable XML document.

NOTE

The XMLSerializer is used, behind the scenes, by ASP.NET XML Web Services to transmit messages across the network. For more information on Web Services see Part VI of this book, "Building ASP.NET Web Services."

Serializing a Class

The general method for serializing and deserializing a class to XML is to create an instance of the XmlSerializer class and call its Serialize and Deserialize methods. You can serialize a class to persistent mediums, such as files and databases.

For example, the page in Listing 25.14 serializes a class that represents a shopping cart to an XML file (see Figure 25.3).

Listing 25.14 SerializeXmlClass.aspx
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.Xml" %>
<%@ Import Namespace="System.Xml.Serialization" %>

<Script runat="Server">

Public Class ShoppingCart
  Public Username As String
  Public CreditCardNumber As String
  Public CartItems() As CartItem
End Class

Public Class CartItem
  Public ProductName As String
  Public ProductPrice As Decimal
End Class

Sub Button_Click( s As Object, e As EventArgs )
  Dim objShoppingCart As ShoppingCart
  Dim colCartItems As ArrayList
  Dim objCartItem As CartItem
  Dim objItem As ListItem
  Dim objStreamWriter As StreamWriter
  Dim objXMLSerializer As XMLSerializer

  ' Create the Shopping Cart
  objShoppingCart = New ShoppingCart
  objShoppingCart.Username = txtUsername.Text
  objShoppingCart.CreditCardNumber = txtCCnumber.Text
  colCartItems = New ArrayList
  For Each objItem in lstCart.Items
    objCartItem = New CartItem
    objCartItem.ProductName = objItem.Text
    objCartItem.ProductPrice = objItem.Value
    colCartItems.Add( objCartItem )
  Next
  objShoppingCart.CartItems = colCartItems.ToArray( GetType( CartItem ) )

  ' Save to XML File
  objStreamWriter = File.CreateText( MapPath( "ShoppingCart.xml" ) )
  objXMLSerializer = New XMLSerializer( GetType( ShoppingCart ) )
  objXMLSerializer.Serialize( objStreamWriter, objShoppingCart )
  objStreamWriter.Close
End Sub
</Script>

<html>
<head><title>SerializeXMLClass.aspx</title></head>
<body>
<form runat="Server">

Username:
<br><asp:TextBox
  id="txtUsername"
  runat="Server" />
<p>
Credit Card Number:
<br><asp:TextBox
  id="txtCCnumber"
  runat="Server" />
<p>
<asp:ListBox
  id="lstCart"
  Runat="Server" >
<asp:ListItem Text="Bread" Value="12.34" />
<asp:ListItem Text="Milk" Value="1.99" />
<asp:ListItem Text="Beer" Value="19" />
</asp:ListBox>
<p>
<asp:Button
  Text="Save!"
  OnClick="Button_Click"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 25.3. Serializing a class to an XML file.

The page in Listing 25.14 contains declarations for two classes. The first class, named ShoppingCart, is used to represent a user's shopping cart. It contains public properties that represent a username, credit card number, and list of items.

Another class named CartItem represents each item in the shopping cart. The CartItem class has public properties that represent the name and price of each product.

When you submit the form in Listing 25.14, the Button_Click subroutine is executed. The Button_Click subroutine creates an instance of the ShoppingCart class that represents all the information in the form. The instance of the ShoppingCart class is serialized to a file named ShoppingCart.xml by calling the Serialize method of the XmlSerializer class.

You should notice that the XmlSerializer is initialized with the following line of code:

objXMLSerializer = New XMLSerializer( GetType( ShoppingCart ) )

When you instantiate the XmlSerializer, you must supply the type of the root class being serialized. In Listing 25.14, two classes are serialized: the ShoppingCart and CartItem classes. Because the ShoppingCart class contains instances of the CartItem class, you instantiate the XmlSerializer with the type of the ShoppingCart class.

The process of deserializing a class from XML is similar to the process of serializing a class. The page in Listing 25.15 illustrates how the ShoppingCart class can be recovered from the file system (see Figure 25.4).

Listing 25.15 DeserializeXmlClass.aspx
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.Xml" %>
<%@ Import Namespace="System.Xml.Serialization" %>

<Script runat="Server">

Public Class ShoppingCart
  Public Username As String
  Public CreditCardNumber As String
  Public CartItems() As CartItem
End Class

Public Class CartItem
  Public ProductName As String
  Public ProductPrice As Decimal
End Class

Sub Page_Load
  Dim objShoppingCart As ShoppingCart
  Dim objItem As CartItem
  Dim objStreamReader As StreamReader
  Dim objXMLSerializer As XMLSerializer

  ' Retrieve XML File
  objStreamReader = File.OpenText( MapPath( "ShoppingCart.xml" ) )
  objXMLSerializer = New XMLSerializer( GetType( ShoppingCart ) )
  objShoppingCart = CType( _
    objXMLSerializer.Deserialize( objStreamReader ), _
    ShoppingCart )
  objStreamReader.Close()

  ' Display Shopping Cart
  txtUsername.Text = objShoppingCart.Username
  txtCCnumber.Text = objShoppingCart.CreditCardNumber
  For Each objItem in objShoppingCart.CartItems
    lblCart.Text &= "<li>" & objItem.ProductName
    lblCart.Text &= "-" & objItem.ProductPrice.ToString( "c" )
  Next
End Sub

</Script>

<html>
<head><title>DeserializeXMLClass.aspx</title></head>
<body>
<form runat="Server">

Username:
<br><asp:Label
  id="txtUsername"
  runat="Server" />
<p>
Credit Card Number:
<br><asp:Label
  id="txtCCnumber"
  runat="Server" />
<p>
<asp:Label
  id="lblCart"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 25.4. Deserializing a class from an XML file.

In the Page_Load subroutine in Listing 25.15, a FileStream is opened to the ShoppingCart.xml file. Next, an instance of the XmlSerializer is instantiated. The Deserialize method is called to retrieve the ShoppingCart from the FileStream.

Finally, the properties of the ShoppingCart are displayed with ASP.NET Label controls.

NOTE

Notice that the ShoppingCart and CartItem classes are defined separately within Listing 25.14 and Listing 25.15. Because the XmlSerializer does not store type information, the class used to serialize an object does not have to be the same as the class used to deserialize the same object.

Controlling Serialization

In the previous section, you serialized a class representing a shopping cart to the file system. The serialized ShoppingCart class is contained in Listing 25.16.

Listing 25.16 ShoppingCart.xml
[View full width]
<?xml version="1.0" encoding="utf-8"?>
<ShoppingCart xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www
  .w3.org/2001/XMLSchema">
  <Username>Edwin C. Orr</Username>
  <CreditCardNumber>5555555555555555</CreditCardNumber>
  <CartItems>
    <CartItem>
      <ProductName>Bread</ProductName>
      <ProductPrice>12.34</ProductPrice>
    </CartItem>
    <CartItem>
      <ProductName>Milk</ProductName>
      <ProductPrice>1.99</ProductPrice>
    </CartItem>
    <CartItem>
      <ProductName>Beer</ProductName>
      <ProductPrice>19</ProductPrice>
    </CartItem>
  </CartItems>
</ShoppingCart>

The C# version of this code can be found on the CD-ROM.

If you look closely at the XML file in Listing 25.16, you'll notice that each of the properties of the ShoppingCart class and CartItem class are represented as separate XML elements. When you create an XML document, you must make a choice about whether a property should be represented by an XML element or an XML attribute. Some people prefer to represent individual properties as elements, while other people prefer attributes.

By default, the XmlSerializer converts class properties to XML elements. However, you can override this default behavior by supplying a special attribute when declaring properties of a class. You can use any of the following attributes to control exactly how a class is serialized or deserialized to XML:

  • XmlArrayAttribute— Indicates that a property that represents an array should be serialized as an XML array.

  • XmlArrayItemAttribute— Used with the XmlArrayAttribute to indicate how elements of an array should be serialized.

  • XmlAttributeAttribute— Indicates that a property should be serialized as an XML attribute.

  • XmlElementAttribute— Indicates that a property should be serialized as an XML element.

  • XmlEnumAttribute— Indicates how enumerations should be serialized

  • XmlIgnoreAttribute— Indicates that the property should not be serialized.

  • XmlIncludeAttribute— Indicates that the property should be serialized.

  • XmlRootAttribute— Indicates that a class should be serialized as the root element of an XML document.

  • XmlTextAttribute— Indicates that the property should be serialized as XML text.

  • XmlTypeAttribute— Indicates that the property should be serialized as an XML type.

For example, if the ShoppingCart and CartItem classes are declared like this:

<XmlRootAttribute("UserShoppingCart")> Public Class ShoppingCart
  <XmlAttributeAttribute()> Public Username As String
  <XmlIgnoreAttribute()> Public CreditCardNumber As String
  Public CartItems() As CartItem
End Class

Public Class CartItem
  <XmlAttributeAttribute()> Public ProductName As String
  <XmlAttributeAttribute()> Public ProductPrice As Decimal
End Class

then the classes will be serialized like this:

[View full width]

<?xml version="1.0" encoding="utf-8"?>
<UserShoppingCart xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http:/
  /www.w3.org/2001/XMLSchema"
  Username="Edwin C Orr">
  <CartItems>
    <CartItem ProductName="Bread" ProductPrice="12.34" />
    <CartItem ProductName="Milk" ProductPrice="1.99" />
    <CartItem ProductName="Beer" ProductPrice="19" />
  </CartItems>
</UserShoppingCart>

Notice that the Username, ProductName, and ProductPrice properties are serialized as XML attributes instead of as elements. The CreditCardNumber property is ignored.


Summary

In this chapter, you learned how to work with the file system. First, you learned how to create and read both text and binary files. You also learned how to retrieve a list of files in a directory and manipulate file path information.

Next, you learned how to serialize objects to persistent mediums, such as the file system or a database. You learned how to use the BinaryFormatter to save an ArrayList to a file. You also learned how to use the BinaryFormatter to serialize a custom class to a database table.

You were also provided with an overview of the XmlSerializer class. You learned how to use the XmlSerializer to serialize and deserialize a custom class to an XML document. You also learned how to control exactly how a class is serialized by using attributes.


Chapter 26. Sending Email and Accessing the Network

IN THIS CHAPTER

  • Sending Email From an ASP.NET Page

  • Using Message Queuing

  • Accessing Other Web Sites with the HTTP Protocol

In this chapter I discuss three topics related to transporting messages over a network. In the first section, you'll learn how to send email from an ASP.NET page. You'll learn how to create and send both text and HTML messages.

Next, you'll be provided with an overview of Microsoft Message Queuing. You'll learn how to use Message Queuing to reliably transfer messages from server to server over a network.

Finally, in the final section, you'll learn how to use the WebClient, HttpWebRequest, and HttpWebResponse classes to simulate both HTTP GET and POST requests. You can use these classes, for example, to retrieve the contents of a Web page from a remote Web site.


Sending Email from an ASP.NET Page

There are several situations in which you will need to send email messages from your ASP.NET applications. For example, you might want to send a confirmation message to a new user after the user has registered. Or, you might want to automatically send a document to a user as a mail attachment.

The ASP.NET Framework includes two classes for sending email: the SmtpMail class and the MailMessage class. These classes were designed to work with the local Microsoft SMTP Service (included as a component of Windows 2000 and Windows XP Professional) or any other email server on your network. In the following sections, you'll be provided with an overview of the Microsoft SMTP Service. Next, you'll learn how to send a simple email message with the SMTP Service. Finally, more advanced topics, such as sending email with attachments and sending HTML email are discussed.

The Microsoft SMTP Service

The Microsoft SMTP Service does the basic job of sending and retrieving email in accordance with the Simple Mail Transport Protocol (SMTP). In some ways it is very limited. The service does not support the Post Office Protocol (POP). This means that you cannot use the service to create mailbox accounts for multiple users or retrieve email from the service using an email client, such as Outlook or Eudora. If you need to create a full-blown email system that goes beyond support for basic SMTP, you will need to invest in additional software, such as Microsoft Exchange Server or Software.com's Post.Office (see http://www.software.com).

On the positive side, the Microsoft SMTP Service is valuable for sending automated messages from your Web site. The service can support sending thousands of email messages a day. For example, you can use it to send automatic notification messages to the users of your Web site.

Configuring the Microsoft SMTP Service

To check whether the SMTP Service is installed and running on your server, open the Internet Services Manager and see whether an icon labeled Default SMTP Virtual Server is listed beneath the name of your server (see Figure 26.1). If the service is installed but not running, start the service by selecting Action, Start.

Figure 26.1. The Default SMTP Virtual Server.

A single computer can have multiple SMTP Virtual Servers. Each SMTP Virtual Server is identified by an IP address and domain name. An SMTP Virtual Server can contain one default local domain and zero or more alias domains.

When the SMTP Service is installed, a single default local domain is created. The name of the default local domain determines how the service delivers messages. If the service receives an email message that is addressed to the same domain as the default local domain, it is delivered locally; otherwise, the service attempts to send it somewhere else.

NOTE

The name of the default local domain is determined by the name designated on the DNS tab for the TCP/IP protocol in the Network application in the Control Panel.

You can create multiple SMTP Virtual Servers on the same server. This is useful when you need to host completely different Web sites on the same computer. Each SMTP Virtual Server can handle email sent to different IP addresses or domain names. To create a new SMTP Virtual Server, right-click on the icon labeled Default SMTP Virtual Server and choose New, Virtual Server.

Finally, you can optionally create alias domains for a single SMTP Virtual Server. Alias domains are useful when your Web site has multiple domain names that point to the same site. When an email message is sent to an alias domain, the message is treated as if it were sent to the default local domain. To create an alias domain, right-click on the icon labeled Domains and choose New, Domain. These additional alias domains must all share the same IP address and Drop directory (see the next section, "How the Microsoft SMTP Service Works") as the default local domain.

You can monitor the performance of the SMTP Service by examining its log files. By default, the service keeps a log of its activity in the \WINNT\System32\LogFiles directory. This directory can be changed by clicking the SMTP Virtual Server's property sheet and editing the entry labeled Log File Directory. You can open and examine the SMTP Service's log files in any standard text editor such as Notepad.

How the Microsoft SMTP Service Works

From a user's perspective, the SMTP Service is a simple component. The service uses two main directories, named Pickup and Drop, to process email. Both of these directories are located, by default, under the InetPub\MailRoot directory.

The Pickup directory is used to send email. The service constantly monitors the Pickup directory for new email messages. Whenever it finds an email message, the service attempts to send it. If the service is unable to immediately deliver the message, it is kept in the Queue directory while the service attempts to keep delivering the message. If the email message cannot be delivered and cannot be returned to the sender, the message is moved to the Badmail directory.

The Drop directory is used to receive email. When email messages are received by the SMTP Service, the service writes out the messages to the Drop directory. It doesn't do anything else with them; they just stay there. Again, the Microsoft SMTP Service does not support multiple mailboxes.

The email messages in these two directories are nothing more than text files. For example, you can send an email message by opening Notepad, entering the following text, and saving the file in the Pickup directory (Inputpub\mailroot\pickup):

To: Webmaster@Superexpert.com
From: someone@somewhere.com
Subject: Testing SMTP Service

Here is the message!

As soon as you save the file, the file should disappear from the Pickup directory because the SMTP Service is attempting to send it.

In theory, you could use the classes from the System.IO namespace from within your ASP.NET pages to write messages to the Pickup directory and send email (see Chapter 25, "Working with the File System"). In practice, however, this is not a good strategy. You can send email more reliably and easily by using the SmtpMail and MailMessage classes.

Sending a Simple Email Message

You can use the SmtpMail class to send an email message from within an ASP.NET page with a single line of code. To send an email with the SmtpMail class, use the Send method:

SmtpMail.Send( _
  "jane@somewhere.com", _
  "webmaster@Superexpert.com", _
  "Testing Mail!", _
  "Just sending a test message!" )

This statement sends an email message from jane@somewhere.com to webmaster@Superexpert.com. The subject of the message is Testing Mail! and the body of the message is Just sending a test message!.

NOTE

Both the SmtpMail and MailMessage classes are found in the System.Web.Mail namespace. Before you can use these classes, you must import the System.Web.Mail namespace into your page.

By default, the SmtpMail class uses the local email server to send email messages. You can use another email server on your network by setting the shared SmtpServer property of the SmtpMail class before sending a message. For example, to use the email server located at MyEmailServer.com, you would use the statement:

SmtpMail.SmtpServer = "MyEmailServer.com"

The ASP.NET page in Listing 26.1 contains a simple form that enables you to send any email message (see Figure 26.2).

Listing 26.1 SendMail.aspx
<%@ Import Namespace="System.Web.Mail" %>

<Script runat="Server">
Sub Button_Click( s As Object, e As EventArgs )
  SmtpMail.Send( _
    mailfrom.Text, _
    mailto.Text, _
    mailsubject.Text, _
    mailbody.Text )
End Sub
</Script>

<html>
<head><title>SendMail.aspx</title></head>
<body>

<h1>Send Mail:</h1>
<form runat="Server">

<b>From:</b>
<br>
<asp:TextBox
  ID="mailfrom"
  Columns="50"
  Runat="Server" />
<p>
<b>To:</b>
<br>
<asp:TextBox
  ID="mailto"
  Columns="50"
  Runat="Server" />
<p>
<b>Subject:</b>
<br>
<asp:TextBox
  ID="mailsubject"
  Columns="50"
  Runat="Server" />
<p>
<b>Body:</b>
<br>
<asp:TextBox
  ID="mailbody"
  TextMode="Multiline"
  Columns="50"
  Rows="10"
  Runat="Server" />
<p>
<asp:Button
  Text="Send!"
  OnClick="Button_Click"
  Runat="Server" />
</form>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 26.2. Sending email from a form.

When you submit the form in Listing 26.1, the Button_Click subroutine executes. This subroutine grabs all the values entered into the form fields and sends an email message with those values.

NOTE

If you are sending a long message, you will probably need to include line breaks in the message body. By default, an email message does not use HTML, so you cannot use the <BR> or <P> HTML tags to create a line break. To create a line break, use the Visual Basic constant, vbCRLF, which adds a carriage return and line feed.

Using the MailMessage Class

As shown in the previous section, you can use the Send method of the SmtpMail class to send simple email messages. However, if you need to set particular properties of an email message, then you'll need to create an instance of the MailMessage class.

The MailMessage class has the following properties:

  • Attachments— Returns a collection representing all the files attached to the message.

  • Bcc— Gets or sets the Blind Copy field of the email message.

  • Body— Gets or sets the body of the email message.

  • BodyEncoding— Gets or sets a value from the MailEncoding enumeration that specifies the method of encoding the message (possible values are Base64 and UUEncode).

  • BodyFormat— Gets or sets a value from the MailFormat enumeration that specifies the format of the body of the message (possible values are Text and Html).

  • Cc— Gets or sets the carbon copy field of the email message.

  • From— Gets or sets the From field of the email message.

  • Headers— Returns a collection of headers used by the email message.

  • Priority— Gets or sets a value from the MailPriority enumeration that specifies the priority of the message (possible values are High, Low, and Normal).

  • Subject— Gets or sets the Subject field of the email message.

  • To— Gets or sets the To field of the email message.

  • UrlContentBase— Gets or sets the Content Base header of an HTML email message. This property is used with the UrlContentLocation property to resolve relative URLs in the body of a message.

  • UrlContentLocation— Gets or sets the Content Location header of an HTML email message. This property can contain an absolute URL or a relative URL. When UrlContentLocation contains a relative URL, it must be used in conjunction with the UrlContentBase property.

For example, imagine that you need to send a high-priority email message to Jane@yourcompany.com and you want to send a blind carbon copy of the message to your supervisor at supervisor@yourcompany.com. You could send this message with the following statements:

Dim objMailMessage As MailMessage

objMailMessage = New MailMessage
objMailMessage.From = "you@yourcompany.com"
objMailMessage.To = "jane@yourcompany.com"
objMailMessage.Subject = "Meeting location changed!"
objMailMessage.Body = "The meeting will be in the Salmon room"
objMailMessage.Priority = MailPriority.High
objMailMessage.Bcc = "supervisor@yourcompany.com"

SmtpMail.Send( objMailMessage )

First, an instance of the MailMessage class is created and several of its properties are set. Next, the MailMessage is sent with the Send method of the SmtpMail class.

Adding Attachments to an Email Message

You can attach one or more files to an email message by using the MailAttachment class. The MailAttachment class represents a particular email attachment. This class has the following two properties:

  • Filename— The path of a file to attach to the email message.

  • Encoding— A value from the MailEncoding enumeration that specifies how the attached file should be encoded (possible values are Base64 and UUEncode).

You can set these properties on an instance of the MailAttachment class. Alternatively, and more conveniently, you can specify the Filename and Encoding parameters when initializing the class through its constructor like this:

objMailMessage = New MailAttachment( "c:\somefile.zip" )

or

objMailMessage = New MailAttachment( "c:\somefile.zip", MailEncoding.UUEncode )

The first statement initializes an instance of the MailAttachment class with the path to a file. The second statement initializes an instance of the MailAttachment class with both a path to a file and an encoding format.

For example, the ASP.NET page in Listing 26.2 attaches a Microsoft Word document named BizPlan.doc to an email message.

Listing 26.2 SendAttachment.aspx
<%@ Import Namespace="System.Web.Mail" %>
<%
Dim objMailMessage As MailMessage
Dim objMailAttachment As MailAttachment

' Create the Mail Attachment
objMailAttachment = New MailAttachment( "c:\BizPlan.doc" )

' Create the Mail Message
objMailMessage = New MailMessage
objMailMessage.From = "you@somewhere.com"
objMailMessage.To = "joe@somewhere.com"
objMailMessage.Subject = "Secret Business Plan"
objMailMessage.Body = "Here's the plan (don't show it to anyone!)"
objMailMessage.Attachments.Add( objMailAttachment )

' Send the Mail Message
SmtpMail.Send( objMailMessage )
%>

BizPlan Sent!

The C# version of this code can be found on the CD-ROM.

When you execute the ASP.NET page in Listing 26.2, the BizPlan.doc file is emailed to joe@somewhere.com.

Sending HTML Email

By default, the SmtpMail class sends email messages as plain text. However, you also have the option to send the body of an email as HTML. If you send an email as an HTML message, then you can include HTML formatting and images in the body of the message.

CAUTION

Not all email clients support HTML email messages. Even worse, some email clients support some HTML tags and not others.

To send an HTML email, set the BodyFormat property of the MailMessage class like this:

objMailMessage.BodyFormat = MailFormat.Html

The page in Listing 26.3 illustrates how to send an HTML email.

Listing 26.3 SendHtml.aspx
<%@ Import Namespace="System.Web.Mail" %>
<%
Dim objMailMessage As MailMessage
Dim strHTMLBody As String

' Create the HTML Message Body
strHTMLBody = "<html><head>" & _
  "<title>Thank You!</title>" & _
  "</head><body bgcolor=lightblue>" & _
  "<font face=Script size=6>" & _
  "Thank you for registering!" & _
  "</font></body></html>"

' Create the Mail Message
objMailMessage = New MailMessage
objMailMessage.From = "you@somewhere.com"
objMailMessage.To = "joe@somewhere.com"
objMailMessage.Subject = "Thanks for Registering!"
objMailMessage.Body = strHTMLBody
objMailMessage.BodyFormat = MailFormat.HTML

' Send the Mail Message
SmtpMail.Send( objMailMessage )
%>
HTML Email Sent!

The C# version of this code can be found on the CD-ROM.

When you request the ASP.NET page in Listing 26.3, an email message containing a light blue background and a script type face is sent (see Figure 26.3).

Figure 26.3. Sending HTML email.

Writing text with HTML formatting can be messy. Listing 26.3 created a string variable named strHTMLBody that contained the entire HTML for the body of the message. There is a better way to work with HTML in the ASP.NET Framework. You can manipulate HTML with the HtmlTextWriter.

NOTE

To learn more about the HtmlTextWriter, see Chapter 28, "Developing Custom Controls."

The page in Listing 26.4 illustrates how you can use the HtmlTextWriter to more cleanly build the body of an HTML message.

Listing 26.4 HtmlTextWriter.aspx
<%@ Import Namespace="System.Web.Mail" %>
<%@ Import Namespace="System.IO" %>
<%

Dim objMailMessage As MailMessage
Dim swHtmlBody As StringWriter
Dim twTextWriter As HtmlTextWriter

swHtmlBody = New StringWriter
twTextWriter = New HtmlTextWriter( swHtmlBody )

twTextWriter.RenderBeginTag( "html" )
twTextWriter.RenderBeginTag( "head" )
twTextWriter.RenderBeginTag( "title" )
twTextWriter.Write( "Thank You!" )
twTextWriter.RenderEndTag
twTextWriter.RenderEndTag
twTextWriter.AddAttribute( "bgcolor", "lightblue" )
twTextWriter.RenderBeginTag( "body" )
twTextWriter.AddAttribute( "face", "Script" )
twTextWriter.AddAttribute( "size", "6" )
twTextWriter.RenderBeginTag( "font" )
twTextWriter.WriteLine( "Thank you for registering!" )
twTextWriter.RenderEndTag
twTextWriter.RenderEndTag
twTextWriter.RenderEndTag

' Create the Mail Message
objMailMessage = New MailMessage
objMailMessage.From = "you@somewhere.com"
objMailMessage.To = "joe@somewhere.com"
objMailMessage.Subject = "Thanks for Registering!"
objMailMessage.Body = swHtmlBody.ToString
objMailMessage.BodyFormat = MailFormat.HTML

' Send the Mail Message
SmtpMail.Send( objMailMessage )
%>
HTML Email Sent!

The C# version of this code can be found on the CD-ROM.

Using Links and Images in HTML Email

When including images or links in an HTML email, you must specify the link as an absolute path—otherwise, the email client won't have enough information to find the proper Web site. In other words, you must use an address like this:

http://www.yourdomain.com/somedir/thepage.aspx

rather than the shorter relative address:

/somedir/thepage.aspx

You can get around this limitation by using the UrlContentBase and UrlContentLocation properties of the MailMessage class. If you assign an absolute URL to the UrlContentLocation property, then all URLs in the body of the message are interpreted relative to that URL.

For example, if you assign a value to the UrlContentLocation property like this:

objMailMessage.UrlContentLocation = "http://www.yourdomain.com/images/"

then you can use the following IMG tag in the body of the email message:

<img src="aspnet.gif">

The path of the image specified in the IMG tag would be resolved to:

http://www.yourdomain.com/images/aspnet.gif

If you assign a relative URL to the UrlContentLocation property, then you must also assign a value to the UrlContentBase property. The combined values of the UrlContentLocation and UrlContentBase properties would be used to construct an absolute URL.

The page in Listing 26.5 demonstrates how you can send an image and resolve the location of the image with the UrlContentLocation property.

Listing 26.5 SendImage.aspx
<%@ Import Namespace="System.Web.Mail" %>
<%@ Import Namespace="System.IO" %>
<%
Dim objMailMessage As MailMessage
Dim swHTMLBody As StringWriter
Dim twTextWriter As HtmlTextWriter

swHTMLBody = New StringWriter
twTextWriter = New HtmlTextWriter( swHTMLBody )

twTextWriter.RenderBeginTag( "html" )
twTextWriter.RenderBeginTag( "head" )
twTextWriter.RenderBeginTag( "title" )
twTextWriter.Write( "Thank You!" )
twTextWriter.RenderEndTag
twTextWriter.RenderEndTag
twTextWriter.AddAttribute( "bgcolor", "lightblue" )
twTextWriter.RenderBeginTag( "body" )
twTextWriter.AddAttribute( "src", "aspnet.gif" )
twTextWriter.RenderBeginTag( "img" )
twTextWriter.RenderEndTag
twTextWriter.RenderEndTag
twTextWriter.RenderEndTag

' Create the Mail Message
objMailMessage = New MailMessage
objMailMessage.From = "you@somewhere.com"
objMailMessage.To = "joe@somewhere.com"
objMailMessage.Subject = "Here's the image!"
objMailMessage.Body = swHTMLBody.ToString
objMailMessage.BodyFormat = MailFormat.HTML
objMailMessage.UrlContentLocation = "http://www.yourdomain.com/"

' Send the Mail Message
SmtpMail.Send( objMailMessage )
%>
Image Sent!

The C# version of this code can be found on the CD-ROM.

In Listing 26.5, an image named aspnet.gif is included in the body of the email message. Because the UrlContentLocation property is set, the absolute path to the image on the origin Web site can be resolved (http://www.yourdomain.com/aspnet.gif).


Using Message Queuing

Email is designed for human-to-human communication. Microsoft Message Queuing, on the other hand, is designed for application-to-application communication. You can use Microsoft Message Queuing to reliably send messages from one computer to another.

Imagine, for example, that you have a central server that stores product orders. The other servers in your organization can use Microsoft Message Queuing to send orders to this central computer. If, for whatever reasons the central computer fails, the messages will not be lost. The orders will wait patiently in the queue until the central server is available again.

You can send simple and complex messages with Microsoft Message Queuing with the same ease. You can use Microsoft Message Queuing to send strings, numbers, or complex custom Visual Basic classes.

Microsoft Message Queuing supports such advanced functionality as message priorities, transactional messages, message acknowledgements, and message encryption and authentication.

Message Queuing Versus XML Web Services

So, when should you use Message Queuing and when should you use Web Services?

Both Message Queuing and Web Services facilitate application-to-application communication. You can use either technology to send complex messages over a network.

However, Web Services, unlike Message Queuing, is based on open standards like HTTP and SOAP. A Web Service facilitates application-to-application communication even when the applications are built on different platforms and operating systems.

In some ways, however, Microsoft Message Queuing is a more mature technology. Microsoft Message Queuing, unlike Web Services, has built-in support for such things as authentication, message priorities, and guaranteed message delivery.

There is no reason why you can't combine the functionality of Web Services and Message Queuing. For example, you could create a Web Service that sends and receives messages from Microsoft Message Queuing.

Microsoft Message Queuing is a complex topic. All aspects of this technology aren't covered here; however, in the following sections, you'll be provided with an overview of the basic operations that you can perform with Message Queuing from an ASP.NET page using the .NET messaging classes.

Configuring Microsoft Message Queuing

Microsoft Message Queuing is an optional component of Windows 2000 Server. Before you can use any of the messaging classes described in this section, you'll need to install it. See your Windows Help documentation for detailed installation instructions.

CAUTION

To take advantage of all the features of Microsoft Message Queuing, you'll need to configure Message Queuing on a server that is part of a Windows domain.

After you install Microsoft Message Queuing, you must complete one more step. You must check that your application is configured to have access to the System.Messaging assembly. You can configure the assemblies available to your application in either the machine.config file or a Web.Config file located in a particular application directory. In either case, you must add a reference to the System.Messaging assembly in the <assemblies> configuration section.

The Web.Config file in Listing 26.6 explicitly adds the System.Messaging assembly to your application.

Listing 26.6 Web.Config
[View full width]
<configuration>
  <system.web>
    <compilation>
      <assemblies>
      <add
        assembly="System.Messaging, Version=1.0.2411.0, Culture=neutral,
   PublicKeyToken=b03f5f7f11d50a3a" />
      </assemblies>
    </compilation>
  </system.web>
</configuration>

The C# version of this code can be found on the CD-ROM.

CAUTION

Most likely, you will have a different version of the System.Messaging assembly installed on your computer. In that case, the Web.Config file described in this section won't work. To verify the version and public key token of your System.Messaging assembly, execute the following statement from a command prompt:

gacutil /l

This command lists the properties of every assembly installed in your server's global assembly cache.

Working with Message Queues

A queue is a temporary storage mechanism for messages while they are in transit from one application to another. Applications can send messages to a queue and receive messages from a queue. For example, you might want to create a queue called OrdersQueue that is used to transmit product orders from a Web server to a central orders database server.

Microsoft Message Queuing supports two types of user queues: public queues, which are available to applications on every server in a domain, and private queues, which are available only to applications on the local server.

Microsoft Message Queuing is also configured with several system queues, such as the dead-letter queues and journal queues. A dead-letter queue is used to hold messages that could not be successfully delivered. A journal queue is used to keep copies of messages.

Retrieving a List of Queues

The classes in the System.Messaging namespace include a number of different methods for retrieving lists of existing queues. The page in Listing 26.7 uses one of these methods, the GetPublicQueues() method, to retrieve a list of all the public queues on the network.

Listing 26.7 GetPublicQueues.aspx
<%@ Import Namespace="System.Messaging" %>

<Script runat="Server">

Sub Page_Load
  dgrdQueues.DataSource = MessageQueue.GetPublicQueues()
  dgrdQueues.DataBind()
End Sub

</Script>

<html>
<head><title>GetPublicQueues.aspx</title></head>
<body>

<asp:DataGrid
  id="dgrdQueues"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

The first line in Listing 26.7 is used to import the System.Messaging namespace. Next, in the Page_Load subroutine, an array of public queues is retrieved from the GetPublicQueues() method and assigned to a DataGrid.

Creating a New Queue

You can create new public and private queues. In either case, you use the CreateQueue() method of the MessageQueue class.

The page in Listing 26.8 creates a new public queue named OrdersQueue.

Listing 26.8 CreatePublicQueue.aspx
<%@ Import Namespace="System.Messaging" %>

<Script runat="Server">

Sub Page_Load
  If Not MessageQueue.Exists( ".\OrdersQueue" ) Then
    MessageQueue.Create( ".\OrdersQueue" )
  End If
End Sub

</Script>

<html>
<head><title>CreatePublicQueue.aspx</title></head>
<body>

<h2>OrdersQueue Created!</h2>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

In Listing 26.8, the OrdersQueue is created with the string .\OrdersQueue. The period is shorthand for the name of the local machine. Instead of using a period, you can specify a machine name.

If you want to create a new private queue, then you need to add the special string Private$ when supplying the path to the queue. For example, the page in Listing 26.9 creates a new private queue named PrivateOrders.

Listing 26.9 CreatePrivateQueue.aspx
<%@ Import Namespace="System.Messaging" %>

<Script runat="Server">

Sub Page_Load
  If Not MessageQueue.Exists( ".\Private$\PrivateOrders" ) Then
    MessageQueue.Create( ".\Private$\PrivateOrders" )
  End If
End Sub

</Script>

<html>
<head><title>CreatePrivateQueue.aspx</title></head>
<body>

<h2>PrivateOrders Created!</h2>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Sending Messages to a Queue

You can use the Send() method of the MessageQueue class to add a new message to a queue. You can use the Send() method regardless of whether you are sending a message that consists of a simple string or you are sending a message that contains an instance of a custom Visual Basic class.

The page in Listing 26.10 sends messages to a queue named myQueue.

Listing 26.10 SendQueue.aspx
<%@ Import Namespace="System.Messaging" %>

<Script runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  Dim mqMessages As MessageQueue

  If Not MessageQueue.Exists( ".\myQueue" ) Then
    MessageQueue.Create( ".\myQueue" )
  End If

  mqMessages = New MessageQueue( ".\myQueue" )
  mqMessages.Send( txtMessageBody.Text, txtMessageLabel.Text )
  txtMessageLabel.Text = ""
  txtMessageBody.Text = ""
End Sub

</Script>

<html>
<head><title>SendQueue.aspx</title></head>
<body>

<h2>Send Message to Message Queue</h2>
<form runat="Server">
<b>Message Label</b>
<br>
<asp:TextBox
  id="txtMessageLabel"
  Runat="Server" />
<p>
<b>Message Body</b>
<br>
<asp:TextBox
  ID="txtMessageBody"
  TextMode="Multiline"
  Columns="50"
  Rows="10"
  Runat="Server" />
<br>
<asp:Button
  Text="Send Message!"
  OnClick="Button_Click"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

The page in Listing 26.10 contains a form with two TextBox controls (see Figure 26.4). The first text box is for a message label and the second text box is for a message body. When you submit the form, the Button_Click subroutine is executed.

Figure 26.4. Sending a message to the queue.

The Button_Click subroutine creates an instance of the MessageQueue class. The Send() method is called to add a new message to the myQueue message. The new message is created with the label from the txtMessageLabel control and the body from the txtMessageBody control.

If you need more fine-grained control over the properties of a message that you send to a queue, then you should explicitly create an instance of the Message class. For example, you can use the properties of the Message class to specify a message's priority, whether an acknowledgement should be returned for a message, and a message timeout.

The page in Listing 26.11 sends three messages with three different priorities.

Listing 26.11 MessagePriorities.aspx
<%@ Import Namespace="System.Messaging" %>

<Script runat="Server">

Sub Page_Load
  Dim mqMessages As MessageQueue
  Dim mqMessage As Message

  If Not MessageQueue.Exists( ".\myQueue" ) Then
    MessageQueue.Create( ".\myQueue" )
  End If

  mqMessages = New MessageQueue( ".\myQueue" )

  ' Send Message 1
  mqMessage = New Message()
  mqMessage.Label = "Message 1"
  mqMessage.Body = "This is message 1"
  mqMessage.Priority = MessagePriority.Normal
  mqMessages.Send( mqMessage )

  ' Send Message 2
  mqMessage = New Message()
  mqMessage.Label = "Message 2"
  mqMessage.Body = "This is message 2"
  mqMessage.Priority = MessagePriority.Lowest
  mqMessages.Send( mqMessage )

  ' Send Message 3
  mqMessage = New Message()
  mqMessage.Label = "Message 3"
  mqMessage.Body = "This is message 3"
  mqMessage.Priority = MessagePriority.Highest
  mqMessages.Send( mqMessage )

End Sub

</Script>

<html>
<head><title>MessagePriorities.aspx</title></head>
<body>

<h2>Messages Sent!</h2>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Each of the three messages sent in Listing 26.11 is sent with a different message priority. The third message will be delivered first (because it has the highest priority), followed by the first and second.

Retrieving Messages from a Queue

The classes in the System.Messaging namespace include a number of different methods for retrieving messages from a queue. Some of these methods are synchronous and some are asynchronous. Some of these methods automatically remove a message from a queue, other methods enable you to peek at a message without removing it.

One of the most flexible methods of retrieving messages from a queue is the GetMessageEnumerator() method. This method returns an enumerator that enables you to iterate through all the messages in the queue in order of priority.

For example, the page in Listing 26.12 displays the labels of all the current messages in the myQueue queue.

Listing 26.12 GetmessageEnumerator.aspx
<%@ Import Namespace="System.Messaging" %>

<Script runat="Server">

Sub Page_Load
  Dim enumMessages As MessageEnumerator
  Dim mqMessages As MessageQueue
  Dim mqMessage As Message

  mqMessages = New MessageQueue( ".\myQueue" )
  enumMessages = mqMessages.GetMessageEnumerator()
  While enumMessages.MoveNext
    mqMessage = CType( enumMessages.Current, Message )
    lblMessages.Text &= "<li>" & mqMessage.Label
  End While
End Sub

</Script>

<html>
<head><title>GetMessageEnumerator.aspx</title></head>
<body>

<asp:Label
  id="lblMessages"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

You can also use the enumerator returned by the GetMessageEnumerator() to remove messages from a queue. The page in Listing 26.13 retrieves a single message at a time from the queue. After the message is retrieved and displayed, it's deleted from the queue with the RemoveCurrent() method.

Listing 26.13 RemoveCurrent.aspx
<%@ Import Namespace="System.Messaging" %>

<Script runat="Server">

Sub Page_Load
  Dim enumMessages As MessageEnumerator
  Dim mqMessages As MessageQueue
  Dim mqMessage As Message

  mqMessages = New MessageQueue( ".\myQueue" )
  enumMessages = mqMessages.GetMessageEnumerator()
  If enumMessages.MoveNext Then
    mqMessage = CType( enumMessages.Current, Message )
    mqMessage.Formatter = New XmlMessageFormatter( New String(){ "System.String" } )
    lblMessageLabel.Text = mqMessage.Label
    lblMessageBody.Text = mqMessage.Body
    enumMessages.RemoveCurrent()
  End If
End Sub

</Script>

<html>
<head><title>RemoveCurrent.aspx</title></head>
<body>
<form runat="Server">

<b>Message Label</b>
<br>
<asp:Label
  id="lblMessageLabel"
  EnableViewState="False"
  Runat="Server" />
<p>
<b>Message Body</b>
<br>
<asp:Label
  id="lblMessageBody"
  EnableViewState="False"
  Runat="Server" />
<p>
<asp:Button
  Text="Next Message"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Displaying the Body of a Message

When you create a new message, the body of the message is automatically serialized before the message is added to a queue. By default, the message is serialized using the XmlMessageFormatter. Serialization when you send a message is automatic. You just send the message and all the serialization happens in the background.

NOTE

To learn more about serialization, see Chapter 25, "Working with the File System."

When you receive a message, however, you have to do some work to deserialize the message. You have to tell the XmlMessageFormatter about the types of objects that it should expect to receive.

For example, the page in Listing 26.14 adds two types of messages to a queue. The first type is a string and the second type is a number (a number of type double).

Listing 26.14 TargetTypes.aspx
[View full width]
<%@ Import Namespace="System.Messaging" %>

<Script runat="Server">

Sub Page_Load
  Dim mqMessages As MessageQueue
  Dim enumMessages As MessageEnumerator
  Dim mqMessage As Message

  If Not MessageQueue.Exists( ".\myQueue" ) Then
    MessageQueue.Create( ".\myQueue" )
  End If

  mqMessages = New MessageQueue( ".\myQueue" )

  ' Send String Message
  mqMessages.Send( "This is a string", "Message 1" )

  ' Send Decimal Message
  mqMessages.Send( 12.34, "Message 2" )

  ' Read from Queue
  enumMessages = mqMessages.GetMessageEnumerator()
  While enumMessages.MoveNext
    mqMessage = CType( enumMessages.Current, Message )
    mqMessage.Formatter = New XmlMessageFormatter( New String(){ "System.String", "System
  .Double" } )
    lblMessages.Text &= "<li>" & mqMessage.Label
    lblMessages.Text &= "<br>" & mqMessage.Body
  End While
End Sub

</Script>

<html>
<head><title>TargetTypes.aspx</title></head>
<body>

<asp:Label
  id="lblMessages"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

When each message is returned from the MessageEnumerator in Listing 26.14, an instance of the XmlMessageFormatter object is assigned to the Message object's Formatter property. The XmlMessageFormatter is initialized with the possible types of objects that might need to be deserialized. In this case, the body of the message might contain either a System.String or System.Double.

If the body of a message contains an unexpected type, you'll receive the error illustrated in Figure 26.5.

Figure 26.5. Unrecognized target type.

Sending and Receiving Complex Messages

You can send and retrieve custom objects from a message queue. The only requirement is that the total size of the object not exceed four megabytes.

For example, if you plan to send product orders from a Web site to a central orders database, then it would make sense to create a custom ProductOrder class. Listing 26.15 contains the source code for this class.

Listing 26.15 ProductOrder.vb
Imports System

Namespace myComponents

Public Class ProductOrder
  Public ProductName As String
  Public UnitPrice As Decimal
  Public Quantity As Integer
  Public EntryDate As DateTime
End Class

End Namespace

The C# version of this code can be found on the CD-ROM.

Before you can use the ProductOrder class, you'll need to compile it by executing the following statement from a command prompt:

vbc /t:library ProductOrder.vb

Next, you'll need to copy the compiled assembly (ProductOrder.dll) to your application /bin directory so that it is visible to the ASP.NET pages in your application.

After you create the ProductOrder class, you can use it when sending messages to a queue. The page in Listing 26.16 contains a form that enables you to add new product orders to a queue named ProductOrders.

Listing 26.16 SendProductOrder.aspx
<%@ Import Namespace="myComponents" %>
<%@ Import Namespace="System.Messaging" %>

<Script runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  Dim mqMessages As MessageQueue
  Dim objProduct As ProductOrder

  ' Create Product Order
  objProduct = New ProductOrder()
  objProduct.ProductName = txtProductName.Text
  objProduct.UnitPrice = txtUnitPrice.Text
  objProduct.Quantity = txtQuantity.Text
  objProduct.EntryDate = DateTime.Now()

  ' Check for ProductOrders Queue
  If Not MessageQueue.Exists( ".\ProductOrders" ) Then
    MessageQueue.Create( ".\ProductOrders" )
  End If

  ' Send Product Order
  mqMessages = New MessageQueue( ".\ProductOrders" )
  mqMessages.Send( objProduct, "Product Order" )

  ' Clear Form Fields
  txtProductName.Text = ""
  txtUnitPrice.Text = ""
  txtQuantity.Text = ""
End Sub

</Script>

<html>
<head><title>SendProductOrder.aspx</title></head>
<body>

<h2>Place Product Order</h2>
<form runat="Server">
<b>Product Name</b>
<br>
<asp:TextBox
  id="txtProductName"
  Runat="Server" />
<p>
<b>Unit Price</b>
<br>
<asp:TextBox
  ID="txtUnitPrice"
  Runat="Server" />
<p>
<b>Quantity</b>
<br>
<asp:TextBox
  ID="txtQuantity"
  Runat="Server" />
<p>
<asp:Button
  Text="Place Order!"
  OnClick="Button_Click"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

When you submit a product order with the form in Listing 26.16, an instance of the ProductOrder class is created. Next, the ProductOrder is sent to the ProductOrders queue with the Send() method.

The page in Listing 26.17 illustrates how you can retrieve product orders from the ProductOrders queue.

Listing 26.17 EnumerateProducts.aspx
[View full width]
<%@ Import Namespace="myComponents" %>
<%@ Import Namespace="System.Messaging" %>

<Script runat="Server">

Sub Page_Load
  Dim enumMessages As MessageEnumerator
  Dim mqMessages As MessageQueue
  Dim mqMessage As Message
  Dim objProduct As ProductOrder

  mqMessages = New MessageQueue( ".\ProductOrders" )
  enumMessages = mqMessages.GetMessageEnumerator()
  If enumMessages.MoveNext Then
    mqMessage = CType( enumMessages.Current, Message )
    mqMessage.Formatter = New XmlMessageFormatter( New String(){ "myComponents
  .ProductOrder,ProductOrder" } )
    objProduct = New ProductOrder
    objProduct = mqMessage.Body
    lblProductName.Text = objProduct.ProductName
    lblUnitPrice.Text = objProduct.UnitPrice
    lblQuantity.Text = objProduct.Quantity
    lblEntryDate.Text = objProduct.EntryDate
    enumMessages.RemoveCurrent()
  End If
End Sub

</Script>

<html>
<head><title>EnumerateProducts.aspx</title></head>
<body>
<form runat="Server">

<b>Product Name:</b>
<br>
<asp:Label
  id="lblProductName"
  EnableViewState="False"
  Runat="Server" />
<p>
<b>Unit Price</b>
<br>
<asp:Label
  id="lblUnitPrice"
  EnableViewState="False"
  Runat="Server" />
<p>
<b>Quantity</b>
<br>
<asp:Label
  id="lblQuantity"
  EnableViewState="False"
  Runat="Server" />
<p>
<b>Entry Date</b>
<br>
<asp:Label
  id="lblEntryDate"
  EnableViewState="False"
  Runat="Server" />
<p>
<asp:Button
  Text="Next Order"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM

Notice that the full name of the ProductOrder class, myComponents.ProductOrder, ProductOrder, is used when initializing the XmlMessageFormatter to deserialize the message body. This page simply grabs the first product order off the queue, displays the order, and deletes it from the queue.


Accessing Other Web Sites with HTTP

The .NET Framework contains a rich set of classes for accessing other Web sites across the Internet. You can use these classes to perform such actions as retrieving Web pages from other Web sites, posting form data to other Web sites, and resolving DNS host names and IP addresses.

Why would you want to do this? One common problem that Web sites face is how to share information across the Internet. Suppose, for example, that you are a book distributor and you want to automatically send a list of top-selling books to all your affiliate Web sites. In this situation, you could create a Web page on the main distribution Web site with a list of top-selling books. Your affiliate Web sites could then use the .NET classes described in this section to automatically retrieve this Web page and display the list of top-selling books.

NOTE

A better alternative for distributing information across the Internet is XML Web Services. XML Web Services are described in detail in Part VI of this book, "Building ASP.NET Web Services."

The .NET classes for Internet access can be roughly divided into three groups:

  • The WebClient class— Contains easy-to-use methods for uploading and downloading data from a remote Internet server. Behind the scenes, this class uses the WebRequest and WebResponse classes.

  • The WebRequest and WebResponse classes— Use descendents of these classes for fine-grained control over Internet requests and responses. The HttpWebRequest and HttpWebResponse classes provide support for both synchronous and asynchronous requests using the HTTP protocol. Behind the scenes, these classes rely on the Sockets classes.

  • The Sockets classes— Implements the Windows Socket Interface (Winsock32). Includes a Socket, TcpClient, TcpListener, and UdpClient class.

All the Internet access classes can be found in the System.Net and System.Net.Sockets namespaces.

Using the WebClient Class

The easiest way to grab a page off another Web server is to use the WebClient class. The WebClient class has three methods for downloading data:

  • DownloadData— Returns a byte array from an Internet address.

  • DownloadFile— Saves a file to the file system retrieved from an Internet address.

  • OpenRead— Returns a stream from an Internet address.

For example, the page in Listing 26.18 retrieves the home page from the Amazon.com Web site, replaces any image tags with the string [image], and displays the altered page (see Figure 26.6).

Listing 26.18 StripImage.aspx
<%@ Import Namespace="System.Net" %>

<Script Runat="Server">

Sub Page_Load
  Dim wcMicrosoft As WebClient
  Dim objUTF8Encoding As UTF8Encoding
  Dim strResponse As String

  wcMicrosoft = New WebClient
  objUTF8Encoding = New UTF8Encoding
  strResponse = objUTF8Encoding.GetString( _
    wcMicrosoft.DownloadData( "http://www.amazon.com" ) )
  strResponse = RegEx.Replace( strResponse, "(<img.*?>)", "[image]", RegExOptions.SingleLine )
  Response.Write( strResponse )
End Sub

</Script>

The C# version of this code can be found on the CD-ROM.

Figure 26.6. Stripping images from Amazon.com.

In Listing 26.18, an instance of the WebClient class is created. Next, the DownloadData() method is called with the address of the Amazon.com Web site.

The DownloadData() method returns a byte array. Before you can do anything interesting with the data returned, you need to convert the byte array into a string. Use the GetString() method of the UTF8Encoding class to accomplish this.

Finally, the Replace()method of the RegEx class is used to replace every <img> tag with the string [image]. Calling Response.Write() renders the downloaded page to the browser.

Using the HttpWebRequest Class

Instead of using the WebClient class, you can use the HttpWebRequest and HttpWebResponse classes to grab a Web page from a Web server. These classes are more difficult to use, but they are also more flexible. For example, you can use the HttpWebRequest and HttpWebResponse classes to request a page both synchronously and asynchronously. Furthermore, you can use these classes to simulate HTML form posts.

The page in Listing 26.19 uses the HttpWebRequest and HttpWebResponse classes to simulate a form post. The page posts whatever data you enter into a text box to another page named Post.aspx and displays the results.

Listing 26.19 HttpWebRequest.aspx
<%@ Import Namespace="System.Net" %>
<%@ Import Namespace="System.IO" %>

<Script Runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  Dim objRequest As HttpWebRequest
  Dim strRequest As String
  Dim arrRequest As Byte()
  Dim objUTF8Encoding As UTF8Encoding
  Dim strmRequest As Stream
  Dim objResponse As HttpWebResponse
  Dim srResponse As StreamReader

  ' Initialize request object
  objRequest = _
    CType( WebRequest.Create( "http://localhost/Post.aspx" ), HttpWebRequest )
  objRequest.Method = "POST"
  objRequest.ContentType = "application/x-www-form-urlencoded"

  ' Create request body
  strRequest = "Message=" & Server.UrlEncode( txtMessage.Text )
  objUTF8Encoding = New UTF8Encoding
  arrRequest = objUTF8Encoding.GetBytes( strRequest )

  ' Add body to request
  objRequest.ContentLength = arrRequest.Length
  strmRequest = objRequest.GetRequestStream()
  strmRequest.Write( arrRequest, 0, arrRequest.Length )
  strmRequest.Close()

  ' Get response
  objResponse = objRequest.GetResponse()
  srResponse = New StreamReader( objResponse.GetResponseStream(), Encoding.ASCII )
  lblResponse.Text =  srResponse.ReadToEnd()
  srResponse.Close()
End Sub

</Script>

<html>
<head><title>HttpWebRequest.aspx</title></head>
<body>

<form runat="Server">

<h3>Enter a message:</h3>

<asp:TextBox
  id="txtMessage"
  Runat="Server" />

<asp:Button
  Text="Post!"
  OnClick="Button_Click"
  Runat="Server" />

<h3>Response:</h3>
<asp:Label
  id="lblResponse"
  EnableViewState="False"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM

NOTE

Before you use the page in Listing 26.19, you might need to change the path passed to the WebRequest.Create() method from http://localhost/post._aspx to the location of the Post.aspx page on your server.

The contents of the Post.aspx page is contained in Listing 26.20.

Listing 26.20 Post.aspx
<Script runat="Server">

Sub Page_Load
  lblMessage.Text = Request.Form( "Message" )
End Sub

</Script>

<html>
<head><title>Post.aspx</title></head>
<body>

You posted the following message:
<br>
<asp:Label
  id="lblMessage"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

The Post.aspx page simply echoes the value of the Message form field.

Resolving Domain Names

Included in the System.Net namespace is a Dns class. You can use the Dns class to translate between IP addresses and domain names.

The Dns class supports the following methods:

  • GetHostByAddress— Returns an IPHostEntry from an IP address.

  • GetHostByName— Returns an IPHostEntry from a domain name.

  • Resolve— Returns an IPHostEntry from either an IP address or domain name.

For example, the page in Listing 26.21 contains a form with a text box for an IP address. If you submit the form, the domain name corresponding to the IP address is displayed (see Figure 26.7).

Listing 26.21 Dns.aspx
<%@ Import Namespace="System.Net" %>

<Script runat="Server">

Sub Button_Click( s As Object, e As EventArgs )
  Dim objIPHostEntry As IPHostEntry

  objIPHostEntry = Dns.GetHostByAddress( txtIP.Text )
  lblHostName.Text = objIPHostEntry.HostName
End Sub

</Script>

<html>
<head><title>Dns.aspx</title></head>

<form runat="Server">

<h2>Enter an IP Address:</h2>

<asp:TextBox
  id="txtIP"
  Runat="Server" />

<asp:Button
  Text="Submit!"
  OnClick="Button_Click"
  Runat="Server" />

<p>
<asp:Label
  id="lblHostName"
  EnableViewState="False"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 26.7. Resolving an IP Address.

    [ Team LiB ]


Summary

In this chapter, you learned three methods to transmit messages over a network. In the first section, you learned how to send email messages from an ASP.NET page. Sending emails with attachments and HTML content was discussed.

Next, transferring messages from one application to another by using Microsoft Message Queuing was examined. You learned how to use the classes from the System.Messaging namespace to create new message queues, send messages to queues, and receive messages from queues. You also learned how to transport custom objects in a message queue.

Finally, the classes in the System.Net namespace for accessing other Web sites over the Internet were discussed. You learned how to use the WebClient class to simulate browser requests. You also learned how to use the HttpWebRequest class to simulate browser posts. Finally, you learned how to use the Dns class to resolve IP addresses into domain names.


Chapter 27. Creating Graphics On-the-Fly with GDI+

IN THIS CHAPTER

  • Building Simple Images

  • Drawing Objects with GDI+

  • Drawing a String of Text

  • Building Sample GDI+ Applications

In this chapter, you learn how to work with images in your ASP.NET applications and take advantage of the newest version of the Windows graphics device interface, GDI+.

In the first section, you examine a simple example of creating and displaying a custom image with an ASP.NET page. You learn how to make an ASP.NET page "pretend" to be an image.

Next, you examine in detail the methods included with GDI+ for drawing objects. You learn how to use the Brush and Pen classes to control the appearance of objects and draw objects, such as lines, rectangles, polygons, and strings of text.

Finally, you examine two complete sample applications using GDI+. First, you learn how to build a simple paint application. Next, you create a simple chart application.

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/


Building Simple Images

Let's begin with a simple ASP.NET page that displays an image. To do so, you need to complete the following steps:

  1. Specify the content type of the page.

  2. Create a bitmap that represents an image.

  3. Save the bitmap to the HTTP output stream.

The page in Listing 27.1 illustrates each of these steps. It displays an image with 1,000 red pixels (see Figure 27.1).

Listing 27.1 SimpleImage.aspx
<%@ Page ContentType="image/gif" %>
<%@ Import namespace="System.Drawing" %>
<%@ Import namespace="System.Drawing.Imaging" %>

<Script Runat="Server">

Sub Page_Load
  Dim objBitmap As Bitmap
  Dim intCounter As Integer
  Dim objRandom As Random

  ' Create Bitmap
  objBitmap = New Bitmap( 400, 200 )

  ' Set 1000 Pixels
  objRandom = New Random
  For intCounter = 1 To 1000
    objBitmap.SetPixel( objRandom.Next( 400 ), objRandom.Next( 200 ), Color.Red )
  Next

  ' Display Bitmap
  objBitmap.Save( Response.OutputStream, ImageFormat.Gif )
End Sub

</Script>

The C# version of this code can be found on the CD-ROM.

Figure 27.1. Displaying a simple image.

Notice the first line in Listing 27.1; it contains a page directive that sets the content type of the page. By default, the ContentType attribute has the value text/html. Because this page displays a GIF image, ContentType is assigned the value image/gif.

If you were generating JPEG images, you would need to assign the value image/jpeg to ContentType. When generating PNG images, you would need to use the value image/png.

Instead of using a page directive to specify the content type of the page, you could set the content type programmatically by using the ContentType property of the Response object. For example, the following statement sets the content type to image/jpeg:

Response.ContentType="image/jpeg"

In Listing 27.1, the image is constructed in the Page_Load subroutine. First, a bitmap that contains 400 horizontal pixels and 200 vertical pixels is created. After the bitmap is created, 1,000 pixels in the bitmap are assigned the color red. The pixels are set with the SetPixel method. Finally, the bitmap is displayed after it is saved to the Response object's output stream. Here, the bitmap is saved as a GIF image.

When the page in Listing 27.1 is opened in a Web browser, the browser interprets the page as an image. From the browser's perspective, it has received a GIF image with the weird name SimpleImage.aspx.

Typically, you want to display the image that you generate with other content, such as text and explanations. You can display a dynamically generated image within a page by using any of the standard methods of displaying an image. For example, the page in Listing 27.2 displays a dynamically generated image with the HTML <img> tag (see Figure 27.2).

Listing 27.2 DisplaySimpleImage.aspx
<html>
<head><title>DisplaySimpleImage.aspx</title></head>
<body>

<h2>Here's the image:</h2>
<img src="SimpleImage.aspx">

<h2>Here's the image again:</h2>
<img src="SimpleImage.aspx">

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 27.2. Displaying an image in a page.

The page in Listing 27.2 displays the image generated by the SimpleImage.aspx page twice. The value SimpleImage.aspx is assigned to the SRC attribute of both instances of the HTML <img> tag.

NOTE

If you use a page to dynamically generate an image and the image changes infrequently, you should consider caching the output of the page. To learn more details about page output caching, see Chapter 17, "Caching ASP.NET Applications."


Drawing Objects with GDI+

The GDI+ classes for drawing images are contained in the following four namespaces:

  • System.Drawing— Contains all the basic classes for creating bitmaps and drawing objects

  • System.Drawing.Drawing2D— Contains advanced classes for working with two-dimensional and vector graphics

  • System.Drawing.Imaging— Contains advanced classes for working with different image formats

  • System.Drawing.Text— Contains classes for working with different fonts

To draw simple images, you need to import both the System.Drawing and System.Drawing.Imaging namespaces. If you need to perform more advanced graphics functions, you also need to import the System.Drawing.Drawing2D namespace. Finally, if you need to display text with different fonts, you need to import the System.Drawing.Text namespace.

The two classes that you'll need to use most often are Bitmap and Graphics. Both of these classes can be found in the System.Drawing namespace.

Creating a Bitmap

The Bitmap class represents an image in memory. It represents all the pixels of an image and contains methods for setting and reading individual pixels.

You can use a bitmap to represent either a new or an existing image. To load an existing image into a bitmap, you initialize it like this:

objBitmap = New Bitmap( "c:\myImage.Gif" )

This statement creates a new bitmap from an image named myImage.Gif.

The Bitmap class includes several methods for constructing a new image. The simplest method is to specify the horizontal and vertical number of pixels like this:

objBitmap = New Bitmap( 400, 100 )

This statement creates a new bitmap with 400 horizontal pixels and 100 vertical pixels.

A bitmap is represented in memory with a certain pixel format. This format determines exactly how many bits are used to represent each pixel. By default, bitmaps are created with a pixel format of Format32bppARGB.

When the pixel format is Format32bppARGB, 32 bits represent each pixel. The ARGB indicates the order of the color components for each pixel. In the case of the Format32bppARGB pixel format, 8 bits represent the alpha color component, 8 bits represent the red color component, 8 bits represent the green color component, and 8 bits represent the blue color component.

NOTE

The alpha component determines the transparency of a pixel. A low alpha value creates a transparent pixel; a high alpha value creates an opaque pixel.

You can change the default pixel format of a bitmap when you create it like this:

objBitmap = New Bitmap( 400, 200, PixelFormat.Format64bppARGB )

This statement creates a new bitmap that uses 64 bits to represent each color. A Format64bppARGB pixel format enables you to represent 16,777,216 shades of color.

NOTE

To view all the possible pixel formats, see the .NET Framework Software Development Kit Documentation. You can find the PixelFormat enumeration in the System.Drawing.Imaging namespace.

You can save an image represented by a bitmap to the hard drive or to a stream by calling its Save method. To save an image to the hard drive, use the following statement (the ASPNET user account requires write permissions on the c:\ directory to do this):

objBitmap.Save( "c:\myImage.Gif", ImageFormat.Gif )

To save a bitmap to the Response object's output stream, use the following statement:

objBitmap.Save( Response.OutputStream, ImageFormat.JPEG )

Notice, in both cases, that you need to specify the format of the image by referring to a member of the ImageFormat enumeration.

Working with the Graphics Object

The Bitmap class enables you to work only with individual pixels. If you want to draw something fancy, such as a rectangle or a line, you need to use the Graphics class.

The page in Listing 27.3, for example, generates an image that contains a rectangle with the Bitmap and Graphics classes.

Listing 27.3 Graphics.aspx
<%@ Page ContentType="image/gif" %>
<%@ Import namespace="System.Drawing" %>
<%@ Import namespace="System.Drawing.Imaging" %>

<Script Runat="Server">

Sub Page_Load
  Dim objBitmap As Bitmap
  Dim objGraphics As Graphics

  ' Create Bitmap
  objBitmap = New Bitmap( 400, 200 )

  ' Initialize Graphics Class
  objGraphics = Graphics.FromImage( objBitmap )
  objGraphics.DrawRectangle( Pens.Red, 100, 100, 50, 50 )

  ' Display Bitmap
  objBitmap.Save( Response.OutputStream, ImageFormat.Gif )
End Sub

</Script>

The C# version of this code can be found on the CD-ROM.

In Listing 27.3, a 400x200 pixel bitmap is initialized. Next, an instance of the Graphics class is created with the FromImage method. The DrawRectangle method then draws a rectangle on the bitmap. Finally, the image is actually displayed by calling the Save method to save the image to the Response object's output stream (see Figure 27.3).

Figure 27.3. Using the Graphics class.

By default, when you create a new bitmap, it uses the color black as the background color. You can use the Graphics class to change the background color by calling the Clear method with a new color like this:

objGraphics.Clear( Color.Red )

This statement sets all the pixels in a bitmap to the color red.

Setting Graphics Quality

When drawing ellipses and other curved objects, you can produce better results by modifying the value of the SmoothingMode property. This property can accept the following five values:

  • AntiAlias

  • Default

  • HighQuality

  • HighSpeed

  • None

If you enable antialiasing, for example, intermediate colors are drawn between the boundaries of different colors to blend them. In theory, antialiasing should result in smoother curves.

The image in Figure 27.4 illustrates how different values of the SmoothingMode property alter the appearance of an ellipse. The page used to generate the images is included on the CD with the name Smooth.aspx.

Figure 27.4. Effects of the SmoothingMode property.

To get the most from antialiasing, you should use JPEG images rather than GIF images, because JPEG images support more colors and can represent gradations in color more efficiently.

Using Colors

Colors are represented by the GDI+ Color structure, which contains hundreds of properties that represent individual colors. For example, if you want to use the color blue, you can use the Blue property of the Color structure (Color.Blue), and if you want to use the color dark cyan, you can use the DarkCyan property of the Color structure (Color.DarkCyan).

NOTE

You can find the Color structure in the System.Drawing namespace.

You can use any of the color properties from the Color structure when working with the image classes. For example, you can set a pixel to the color blue by using the following statement:

objBitmap.SetPixel( 100, 100, Color.Blue )

A color has alpha, red, green, and blue components. The following code, for example, displays the alpha, red, green, and blue components for the color MediumOrchid:

Response.Write( "<li> Alpha:" & Color.MediumOrchid.A  )
Response.Write( "<li> Red:" & Color.MediumOrchid.R  )
Response.Write( "<li> Green:" & Color.MediumOrchid.G  )
Response.Write( "<li> Blue:" & Color.MediumOrchid.B  )

Although a color is completely defined by its alpha, red, green, and blue components, you also can use the properties of the Color structure to retrieve a color's brightness, hue, and saturation like this:

Response.Write( "<li> Brightness: " & Color.MediumOrchid.GetBrightness  )
Response.Write( "<li> Hue: " & Color.MediumOrchid.GetHue  )
Response.Write( "<li> Saturation: " & Color.MediumOrchid.GetSaturation  )

The brightness is the amount of white or black in a color. The hue refers to the color's position on the linear spectrum of the color wheel. The saturation defines the light intensity of the color.

Converting Colors

You can convert a color string into a GDI+ color by using the Color structure's FromName method like this:

Dim objColor As Color
objColor = Color.FromName( "blue" )

This statement takes the string blue and converts it into a GDI+ color.

You also can use the FromArgb method to convert a red-green-blue value into a color. For example, the following statement assigns the color red to the objColor variable:

Dim objColor As Color
objColor = Color.FromArgb( 255, 0, 0 )

You also have the option of specifying an alpha value with the FromArgb method:

Dim objColor As Color
objColor = Color.FromArgb( 100, 255, 0, 0 )

In this example, the first value passed to the FromArgb method represents the alpha value. The remaining three values represent the red, green, and blue values.

If you want to create a color with a new alpha value from an existing color, you can change it like this:

Dim objColor As Color
objColor = Color.FromArgb( 100, Color.Yellow )

This statement assigns the color yellow, with an alpha value of 100, to the objColor variable.

Finally, if you need to convert between HTML colors and GDI+ colors, you can use the methods of the ColorTranslator class. The FromHTML method takes an HTML color string, such as white or #FFFFFF, and converts it into a GDI+ color. The ToHTML method performs the opposite operation, as you can see in this example:

Dim objColor As Color
objColor = ColorTranslator.FromHTML( "#00FF00" )
Response.Write( ColorTranslator.ToHTML( Color.Blue ) )
Setting a Color's Alpha Value

Specifying an alpha value for a color is useful when you're working with overlapping images. For example, the page in Listing 27.4 contains two overlapping filled rectangles. Because the top rectangle has an alpha value less than 255, the colors of the two rectangles are blended everywhere they overlap (see Figure 27.5).

Listing 27.4 Alpha.aspx
<%@ Page ContentType="image/gif" %>
<%@ Import Namespace="System.Drawing" %>
<%@ Import Namespace="System.Drawing.Imaging" %>

<Script Runat="Server">

Sub Page_Load
  Dim objBitmap As Bitmap
  Dim objGraphics As Graphics
  Dim objBrush, objBrush2 As Brush

  ' Create Bitmap
  objBitmap = New Bitmap( 400, 400 )
  objGraphics = Graphics.FromImage( objBitmap )

  ' Create Different Colored Brushes
  objBrush = New SolidBrush( Color.Blue )
  objBrush2 = New SolidBrush( Color.FromArgb( 100, Color.Orange ) )

  ' Create Rectangles
  objGraphics.FillRectangle( objBrush, 10, 10, 100, 100 )
  objGraphics.FillRectangle( objBrush2, 50, 50, 100, 100 )

  ' Display the Bitmap
  objBitmap.Save( Response.OutputStream, ImageFormat.Gif )
End Sub

</Script>

The C# version of this code can be found on the CD-ROM.

Figure 27.5. Modifying the alpha value.

The two rectangles displayed in the page in Listing 27.4 are painted with different colored brushes. The first rectangle is painted with a solid blue brush. The second rectangle is painted with an orange brush that has an alpha value of 100. When the orange rectangle is rendered, the orange pixels are blended with the blue pixels wherever the two rectangles overlap.

Using Brushes

You use a brush to fill the shapes that you create with the Graphics class, such as rectangles, ellipses, and polygons. You can choose from these five predefined brushes:

  • SolidBrush— Fills a shape with a solid color

  • HatchBrush— Fills a shape with a hatch pattern

  • LinearGradientBrush— Fills a shape with a color gradient

  • PathGradientBrush— Fills a graphics path with a color gradient

  • TextureBrush— Fills a shape with an image

In addition to these five types of brushes, GDI+ includes a number of standard brushes. The following sections provide examples showing how you can use all the different types of brushes.

Using the Standard Brushes

The easiest way to use a brush is to use one of the standard brushes included in the Brushes class. The Brushes class contains brushes that correspond to all the standard colors. For example, the page in Listing 27.5 uses a Blue brush to fill the interior of a circle.

Listing 27.5 StandardBrush.aspx
<%@ Page ContentType="image/gif" %>
<%@ Import namespace="System.Drawing" %>
<%@ Import namespace="System.Drawing.Imaging" %>

<Script Runat="Server">

Sub Page_Load
  Dim objBitmap As Bitmap
  Dim objGraphics As Graphics

  ' Create Bitmap
  objBitmap = New Bitmap( 300, 300 )

  ' Create Graphics
  objGraphics = Graphics.FromImage( objBitmap )

  ' Draw Ellipse
  objGraphics.FillEllipse( Brushes.Blue, 10, 10, 200, 200 )

  ' Display Bitmap
  objBitmap.Save( Response.OutputStream, ImageFormat.Gif )
End Sub

</Script>

The C# version of this code can be found on the CD-ROM.

The standard Blue brush from the Brushes class is used with the FillEllipse method to render a circle with a blue background.

Using SolidBrush

If you need to use a color that is not represented by one of the standard brushes described in the preceding section, you need to use SolidBrush. For example, the page in Listing 27.6 renders a rectangle that is filled with a custom background color.

Listing 27.6 SolidBrush.aspx
<%@ Page ContentType="image/gif" %>
<%@ Import namespace="System.Drawing" %>
<%@ Import namespace="System.Drawing.Imaging" %>
<%@ Import namespace="System.Drawing.Drawing2D" %>

<Script Runat="Server">

Sub Page_Load
  Dim objBitmap As Bitmap
  Dim objGraphics As Graphics
  Dim objBrush As SolidBrush

  ' Create Bitmap
  objBitmap = New Bitmap( 400, 200 )

  ' Create Graphics
  objGraphics = Graphics.FromImage( objBitmap )

  ' Create Solid Brush
  objBrush = New SolidBrush( Color.FromArgb( 80, 132, 234 ) )

  ' Draw Rectangle
  objGraphics.FillRectangle( objBrush, 10, 10, 380, 180 )

  ' Display Bitmap
  objBitmap.Save( Response.OutputStream, ImageFormat.Gif )
End Sub

</Script>

The C# version of this code can be found on the CD-ROM.

In Listing 27.6, SolidBrush is initialized with a custom color, which is created with help from the FromArgb method. SolidBrush is passed to the FillRectangle method to render the rectangle with the custom background color.

Using HatchBrush

HatchBrush enables you to fill a shape with a hatch pattern. For example, the page in Listing 27.7 fills an ellipse with a plaid HatchBrush (see Figure 27.6).

Listing 27.7 HatchBrush.aspx
<%@ Page ContentType="image/gif" %>
<%@ Import namespace="System.Drawing" %>
<%@ Import namespace="System.Drawing.Imaging" %>
<%@ Import namespace="System.Drawing.Drawing2D" %>

<Script Runat="Server">

Sub Page_Load
  Dim objBitmap As Bitmap
  Dim objGraphics As Graphics
  Dim objBrush As HatchBrush

  ' Create Bitmap
  objBitmap = New Bitmap( 400, 200 )

  ' Create Graphics
  objGraphics = Graphics.FromImage( objBitmap )

  ' Create Hatch Brush
  objBrush = New HatchBrush( HatchStyle.Plaid, Color.Red )

  ' Draw Ellipse
  objGraphics.FillEllipse( objBrush, 10, 10, 380, 180 )

  ' Display Bitmap
  objBitmap.Save( Response.OutputStream, ImageFormat.Gif )
End Sub

</Script>

The C# version of this code can be found on the CD-ROM.

Figure 27.6. Using a HatchBrush.

In Listing 27.7, a HatchBrush named objBrush is created. HatchBrush is initialized with a particular HatchStyle property and a particular foreground color. You can choose from dozens of HatchStyle properties, including Divot, DottedGrid, and SmallConfetti. See the .NET Framework SDK Documentation for a complete list (look up HatchStyle in the index).

Using LinearGradientBrush

LinearGradientBrush enables you to fill a shape with a color gradient. For example, the page in Listing 27.8 draws a rectangle that is filled with a gradation of colors between red and yellow (see Figure 27.7).

Listing 27.8 LinearGradientBrush.aspx
<%@ Page ContentType="image/gif" %>
<%@ Import namespace="System.Drawing" %>
<%@ Import namespace="System.Drawing.Imaging" %>
<%@ Import namespace="System.Drawing.Drawing2D" %>

<Script Runat="Server">

Sub Page_Load
  Dim objBitmap As Bitmap
  Dim objGraphics As Graphics
  Dim objRect As Rectangle
  Dim objBrush As LinearGradientBrush

  ' Create Bitmap
  objBitmap = New Bitmap( 400, 200 )

  ' Create Graphics
  objGraphics = Graphics.FromImage( objBitmap )

  ' Create Rectangle
  objRect = New Rectangle( 10, 10, 300, 100 )

  ' Create LinearGradient Brush
  objBrush = New LinearGradientBrush( objRect, Color.Red, _
    Color.Yellow, LinearGradientMode.ForwardDiagonal )

  ' Draw Rectangle
  objGraphics.FillRectangle( objBrush, objRect )

  ' Display Bitmap
  objBitmap.Save( Response.OutputStream, ImageFormat.Gif )
End Sub

</Script>

The C# version of this code can be found on the CD-ROM.

Figure 27.7. Using a LinearGradientBrush.

In Listing 27.8, LinearGradientBrush is initialized with four parameters: the Rectangle to fill, the starting color, the ending color, and LinearGradientMode. LinearGradientMode determines exactly how the gradation of colors is rendered. It can have the following four values:

  • BackwardDiagonal— Gradient from upper right to lower left

  • ForwardDiagonal— Gradient from upper left to lower right

  • Horizontal— Gradient from left to right

  • Vertical— Gradient from top to bottom

Using PathGradientBrush

PathGradientBrush is similar to LinearGradientBrush, except that it enables you to create more complicated effects. For example, the page in Listing 27.9 uses PathGradientBrush to fill a path built from two lines and two circles (see Figure 27.8).

Listing 27.9 PathGradientBrush.aspx
<%@ Page ContentType="image/gif" %>
<%@ Import namespace="System.Drawing" %>
<%@ Import namespace="System.Drawing.Imaging" %>
<%@ Import namespace="System.Drawing.Drawing2D" %>

<Script Runat="Server">

Sub Page_Load
  Dim objBitmap As Bitmap
  Dim objGraphics As Graphics
  Dim objPath As GraphicsPath
  Dim objBrush As PathGradientBrush

  ' Create Bitmap
  objBitmap = New Bitmap( 400, 400 )

  ' Create Graphics
  objGraphics = Graphics.FromImage( objBitmap )

  ' Create Path
  objPath = New
GraphicsPath( )
  objPath.AddLine( 10, 10, 300, 10 )
  objPath.AddEllipse( 50, 50, 300, 300 )
  objPath.AddEllipse( 10, 10, 200, 200 )
  objPath.AddLine( 10, 300, 100, 300 )

  ' Create PathGradient Brush
  objBrush = New PathGradientBrush( objPath )
  objBrush.CenterColor = Color.Red

  ' Draw Path
  objGraphics.FillPath( objBrush, objPath )

  ' Display Bitmap
  objBitmap.Save( Response.OutputStream, ImageFormat.Gif )
End Sub

</Script>

The C# version of this code can be found on the CD-ROM.

Figure 27.8. Using a PathGradientBrush.

PathGradientBrush is initialized with an instance of the GraphicsPath class. Next, the CenterColor property is assigned the color red. When the path is rendered, it will be filled with a red gradient.

Using TextureBrush

You can use TextureBrush to fill a shape with an image. By default, the image is tiled across the background of the shape. The page in Listing 27.10 illustrates how to use TextureBrush (see Figure 27.9).

Listing 27.10 TextureBrush.aspx
<%@ Page ContentType="image/gif" %>
<%@ Import namespace="System.Drawing" %>
<%@ Import namespace="System.Drawing.Imaging" %>
<%@ Import namespace="System.Drawing.Drawing2D" %>

<Script Runat="Server">

Sub Page_Load
  Dim objBitmap As Bitmap
  Dim objGraphics As Graphics
  Dim objBrushImage As Bitmap
  Dim objBrush As TextureBrush

  ' Create Bitmap
  objBitmap = New Bitmap( 400, 200 )

  ' Create Graphics
  objGraphics = Graphics.FromImage( objBitmap )

  ' Create Texture Brush
  objBrushImage = New Bitmap( "c:\sphere.gif" )
  objBrush = New TextureBrush( objBrushImage )

  ' Draw Rectangle
  objGraphics.FillRectangle( objBrush, 10, 10, 380, 180 )

  ' Display Bitmap
  objBitmap.Save( Response.OutputStream, ImageFormat.Gif )
End Sub

</Script>

The C# version of this code can be found on the CD-ROM.

Figure 27.9. Using a TextureBrush.

In Listing 27.10, a GIF image named sphere.gif is used with TextureBrush. First, the image is loaded from the hard drive into a bitmap. (You'll need to supply the correct path for this image on your hard drive.) Next, TextureBrush is initialized with the image. Finally, TextureBrush is used with the FillRectangle method when rendering the rectangle.

Using Pens

Brushes are used to fill the interiors of shapes; pens are used to draw lines and curves with a certain width, style, and color. For example, by setting certain properties of a pen, you can draw dashed or dotted lines. By setting other pen properties, you can draw lines that start and end with arrows. In the following sections, you learn how to draw shapes with the various styles of pens.

Using the Standard Pens

The easiest method of using a pen is to use one of the standard pens represented by the properties of the Pens class. The Pens class contains pens that represent all the standard colors. For example, in Listing 27.11, a rectangle is drawn with a green pen.

Listing 27.11 StandardPens.aspx
<%@ Page ContentType="image/gif" %>
<%@ Import namespace="System.Drawing" %>
<%@ Import namespace="System.Drawing.Imaging" %>

<Script Runat="Server">

Sub Page_Load
  Dim objBitmap As Bitmap
  Dim objGraphics As Graphics

  ' Create Bitmap
  objBitmap = New Bitmap( 400, 200 )

  ' Create Graphics
  objGraphics = Graphics.FromImage( objBitmap )

  ' Draw Rectangle
  objGraphics.DrawRectangle( Pens.Green, 50, 50, 100, 100 )

  ' Display Bitmap
  objBitmap.Save( Response.OutputStream, ImageFormat.Gif )
End Sub

</Script>

The C# version of this code can be found on the CD-ROM.

The green pen is represented by the Green property of the Pens class. This pen is passed as the first parameter to the DrawRectangle method.

Creating Custom Pens

If you need to create a pen with custom properties, you can explicitly initialize an instance of the Pen class. For example, the page in Listing 27.12 uses a 5-pixel wide purple pen to draw a circle (see Figure 27.10).

Listing 27.12 CustomPen.aspx
<%@ Page ContentType="image/gif" %>
<%@ Import namespace="System.Drawing" %>
<%@ Import namespace="System.Drawing.Imaging" %>

<Script Runat="Server">

Sub Page_Load
  Dim objBitmap As Bitmap
  Dim objGraphics As Graphics
  Dim objPen As Pen

  ' Create Bitmap
  objBitmap = New Bitmap( 400, 400 )

  ' Create Graphics
  objGraphics = Graphics.FromImage( objBitmap )

  ' Create Custom Pen
  objPen = New Pen( Color.Purple )
  objPen.Width = 5

  ' Draw Circle
  objGraphics.DrawEllipse( objPen, 10, 10, 200, 200 )

  ' Display Bitmap
  objBitmap.Save( Response.OutputStream, ImageFormat.Gif )
End Sub

</Script>

The C# version of this code can be found on the CD-ROM.

Figure 27.10. Using a custom pen.

The instance of the Pen class is initialized with the color purple. Next, the pen's Width property is assigned the value 5. The circle is rendered by passing the pen as the first parameter to the DrawEllipse method.

Setting Pen Styles

You can draw dashed or dotted lines with a pen by setting the DashStyle property. This property accepts the following values from the DashStyle enumeration:

  • Custom

  • Dash

  • DashDot

  • DashDotDot

  • Dot

  • Solid

The page in Listing 27.13 illustrates how setting different DashStyle properties modifies the appearance of a line (see Figure 27.11).

Listing 27.13 DashStyle.aspx
<%@ Page ContentType="image/gif" %>
<%@ Import namespace="System.Drawing" %>
<%@ Import namespace="System.Drawing.Imaging" %>
<%@ Import namespace="System.Drawing.Drawing2D" %>

<Script Runat="Server">

Sub Page_Load
  Dim objBitmap As Bitmap
  Dim objGraphics As Graphics
  Dim objPen As Pen

  ' Create Bitmap
  objBitmap = New Bitmap( 400, 200 )

  ' Create Graphics
  objGraphics = Graphics.FromImage( objBitmap )

  ' Create Custom Pen
  objPen = New Pen( Color.White )
  objPen.Width = 4

  ' Draw Dash Line
  objPen.DashStyle = DashStyle.Dash
  objGraphics.DrawLine( objPen, 10, 20, 350, 20 )

  ' Draw DashDot Line
  objPen.DashStyle = DashStyle.DashDot
  objGraphics.DrawLine( objPen, 10, 40, 350, 40 )

  ' Draw DashDotDot Line
  objPen.DashStyle = DashStyle.DashDotDot
  objGraphics.DrawLine( objPen, 10, 60, 350, 60 )

  ' Draw Dot Line
  objPen.DashStyle = DashStyle.Dot
  objGraphics.DrawLine( objPen, 10, 80, 350, 80 )

  ' Draw DashDotDot Line
  objPen.DashStyle = DashStyle.Solid
  objGraphics.DrawLine( objPen, 10, 100, 350, 100 )

  ' Display Bitmap
  objBitmap.Save( Response.OutputStream, ImageFormat.Gif )
End Sub

</Script>

The C# version of this code can be found on the CD-ROM.

Figure 27.11. Modifying the DashStyle property.

In Listing 27.13, you set different DashStyle properties by assigning different values to these properties of the Pen class.

Setting Pen Cap Styles

You can set various cap styles with the Pen class. For example, you can set the cap style for both the start and end of a line. Here's a list of the values that you can use for setting the cap style (from the LineCap enumeration):

  • AnchorMask

  • ArrowAnchor

  • Custom

  • DiamondAnchor

  • Flat

  • NoAnchor

  • Round

  • RoundAnchor

  • Square

  • SquareAnchor

  • Triangle

When working with dashed lines, you can use the following three values (from the DashCap enumeration):

  • Flat

  • Round

  • Triangle

You can set the cap styles for a pen with the StartCap, EndCap, and DashCap properties like this:

objPen.StartCap = LineCap.Square
objPen.EndCap = LineCap.Round
objPen.DashCap = DashCap.Triangle

The page in Listing 27.14 illustrates how different cap styles alter the appearance of a line (see Figure 27.12).

Listing 27.14 PenCaps.aspx
<%@ Page ContentType="image/gif" %>
<%@ Import namespace="System.Drawing" %>
<%@ Import namespace="System.Drawing.Imaging" %>
<%@ Import namespace="System.Drawing.Drawing2D" %>

<Script Runat="Server">

Sub Page_Load
  Dim objBitmap As Bitmap
  Dim objGraphics As Graphics
  Dim objPen As Pen

  ' Create Bitmap
  objBitmap = New Bitmap( 400, 200 )

  ' Create Graphics
  objGraphics = Graphics.FromImage( objBitmap )

  ' Create Custom Pen
  objPen = New Pen( Color.White )
  objPen.Width = 6

  ' Draw ArrowAnchor Cap
  objPen.EndCap = LineCap.ArrowAnchor
  objGraphics.DrawLine( objPen, 10, 20, 350, 20 )

  ' Draw DiamondAnchor Cap
  objPen.EndCap = LineCap.DiamondAnchor
  objGraphics.DrawLine( objPen, 10, 40, 350, 40 )

  ' Draw Flat Cap
  objPen.EndCap = LineCap.Flat
  objGraphics.DrawLine( objPen, 10, 60, 350, 60 )

  ' Draw Round Cap
  objPen.EndCap = LineCap.Round
  objGraphics.DrawLine( objPen, 10, 80, 350, 80 )

  ' Draw RoundAnchor Cap
  objPen.EndCap = LineCap.RoundAnchor
  objGraphics.DrawLine( objPen, 10, 100, 350, 100 )

  ' Draw Square Cap
  objPen.EndCap = LineCap.Square
  objGraphics.DrawLine( objPen, 10, 120, 350, 120 )

  ' Draw SquareAnchorCap
  objPen.EndCap = LineCap.SquareAnchor
  objGraphics.DrawLine( objPen, 10, 140, 350, 140 )

  ' Draw TriangleCap
  objPen.EndCap = LineCap.Triangle
  objGraphics.DrawLine( objPen, 10, 160, 350, 160 )

  ' Display Bitmap
  objBitmap.Save( Response.OutputStream, ImageFormat.Gif )
End Sub

</Script>

The C# version of this code can be found on the CD-ROM.

Figure 27.12. Using different cap styles.

Setting Pen Line Join Styles

You can control the appearance of joins between lines drawn with a pen by modifying the value of the LineJoin property. You can assign the following values to this property (from the LineJoin enumeration):

  • Bevel

  • Miter

  • MiterClipped

  • Round

The page in Listing 27.15 illustrates how different LineJoin values modify the appearance of a rectangle (see Figure 27.13).

Listing 27.15 LineJoin.aspx
<%@ Page ContentType="image/gif" %>
<%@ Import namespace="System.Drawing" %>
<%@ Import namespace="System.Drawing.Imaging" %>
<%@ Import namespace="System.Drawing.Drawing2D" %>

<Script Runat="Server">

Sub Page_Load
  Dim objBitmap As Bitmap
  Dim objGraphics As Graphics
  Dim objPen As Pen

  ' Create Bitmap
  objBitmap = New Bitmap( 400, 200 )

  ' Create Graphics
  objGraphics = Graphics.FromImage( objBitmap )

  ' Create Custom Pen
  objPen = New Pen( Color.White )
  objPen.Width = 10

  ' Draw Bevel LineJoin
  objPen.LineJoin = LineJoin.Bevel
  objGraphics.DrawRectangle( objPen, 10, 20, 350, 40 )

  ' Draw Miter LineJoin
  objPen.LineJoin = LineJoin.Miter
  objGraphics.DrawRectangle( objPen, 10, 80, 350, 40 )

  ' Draw Bevel LineJoin
  objPen.LineJoin = LineJoin.Round
  objGraphics.DrawRectangle( objPen, 10, 140, 350, 40 )

  ' Display Bitmap
  objBitmap.Save( Response.OutputStream, ImageFormat.Gif )
End Sub

</Script>

The C# version of this code can be found on the CD-ROM.

Figure 27.13. Using different LineJoin values.

Using different LineJoin properties can be very valuable when you're generating images of buttons dynamically. For example, by setting LineJoin to Round, you can display rounded button images.

Drawing a Rectangle

The Graphics class includes two methods for drawing rectangles: DrawRectangle and FillRectangle. The DrawRectangle method draws the outlines of a rectangle, and the FillRectangle method draws a rectangle with a filled interior.

You use the DrawRectangle method with a pen. The pen specifies the color and style of the outline of the rectangle. For example, the following statement draws a rectangle with the standard Yellow pen:

objGraphics.DrawRectangle( Pens.Yellow, 10, 10, 50, 50 )

The first parameter represents a pen, the second parameter represents the x coordinate of the rectangle's upper-left corner, the third parameter represents the y coordinate of the rectangle's upper-left corner, the fourth parameter represents the rectangle's width, and the final parameter represents the rectangle's height.

The FillRectangle method draws a rectangle with its interior filled. You use the FillRectangle method with a brush. For example, the following statement creates a rectangle filled with the color green:

objGraphics.FillRectangle( Brushes.Green, 10, 10, 50, 50 )

Drawing a Line

You can draw a line by using the DrawLine method. For example, the following statement draws a line between the coordinates 10, 10 and 50,50:

objGraphics.DrawLine( Pens.Red, 10, 10, 50, 50 )

Notice that you must specify a pen when drawing a line. This example uses the standard Red pen. By setting properties of the pen, you can control exactly how the line is rendered.

Drawing an Ellipse

You can draw circles and other ellipses by using either the DrawEllipse or FillEllipse method of the Graphics class. The DrawEllipse method draws the outline of an ellipse, and the FillEllipse method draws an ellipse with its interior filled.

You use the DrawEllipse method with a pen. For example, the following statement draws an ellipse with an orange outline:

objGraphics.DrawEllipse( Pens.Orange, 10, 10, 50, 50 )

The first parameter represents an orange pen; the remaining four parameters define a bounding rectangle for the ellipse.

To fill an ellipse with a certain color or pattern, use the FillEllipse method with a brush. For example, the following statement renders an ellipse filled with the color purple:

objGraphics.FillEllipse( Brushes.Purple, 10, 10, 50, 50 )

Drawing a Curve

You can draw a curve by using the DrawCurve method. For example, the following statements draw a curve through four points:

Dim arrPoints() As Point = { _
       New Point( 10, 10 ), _
       New Point( 60, 30 ), _
       New Point( 110, 30 ), _
       New Point( 160, 10 ) }
objGraphics.DrawCurve( Pens.White, arrPoints )

When using the DrawCurve method, you must supply both a pen and an array that contains at least four points.

You also have the option of drawing a closed curve. In a closed curve, the last point is automatically connected to the first point, as shown here:

Dim arrPoints() As Point = { _
       New Point( 10, 10 ), _
       New Point( 60, 30 ), _
       New Point( 110, 30 ), _
       New Point( 160, 10 ) }
objGraphics.DrawClosedCurve( Pens.White, arrPoints )

Finally, you can draw a filled closed curve by using the FillClosedCurve method and supplying a brush like this:

Dim arrPoints() As Point = { _
       New Point( 10, 10 ), _
       New Point( 60, 30 ), _
       New Point( 110, 30 ), _
       New Point( 160, 10 ) }
objGraphics.FillClosedCurve( Brushes.White, arrPoints )

In this example, the interior of the closed curve is filled with a white brush.

Drawing a Bézier Curve

You can draw a Bézier curve by using the DrawBezier method. This method accepts five parameters: a pen and four points that describe the shape of the curve. The first and last points indicate the start and end points of the curve. The second and third points control the shape of the curve, as you can see here:

Dim objPoint1 As Point = New Point( 10, 10 )
Dim objPoint2 As Point = New Point( 60, 30 )
Dim objPoint3 As Point = New Point( 110, 30 )
Dim objPoint4 As Point = New Point( 160, 10 )
objGraphics.DrawBezier( Pens.Blue, objPoint1, objPoint2, objPoint3, objPoint4 )

This statement draws a Bézier curve with a blue pen.

Drawing an Arc

To draw an arc, use the DrawArc method. For example, the following statement draws a half circle:

objGraphics.DrawArc( Pens.Green, 50, 50, 100, 100, 90, 180 )

The first parameter is a pen. The next four parameters describe a rectangle that bounds the arc, and the last two parameters represent the start and sweep angle of the arc.

Drawing a Pie Section

If you need to draw a pie chart, you can use the DrawPie and FillPie methods. For example, the following statement draws a pie section in the shape of a well-known arcade game character:

objGraphics.DrawPie( Pens.Yellow, 50, 50, 100, 100, 45, 270 )

The first parameter represents a pen. The next four parameters describe a bounding rectangle for the arc, and the last two parameters represent the start and sweep angle of the arc.

You can render a filled pie section by using the FillPie method like this:

objGraphics.FillPie( Brushes.Yellow, 50, 50, 100, 100, 45, 270 )

This statement draws a pie section filled with the color yellow.

Drawing a Polygon

A polygon represents a sequence of points connected by lines. You can draw a polygon by using the DrawPolygon method. For example, the following statements draw the outlines of a triangle with four points (the first and last points are the same):

Dim arrPoints() As Point = { _
  New Point( 100, 10 ), _
  New Point( 10, 100 ), _
  New Point( 190, 100 ), _
  New Point( 100, 10 ) }
objGraphics.DrawPolygon( Pens.Maroon, arrPoints )

You also have the option of drawing a filled polygon by using the FillPolygon method like this:

Dim arrPoints() As Point = { _
  New Point( 100, 10 ), _
  New Point( 10, 100 ), _
  New Point( 190, 100 ), _
  New Point( 100, 10 ) }
objGraphics.FillPolygon( Brushes.Maroon, arrPoints )

In this example, the FillPolygon method draws a triangle filled with the color maroon.

Drawing an Image

If you need to add an image to another image, you can use the DrawImage method. For example, the following statement adds an image named sphere.gif to the current bitmap:

Dim objNewImage As Bitmap = New Bitmap( "c:\sphere.gif" )
objGraphics.DrawImage( objNewImage, 10, 10 )

When you add an image using the DrawImage method, you also can specify a new width and height for the image being added. For example, the following statements stretch the sphere.gif image to 300 pixels wide and 100 pixels high:

Dim objNewImage As Bitmap = New Bitmap( "c:\sphere.gif" )
objGraphics.DrawImage( objNewImage, 10, 10, 300, 100 )

When adding an image, you might need to make one of the colors contained in the image transparent. You can make any color transparent by using the MakeTransparent method like this:

Dim objNewImage As Bitmap = New Bitmap( "c:\sphere.gif" )
objNewImage.MakeTransparent( Color.White )
objGraphics.DrawImage( objNewImage, 10, 10 )

In this example, the color white is made transparent. When the sphere.gif image is added, any white pixels in the image appear transparent.


Drawing a String of Text

You can draw a string of text by using the DrawString method. For example, the following statements render the text ASP.NET on an image (see Figure 27.14).

Dim objFont As New Font( "Arial", 16 )
objGraphics.DrawString( "ASP.NET", objFont, Brushes.Blue, 10, 10 )
Figure 27.14. Rendering text as an image.

In this example, the DrawString method accepts five parameters: the text to display, a font, a brush, and the x and y coordinates of the text.

If you need to wrap the text rendered by the DrawString method, you should supply a rectangle that indicates the outer bounds of the text, as shown here:

Dim strString As String = "Here is a very, very, long " & _
  "string of text that is used to illustrate how you " & _
  "can wrap text rendered by the DrawString method."
Dim objFont As New Font( "Arial", 16 )
Dim objRec As RectangleF = New RectangleF( 10, 10, 300, 200 )
objGraphics.DrawString( strString, objFont, Brushes.Blue, objRec )

In this example, the text is contained in a 300-pixel-wide by 200-pixel-high rectangle. The text automatically wraps when it reaches the right edge of the rectangle.

Another advantage of rendering text in a rectangle is that you can align the text. The following example centers the text that appears in the rectangle:

Dim strString As String = "Here is a very, very, long " & _
  "string of text that is used to illustrate how you " & _
  "can center text rendered by the DrawString method."
Dim objFont As New Font( "Arial", 16 )
Dim objRec As RectangleF = New RectangleF( 10, 10, 300, 200 )
Dim objFormat As New StringFormat()
objFormat.Alignment = StringAlignment.Center
objGraphics.DrawString( strString, objFont, Brushes.Blue, objRec, objFormat )

Notice that the DrawString method uses a brush. You can use any of the Brush classes with the DrawString method, including TextureBrush or LinearGradientBrush. For example, the following DrawString method uses LinearGradientBrush:

[View full width]

Dim objFont As New Font( "Arial", 24 )
Dim objRect As New Rectangle( 10, 10, 300, 50 )
Dim objBrush As New LinearGradientBrush( objRect, Color.Red, Color.Yellow,
   LinearGradientMode.Vertical )
objGraphics.DrawString( "ASP.NET", objFont, objBrush, 10, 10 )

When working with strings of text, you often need to measure the size of the text before you render it. You can retrieve the width and height of a string of text by using the MeasureString method of the Graphics class like this:

Dim strString As String = "Here is some text!"
Dim objFont As New Font( "Arial", 12 )
Dim objTextSize As SizeF

objGraphics.DrawString( strString, objFont, Brushes.White, 10, 10 )
objTextSize = objGraphics.MeasureString( strString, objFont )
strString = "Width: " & objTextSize.Width
objGraphics.DrawString( strString, objFont, Brushes.White, 10, 50 )
strString = "Height: " & objTextSize.Height
objGraphics.DrawString( strString, objFont, Brushes.White, 10, 80 )

In the preceding code, the width and height are returned from the MeasureString method as a SizeF structure. The SizeF structure contains both Width and Height properties. The values of both of these properties are displayed.

You can control the quality of the text rendered by the DrawString method by setting the TextRenderingHint property, which accepts any of the following values:

  • AntiAlias

  • AntiAliasGridFit

  • ClearTypeGridFit

  • SingleBitPerPixel

  • SingleBitPerPixelGridFit

  • SystemDefault

For example, to use antialiased text, you set the TextRenderingHint property like this:

objGraphics.TextRenderingHint = TextRenderingHint.AntiAlias

In general, antialiased text produces the best quality, but it takes longer to render. ClearType text looks best on LCD devices. The effects of different values of the TextRenderingHint property are illustrated in Figure 27.15. (The page used to create this figure is included on the CD with the name TextRenderingHint.aspx.)

Figure 27.15. Effects of TextRenderingHint property.

    [ Team LiB ]


Building Sample GDI+ Applications

In the following sections, you examine two ways to use GDI+ classes in an ASP.NET application. First, you build a simple paint application. The paint application enables you to draw complex images from a Web page. Next, you tackle a chart application. You create an application that enables you to automatically generate bar charts from database data.

Creating an ASP.NET Paint Application

This ASP.NET paint application enables you to draw objects with different colors and different shapes from a Web page (see Figure 27.16). The paint application consists of the following two files:

  • Painter.aspx— This ASP.NET page contains a form for picking a drawing shape and color. It also contains the canvas for drawing the image.

  • PaintImage.aspx— This ASP.NET page renders the image. It retrieves commands from the Painter.aspx page and updates the image.

Figure 27.16. The ASP.NET Paint application.

Start by constructing the Painter.aspx page. The complete code for this page is contained in Listing 27.16.

Listing 27.16 Painter.aspx
<%@ Import Namespace="System.Drawing" %>

<Script Runat="Server">

Sub Page_Load
  If Not IsPostBack Then
    ibtnCanvas.ImageUrl = "PaintImage.aspx"
    lstColor.DataSource = System.Enum.GetValues( GetType( KnownColor ) )
    lstColor.SelectedIndex = 0
    lstAction.SelectedIndex = 0
    DataBind()
  End If
End Sub

Sub ibtnCanvas_Click( s As Object, e As ImageClickEventArgs )
  Dim strQueryString As String

  strQueryString &= "a=" & lstAction.SelectedItem.Text
  strQueryString &= "&color=" & lstColor.SelectedItem.Text
  strQueryString &= "&x=" & e.X
  strQueryString &= "&y=" & e.y
  ibtnCanvas.ImageUrl = "PaintImage.aspx?" & strQueryString
End Sub

Sub btnClear_Click( s As Object, e As EventArgs )
  Session( "Canvas" ) = Nothing
  ibtnCanvas.ImageURL = "PaintImage.aspx"
End Sub

</Script>

<html>
<head><title>Painter.aspx</title></head>
<body>
<form Runat="Server">

<table width="100%">
<tr><td valign="top">

<h3>Draw:</h3>
<asp:ListBox
  ID="lstAction"
  Runat="Server" >
<asp:ListItem Text="SetPixel" />
<asp:ListItem Text="DrawLine" />
<asp:ListItem Text="DrawRectangle" />
<asp:ListItem Text="FillRectangle" />
</asp:ListBox>

<h3>Color:</h3>
<asp:ListBox
  id="lstColor"
  Runat="Server" />
<p>
<asp:Button
  id="btnClear"
  Text="Clear All"
  OnClick="btnClear_Click"
  Runat="Server" />

</td><td valign="top">
<asp:ImageButton
  id="ibtnCanvas"
  OnClick="ibtnCanvas_Click"
  Runat="Server" />

</td></tr>
</table>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

The Painter.aspx page contains a form with four controls. The first control, the lstAction ListBox, enables you to select a particular drawing action. For example, you can choose to draw a line or rectangle.

The second control, lstColor, is also a ListBox control. It enables you to select a color to use when drawing an object. The list of colors is retrieved from the KnownColor enumeration and bound to the ListBox control.

The third control, the btnClear Button, enables you to clear the current image from memory and start over drawing a new image.

Finally, the drawing canvas is represented by an ImageButton control named ibtnCanvas. The ImageURL property of the ImageButton control points to the PaintImage.aspx page. When you click any spot on the image, the ibtnCanvas_Click subroutine is executed.

The ibtnCanvas_Click subroutine passes information to the PaintImage.aspx page through a query string. The subroutine passes four bits of information: the selected action, selected color, and x and y coordinates of the mouse click.

The PaintImage.aspx page is contained in Listing 27.17. This page actually renders the image.

Listing 27.17 PaintImage.aspx
[View full width]
<%@ Page ContentType="image/gif" %>
<%@ Import namespace="System.Drawing" %>
<%@ Import namespace="System.Drawing.Imaging" %>

<Script Runat="Server">

Sub Page_Load
  Dim objBitmap As Bitmap
  Dim objGraphics As Graphics
  Dim objPen As Pen
  Dim strAction As String
  Dim strColor As String
  Dim objColor As Color
  Dim intOldX, intOldY As Integer
  Dim intNewX, intNewY As Integer

  ' Create or Retrieve Bitmap
  objBitmap = Session( "Canvas" )
  If objBitmap Is Nothing Then
    objBitmap = New Bitmap( 600, 400, PixelFormat.Format32bppARGB )
  End If
  objGraphics  = Graphics.FromImage( objBitmap )

  ' Get Action, Color, and Coordinates
  strAction = Request.QueryString( "a" )
  strColor = Request.QueryString( "color" )
  intNewX = Request.QueryString( "x" )
  intNewY = Request.QueryString( "y" )
  intOldX = Session( "OldX" )
  intOldY = Session( "OldY" )

  ' Create Pen
  If strColor <> Nothing Then
    objColor = Color.FromName( strColor )
    objPen = New Pen( objColor )
  Else
    objColor = Color.White
    objPen = New Pen( objColor )
  End If

  ' Perform Action
  Select Case strAction
    Case "SetPixel"
      objBitMap.SetPixel( intNewX, intNewY, objColor )
    Case "DrawLine"
      If intOldX <> Nothing Then
        objGraphics.DrawLine( objPen, intOldX, intOldY, intNewX, intNewY )
      End If
    Case "DrawLine"
      If intOldX <> Nothing Then
        objGraphics.DrawLine( objPen, intOldX, intOldY, intNewX, intNewY )
      End If
    Case "DrawRectangle"
      If intOldX <> Nothing Then
        objGraphics.DrawRectangle( objPen, intOldX, intOldY, intNewX - intOldX, intNewY -
   intOldY )
      End If
    Case "FillRectangle"
      If intOldX <> Nothing Then
        objGraphics.FillRectangle( New SolidBrush( objColor ), intOldX, intOldY, intNewX -
   intOldX, intNewY - intOldY )
      End If
  End Select

  ' Display the Bitmap
  objBitmap.Save( Response.OutputStream, ImageFormat.GIF)

  ' Save the Bitmap
  Session( "Canvas" ) = objBitMap

  If Session( "OldX" ) <> Nothing Then
    Session( "OldX" ) = Nothing
    Session( "OldY" ) = Nothing
  Else
    Session( "OldX" ) = intNewX
    Session( "OldY" ) = intNewY
  End If
End Sub

</Script>

The C# version of this code can be found on the CD-ROM.

The PaintImage.aspx page starts by checking whether a bitmap already exists in session state. If the bitmap does not exist, it is created.

The image is saved to session state between requests to the Web server. This step is necessary to preserve any modifications made to the image.

NOTE

To learn more information about session state, see Chapter 16, "Tracking User Sessions."

Next, all the information passed in the query string from the Painter.aspx page is retrieved. The selected action, selected color, and x and y coordinates are all assigned to local variables.

The bulk of the code for the PaintImage.aspx page is devoted to a Select...Case statement. This statement performs different modifications to the image, depending on the selected action. For example, if the selected action is DrawLine, a line is rendered on the image.

The image is actually displayed when the bitmap is rendered to the output stream of the Response object. The image is displayed by the ImageButton control in the Painter.aspx page.

Finally, the image is saved into session state. Placing the image into session state preserves the image until PaintImage.aspx is requested again.

Creating a Simple Chart Application

In this section, you build a simple chart application that enables you to display bar charts in a Web page (see Figure 27.17). The chart application consists of the following three files:

  • Chart.ascx— This file is a user control that transmits all the data to be charted to the ChartImage.aspx page. All the data is transmitted through a query string.

  • ChartImage.aspx— This page renders the bar chart as a GIF image.

  • TestChart.aspx— This page enables you to test the bar chart application. It instantiates the user control and binds the data from a database table to the control.

Figure 27.17. The chart application.

Start with the TestChart.aspx page because it gives you an idea of how the overall chart application works. The complete code for this page is included in Listing 27.18.

Listing 27.18 TestChart.aspx
<%@ Register TagPrefix="myControl" TagName="Chart"
  SRC="chart.ascx" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

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 ytd_sales From Titles", conPubs )
  conPubs.Open()
  ctrlChart.DataSource = cmdSelect.ExecuteReader()
  ctrlChart.DataField = "ytd_sales"
  ctrlChart.DataBind()
  conPubs.Close
End Sub

</Script>

<html>
<head><title>TestChart.aspx</title></head>
<body>

<h2>Year-To-Date Sales</h2>
<myControl:Chart
  id="ctrlChart"
  BackColor="Orange"
  ForeColor="Purple"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

The first line in the TestChart.aspx page registers the Chart.ascx user control with the TagPrefix named myControl and TagName named Chart. The Chart control is instantiated in the body of the page with an ID of ctrlChart.

The Chart control is bound to a database table in the Page_Load subroutine. This subroutine opens a database connection and creates a DataReader that represents records from the Titles table. The DataReader is bound to the Chart control with the help of the DataSource property.

You should notice that the DataField property of the Chart control is set. This property indicates the field from the DataReader that contains the data to be charted.

Finally, notice that the Chart control is declared with an orange backcolor and a purple forecolor. The Chart control renders a purple bar chart against an orange background. (You can, of course, change these color values to anything you want.)

The source of the Chart user control is contained in Listing 27.19.

Listing 27.19 Chart.ascx
<Script Runat="Server">

Public DataSource As IEnumerable
Public DataField As String
Public BackColor As String = "White"
Public ForeColor As String = "Blue"

Protected Overrides Sub OnDataBinding( e As EventArgs )
  Dim objDataEnum As IEnumerator
  Dim objDataItem As Object
  Dim strUrl As String
  Dim intMaxData As Integer

  If Not DataSource Is Nothing And DataField <> Nothing Then
    objDataEnum = DataSource.GetEnumerator()
    Do While( objDataEnum.MoveNext() )
      objDataItem = DataBinder.Eval( objDataEnum.Current, DataField )
      If IsNumeric( objDataItem ) Then
        strUrl &= "&d=" & Server.UrlEncode( objDataItem.ToString )
        If objDataItem > intMaxData Then intMaxData = objDataItem
      End If
    Loop
  strUrl = "x=" & intMaxData & "&backcolor=" & BackColor & "&forecolor=" & ForeColor & strUrl
  imgChartImage.ImageURL = "ChartImage.aspx?" & strURL
  End If
End Sub
</Script>

<asp:Image
  ID="imgChartImage"
  Runat="Server" />

The C# version of this code can be found on the CD-ROM.

The Chart user control has four public properties. DataField represents the field in the DataSource that contains the data to be charted. DataSource contains the actual data. The BackColor and ForeColor properties indicate the colors used when rendering the bar chart.

The DataSource property implements the IEnumerable interface. Because the DataReader implements the IEnumerable interface, you can bind data from a DataReader to the Chart control.

The bulk of the code for the Chart.ascx user control is contained in the OnDataBinding subroutine. This subroutine is invoked when the DataBind method is called in the TestChart.aspx page.

The OnDataBinding subroutine steps through all the data from the DataSource to build a query string. For example, assume that the DataSource contains the following values:

23.45, 89.12, 78, 22.12

In this case, the following query string is constructed:

d=23.45&d=89.12&d=78&d=22.12

After the query string is built, three additional query string variables are added to the beginning of it: x, BackColor, and ForeColor. The x variable represents the maximum value from the DataSource. You use this value when scaling the bar chart.

Finally, the query string is passed to an Image control, which displays the image rendered by the ChartImage.aspx page.

The complete code for the ChartImage.aspx page is contained in Listing 27.20.

Listing 27.20 ChartImage.aspx
<%@ Page ContentType="image/gif" %>
<%@ Import Namespace="System.Drawing" %>
<%@ Import Namespace="System.Drawing.Imaging" %>

<Script Runat="Server">

Sub Page_Load
  Dim objBitmap As Bitmap
  Dim objGraphics As Graphics
  Dim decMaxData As Decimal
  Dim strBackColor As String
  Dim strForeColor As String
  Dim objBackColor As Color
  Dim objForeColor As Color
  Dim decMultiplier As Decimal
  Dim intWidth As Integer
  Dim intHeight As Integer
  Dim arrChartData() As String
  Dim intDataIndex As Integer
  Dim intDataItem As Integer
  Dim objBrush As SolidBrush

  ' Calculate Multiplier
  decMaxData = Request.QueryString( "x" )
  decMultiplier = 100 / decMaxData

  ' Retrieve Colors
  strBackColor = Request.QueryString( "backcolor" )
  strForeColor = Request.QueryString( "forecolor" )
  objBackColor = ColorTranslator.FromHTML( strBackColor )
  objForeColor = ColorTranslator.FromHTML( strForeColor )
  objBrush = New SolidBrush( objForeColor )

  ' Build Bar Chart
  arrChartData = Request.QueryString.GetValues( "d" )
  intWidth = ( arrChartData.Length * 15 ) + 10
  intHeight = ( decMaxData * decMultiplier ) + 5
  If Not arrChartData Is Nothing Then
    objBitmap = New Bitmap( intWidth, intHeight )
    objGraphics = Graphics.FromImage( objBitmap )
    objGraphics.Clear( objBackColor )
    For intDataIndex = 0 To arrChartData.Length - 1
      intDataItem = arrChartData( intDataIndex ) * decMultiplier
      objGraphics.FillRectangle( _
        objBrush, _
        ( intDataIndex * 15 ) + 5, _
        intHeight - intDataItem, _
        10, _
        intHeight )
    Next
    objBitmap.Save( Response.OutputStream, ImageFormat.GIF )
  End If
End Sub

</Script>

The C# version of this code can be found on the CD-ROM.

The ChartImage.aspx page retrieves all the information from the query string. All the data to be charted is assigned to a string array named ChartArray.

Next, a bitmap is created, and the Clear method is called to set the background color. A For...Next loop steps through all the elements in arrChartData. A rectangle is drawn for each element with the FillRectangle method.

Finally, the bitmap is saved to the output stream of the Response object. At this point, the bar chart is displayed.


Summary

In this chapter, you learned how to take advantage of the GDI+ classes within an ASP.NET page. In the first section, you learned how to render a simple image with an ASP.NET page by creating and displaying a bitmap.

Next, you learned how to use brushes and pens to create fancy drawing effects. You also learned how to take advantage of the methods of the Graphics class to draw different types of objects, such as rectangles, pie sections, Bézier curves, and strings of text.

Finally, you examined two sample applications that use the GDI+ classes. You learned how to create a simple ASP.NET paint application and a simple bar chart application.


Part VIII: Building Custom ASP.NET Controls

IN THIS PART

CHAPTER 28 Developing Custom Controls

CHAPTER 29 Advanced Control Development


Chapter 28. Developing Custom Controls

IN THIS CHAPTER

  • Creating a Simple Control

  • Using HtmlTextWriter

  • Adding Properties and Methods to Controls

  • Parsing Inner Content

  • Adding Child Controls to a Control

  • Examining Custom Controls and Events

  • Participating in Postbacks

  • Creating Composite Controls

  • Inheriting from Existing Controls

  • Accessing the Current Context

  • Debugging Controls

In this chapter, you learn how to develop custom controls for the ASP.NET framework. I divided the topic of building custom controls into two chapters. In this chapter, I discuss all the basic topics of control development. In the next chapter, I cover more advanced topics, such as using templates and binding to a data source.

Here, you start with a simple control. In the first section, you learn how to build a custom control that displays the text "Hello World!".

Next, you jump deeper into the details of control building. You examine such topics as how to manipulate the output of a control by using HtmlTextWriter and how to add methods, properties, and events to a control.

You also examine the subject of building controls from existing controls. You learn how to build composite controls and inherit new controls from existing controls.

Finally, you learn how to access the current HTTP context from within a control. I discuss how you can access the Request and Response objects and retrieve information about browser capabilities through a control's Context property.


Creating a Simple Control

Let's start with a simple control: HelloWorld. The HelloWorld control displays the text "Hello World!" in a page. The code for this control is included in Listing 28.1.

NOTE

The CD that accompanies this book includes two new ASP.NET controls that you can use in your applications: the SuperDataGrid control and the SuperContentRotator control. The source code for these controls is included on the CD in both C# and VB.NET versions.

The SuperDataGrid control enables you to automatically sort, page, and edit database data. The SuperContentRotator control enables you to randomly display 1 or more items from a data source such as a database table or XML file. Both of these controls can be found in the SuperexpertControls folder on the CD.

Listing 28.1 HelloWorld.vb
Imports System
Imports System.Web
Imports System.Web.UI

Namespace myControls

Public Class HelloWorld: Inherits Control

  Protected Overrides Sub Render( objTextWriter As HtmlTextWriter )
    objTextWriter.Write( "Hello World!" )
  End Sub

End Class

End Namespace

The C# version of this code can be found on the CD-ROM.

Listing 28.1 contains the source code for a Visual Basic class named HelloWorld. What makes the class a control is the fact that it inherits from the Control class.

You inherit the Control class by using the Inherits statement. In particular, you inherit from the System.Web.UI Control class. This means that you can take advantage of all the properties, methods, and events of the base Control class in this control.

In Listing 28.1, you override the Render method of the Control class. When an ASP.NET page renders content, the page calls the Render method for each control on the page. So, any content that you output by using the Render method is rendered when the page itself is rendered.

The Render method has one parameter: an instance of the HtmlTextWriter class. By calling the Write method of the HtmlTextWriter class, you can display content. In this example, the Write method displays the text "Hello World!".

At the top of Listing 28.1, you import three namespaces: System, System.Web, and System.Web.UI. In general, it's a good idea to always import at least these three namespaces when building a control. These namespaces contain the basic classes used by controls, such as the base data types and the base Control and WebControl classes.

Finally, notice that you create a namespace named myControls for this control. You can create a namespace with any name that you wish. You need to refer to the namespace when you instantiate the control in an ASP.NET page.

Before you can use the HelloWorld control contained in Listing 28.1, you first need to compile it by executing the following statement from the command line:

vbc /t:library /r:System.dll,System.Web.dll HelloWorld.vb

NOTE

If you install Visual Studio .NET, then you cannot execute the vbc compiler from the default command prompt (the proper path environmental variable is not set). Instead, you need to execute the compiler by opening the Visual Studio .NET Command Prompt located at Start, Programs, Microsoft Visual Studio .NET, Visual Studio .NET Tools.

This statement compiles the control by using the Visual Basic command-line compiler. Two assemblies are referenced by the control when it's compiled: System.dll and System.Web.dll. If everything works correctly, the statement should produce a new file named HelloWorld.dll. This file is the compiled control.

Before you can use the compiled HelloWorld control, you need to copy the HelloWorld.dll to the /bin directory of your ASP.NET application. It needs to be located in this special directory so that ASP.NET pages can find it. If the /bin directory does not exist, you can create it as a subdirectory of the root directory of your ASP.NET application.

The page in Listing 28.2 illustrates how you can use the HelloWorld control in an ASP.NET page.

Listing 28.2 DisplayHelloWorld.aspx
<%@ Register TagPrefix="myControls" Namespace="myControls"
Assembly="HelloWorld"%>

<html>
<head><title>DisplayHelloWorld.aspx</title></head>
<body>

<myControls:HelloWorld
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

The first line in Listing 28.2 contains a Register page directive used to associate the tag prefix myControls with the namespace myControls. By default, an ASP.NET page recognizes the ASP TagPrefix for the System.Web.UI.WebControls namespace. When you add the Register directive, the page also recognizes the myControls tag prefix.

The HelloWorld control is instantiated in the body of the ASP.NET page. When the page in Listing 28.2 is rendered, the HelloWorld control's Render method is called, and the text "Hello World!" is displayed (see Figure 28.1).

Figure 28.1. Displaying the HelloWorld control.

    [ Team LiB ]


Using HtmlTextWriter

In the preceding section, you used HtmlTextWriter within the Render method to output content. HtmlTextWriter was designed to make it easier to output formatted content that contains HTML.

When you use HtmlTextWriter, the text that you output is automatically indented. So, if you render multiple nested HTML tags, HtmlTextWriter indents each nested HTML tag correctly.

HtmlTextWriter also includes several properties and methods that make it easier to output HTML content. The following is a partial list of the methods of HtmlTextWriter:

  • AddAttribute— Adds an HTML attribute that is rendered with a tag when the RenderBeginTag method is called; for example, Color="Red"

  • AddStyleAttribute— Adds a Style attribute that is rendered with a tag when the RenderBeginTag method is called; for example, Style="Font:Arial;"

  • RenderBeginTag— Renders an opening HTML tag with attributes; for example, <Font>

  • RenderEndTag— Renders the closing HTML tag for the last tag opened with the RenderBeginTag method; for example, </Font>

  • Write— Renders text

  • WriteAttribute— Renders an attribute name and value pair; for example, Color="Red"

  • WriteBeginTag— Renders the beginning of an HTML tag without the closing >; for example, <Font

  • WriteFullBeginTag— Renders a full opening HTML tag; for example, <Font>

  • WriteLine— Renders text followed by a line terminator

  • WriteLineNoTabs— Renders text without automatically adding tabs

  • WriteStyleAttribute— Renders a style attribute name and value pair; for example, Style="Font:Arial;"

In the preceding section, you created a control that displays the text "Hello World!". Suppose that you want to modify the control so that it displays the text with a green font. To do so, you could update the Render method like this:

Protected Overrides Sub Render( objTextWriter As HtmlTextWriter )
  Dim strText As String
  strText = "<font color=""green"">Hello World!</font>"
  objTextWriter.Write( strText )
End Sub

Here, you add an HTML <font> tag with its color attribute set to the value green. Writing HTML in this manner can get messy. Notice that you must double up the quotation marks to get them to display correctly within a Visual Basic string. If the tag had multiple attributes, the situation would only be worse.

Instead of writing the HTML tags yourself, you can have HtmlTextWriter do it for you. Here's how you could rewrite the Render method, taking full advantage of the methods of HtmlTextWriter:

Protected Overrides Sub Render( objTextWriter As HtmlTextWriter )
  objTextWriter.AddAttribute( "color", "green" )
  objTextWriter.RenderBeginTag( "font" )
  objTextWriter.Write( "Hello World!" )
  objTextWriter.RenderEndTag()
End Sub

When the Render method is called, the following text is output:

<font color="green">Hello World!</font>

By taking advantage of the methods of the HtmlTextWriter class, you can write code that is easier to read and maintain.

NOTE

When using the methods of HtmlTextWriter, you also have the option of using the HtmlTextWriterTag, HtmlTextWriterAttribute, and HtmlTextWriterStyle enumerations. These enumerations contain lists of standard HTML tags, attributes, and style attributes.


Adding Properties and Methods to Controls

You can add properties and methods to a control in the same way you would add properties and methods to any Visual Basic class. If a property or method is declared with the Public modifier, you can access it from within an ASP.NET page.

Suppose that you want to create a control that displays text with different colors. The control in Listing 28.3 demonstrates how you can create a control with properties.

Listing 28.3 ShowColor.vb
Imports System
Imports System.Web
Imports System.Web.UI

Namespace myControls

Public Class ShowColor: Inherits Control

Public Text As String
Public Color As String

Protected Overrides Sub Render( objTextWriter As HtmlTextWriter )
  objTextWriter.AddAttribute( "Color", Color )
  objTextWriter.RenderBeginTag( "Font" )
  objTextWriter.Write( Text )
  objTextWriter.RenderEndTag()
End Sub

End Class

End Namespace

The C# version of this code can be found on the CD-ROM.

The ShowColor class in Listing 28.3 contains two public variables named Text and Color. In the Render method, the value of the Text variable is rendered with the value of the Color variable.

The page in Listing 28.4 illustrates how you can use the ShowColor class within an ASP.NET page.

Listing 28.4 DisplayShowColor.aspx
<%@ Register TagPrefix="myControls" Namespace="myControls"
Assembly="ShowColor"%>

<html>
<head><title>DisplayShowColor.aspx</title></head>
<body>

<myControls:ShowColor
  Text="Hello World!"
  Color="Red"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

In Listing 28.4, the properties of the ShowColor control are set when the control is declared. The ShowColor control displays the text Hello World! in the color red.

Instead of setting properties declaratively, you also can set control properties programmatically. For example, the page in Listing 28.5 sets the Text and Color properties in the Page_Load subroutine.

Listing 28.5 DisplayShowColor2.aspx
<%@ Register TagPrefix="myControls" Namespace="myControls"
Assembly="ShowColor"%>

<Script Runat="Server">

Sub Page_Load
  ctrlShowColor.Text = "Hello World!"
  ctrlShowColor.Color = "Red"
End Sub

</Script>

<html>
<head><title>DisplayShowColor2.aspx</title></head>
<body>

<myControls:ShowColor
  ID="ctrlShowColor"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Using Property Accessor Functions

You can create properties by using property Get and Set accessor functions. The advantage of using accessor functions is that they enable you to execute additional programming logic when you read or set a property. For example, you can use accessor functions to perform data type conversions and handle errors.

The control in Listing 28.6 uses accessor functions for both the Text and Color properties. If you attempt to assign anything but Hello World! to the Text property, an ArgumentException is generated.

Listing 28.6 ShowColorAccessor.vb
Imports System
Imports System.Web
Imports System.Web.UI

Namespace myControls

Public Class ShowColorAccessor: Inherits Control

Private _text As String
Private _color As String

Public Property Text As String
Get
  Return _text
End Get
Set
  If Value <> "Hello World!" Then
    Throw New ArgumentException( "You didn't enter Hello World!" )
  Else
    _text = Value
  End If
End Set
End Property

Public Property Color As String
Get
  Return _color
End Get
Set
  _color = Value
End Set
End Property

Protected Overrides Sub Render( objTextWriter As HtmlTextWriter )
  objTextWriter.AddAttribute( "Color", _color )
  objTextWriter.RenderBeginTag( "Font" )
  objTextWriter.Write( _Text )
  objTextWriter.RenderEndTag()
End Sub

End Class

End Namespace

The C# version of this code can be found on the CD-ROM.

In the Set accessor function in Listing 28.6, an ArgumentException is raised if a Value other than Hello World! is passed to the function (see Figure 28.2).

Figure 28.2. Argument exception from ShowColorAccessor control.

Using Methods with Controls

You can add a method to a control by declaring a function or subroutine with a Public modifier. For example, the control in Listing 28.7 has a method named Reverse. When this method is called, the contents of the Text property are reversed.

Listing 28.7 ReverseMethodControl.vb
Imports System
Imports System.Web
Imports System.Web.UI
Imports Microsoft.VisualBasic

Namespace myControls

Public Class ReverseMethodControl: Inherits Control

Public Text As String

Public Sub Reverse()
  Text = StrReverse( Text )
End Sub

Protected Overrides Sub Render( objTextWriter As HtmlTextWriter )
  objTextWriter.Write( Text )
End Sub

End Class

End Namespace

The C# version of this code can be found on the CD-ROM.

Notice that the Microsoft.VisualBasic namespace is imported. You need to import this namespace because the StrReverse method is a member of it. The StrReverse method reverses the text in a string.

The page in Listing 28.8 demonstrates how you can call the Reverse method from within an ASP.NET page.

Listing 28.8 DisplayReverseMethodControl.aspx
<%@ Register TagPrefix="myControls" Namespace="myControls"
Assembly="ReverseMethodControl"%>

<Script Runat="Server">

Sub Page_Load
  ctrlReverse.Text = "Hello World!"
  ctrlReverse.Reverse()
End Sub

</Script>

<html>
<head><title>DisplayReverseMethodControl.aspx</title></head>
<body>

<myControls:ReverseMethodControl
  ID="ctrlReverse"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

In Listing 28.8, the Text property of ReverseMethodControl is assigned the value Hello World!. Next, the Reverse method is called, and the value of the Text property is reversed. The control outputs this string:

!dlroW olleH


Parsing Inner Content

Thus far, you have seen how to use properties to add values to a control. In this section, you explore how to assign values to a control by adding the values between a control's opening and closing tags.

Every control has a Controls collection that represents all of its child controls. When you add text between the opening and closing tags of a control, the text is added as a LiteralControl to the Controls collection. If you add text and, say, a TextBox control, two controls are added to the Controls collection: LiteralControl and TextBox.

The control in Listing 28.9, for example, displays whatever text appears between its opening and closing tags in a bold green font.

Listing 28.9 ShowGreen.vb
Imports System
Imports System.Web
Imports System.Web.UI

Namespace myControls

Public Class ShowGreen: Inherits Control

Protected Overrides Sub Render( objTextWriter As HtmlTextWriter )
  Dim strInnerText As String

  If IsLiteralContent
    strInnerText = CType( Controls( 0 ), LiteralControl ).Text
    objTextWriter.AddAttribute( "color", "green" )
    objTextWriter.RenderBeginTag( "font" )
    objTextWriter.RenderBeginTag( "b" )
    objTextWriter.Write( strInnerText )
    objTextWriter.RenderEndTag()
    objTextWriter.RenderEndTag()
  End If
End Sub

End Class

End Namespace

The C# version of this code can be found on the CD-ROM.

In the Render method in Listing 28.9, the IsLiteralContent method checks whether the control contains literal content between its opening and closing tags. If the control contains a single LiteralControl control, the IsLiteralContent method returns True.

LiteralControl is retrieved from the Controls collection, converted to the right type, and the value of its Text property is assigned to a variable named strInnerText. The value of the strInnerText variable is output using a bold green font.

The page in Listing 28.10 demonstrates how you can use the ShowGreen control with an ASP.NET page.

Listing 28.10 DisplayShowGreen.aspx
<%@ Register TagPrefix="myControls" Namespace="myControls"
Assembly="ShowGreen"%>

<html>
<head><title>DisplayShowGreen.aspx</title></head>
<body>

<myControls:ShowGreen
  Runat="Server">
  Hello World!
</myControls:ShowGreen>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

When the page in Listing 28.10 is displayed, the text that appears between the opening and closing ShowGreen tags is displayed with a bold green font. Because you use the IsLiteralContent method in the control's Render method, placing any other control within the opening and closing tags would prevent the control from displaying anything.


Adding Child Controls to a Control

Several of the standard ASP.NET controls, such as ListBox and RadioButtonList, are declared using child controls. For example, you declare a ListBox control with ListItem controls like this:

<asp:ListBox
  Runat="Server">
  <asp:ListItem Text="First Item" />
  <asp:ListItem Text="Second Item" />
  <asp:ListItem Text="Third Item" />
</asp:ListBox>

Here, ListBox contains three ListItem controls. When the ListBox control is displayed, the Text property for each ListItem control is displayed within the ListBox control.

You can create controls similar to the ListBox control. You simply need to declare two controls and embed the first control within the opening and closing tags of the second control.

Let's start with some code that doesn't quite work. I'll explain in a moment why the code doesn't work, but first take a look at Listing 28.11.

Listing 28.11 SimpleRotator1.vb
Imports System
Imports System.Web
Imports System.Web.UI

Namespace myControls

Public Class SimpleRotator1: Inherits Control

Protected Overrides Sub Render( objTextWriter As HtmlTextWriter)
  Dim objRandom As New Random
  Dim intRanIndex As Integer
  Dim objSelectedItem As RotatorItem1

  If Controls.Count > 0 Then
    intRanIndex = objRandom.Next( Controls.Count )
    objSelectedItem = CType( Controls( intRanIndex ), RotatorItem1 )
    objTextWriter.Write( objSelectedItem.Text )
  End If
End Sub

End Class

Public Class RotatorItem1 : Inherits Control
 Public Text As String
End Class

End Namespace

The C# version of this code can be found on the CD-ROM.

In Listing 28.11, two controls are declared. The first control is named SimpleRotator1, and the second control is named RotatorItem1.

When the SimpleRotator1 control is rendered, one of the controls in its Controls collection is randomly selected. The value of the Text property of the selected control is outputted.

Listing 28.12 illustrates how the controls are intended to be used in an ASP.NET page.

Listing 28.12 DisplaySimpleRotator1.aspx
<%@ Register TagPrefix="myControls" Namespace="myControls"
Assembly="SimpleRotator1"%>

<html>
<head><title>DisplaySimpleRotator1.aspx</title></head>
<body>

<myControls:SimpleRotator1
  Runat="Server">
  <myControls:RotatorItem1
    Text="First Item"
    Runat="Server" />
  <myControls:RotatorItem1
    Text="Second Item"
    Runat="Server" />
  <myControls:RotatorItem1
    Text="Third Item"
    Runat="Server" />
</myControls:SimpleRotator1>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

In Listing 28.12, an instance of the SimpleRotator1 control is declared; it contains three instances of the RotatorItem1 control. In theory, every time the page is loaded, one of the RotatorItem1 controls should be selected and the value of its Text property should be displayed.

If you attempt to request the page in Listing 28.12; however, you receive an InvalidCastException error slightly more than 50% of the time. What causes this error?

The problem is that the SimpleRotator1 control in Listing 28.12 contains seven controls instead of three. Three of the controls are RotatorItem1 controls. The other four controls are LiteralControl controls.

In Listing 28.12, a newline character separates each RotatorItem1 control. The additional controls are LiteralControl controls that represent these newline characters around each of the RotatorItem1 controls. One LiteralControl appears before the first RotatorItem1 control, and an additional LiteralControl appears after each RotatorItem1 control, which results in a sum total of four unexpected extra controls.

When the Render method executes, it randomly selects a control from the Controls collection and attempts to convert it into a RotatorItem1 control. Because slightly more than half of the controls are not RotatorItem1 controls, this causes an InvalidCastException error slightly more than half the time you request the page.

You need to get the ContentRotator control to ignore anything but RotatorItem1_ controls in its Controls collection. Fortunately, you can do so easily. Take a look at Listing 28.13.

Listing 28.13 SimpleRotator2.vb
Imports System
Imports System.Web
Imports System.Web.UI
Imports System.Collections

Namespace myControls

Public Class SimpleRotator2: Inherits Control

Public Items As New ArrayList()

Protected Overrides Sub AddParsedSubObject( obj As Object )
  If ( TypeOf Obj Is RotatorItem2 )
    Items.Add( Obj )
  End If
End Sub

Protected Overrides Sub Render(objTextWriter As HtmlTextWriter)
  Dim objRandom As New Random
  Dim intRanIndex As Integer
  Dim objSelectedItem As RotatorItem2
  If Items.Count > 0 Then
    intRanIndex = objRandom.Next( Items.Count )
    objSelectedItem = CType( Items( intRanIndex ), RotatorItem2 )
    objTextWriter.Write( objSelectedItem.Text )
  End If
End Sub

End Class

Public Class RotatorItem2 : Inherits Control
 Public Text As String
End Class

End Namespace

The C# version of this code can be found on the CD-ROM.

In Listing 28.13, you make some important changes to the original SimpleRotator control. The first thing you should notice is the new subroutine named AddParsedSubObject. This subroutine is called every time a new object is added to the Controls collection.

You override the AddParsedSubObject method to capture only RotatorItem2 objects and exclude LiteralControl objects. If you capture a RotatorItem2 object, you add it to a collection named Items. Otherwise, you ignore it.

Finally, you modify the Render method so that it randomly selects an object from the Items collection, rather than from the Controls collection. Because you exclude any LiteralControl controls from the Items collection, the Render method no longer produces an error.

After the SimpleRotator2.vb file is compiled and copied to the application's /bin directory, you can use the control in the ASP.NET page in Listing 28.14.

Listing 28.14 DisplaySimpleRotator2.aspx
<%@ Register TagPrefix="myControls" Namespace="myControls"
Assembly="SimpleRotator2"%>

<html>
<head><title>DisplaySimpleRotator2.aspx</title></head>
<body>

<myControls:SimpleRotator2
  Runat="Server">
  <myControls:RotatorItem2
    Text="First Item"
    Runat="Server" />
  <myControls:RotatorItem2
    Text="Second Item"
    Runat="Server" />
  <myControls:RotatorItem2
    Text="Third Item"
    Runat="Server" />
</myControls:SimpleRotator2>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Creating a Custom Control Builder

In the preceding section, you created a control that enables you to randomly display content in an ASP.NET page. The control works perfectly fine, but it does require some additional unnecessary typing. You need to declare the control like this:

<myControls:SimpleRotator2
  Runat="Server">
  <myControls:RotatorItem2
    Text="First Item"
    Runat="Server" />
  <myControls:RotatorItem2
    Text="Second Item"
    Runat="Server" />
  <myControls:RotatorItem2
    Text="Third Item"
    Runat="Server" />
</myControls:SimpleRotator2>

For each RotatorItem2 control, you need to include both the opening myControls tag prefix and the Runat="Server" attribute. This seems like a lot of extra typing for no apparent reason.

You can modify how the inner content of a control is parsed by creating a custom Control Builder, as illustrated in Listing 28.15. In particular, you can map particular tags to particular types with a Control Builder.

Listing 28.15 SimpleRotator3.vb
[View full width]
Imports System
Imports System.Web
Imports System.Web.UI
Imports System.Collections

Namespace myControls

' Declare Custom Control Builder
Public Class SimpleRotator3ControlBuilder : Inherits ControlBuilder
  Public Overrides Function GetChildControlType( TagName As String, Attributes As
   IDictionary ) As Type
    If TagName = "RotatorItem3"
      Return GetType( RotatorItem3 )
    End If
    Return Nothing
  End Function
End Class

' Declare SimpleRotator
<ControlBuilderAttribute(GetType( SimpleRotator3ControlBuilder))> _
Public Class SimpleRotator3: Inherits Control

  Public Items As New ArrayList()

  ' Exclude Everything but RotatorItem controls
  Protected Overrides Sub AddParsedSubObject( Obj As Object )
    If ( TypeOf Obj Is RotatorItem3 )
      Items.Add(Obj)
    End If
  End Sub

  Protected Overrides Sub Render( objTextWriter As HtmlTextWriter)
    Dim objRandom As New Random
    Dim intRanIndex As Integer
    Dim objSelectedItem As RotatorItem3

    If Items.Count > 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

The C# version of this code can be found on the CD-ROM.

In Listing 28.15, you create a new class named SimpleRotator3ControlBuilder. The SimpleRotator3ControlBuilder class inherits from the .NET ControlBuilder class.

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.

Finally, notice that you add a custom attribute to the declaration of the SimpleRotator3 class. You declare the class like this:

<ControlBuilderAttribute(GetType( SimpleRotator3ControlBuilder))> _
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.

Listing 28.16 DisplaySimpleRotator3.aspx
<%@ Register TagPrefix="myControls" Namespace="myControls"
Assembly="SimpleRotator3"%>

<html>
<head><title>DisplaySimpleRotator3.aspx</title></head>
<body>

<myControls:SimpleRotator3
  Runat="Server">
  <RotatorItem3 Text="First Item" />
  <RotatorItem3 Text="Second Item" />
  <RotatorItem3 Text="Third Item" />
</myControls:SimpleRotator3>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

In Listing 28.16, notice that you can declare each RotatorItem without using the myControls tag prefix or the Runat="Server" attribute.


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 event occurs right before the control's Render method is called.

  • Unload— This event occurs when the control is unloaded from memory.

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( "<li> OnInit Event!" )
End Sub

Protected Overrides Sub OnLoad( e As EventArgs )
  Context.Response.Write( "<li> OnLoad Event!" )
End Sub

Protected Overrides Sub OnPreRender( e As EventArgs )
  Context.Response.Write( "<li> OnPreRender Event!" )
End Sub

End Class

End Namespace

The C# version of this code can be found on the CD-ROM.

In Listing 28.17, the protected control methods OnInit, OnLoad, and OnPreRender are overridden to display a message when each event occurs.

Adding Custom Events to a Control

Several of the standard ASP.NET controls raise custom events. For example, the AdRotator control raises an AdCreated event right before it renders a banner advertisement. The Calendar control raises a DayRender event right before it renders each day on the calendar.

You can add events to your own controls by completing the following two steps:

  1. Declare an event by using the Event statement.

  2. Raise the event by creating a subroutine that contains the RaiseEvent statement.

The control in Listing 28.18, for example, randomly raises one of two events.

Listing 28.18 RandomEvent.vb
Imports System
Imports System.Web
Imports System.Web.UI

Namespace myControls

Public Class RandomEvent
Inherits Control

Public Event Event1( s As Object, e As EventArgs )
Public Event Event2( s As Object, e As EventArgs )

Protected Sub OnEvent1( e As EventArgs )
  RaiseEvent Event1( Me, EventArgs.Empty )
End Sub

Protected Sub OnEvent2( e As EventArgs )
  RaiseEvent Event2( Me, EventArgs.Empty )
End Sub

Protected Overrides Sub Render( objTextWriter As HtmlTextWriter )
  Dim objRandom As New Random
  Dim intRanSelect As Integer

  intRanSelect = objRandom.Next( 2 )
  If intRanSelect = 0 Then
    OnEvent1( EventArgs.Empty )
  Else
    OnEvent2( EventArgs.Empty )
  End If
End Sub

End Class

End Namespace

The C# version of this code can be found on the CD-ROM.

In Listing 28.18, two events are declared with the Event statement like this:

Public Event Event1( s As Object, e As EventArgs )
Public Event Event2( s As Object, e As EventArgs )

The control contains two subroutines for raising these events. The first of these subroutines looks like this:

Protected Sub OnEvent1( e As EventArgs )
  RaiseEvent Event1( Me, EventArgs.Empty )
End Sub

This subroutine simply raises Event1 by using the RaiseEvent statement. Two parameters are passed to the event: a reference to the current control and an empty EventArgs argument.

One or the other of these two subroutines is called within the Render method, which looks like this:

Protected Overrides Sub Render( objTextWriter As HtmlTextWriter )
  Dim objRandom As New Random
  Dim intRanSelect As Integer

  intRanSelect = objRandom.Next( 2 )
  If intRanSelect = 0 Then
    OnEvent1( EventArgs.Empty )
  Else
    OnEvent2( EventArgs.Empty )
  End If
End Sub

An instance of the Random class is used to randomly return either the number 0 or 1. If 0 is returned, the OnEvent1 subroutine is called; otherwise, the OnEvent2 subroutine is called.

After you compile and copy the RandomEvent control to the application's \bin directory, you can wire up subroutines to the two events. The ASP.NET page in Listing 28.19 demonstrates how you can create subroutines that handle the two events.

Listing 28.19 DisplayRandomEvent.aspx
<%@ Register TagPrefix="myControls" Namespace="myControls"
Assembly="RandomEvent"%>

<Script Runat="Server">

Sub doSomething1( s As Object, E As EventArgs )
  Response.Write( "Event1 Raised!" )
End Sub

Sub doSomething2( s As Object, E As EventArgs )
  Response.Write( "Event2 Raised!" )
End Sub

</Script>

<html>
<head><title>DisplayRandomEvent.aspx</title></head>
<body>

<myControls:RandomEvent
  OnEvent1="doSomething1"
  OnEvent2="doSomething2"
  Runat="Server"/>

</body>
</html>

The C# version of this code can be found on the CD-ROM.

When the RandomEvent control is declared, the doSomething1 subroutine is associated with the control's Event1 event, and the doSomething2 subroutine is associated with the control's Event2 event. Whenever you request the page, one or the other of the two subroutines executes.


Participating in Postbacks

Suppose that you want to display all the text boxes at your Web site with a blue background and a yellow font. It might make sense, in this situation, to simply create a custom control that renders a text box with the proper attributes. For example, the control in Listing 28.20 renders an <input> tag with a blue background and yellow foreground.

Listing 28.20 BlueTextBox.vb
Imports System
Imports System.Web
Imports System.Web.UI

Namespace myControls

Public Class BlueTextBox
Inherits Control

Protected Overrides Sub Render( objTextWriter As HtmlTextWriter)
  objTextWriter.AddAttribute( "Name", Me.UniqueID )
  objTextWriter.AddStyleAttribute( "background-color", "Blue" )
  objTextWriter.AddStyleAttribute( "color", "Yellow" )
  objTextWriter.RenderBeginTag( "input" )
  objTextWriter.RenderEndTag
End Sub

End Class

End Namespace

The C# version of this code can be found on the CD-ROM.

You can display the BlueTextBox control by using the page in Listing 28.21.

Listing 28.21 DisplayBlueTextBox.aspx
<%@ Register TagPrefix="myControls" Namespace="myControls"
Assembly="BlueTextBox"%>

<html>
<head><title>DisplayBlueTextBox.aspx</title></head>
<body>
<form Runat="Server">

<myControls:BlueTextBox
  Runat="Server"/>
<asp:Button
  Text="Submit!"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

If you post the form containing the BlueTextBox control in Listing 28.21, you run into a problem. The BlueTextBox control does not retain its value between form posts. In other words, the control does not support postbacks.

To get the BlueTextBox control to support postbacks, you need to do slightly more work. You need to implement the IPostBackDataHandler interface. This interface defines the following two methods:

  • LoadPostData— Processes the data being posted back to the server

  • RaisePostDataChangedEvent— Raises the change event for a control

The modified BlueTextBox control in Listing 28.22, named BlueTextBoxPostBack, illustrates how you can implement the IPostBackDataHandler interface.

Listing 28.22 BlueTextBoxPostBack.vb
[View full width]
Imports System
Imports System.Web
Imports System.Web.UI
Imports System.Collections.Specialized

Namespace myControls

Public Class BlueTextBoxPostBack
Inherits Control
Implements IPostBackDataHandler

Public Text As String

Public Function LoadPostData( PostDataKey As String, Values As NameValueCollection) As
   Boolean _
Implements IPostBackDataHandler.LoadPostData
  Text =  Values( Me.UniqueID )
  Return False
End Function

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", "Blue" )
  objTextWriter.AddStyleAttribute( "color", "Yellow" )
  objTextWriter.AddAttribute( "value", Text )
  objTextWriter.RenderBeginTag( "input" )
  objTextWriter.RenderEndTag
End Sub

End Class

End Namespace

The C# version of this code can be found on the CD-ROM.

You add one new function and one new subroutine in Listing 28.22. The LoadPostData function has two parameters. The first parameter, PostDataKey, represents the unique ID for the control. The second parameter, Values, represents the values of all the form elements being posted back to the server.

NOTE

In Listing 28.22, the first parameter passed to the LoadPostData function is an instance of the NameValueCollection collection. At the top of Listing 28.22, you import System.Collections.Specialized so that you have access to this collection.

Within the LoadPostData function, you grab the value from the Values collection for the current control and assign it to the string variable named Text. Next, you return the value False from the function.

The RaisePostBackDataChanged subroutine is called only when the LoadPostData function returns the value True. In Listing 28.22, you don't do anything with this subroutine. I'll explain its purpose in a moment.

Within the Render method, you add an additional attribute, named Value, to the text box by using the AddAttribute method. You assign the value of the Text property to this attribute.

The page in Listing 28.23 illustrates how you can display the new and improved BlueTextBox control.

Listing 28.23 DisplayBlueTextBoxPostBack.aspx
<%@ Register TagPrefix="myControls" Namespace="myControls"
Assembly="BlueTextBoxPostBack"%>

<html>
<head><title>DisplayBlueTextBoxPostBack.aspx</title></head>
<body>
<form Runat="Server">

<myControls:BlueTextBoxPostBack
  Runat="Server"/>
<asp:Button
  Text="Submit!"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

If you enter data in the text box in Listing 28.23 and submit the form, the text box redisplays the data when the form is reloaded.

Remember that the BlueTextBox control had an additional subroutine, named RaisePostDataChangedEvent, which you ignored earlier. You can use this subroutine to raise a change event whenever the data in the text box changes. The control in Listing 28.24 illustrates how to do so.

Listing 28.24 BlueTextBoxChanged.vb
[View full width]
Imports System
Imports System.Web
Imports System.Web.UI
Imports System.Collections.Specialized

Namespace myControls

Public Class BlueTextBoxChanged
Inherits Control
Implements IPostBackDataHandler

Public Event TextChanged( s As Object, e As EventArgs )

Public Property Text As String
Set
  ViewState( "Text" ) = Value
End Set
Get
  If ViewState( "Text" ) <> Nothing Then
    Return ViewState( "Text" )
  Else
    Return String.Empty
  End If
End Get
End Property

Public Function LoadPostData( PostDataKey As String, Values As NameValueCollection ) As
   Boolean _
Implements IPostBackDataHandler.LoadPostData
  Dim strNewValue As String

  strNewValue = Values( Me.UniqueID )
  If strNewValue <> Text Then
    Text = strNewValue
    Return True
  Else
    Return False
  End If
End Function

Public Sub RaisePostDataChangedEvent() _
Implements IPostBackDataHandler.RaisePostDataChangedEvent
  OnTextChanged( EventArgs.Empty )
End Sub

Protected Sub OnTextChanged( e As EventArgs )
  RaiseEvent TextChanged( Me, e )
End Sub

Protected Overrides Sub Render( objTextWriter As HtmlTextWriter )
  objTextWriter.AddAttribute( "Name", Me.UniqueID )
  objTextWriter.AddStyleAttribute( "background-color", "Blue" )
  objTextWriter.AddStyleAttribute( "color", "Yellow" )
  objTextWriter.AddAttribute( "value", Text )
  objTextWriter.RenderBeginTag( "input" )
  objTextWriter.RenderEndTag
End Sub

End Class

End Namespace

The C# version of this code can be found on the CD-ROM.

In Listing 28.24, you modify both the LoadPostData function and RaisePostDataChangedEvent subroutine. The LoadPostData function will now return True when the contents of the text box are changed. The new value entered into the text box is compared against a value saved in the control's view state.

If the LoadPostData function returns True, the RaisePostDataChangedEvent subroutine executes. This subroutine calls the OnTextChanged subroutine, which, in turn, raises the TextChanged event.

The ASP.NET page in Listing 28.25 illustrates how you can handle the OnTextChanged event.

Listing 28.25 DisplayBlueTextBoxChanged.aspx
<%@ Register TagPrefix="myControls" Namespace="myControls"
Assembly="BlueTextBoxChanged"%>

<Script Runat="Server">

Sub BlueTextBoxChanged_TextChanged( s As Object, e As EventArgs )
  lblMessage.Text = "Text Changed!"
End Sub

</Script>

<html>
<head><title>DisplayBlueTextBoxChanged.aspx</title></head>
<body>
<form Runat="Server">

<myControls:BlueTextBoxChanged
  OnTextChanged="BlueTextBoxChanged_TextChanged"
  Runat="Server"/>
<asp:Button
  Text="Submit!"
  Runat="Server" />
<p>
<asp:Label
  ID="lblMessage"
  ForeColor="Red"
  Font-Bold="True"
  EnableViewState="False"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

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 TextBoxColor.vb
[View full width]
Imports System
Imports System.Web
Imports System.Web.UI
Imports System.Collections.Specialized

Namespace myControls

Public Class TextBoxColor
Inherits Control
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

  Text = Values( Me.UniqueID )
  Return False
End Function

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( "<p>" )
  objTextWriter.AddAttribute( "Type", "Button" )
  objTextWriter.AddAttribute( "Value", "Display Red!" )
  objTextWriter.AddAttribute( "OnClick", "JScript:" & Page.GetPostBackEventReference( Me,
   "Red" ) )
  objTextWriter.RenderBeginTag( "Input" )
  objTextWriter.RenderEndTag()
  objTextWriter.WriteLine( "&nbsp;&nbsp;" )
  objTextWriter.AddAttribute( "Type", "Button" )
  objTextWriter.AddAttribute( "Value", "Display Blue!" )
  objTextWriter.AddAttribute( "OnClick", "JScript:" & Page.GetPostBackEventReference( Me,
   "Blue" ) )
  objTextWriter.RenderBeginTag( "Input" )
  objTextWriter.RenderEndTag()
End Sub

End Class

End Namespace

The C# version of this code can be found on the CD-ROM.

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 OnClick attribute that has a JScript function as its value. For example, the OnClick attribute for the Display Red! button is declared like this:

[View full width]

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:

<input Type="Button" Value="Display Red!" OnClick="JScript:__doPostBack ('ctrl4','Red') " />

The Display Red! button triggers the client-side JavaScript function named __doPostBack. Notice that the unique ID of the control and parameter are passed by the 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()
}

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.

Listing 28.27 DisplayTextBoxColor.aspx
<%@ Register TagPrefix="myControls" Namespace="myControls"
Assembly="TextBoxColor"%>

<html>
<head><title>DisplayTextBoxColor.aspx</title></head>
<body>
<form Runat="Server">

<myControls:TextBoxColor
  Runat="Server"/>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.


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 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 Login.vb
Imports System
Imports System.Web
Imports System.Web.UI
Imports 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( "<div style=""border: 5px " & _
    "inset #cccccc;background-color:#eeeeee;width:50%;padding:10px"">" ) )

  ' Add Username
  Me.Controls.Add( New LiteralControl( "<b>Username:</b> " ) )
  Me.Controls.Add( New TextBox )
  Me.Controls.Add( New LiteralControl( "<p>" ) )

  ' Add Password
  Dim txtPass As New TextBox
  Me.Controls.Add( New LiteralControl( "<b>Password:</b> " ) )
  txtPass.TextMode = TextBoxMode.Password
  Me.Controls.Add( txtPass )
  Me.Controls.Add( New LiteralControl( "<p>" ) )

  ' Add Submit Button
  Dim btnButton As New Button
  btnButton.Text = "Login!"
  Me.Controls.Add( btnButton )
  Me.Controls.Add( New LiteralControl( "</div>" ) )
End Sub

End Class

End Namespace

The C# version of this code can be found on the CD-ROM.

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.

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.

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
<%@ Register TagPrefix="myControls" Namespace="myControls"
Assembly="Login"%>

<Script Runat="Server">

Sub Page_Load
  If IsPostBack Then
    lblMessage.Text = "Hi " & ctrlLogin.Username
    lblMessage.Text &= ", your password is: "
    lblMessage.Text &= ctrlLogin.Password
  End If
End Sub

</Script>

<html>
<head><title>DisplayLogin.aspx</title></head>
<body>
<form Runat="Server">

<myControls:Login
  ID="ctrlLogin"
  Runat="Server"/>
<p>
<asp:Label
  ID="lblMessage"
  EnableViewState="False"
  Font-Size="18pt"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

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

Figure 28.3. Displaying the Login control.

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.

Listing 28.30 LoginEvent.vb
Imports System
Imports System.Web
Imports System.Web.UI
Imports 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

Protected Overrides Sub CreateChildControls()
  Me.Controls.Add( New LiteralControl( "<div style=""border: 5px " & _
    "inset #cccccc;background-color:#eeeeee;width:50%;padding:10px"">" ) )

  ' Add Username
  Me.Controls.Add( New LiteralControl( "<b>Username:</b> " ) )
  Me.Controls.Add( New TextBox )
  Me.Controls.Add( New LiteralControl( "<p>" ) )

  ' Add Password
  Dim txtPass As New TextBox
  Me.Controls.Add( New LiteralControl( "<b>Password:</b> " ) )
  txtPass.TextMode = TextBoxMode.Password
  Me.Controls.Add( txtPass )
  Me.Controls.Add( New LiteralControl( "<p>" ) )

  ' 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( "</div>" ) )

  ' Add Label Control
  Dim lblLabel As New Label
  lblLabel.EnableViewState = False
  Me.Controls.Add( lblLabel )
End Sub

End Class

End Namespace

The C# version of this code can be found on the CD-ROM.

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.

Listing 28.31 DisplayLoginEvent.aspx
<%@ Register TagPrefix="myControls" Namespace="myControls"
Assembly="LoginEvent"%>

<html>
<head><title>DisplayLoginEvent.aspx</title></head>
<body>
<form Runat="Server">

<myControls:LoginEvent
  Runat="Server"/>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.


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.

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.

Listing 28.32 myWebControl.vb
Imports System
Imports System.Web
Imports System.Web.UI
Imports 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

The C# version of this code can be found on the CD-ROM.

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 myWebControl control in a bold red font with a yellow background.

Listing 28.33 DisplayWebControl.aspx
<%@ Register TagPrefix="myControls" Namespace="myControls"
Assembly="myWebControl"%>

<html>
<head><title>DisplayWebControl.aspx</title></head>
<body>
<form Runat="Server">

<myControls:myWebControl
  BackColor="Yellow"
  Font-Bold="True"
  ForeColor="Red"
  Runat="Server"/>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.


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

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( "<li> HostAddress: " & strHostAddress )
  objTextWriter.WriteLine( "<li> UserAgent: " & strUserAgent )
End Sub

End Class

End Namespace

The C# version of this code can be found on the CD-ROM.

In Listing 28.34, the values of the UserHostAddress and UserAgent properties are displayed in the Render method. Both properties are retrieved through the Context property of the Control class.

You also can use the Context property to retrieve information about the capabilities of the browser making the request. For example, the control in Listing 28.35 displays the browser type, the browser version, and whether the browser supports JavaScript.

Listing 28.35 ControlCaps.vb
Imports System
Imports System.Web
Imports System.Web.UI

Namespace myControls

Public Class ControlCaps
Inherits Control

Overrides Protected Sub Render( objTextWriter As HtmlTextWriter )
  Dim strBrowserType As String
  Dim strVersion As String
  Dim blnJavascript As Boolean

  strBrowserType = Context.Request.Browser.Browser
  strVersion = Context.Request.Browser.Version
  blnJavascript = Context.Request.Browser.Javascript

  objTextWriter.WriteLine( "<li> Browser: " & strBrowserType )
  objTextWriter.WriteLine( "<li> Version: " & strVersion )
  objTextWriter.WriteLine( "<li> Javascript: " & blnJavascript.ToString() )
End Sub

End Class

End Namespace

The C# version of this code can be found on the CD-ROM.

In Listing 28.35, the Context property retrieves an instance of the Browser object, which contains properties that represent the browser capabilities.


Debugging Controls

There is one last topic that you must examine before completing this chapter. In previous sections, I talked about methods of developing basic controls, but I haven't discussed how you can debug a control.

You can use the Trace object within a control, as shown in Listing 28.36, to display trace messages.

Listing 28.36 ControlTrace.vb
Imports System
Imports System.Web
Imports System.Web.UI

Namespace myControls

Public Class ControlTrace
Inherits Control

Overrides Protected Sub OnLoad( e As EventArgs )
  Context.Trace.Warn( "Executing OnLoad Method!" )
End Sub

Overrides Protected Sub OnPreRender( e As EventArgs )
  Context.Trace.Warn( "Executing OnPreRender Method!" )
End Sub

Overrides Protected Sub Render( objTextWriter As HtmlTextWriter )
  Context.Trace.Warn( "Executing Render Method!" )
  objTextWriter.WriteLine( "Hello World!" )
End Sub

End Class

End Namespace

The C# version of this code can be found on the CD-ROM.

After you add trace messages to a control, you need to enable tracing for the page that contains the control. The page in Listing 28.37 illustrates how you can enable tracing and show all the trace messages from the ControlTrace control.

Listing 28.37 DisplayControlTrace.aspx
<%@ Page Trace="True" %>
<%@ Register TagPrefix="myControls" Namespace="myControls"
Assembly="ControlTrace"%>

<html>
<head><title>DisplayControlTrace.aspx</title></head>
<body>
<form Runat="Server">

<myControls:ControlTrace
  Runat="Server"/>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

When the page in Listing 28.37 is displayed, all the trace messages from the control appear at the bottom of the page in a red font (see Figure 28.4).

Figure 28.4. Displaying control trace messages.

    [ Team LiB ]


Summary

This chapter provided a detailed overview of custom control development in the ASP.NET framework. You learned how to create controls that have properties, methods, and events and create new controls from existing controls through control composition and inheritance. Finally, you learned how to access the Request, Response, and Trace objects through a control's Context property.

In the next chapter, you will tackle some more advanced topics in control development, and you will learn how to create controls that support templates and binding to a data source, such as a database table or XML file.


Chapter 29. Advanced Control Development

IN THIS CHAPTER

  • Adding Templates to a Control

  • Creating DataBound Controls

  • Using Custom Controls and Web Services

This chapter tackles some of the more advanced topics involved in building custom controls. In the first section, you learn how to add templates to a custom control. Implementing templates in a control enables users to customize its output.

Next, you examine the topic of data binding. You learn how to add properties to a control to enable users to bind the control to data sources, such as database tables and collections.

Finally, you learn how to create controls that integrate with Web Services by building a sample control that retrieves and displays information from a Web Service.


Adding Templates to a Control

Several of the standard ASP.NET controls, such as Repeater, DataList, and DataGrid, support templates. By adding templates to a control, you can enable its users to customize its output.

To add a template to a control, you need to complete each of the following steps:

  1. Add a property that represents the template to the custom control. This property must implement the ITemplate interface.

  2. Create a new control that functions as a container for the template (the template container control).

  3. Instantiate the template in the template container control by calling the template's InstantiateIn method.

In Listing 29.1, for example, a new custom control named SimpleTemplate is created. This control has a property named ItemTemplate that represents a template. This property implements the ITemplate interface.

Listing 29.1 also contains a declaration for a second control named TemplateItem. This control functions as a container control for the template.

Finally, the SimpleTemplate control contains a CreateChildControls subroutine. Within this subroutine, the TemplateItem control is instantiated. Next, the InstantiateIn method is called on the template to instantiate all the elements of the template in the TemplateItem control. Finally, the TemplateItem is added to the SimpleTemplate control's Controls collection.

Listing 29.1 SimpleTemplate.vb
Imports System
Imports System.Web
Imports System.Web.UI

Namespace myControls

Public Class TemplateItem
Inherits Control
Implements INamingContainer

  Private _dataItem As String

  Public Sub New( DataItem As String )
    _dataItem = DataItem
  End Sub

  Public Property DataItem As String
    Get
      Return _dataItem
    End Get
    Set
      _dataItem = Value
    End Set
  End Property
End Class

<ParseChildren(true)> Public Class SimpleTemplate
Inherits Control
Implements INamingContainer

  Private _itemTemplate As ITemplate
  Private _text As String

  Public Property Text As String
    Get
      Return _text
    End Get
    Set
      _text = Value
    End Set
  End Property

  <TemplateContainer(GetType(TemplateItem))> _
  Public Property ItemTemplate As ITemplate
    Get
      Return _itemTemplate
    End Get
    Set
      _itemTemplate = Value
    End Set
  End Property

  Public Overrides Sub DataBind()
    EnsureChildControls()
    MyBase.DataBind()
  End Sub

  Protected Overrides Sub CreateChildControls()
    Dim objTemplateItem As TemplateItem

    Controls.Clear()
    objTemplateItem = New TemplateItem( Me.Text )
    ItemTemplate.InstantiateIn( objTemplateItem )
    Controls.Add( objTemplateItem )
  End Sub
End Class

End Namespace

The C# version of this code can be found on the CD-ROM.

After you create the file in Listing 29.1, you need to compile it by executing the following statement from the command line:

vbc /t:library /r:System.dll,System.Web.dll SimpleTemplate.vb

This statement compiles the file into an assembly named SimpleTemplate.dll. You need to copy the SimpleTemplate.dll file into your application's /bin directory before you can use the SimpleTemplate control in an ASP.NET page.

The page in Listing 29.2 uses the SimpleTemplate control to format the value of a property named Text.

Listing 29.2 DisplaySimpleTemplate.aspx
<%@ Register TagPrefix="myControls" Namespace="myControls"
Assembly="SimpleTemplate"%>

<Script Runat="Server">

Sub Page_Load
  DataBind()
End Sub

</Script>

<html>
<head><title>DisplaySimpleTemplate.aspx</title></head>
<body>
<form Runat="Server">

<myControls:SimpleTemplate
  Text="Hello World!"
  Runat="Server">
<ItemTemplate>
  The value of DataItem is:
  <b><%# Container.DataItem %></b>
</ItemTemplate>
</myControls:SimpleTemplate>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

In Listing 29.2, an instance of the SimpleTemplate control is declared. Within this control, the ItemTemplate template is declared.

The myTemplate template formats the output of a property of the SimpleTemplate property named Text. The value of the Text property is displayed in bold by the template.

Notice that you must call the DataBind() method in the Page_Load subroutine. The DataBind() method causes all the databinding expressions in the page to be interpreted including the databinding expression in the ItemTemplate.

Creating Multiple Instances of a Template

After you create a template, you can instantiate it multiple times within a control. For example, imagine that you decided to create a control that displays the numbers 1 through 10. Now imagine that you want to add a template to the control to format how each number is displayed. The control in Listing 29.3 does exactly that.

Listing 29.3 TemplateNumbers.vb
Imports System
Imports System.Web
Imports System.Web.UI

Namespace myControls

Public Class TemplateNumbersItem
Inherits Control
Implements INamingContainer

  Private _dataItem As Integer

  Public Sub New( DataItem As Integer )
    _dataItem = DataItem
  End Sub

  Public Property DataItem As Integer
    Get
      Return _dataItem
    End Get
    Set
      _dataItem = Value
    End Set
  End Property
End Class

<ParseChildren(true)> Public Class TemplateNumbers
Inherits Control
Implements INamingContainer

  Private _numberTemplate As ITemplate

  <TemplateContainer(GetType(TemplateNumbersItem))> _
  Public Property NumberTemplate As ITemplate
    Get
      Return _numberTemplate
    End Get
    Set
      _numberTemplate = Value
    End Set
  End Property

  Public Overrides Sub DataBind()
    EnsureChildControls()
    MyBase.DataBind()
  End Sub

  Protected Overrides Sub CreateChildControls()
    Dim intCounter As Integer
    Dim objTemplateNumbersItem As TemplateNumbersItem

    Controls.Clear()
    For intCounter = 1 To 10
      objTemplateNumbersItem = New TemplateNumbersItem( intCounter )
      NumberTemplate.InstantiateIn( objTemplateNumbersItem )
      Controls.Add( objTemplateNumbersItem )
    Next
  End Sub
End Class

End Namespace

The C# version of this code can be found on the CD-ROM.

Listing 29.3 contains the declaration of a control named TemplateNumbers. This control contains the CreateChildControls subroutine, which creates multiple instances of the TemplateNumbersItem template container. Within the subroutine, a FOR...NEXT loop steps through the numbers 1 through 10. For each step in the loop, an instance of TemplateNumbersItem is created and added to the controls collection.

The page in Listing 29.4 illustrates how you can use the TemplateNumbers control in an ASP.NET page.

Listing 29.4 DisplayTemplateNumbers.aspx
<%@ Register TagPrefix="myControls" Namespace="myControls"
Assembly="TemplateNumbers"%>

<Script runat="Server">

Sub Page_Load
  DataBind()
End Sub

</Script>

<html>
<head><title>DisplayTemplateNumbers.aspx</title></head>
<body>
<form Runat="Server">

<myControls:TemplateNumbers
  Runat="Server">
  <NumberTemplate>
  <li><i><%# Container.DataItem %></i>
  </NumberTemplate>
</myControls:TemplateNumbers>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

The page in Listing 29.4 uses the TemplateNumbers control to display the numbers 1 through 10. Each number is formatted with the NumberTemplate template.

Adding Multiple Templates to a Control

Most likely, you need to add multiple templates to a control. For example, the standard ASP.NET Repeater control has six templates: HeaderTemplate, ItemTemplate, AlternatingItemTemplate, SeparatorTemplate, and FooterTemplate.

You can create multiple templates for a control by adding multiple properties that implement the ITemplate interface to the control. For example, the control in Listing 29.5 contains both ItemTemplate and AlternatingItemTemplate.

Listing 29.5 MultipleTemplates.vb
Imports System
Imports System.Web
Imports System.Web.UI

Namespace myControls

Public Class MultipleTemplatesTemplateItem
Inherits Control
Implements INamingContainer

    Private _dataItem As Integer

    Public Sub New( DataItem As Integer )
      _dataItem = DataItem
    End Sub

    Public Property DataItem As Integer
    Get
      Return _dataItem
    End Get
    Set
      _dataItem = Value
    End Set
  End Property
End Class

<ParseChildren(true)> Public Class MultipleTemplates
Inherits Control
Implements INamingContainer

  Private _itemTemplate As ITemplate
  Private _alternatingItemTemplate As ITemplate

  <TemplateContainer(GetType(MultipleTemplatesTemplateItem))> _
  Public Property ItemTemplate As ITemplate
    Get
      Return _itemTemplate
    End Get
    Set
      _itemTemplate = Value
    End Set
  End Property

  <TemplateContainer(GetType(MultipleTemplatesTemplateItem))> _
  Public Property AlternatingItemTemplate As ITemplate
    Get
      Return _alternatingItemTemplate
    End Get
    Set
      _alternatingItemTemplate = Value
    End Set
  End Property

  Public Overrides Sub DataBind()
    EnsureChildControls()
    MyBase.DataBind()
  End Sub

  Protected Overrides Sub CreateChildControls()
    Dim intCounter As Integer
    Dim objTemplateItem As MultipleTemplatesTemplateItem

    Controls.Clear()
    For intCounter = 1 To 10
      objTemplateItem = New MultipleTemplatesTemplateItem( intCounter )
      If Decimal.Remainder( intCounter, 2 ) = 0 Then
        ItemTemplate.InstantiateIn( objTemplateItem )
      Else
        AlternatingItemTemplate.InstantiateIn( objTemplateItem )
    End If
    Controls.Add( objTemplateItem )
  Next
End Sub

End Class

End Namespace

The C# version of this code can be found on the CD-ROM.

Listing 29.5 contains declarations for two templates. The MultipleTemplate control has properties named ItemTemplate and AlternatingItemTemplate.

Within the FOR...NEXT loop, every number is displayed in either ItemTemplate or AlternatingItemTemplate. The Remainder method of the Decimal class determines whether a number is even or odd. If a number is even, it's displayed by AlternatingItemTemplate; otherwise, it's displayed with ItemTemplate.

The page in Listing 29.6 illustrates how you can specify ItemTemplate and AlternatingItemTemplate within an ASP.NET page.

Listing 29.6 DisplayMultipleTemplates.aspx
<%@ Register TagPrefix="myControls" Namespace="myControls"
Assembly="MultipleTemplates"%>

<Script runat="Server">

Sub Page_Load
  DataBind()
End Sub

</Script>

<html>
<head><title>DisplayMultipleTemplates.aspx</title></head>
<body>
<form Runat="Server">

<myControls:MultipleTemplates
  Runat="Server">
  <ItemTemplate>
    <li><i><%# Container.DataItem %></i>
  </ItemTemplate>
  <AlternatingItemTemplate>
    <li><h2><%# Container.DataItem %></h2>
  </AlternatingItemTemplate>
</myControls:MultipleTemplates>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

In Listing 29.6, an instance of the MultipleTemplates control is declared. ItemTemplate formats each number in italics, and AlternatingItemTemplate formats each number with the HTML <H2> heading tag (see Figure 29.1).

Figure 29.1. Displaying content with multiple templates.

    [ Team LiB ]


Creating DataBound Controls

You can bind several of the standard ASP.NET controls to a data source. For example, you can bind a Repeater control to data sources, such as DataReaders, ArrayLists, and DataViews.

You too can create a control that supports binding to a data source. To create a DataBound control, you must do the following:

  • Add a DataSource property. This property typically implements the IEnumerable, ICollection, IList, or IListSource interface.

  • Add an OnDataBinding subroutine. This subroutine is invoked when the DataBind method is called.

You examine the DataSource property and OnDataBinding subroutine in the following sections.

NOTE

Classes that support the IEnumerable interface are required to have a method named GetEnumerator. The GetEnumerator method returns an enumerator that can be used to iterate through all the items in the underlying collection.

Implementing Different Data Sources

In most cases, a control's DataSource property should implement the IEnumerable interface, which is implemented by such classes as DataReader, DataView, and ArrayList. The IEnumerable interface exposes a method named GetEnumerator that enables you to walk through all the elements of a collection. If you simply want to display a list of items from a data source, you should implement this interface.

In some situations, however, you need to implement the ICollection interface, which enables you to work with the contents of a data source as a whole. For example, it includes a Count property, which enables you to get a total count of the number of items in the collection. The ICollection interface also has a CopyTo method, which copies the contents of a data source to an array.

If you're implementing a control, such as a content rotator control that enables you to randomly select an item from a data source, you need to use the ICollection interface. Otherwise, you cannot randomly select an item from the whole data source.

Standard ASP.NET controls—such as Repeater, DataList, and DataGrid—also support the IListSource interface. They support this interface so that they can be bound directly to a DataSet. For example, you can specify the data source for a Repeater control like this:

rptRepeater.DataSource = dstDataSet
rptRepeater.DataMember = "Products"

The first statement binds the Repeater control to a DataSet. The second statement selects a particular table in the DataSet to bind to.

Implementing a Simple DataBound Control

For this example, you can start by creating a simple DataBound control that binds to an ArrayList. The control in Listing 29.7 has a DataSource property that implements the IEnumerable interface.

Listing 29.7 SimpleDataBound.vb
Imports System
Imports System.Web
Imports System.Web.UI
Imports System.Collections

Namespace myControls

Public Class SimpleDataBound
Inherits Control

Private _dataSource As IEnumerable

Public Property DataSource As IEnumerable
Get
  Return _dataSource
End Get
Set
  _dataSource = Value
End Set
End Property

Protected Overrides Sub OnDataBinding( e As EventArgs )
  Dim DataEnum As IEnumerator
  Dim ltlLiteral As LiteralControl

  If Not DataSource Is Nothing
    Controls.Clear()
    DataEnum = DataSource.GetEnumerator()
    While ( DataEnum.MoveNext() )
      ltlLiteral = New LiteralControl( DataEnum.Current.ToString() )
      Controls.Add( ltlLiteral )
      Controls.Add( New LiteralControl( "<br>" ) )
    End While
  End If
End Sub

End Class

End Namespace

The C# version of this code can be found on the CD-ROM.

The control in Listing 29.7, named SimpleDataBound, has a single public property named DataSource. This property reads and sets a private variable named _dataSource.

The control also has an OnDataBinding subroutine, which is executed whenever the control's DataBind() method is called.

Within the OnDataBinding subroutine, an enumerator named DataEnum is retrieved from the data source by calling the GetEnumerator method. The MoveNext method loops through all the values contained in the data source. Each item from the data source is transformed into a LiteralControl and added to the control's Controls collection. When the control is rendered, all the items from the data source are rendered.

The page in Listing 29.8 illustrates how you can bind a data source to this control.

Listing 29.8 DisplaySimpleDataBound.aspx
<%@ Register TagPrefix="myControls" Namespace="myControls"
Assembly="SimpleDataBound"%>

<Script Runat="Server">

Sub Page_Load
  Dim colArrayList As ArrayList

  colArrayList = New ArrayList
  colArrayList.Add( "Seattle" )
  colArrayList.Add( "Paris" )
  colArrayList.Add( "Banjarmasin" )
  colArrayList.Add( "Tokyo" )

  ctrlSimpleDataBound.DataSource = colArrayList
  ctrlSimpleDataBound.DataBind()
End Sub
</Script>

<html>
<head><title>DisplaySimpleDataBound.aspx</title></head>
<body>
<form Runat="Server">

<myControls:SimpleDataBound
  id="ctrlSimpleDataBound"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

In Listing 29.8, an ArrayList is bound to the custom control in the Page_Load subroutine. This ArrayList contains a list of cities. When the DataBind method is called, the OnDataBinding method of the SimpleDataBound control is executed, and the items from the ArrayList are added to the Controls collection of the custom control.

Using DataBound Controls and Templates

Typically, when you create DataBound controls, you use the control with a template. By implementing a template, you can format each of the data source items displayed by the control.

The control in Listing 29.9 contains a template named ItemTemplate.

Listing 29.9 DataBoundTemplate.vb
Imports System
Imports System.Web
Imports System.Web.UI
Imports System.Collections

Namespace myControls

<ParseChildren(true)> Public Class DataBoundTemplate
Inherits Control

  Private _dataSource As IEnumerable
  Private _itemTemplate As ITemplate

  Public Property DataSource As IEnumerable
    Get
      Return _dataSource
    End Get
    Set
      _dataSource = Value
    End Set
  End Property

  Protected Overrides Sub OnDataBinding( e As EventArgs )
    Dim objDataEnum As IEnumerator
    Dim objItem As DataBoundTemplateItem

    If Not DataSource Is Nothing
      Controls.Clear()
      objDataEnum = DataSource.GetEnumerator()
      While ( objDataEnum.MoveNext() )
        objItem = New DataBoundTemplateItem( objDataEnum.Current )
        ItemTemplate.InstantiateIn( objItem )
        Controls.Add( objItem )
      End While
    End If
  End Sub

  <TemplateContainer(GetType(DataBoundTemplateItem))> _
  Public Property ItemTemplate As ITemplate
    Get
      Return _itemTemplate
    End Get
    Set
      _itemTemplate = value
    End Set
  End Property
End Class

Public Class DataBoundTemplateItem
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

The C# version of this code can be found on the CD-ROM.

In Listing 29.9, a control named DataBoundTemplate is declared. This control has a single template named ItemTemplate.

When you call the DataBind method on the DataBoundTemplate control, its OnDataBinding subroutine executes. This subroutine iterates through the items in the data source. For each item, a new control named DataBoundTemplateItem is created to represent the item.

Each element of the ItemTemplate is instantiated in the DataBoundTemplateItem control with the InstantiateIn method. The DataBoundTemplateItem control is then added to the Controls collection of the DataBoundTemplate control.

The ASP.NET page in Listing 29.10 demonstrates how you can bind the DataBoundTemplate control to a database table named Authors.

Listing 29.10 DisplayDataBoundTemplate.aspx
<%@ Register TagPrefix="myControls" Namespace="myControls"
Assembly="DataBoundTemplate"%>
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

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 * From Authors", conPubs )
  conPubs.Open()
  ctrlDataBoundTemplate.DataSource = cmdSelect.ExecuteReader()
  ctrlDataBoundTemplate.DataBind()
  conPubs.Close()
End Sub

</Script>

<html>
<head><title>DisplayDataBoundTemplate.aspx</title></head>
<body>
<form Runat="Server">

<myControls:DataBoundTemplate
  ID="ctrlDataBoundTemplate"
  Runat="Server">
  <ItemTemplate>
    <li> <i> <%# Container.DataItem( "au_lname" ) %> </i>
  </ItemTemplate>
</myControls:DataBoundTemplate>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

In the Page_Load subroutine in Listing 29.10, a SqlDataReader is created to represent all the records from the Authors table. This SqlDataReader is bound to the DataBoundTemplate control.

The DataBoundTemplate control is declared with a template named ItemTemplate. This template formats each item in italics.

Using DataBound Controls and State

When you bind a data source to the standard ASP.NET controls, you typically need to bind the data source only when the page is first loaded. If the page is posted back to the server, the control maintains its state.

You can maintain the state of a control by taking advantage of a control's view state. Items added to view state are automatically preserved between postbacks in a hidden __VIEWSTATE form field. The control in Listing 29.11 demonstrates how you can store the items from a data source in a control's view state.

Listing 29.11 DataBoundState.vb
Imports System
Imports System.Web
Imports System.Web.UI
Imports System.Collections

Namespace myControls

<ParseChildren(true)> _
Public Class DataBoundState
Inherits Control

  Private _dataSource As IEnumerable
  Private _itemTemplate As ITemplate

  Public Property DataSource As IEnumerable
    Get
      Return _dataSource
    End Get
    Set
      _dataSource = Value
    End Set
  End Property

  Protected Overrides Sub OnDataBinding( e As EventArgs )
    Dim objDataEnum As IEnumerator
    Dim objItem As DataBoundStateItem
    Dim intCounter As Integer

    If Not DataSource Is Nothing
      Controls.Clear()
      ClearChildViewState()
      objDataEnum = DataSource.GetEnumerator()
      While ( objDataEnum.MoveNext() )
        objItem = New DataBoundStateItem( objDataEnum.Current )
        ItemTemplate.InstantiateIn( objItem )
        Controls.Add( objItem )
        intCounter += 1
      End While
      ViewState( "NumItems" ) = intCounter
      ChildControlsCreated = True
    End If
  End Sub

  Protected Overrides Sub CreateChildControls()
    Dim objNumItems As Object
    Dim intItemCount As Integer
    Dim intCounter As Integer
    Dim objItem As DataBoundStateItem

    objNumItems = ViewState( "NumItems" )
    If Not objNumItems = Nothing Then
      Controls.Clear()
      intItemCount = CInt( objNumItems)
      For intCounter = 0 To intItemCount - 1
        objItem  = New DataBoundStateItem( Nothing )
        ItemTemplate.InstantiateIn( objItem )
        Controls.Add( objItem )
      Next
    End If
  End Sub

  <TemplateContainer(GetType(DataBoundStateItem))> _
  Public Property ItemTemplate As ITemplate
    Get
      Return _itemTemplate
    End Get
    Set
      _itemTemplate = value
    End Set
  End Property
End Class

Public Class DataBoundStateItem
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

The C# version of this code can be found on the CD-ROM.

In the OnDataBinding subroutine in Listing 29.11, a count of the number of items from the data source is added to the control's view state. A new item, named NumItems, is assigned to the ViewState property. If NumItems can be retrieved from the control's ViewState property, the value of NumItems is used to determine the number of child controls to add to the Controls collection.

When each DataBoundStateItem control is added to the Controls collection, notice that each DataBoundStateItem is initialized with the value Nothing. The ASP.NET framework automatically retrieves the previous value of each control. You need only to instantiate each control.

Here's one final thing you should notice about Listing 29.11. At the end of the OnDataBinding subroutine, the ChildControlsCreated property is set to the value True. You set this value to prevent the CreateChildControls subroutine from executing and overwriting the controls added by the OnDataBinding subroutine.

The page in Listing 29.12 illustrates how you can use the DataBoundState custom control.

Listing 29.12 DisplayDataBoundState.aspx
<%@ Register TagPrefix="myControls" Namespace="myControls"
Assembly="DataBoundState"%>
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

Sub Page_Load
  If Not IsPostBack Then
    Dim conPubs As SqlConnection
    Dim cmdSelect As SqlCommand

    conPubs = New SqlConnection( "Server=LocalHost;UID=sa;PWD=secret;Database=Pubs" )
    cmdSelect = New SqlCommand( "Select * From Authors", conPubs )
    conPubs.Open()
    ctrlDataBoundState.DataSource = cmdSelect.ExecuteReader()
    ctrlDataBoundState.DataBind()
    conPubs.Close()
  End If
End Sub

</Script>

<html>
<head><title>DisplayDataBoundState.aspx</title></head>
<body>
<form Runat="Server">

<myControls:DataBoundState
  ID="ctrlDataBoundState"
  Runat="Server">
  <ItemTemplate>
    <li> <i> <%# Container.DataItem( "au_lname" ) %>
  </ItemTemplate>
</myControls:DataBoundState>

<p>
<asp:Button
  Text="Reload Page!"
  Runat="Server" />

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

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.

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

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 DataBoundDataSet.aspx
Imports System
Imports System.Web
Imports System.Web.UI
Imports System.Collections
Imports System.Data

Namespace myControls

<ParseChildren(true)> _
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

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

      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

  Protected Overrides Sub CreateChildControls()
    Dim objNumItems As Object
    Dim intItemCount As Integer
    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

  <TemplateContainer(GetType(DataBoundDataSetItem))> _
  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

The C# version of this code can be found on the CD-ROM.

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

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

Listing 29.14 DisplayDataBoundDataSet.aspx
<%@ Register TagPrefix="myControls" Namespace="myControls"
Assembly="DataBoundDataSet"%>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<Script Runat="Server">

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

    ' 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
</Script>

<html>
<head><title>DisplayDataBoundDataSet.aspx</title></head>
<body>
<form Runat="Server">

<h3>DataSet and no DataMember</h2>
<myControls:DataBoundDataSet
  ID="ctrlDataBound1"
  Runat="Server">
  <ItemTemplate>
    <li> <i><%# Container.DataItem( "Title" ) %></i>
  </ItemTemplate>
</myControls:DataBoundDataSet>

<h3>DataSet with DataMember</h2>
<myControls:DataBoundDataSet
  ID="ctrlDataBound2"
  Runat="Server">
  <ItemTemplate>
    <li> <i><%# Container.DataItem( "au_lname" ) %></i>
  </ItemTemplate>
</myControls:DataBoundDataSet>

<h3>DataReader</h2>
<myControls:DataBoundDataSet
  ID="ctrlDataBound3"
  Runat="Server">
  <ItemTemplate>
    <li> <i><%# Container.DataItem( "Title" ) %></i>
  </ItemTemplate>
</myControls:DataBoundDataSet>

<h3>ArrayList</h2>
<myControls:DataBoundDataSet
  ID="ctrlDataBound4"
  Runat="Server">
  <ItemTemplate>
    <li> <i><%# Container.DataItem %></i>
  </ItemTemplate>
</myControls:DataBoundDataSet>

</form>
</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 29.2. Binding to multiple data sources.

In the Page_Load subroutine in Listing 29.14, the DataBoundDataSet control is bound to four different types of data sources. The control is bound to a DataSet, a DataSet and DataMember, a DataReader, and an ArrayList.


Using Custom Controls and Web Services

An ASP.NET Web Service enables you to access object properties and methods across any network, including the Internet. You can integrate a Web Service into a custom control. When you use the control, it can use one or more Web Services behind the scenes to communicate across the network.

NOTE

To learn more details about Web Services, see the two chapters in Part VI of this book, "Building ASP.NET Web Services."

In the following sections, you create a custom control that displays a list of "featured products." The control displays a list of product names and prices.

The FeaturedProduct control communicates with a Web Service to retrieve the list of products. The Web Service has a single method called GetFeatured that returns the records from a database of products.

This control could be used, for example, in an affiliate program. If a company sells computer games, the company could convince affiliate Web sites to use the FeaturedProduct control to promote the company's games across the Internet.

Creating the Featured Products Web Service

Before you can create the FeaturedProduct custom control, you need to create the FeaturedService Web Service; the complete code for it is contained in Listing 29.15.

Listing 29.15 FeaturedService.asmx
[View full width]
<%@ WebService Language="VB" Class="FeaturedService" %>

Imports System
Imports System.Web.Services
Imports System.Data
Imports System.Data.SqlClient

<WebService( Namespace:="http://yourdomain.com/webservices" )> _
Public Class FeaturedService : Inherits WebService

  <WebMethod(CacheDuration:=60)> _
  Public Function GetFeatured() As DataSet
    Dim dstDataSet As DataSet
    Dim conNorthwind As SqlConnection
    Dim dadProducts As SqlDataAdapter

    conNorthwind = New SqlConnection(
   "Server=Localhost;UID=sa;PWD=secret;Database=Northwind" )
    dadProducts = New SqlDataAdapter( "Select * From Products", conNorthwind )
    dstDataSet = New DataSet()
    dadProducts.Fill( dstDataSet, "Products" )

    Return dstDataSet
  End Function

End Class

The C# version of this code can be found on the CD-ROM.

The Web Service in Listing 29.15 has a single method named GetFeatured that returns a DataSet that represents a list of products. Notice that the CacheDuration property of the WebMethod attribute is set to cache the output of the method for 60 seconds.

Before you can use the FeaturedService Web Service in a custom control, you must first create a proxy class for the service. To do so, execute the following statement from the command line:

Wsdl /l:vb http://www.YourSite.com/FeaturedService.asmx?WSDL

This statement generates a new file named FeaturedService.vb that contains the source code for a proxy class for the FeaturedService Web Service.

CAUTION

Except for the purposes of testing a Web Service locally, it is generally not a good idea to pass a URL that contains localhost to the Wsdl tool. If you generate a proxy class by using localhost, the proxy class will work only on the current machine.

Before you can use the proxy class, you must compile the FeatureService.vb file. To do so, execute the following statement from the command line:

[View full width]

vbc /t:library /r:System.dll,System.Web.Services.dll,System.XML.dll,System.Data.dll
   FeaturedService.vb

This statement references the System, System.Web, System.XML, and System.Data assemblies when compiling the proxy class.

After you create the compiled proxy class, the last step is to copy the class, the file named FeaturedService.dll, into the application's /bin directory.

Creating the Featured Products Custom Control

Now that you have the FeaturedService Web Service, you can create a control that interacts with the service. The complete code for the control is contained in Listing 29.16.

Listing 29.16 FeaturedControl.vb
Imports System
Imports System.Web
Imports System.Web.UI
Imports System.Web.UI.WebControls
Imports System.Data

Namespace myControls

Public Class FeaturedControl
Inherits WebControl

Private Function GetDataSet() As DataSet
  Dim objService As FeaturedService

  objService = New FeaturedService
  Return objService.GetFeatured()
End Function

Protected Overrides Sub RenderContents( objTextWriter As HtmlTextWriter )
  Dim dstDataSet As DataSet
  Dim dtblDataTable As DataTable
  Dim drowRow As DataRow

  dstDataSet = Context.Cache( "FeaturedProducts" )
  If dstDataSet Is Nothing Then
    dstDataSet = GetDataSet()
    Context.Cache.Insert( "FeaturedProducts", dstDataSet, Nothing, _
             DateTime.Now.AddMinutes(5), TimeSpan.Zero )
  End If

  dtblDataTable = dstDataSet.Tables( "Products" )
  For each drowRow in dtblDataTable.Rows
    objTextWriter.Write( "<li>" )
    objTextWriter.Write( drowRow( "ProductName" ) )
    objTextWriter.Write( " - " )
    objTextWriter.Write( String.Format( "{0:c}", drowRow( "UnitPrice" ) ) )
    objTextWRiter.WriteLine( "<p>" )
  Next
End Sub

End Class

End Namespace

The C# version of this code can be found on the CD-ROM.

For performance and reliability reasons, the control in Listing 29.16 does not access the Web Service every time it executes. Instead, it caches in memory the data it retrieves from the Web Service. The cache is automatically cleared every 5 minutes, and the control grabs the data from the Web Service again.

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 DataSet of products retrieved from the Web Service.

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

[View full width]

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
<%@ Register TagPrefix="myControls" Namespace="myControls"
Assembly="FeaturedControl"%>

<html>
<head><title>DisplayFeatured.aspx</title></head>
<body>

<h2>Featured Products:</h2>

<myControls:FeaturedControl
  Font-Name="Arial"
  Font-Size="14pt"
  ForeColor="Blue"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

Figure 29.3. Output of the FeaturedControl.

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.


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.

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.


Part IX: Sample ASP.NET Applications

IN THIS PART

CHAPTER 30 Creating a Job Site

CHAPTER 31 Creating an Online Store


Chapter 30. Creating a Job Site

IN THIS CHAPTER

  • Installing the Job Site

  • Creating the Home Page

  • Authenticating Users

  • Creating Vanity URLs

  • Listing and Updating Jobs

  • Creating the Jobs XML Web Service

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 enquiries about their resumes.

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.


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.

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.

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
<!-- #INCLUDE Virtual="/aspnetjobs/site/includes/header.aspx" -->
<%@ Register TagPrefix="myControls" TagName="NewListings"
SRC="/aspnetjobs/site/usercontrols/newlistings.ascx" %>

<myControls:Header
  pageTitle="ASP.NET Jobs"
  Runat="Server" />

<form runat="Server">
<myControls:UserLinks
  Runat="Server" />
<table border="0" width="100%"
  cellpadding="10" cellspacing="20">
<tr><td width="400">
<myControls:NewListings
  Runat="Server" />

</td><td valign="top">

<img src="site/images/homeimage.gif">

</td></tr>
</table>
</form>

<myControls:Footer
  Runat="Server" />

The C# version of this code can be found on the CD-ROM.

Figure 30.1. The ASP.NET job site home page.

At the top of the home page in Listing 30.1 is an include directive that includes a file named header.aspx. This file contains page Import directives for importing standard namespaces, such as System.Data and System.Data.SqlClient. It also includes page Register directives for the Header, Footer, and UserLinks user controls. If you know that you'll need to add the same Import and Register directives to several pages, it's much easier to throw all the statements into a single include file.

If you examine the page in Listing 30.1, you'll notice that it includes four user controls: Header, Footer, UserLinks, and NewListings. The Header and Footer user controls are used throughout the job site to render standard content for the top and bottom of each page. For example, the Header user control contains the AdRotator control that renders the banner advertisement at the top of each page.

NOTE

To learn more about user controls see Chapter 5, "Creating Custom Controls with User Controls."

The UserLinks user control is used to display one of two sets of navigation links. If a user is not authenticated, the UserLinks control displays a link for logging in, a link for listing a new job, and a link for listing a new resume. If, on the other hand, the user of the page is authenticated, the UserLinks control displays a link to the user's home page, a link for listing a new job, and a link for logging out.

Finally, the NewListings user control is used to display the newest jobs and resumes posted to the site. The control contains two DataGrids, named dgrdNewJobs and dgrdNewResumes, which actually display the new listings.

The NewListings user control uses the Cache object to cache the new job and resume listings between requests. Using the Cache object in this way can dramatically improve the performance of the home page. Performing a database operation can be a slow procedure. By placing the results of the last database operation in the Cache object, you can avoid communicating with the database.

NOTE

To learn more about the Cache object, see Chapter 17, "Caching ASP.NET Applications."

What happens when the database data changes? If a person adds a new job or adds a new resume, the cached listings are automatically invalidated. For example, the following statement appears in the ListJob.aspx page:

Cache.Remove( "NewJobs" )

This statement is executed immediately after a new job is added to the database. Because the statement removes the list of new jobs from the Cache object, the job just added appears without any delay on the home page of the job site.


Authenticating Users

The ASP.NET job site uses Forms authentication to authenticate users. Forms authentication is enabled for the site by including the Web.Config file in Listing 30.2 in the root directory of the site.

Listing 30.2 Web.Config
<configuration>
  <appSettings>
    <add key="constring"
      value="Server=localhost;UID=AspNETJobsUser;PWD=secret; Database=AspNetJobs" />
  </appSettings>
  <system.web>
  <authentication mode="Forms" >
  <forms name=".ASPNETJOBS"
    loginUrl="/aspnetjobs/site/password/login.aspx" />
  </authentication>
  </system.web>
</configuration>

The C# version of this code can be found on the CD-ROM.

Forms authentication is configured in the authentication section in Listing 30.2. This authentication section sets the authentication mode to Forms. It also specifies the login page where unauthenticated users are automatically redirected when they request a password-protected page.

NOTE

To learn more about Forms authentication, see Chapter 19, "Using Forms-Based Authentication."

Notice that the Web.Config file in Listing 30.2 does not contain an authorization section. Anonymous users can access any page within the job site except those pages in a directory named password. The password directory contains a separate Web.Config file that contains an authorization section that prevents anonymous access to pages in that directory. The Web.Config file in the password subdirectory is included in Listing 30.3.

Listing 30.3 Password\Web.Config
<configuration>
  <system.web>
  <authorization>
    <deny users="?" />
  </authorization>
  </system.web>
</configuration>

The C# version of this code can be found on the CD-ROM.

The Web.Config file in Listing 30.3 contains an authorization section that denies access to all anonymous users (anonymous users are represented in the Web.Config file with the special symbol "?").

The password directory includes pages that require authentication, such as the ListResume.aspx and ListJob.aspx pages. If you try to request one of these pages, and you can't be identified by the Forms Authentication module, you are automatically redirected to the Login.aspx page (also included in the password directory).

The login.aspx page enables you to log in, if you are an existing user, or click a link to navigate to the register.aspx page, if you are a new user (see Figure 30.2). The login.aspx page is contained in Listing 30.4.

Listing 30.4 Site\Password\Login.aspx
<!-- #INCLUDE Virtual="/aspnetjobs/site/includes/header.aspx" -->

<Script runat="Server">

Sub Page_Load
  Dim strReturnUrl As String
  If Not IsPostBack Then
    strReturnUrl = Request.QueryString( "ReturnUrl" )
    lnkRegister.NavigateUrl = _
      String.Format( _
        "/aspnetjobs/site/register.aspx?ReturnUrl={0}", _
        Server.URLEncode( strReturnUrl ) )
  End If
End Sub

Sub Button_Click( s As Object, e As EventArgs )
  If isValid Then
  Select Case VerifyPassword( txtUsername.Text, txtPassword.Text )
  Case 0
    FormsAuthentication.RedirectFromLoginPage( txtUsername.Text, chkPersist.Checked )
  Case 1
    lblError.Text = "You did not enter a registered username"
  Case 2
    lblError.Text = "You did not enter a valid password"
  End Select
  End If
End Sub

Function VerifyPassword( strUsername, strPassword ) As Integer
  Dim strConString As String
  Dim conJobs As SqlConnection
  Dim cmdVerify As SqlCommand
  Dim parmReturn As SqlParameter

  strConString = ConfigurationSettings.AppSettings( "constring" )
  conJobs = New SqlConnection( strConString )
  cmdVerify = New SqlCommand( "VerifyPassword", conJobs )
  cmdVerify.CommandType = CommandType.StoredProcedure
  parmReturn = cmdVerify.Parameters.Add( "@return", SqlDbType.Int )
  parmReturn.Direction = ParameterDirection.ReturnValue
  cmdVerify.Parameters.Add( "@username", strUsername )
  cmdVerify.Parameters.Add( "@password", strPassword )
  conJobs.Open()
    cmdVerify.ExecuteNonQuery()
  conJobs.Close()
  Return cmdVerify.Parameters( "@return" ).Value
End Function

</Script>

<myControls:Header
  pageTitle="Please Login"
  Runat="Server" />
<form runat="Server">

<table cellpadding="10" cellspacing="15"
  border="0">
<tr><td>
<h3>Login</h3>
<asp:Label
  ID="lblError"
  EnableViewState="False"
  forecolor="red"
  font-bold="True"
  runat="Server" />
<p>
<b>Username:</b>
<br>
<asp:TextBox
  ID="txtUsername"
  CssClass="formfield"
  runat="Server" />
<asp:RequiredFieldValidator
  ControlToValidate="txtUsername"
  Text="Required!"
  runat="Server" />
<p>
<b>Password:</b>
<br>
<asp:TextBox
  ID="txtPassword"
  TextMode="password"
  CssClass="formfield"
  runat="Server" />
<asp:RequiredFieldValidator
  ControlToValidate="txtPassword"
  Text="Required!"
  runat="Server" />
<p>
<asp:Button
  Text="Login!"
  OnClick="Button_Click"
  runat="Server" />
<p>
<asp:CheckBox
  ID="chkPersist"
  Checked="True"
  runat="Server" />
Automatically remember me

</td><td valign="top">

<h3>Register</h3>
Are you a new user?
<p>
<asp:HyperLink
  ID="lnkRegister"
  Text="Click here to register!"
  runat="Server" />
<p>
It's free and it only takes a minute.
</td></tr>
</table>
</form>
<myControls:Footer
  runat="Server"/>

The C# version of this code can be found on the CD-ROM.

Figure 30.2. The Login page.

If you enter a username and password in the login form contained in Listing 30.4, the Button_Click subroutine is executed. This subroutine passes the username and password entered into the form to a SQL stored procedure named VerifyPassword. The VerifyPassword stored procedure is included in Listing 30.5.

Listing 30.5 VerifyPassword
CREATE PROCEDURE VerifyPassword
(
  @username Varchar( 20 ),
  @password Varchar( 20 )
)
AS
DECLARE @foundUser Varchar( 20 )

SELECT @foundUser = ul_username
  FROM UserList
  WHERE ul_username = @username
  AND ul_password = @password

IF @foundUser IS NOT NULL
  Return 0
ELSE
  IF Exists( SELECT ul_username
    FROM UserList WHERE ul_username = @username )
    Return 2
  ELSE
    RETURN 1

The C# version of this code can be found on the CD-ROM.

The VerifyPassword stored procedure matches a username and password against a table named UserList. The stored procedure returns one of three possible values:

  • 0— When the username and password combination is matched in the UserList table.

  • 2— When the username is found, but the password is invalid.

  • 1— When the username cannot be found in the UserList table.

The Login.aspx page displays different error messages depending on the value returned by the VerifyPassword stored procedure. When a person attempting to login cannot be authenticated against the database, the person is given an exact reason.

The Login.aspx page also enables new users to link to the Register.aspx page. The Register.aspx page contains a form for registering at the ASP.NET job site (see Figure 30.3). When a new user registers, the AddUser stored procedure is executed and the new user is added to the UserList database table. The AddUser stored procedure is contained in Listing 30.6.

Listing 30.6 AddUser
CREATE procedure AddUser
(
  @username Varchar( 20 ),
  @password varchar( 20 ),
  @firstname varchar( 30 ),
  @lastname varchar( 30 ),
  @email varchar( 255 ),
  @briefdesc varchar( 50 ),
  @fulldesc Text,
  @isresume Bit
)
As
If Exists( SELECT ul_username FROM UserList
           WHERE ul_username = @username )
  RETURN -1
ELSE
  INSERT UserList (
    ul_username,
    ul_password,
    ul_firstname,
    ul_lastname,
    ul_email,
    ul_briefdesc,
    ul_fulldesc,
    ul_isresume
  ) VALUES (
    @username,
    @password,
    @firstname,
    @lastname,
    @email,
    @briefdesc,
    @fulldesc,
    @isresume
  )

The C# version of this code can be found on the CD-ROM.

Figure 30.3. The Register page.

The AddUser stored procedure does one of two things. If the username passed to the stored procedure already exists in the UserList database table, the stored procedure returns the value -1. You don't want two users to be assigned the same username. So, if someone attempts to register with a username that has already been taken, an error message is displayed on the Register.aspx page.

If the username does not already exist in the UserList table, the AddUser stored procedure adds the new user to the table. After the user is added, the user can log in by using the username and password combination at any point in the future.


Creating Vanity URLs

One interesting feature of the ASP.NET job site is its use of vanity URLs. Each user who registers at the job site is automatically given a personalized home page. For example, if you register with the username marksmith, you can access your home page by going to /aspnetjobs/marksmith.aspx (see Figure 30.4).

Figure 30.4. A vanity URL.

The vanity URLs are implemented with two files: the Global.asax and UserPage.aspx files. The Global.asax file checks the path of each request made to the job site. When appropriate, the Global.asax file rewrites the path of the request to the UserPage.aspx page. The complete code for the Global.asax file is contained in Listing 30.7.

Listing 30.7 Global.asax
<%@ Import Namespace="System.IO" %>

<Script Runat="Server">

Sub Application_BeginRequest
  Dim strPageOwner As String
  Dim strCurrentPath As String
  Dim strCustomPath As String

  strCurrentPath = Request.Path.ToLower()
  If strCurrentPath.IndexOf( "/site/" ) = -1 _
  AND strCurrentPath.IndexOf( "userpage.aspx" ) = -1 _
  AND strCurrentPath.IndexOf( "default.aspx" ) = -1 Then
    strCustomPath = _
      String.Format( _
        "/aspnetjobs/userpage.aspx?pageowner={0}", _
        Path.GetFileNameWithoutExtension( Request.Path ) )
    Context.RewritePath( strCustomPath )
  End If
End Sub

</Script>

The C# version of this code can be found on the CD-ROM.

The Global.asax file in Listing 30.7 handles the BeginRequest event that is raised whenever someone requests a page from the job site. When appropriate, the RewritePath method is used to redirect a page request to the UserPage.aspx page.

First, the Application_BeginRequest subroutine checks whether the request is for the Default.aspx page, the Userpage.aspx page, or a page below the /site/ subdirectory. These requests are ignored. You only want to redirect a user when the user makes a request with a vanity URL. You must be careful not to redirect users when they are requesting actual pages at the job site.

NOTE

You also can implement vanity URLs by creating a custom HTTP module. An HTTP module is a class that is executed whenever a request is made for an ASP.NET page. To learn more about creating HTTP modules, see Chapter 15, "Creating ASP.NET Applications."

If the user is not requesting an actual page at the Web site, the RewritePath method is used to redirect the user to the UserPage.aspx page. For example, if the user requests

/aspnetjobs/marksmith.aspx

the Global.asax file automatically rewrites the request like this:

/aspnetjobs/userpage.aspx?pageowner=marksmith

The value of the pageowner query string variable is captured within the UserPage.aspx page. The pageowner variable is used within the UserPage.aspx page to look up and display the information for the appropriate user from the database.

When using the Global.asax file in Listing 30.7, you must be cautious about the physical layout of the files in your Web site. All the actual files for the job site are located beneath the /site/ subdirectory. If you add a new page to the root directory, the page could never be accessed because any requests for the page would be interpreted as a request for a vanity URL. If you need to add new files to the ASP.NET job site, add the new files beneath the /site/ subdirectory.


Listing and Updating Jobs

You can list a new job at the ASP.NET job site by clicking the list job link that appears at the top of most pages. When you click the list job link, you are brought to the ListJob.aspx page (see Figure 30.5).

Figure 30.5. The List Job page.

This page contains a form for entering a brief and long description of the job. When you submit the form, the job information is added to the database with the stored procedure in Listing 30.8.

Listing 30.8 AddJob
CREATE Procedure addJob
(
  @username Varchar( 20 ),
  @briefdesc Varchar( 50 ),
  @fulldesc Text
)
AS
Insert Jobs (
  job_username,
  job_briefdesc,
  job_fulldesc
  ) Values (
  @username,
  @briefdesc,
  @fulldesc
)

UPDATE Userlist
  SET ul_isresume = 0
  WHERE ul_username = @username

The C# version of this code can be found on the CD-ROM.

The stored procedure in Listing 30.8 adds a new job listing to a database table named Jobs. The stored procedure also updates the UserList table to identify the person who listed the job as an employer, rather than an employee.

After a new job has been listed, a link to the job appears on the home page of the company at the job site. If you click the link to the job listing, you are brought to the Job.aspx page.

The Job.aspx page satisfies a dual function. For the public, the Job.aspx page displays the job listing. However, if you are the person who listed the job, the Job.aspx page also enables you to update the job listing.

If you are identified as the person who listed the job, a panel appears that gives you the option of updating the job listing (see Figure 30.6). If you decide to update the job listing, the listing is converted into a form. When you save any changes to the form, the updated job listing appears immediately.

Figure 30.6. Updating a job listing.

The Job.aspx page uses Panel and Placeholder controls to display either the job listing itself, or a form for updating the job. By setting the Visible property of the various Panels and Placeholders in the page, either the job listing or form for updating the job listing is displayed.

NOTE

To learn more about using Panel and Placeholder controls, see Chapter 4, "Advanced Control Programming."


Creating the Jobs XML Web Service

One effective way of driving traffic to your Web site is by creating an affiliate program. For example, Amazon has an affiliate program for books. You can list books on your Web site that link back to the Amazon.com Web site. Every time a person buys a book after linking to Amazon through your Web site, you get a small commission.

In this section, you'll build the infrastructure to create an affiliate program for the ASP.NET job site. To do this, you must create an XML Web Service and two custom controls. The custom controls work with the XML Web Service to display the most recent jobs and resumes listed at the job site. These controls can be distributed to affiliate Web sites to display current job and resume listings.

The ASP.NET job site exposes an XML Web Service called the JobService. This Web Service has two methods: GetNewJobs and GetNewResumes. The GetNewJobs method returns a DataSet that represents the most recent 50 jobs listed and the GetNewResumes method returns a DataSet that represents the most recent 50 resumes listed.

NOTE

To learn more about XML Web Services see Part VI, "Building ASP.NET Web Services."

The complete code for the XML Web Service is contained in Listing 30.9.

Listing 30.9 Site\Services\JobService.asmx
<%@ WebService Language="vb" Class="myServices.JobService" %>

Imports System
Imports System.Web.Services
Imports System.Data
Imports System.Data.SqlClient
Imports System.Configuration

Namespace myServices

<WebService(Namespace:="http://yourdomain.com/webservices/")> _
Public Class JobService
Inherits WebService

<WebMethod()> Function GetNewJobs() As DataSet
  Dim strConString As String
  Dim conJobs As SqlConnection
  Dim dadJobs As SqlDataAdapter
  Dim dstJobs As DataSet

  dstJobs = Context.Cache( "NewJobs" )
  If dstJobs Is Nothing Then
    ' Ready Database Connection
    strConString = ConfigurationSettings.AppSettings( "constring" )
    conJobs = New SqlConnection( strConString )
    dstJobs = New DataSet()
    ' Get Newest 50 Jobs From Database
    dadJobs = New SqlDataAdapter( "getNewJobs", conJobs )
    dadJobs.SelectCommand.CommandType = CommandType.StoredProcedure
    dadJobs.Fill( dstJobs, "NewJobs" )
    Context.Cache( "NewJobs" ) = dstJobs
  End If
  Return dstJobs
End Function

<WebMethod()> Function GetNewResumes() As DataSet
  Dim strConString As String
  Dim conJobs As SqlConnection
  Dim dadResumes As SqlDataAdapter
  Dim dstResumes As DataSet

  dstResumes = Context.Cache( "NewResumes" )
  If dstResumes Is Nothing Then
    ' Ready Database Connection
    strConString = ConfigurationSettings.AppSettings( "constring" )
    conJobs = New SqlConnection( strConString )
    dstResumes = New DataSet()
    ' Get Newest 50 Resumes From Database
    dadResumes = New SqlDataAdapter( "getNewResumes", conJobs )
    dadResumes.SelectCommand.CommandType = CommandType.StoredProcedure
    dadResumes.Fill( dstResumes, "NewResumes" )
    Context.Cache( "NewResumes" ) = dstResumes
  End If
  Return dstResumes
End Function

End Class
End Namespace

The C# version of this code can be found on the CD-ROM.

To access the XML Web Service in Listing 30.9, you can enter the following URL in the Address Bar of your Web browser:

http://localhost/aspnetjobs/site/services/jobservice.asmx

Included with the job site are two custom controls, named NewJobs and NewResumes, which interact with the XML Web Service to display a list of new jobs and resumes. The custom controls are located in an assembly named NewListings.dll, which can be found in the /bin directory of the job site.

You can distribute these controls to other affiliate Web sites. The affiliate Web sites can use these controls in their ASP.NET pages to display links to jobs at your Web site (see Figure 30.7). Because the controls retrieve the list of new jobs and resumes from an XML Web Service, the list of jobs and resumes displayed by the controls is always current.

Figure 30.7. The NewJobs and NewResumes controls.

The complete code for the NewJobs and NewResumes controls is contained in Listing 30.10. (This file can be found in the aspnetjobs\site\services subdirectory of the job site.)

Listing 30.10 NewListings.vb
Imports myControls
Imports System
Imports System.Web
Imports System.Web.UI
Imports System.Web.UI.WebControls
Imports System.Web.Services
Imports System.Data

Namespace myControls

Public Class NewJobs: Inherits WebControl

  Const aspnetjobsUrl = "http://localhost/aspnetjobs/"

  Public PageSize AS Integer = 5

  Protected Overrides Sub CreateChildControls
    Dim dgrdNewJobs As DataGrid
    Dim objBoundColumn As BoundColumn
    Dim objHyperLinkColumn As HyperLinkColumn

    ' Build DataGrid
    dgrdNewJobs = New DataGrid
    dgrdNewJobs.ID = "NewJobsGrid"
    dgrdNewJobs.AllowPaging = True
    dgrdNewJobs.PageSize = PageSize
    dgrdNewJobs.PagerStyle.Visible = False
    dgrdNewJobs.Cellpadding = 4
    dgrdNewJobs.BorderStyle = BorderStyle.Solid
    dgrdNewJobs.AutoGenerateColumns = False
    dgrdNewJobs.ShowHeader = False
    dgrdNewJobs.GridLines = GridLines.None
    ' Add Date Column
    objBoundColumn = New BoundColumn()
    objBoundColumn.DataField = "job_entrydate"
    objBoundColumn.DataFormatString = "{0:m}"
    dgrdNewJobs.Columns.Add( objBoundColumn )
    ' Add HyperLink Column
    objHyperLinkColumn = New HyperlinkColumn
    objHyperLinkColumn.DataTextField = "job_briefdesc"
    objHyperLinkColumn.DataNavigateUrlField = "job_id"
    objHyperLinkColumn.DataNavigateUrlFormatString = _
      aspnetjobsUrl & "site/job.aspx?id={0}"
    dgrdNewJobs.Columns.Add( objHyperLinkColumn )

    ' Bind DataGrid
    dgrdNewJobs.Datasource = GetNewJobs()
    dgrdNewJobs.Databind()

    ' Add Child Controls
    Controls.Add( New LiteralControl( "<b>New Jobs!<b>" ) )
    Controls.Add( dgrdNewJobs )
  End Sub

  Private Function GetNewJobs() As DataSet
    Dim dstJobs As DataSet
    Dim objJobService As JobService

    dstJobs = Context.Cache( "JobService_NewJobs" )
    If dstJobs Is Nothing Then
      objJobService = New JobService
      dstJobs = objJobService.GetNewJobs()
      Context.Cache.Insert( _
        "JobService_NewJobs", _
        dstJobs, _
        Nothing, _
        DateTime.Now.AddMinutes( 15 ), _
        Timespan.Zero )
    End If
    Return dstJobs
  End Function
End Class

Public Class NewResumes : Inherits WebControl

  Const aspnetjobsUrl = "http://localhost/aspnetjobs/"

  Public PageSize AS Integer = 5

  Protected Overrides Sub CreateChildControls
    Dim dgrdNewResumes As DataGrid
    Dim objBoundColumn As BoundColumn
    Dim objHyperLinkColumn As HyperLinkColumn

    ' Build DataGrid
    dgrdNewResumes = New DataGrid
    dgrdNewResumes.ID = "NewResumesGrid"
    dgrdNewResumes.AllowPaging = True
    dgrdNewResumes.PageSize = PageSize
    dgrdNewResumes.PagerStyle.Visible = False
    dgrdNewResumes.Cellpadding = 4
    dgrdNewResumes.BorderStyle = BorderStyle.Solid
    dgrdNewResumes.AutoGenerateColumns = False
    dgrdNewResumes.ShowHeader = False
    dgrdNewResumes.GridLines = GridLines.None
    ' Add Date Column
    objBoundColumn = New BoundColumn()
    objBoundColumn.DataField = "ul_entrydate"
    objBoundColumn.DataFormatString = "{0:m}"
    dgrdNewResumes.Columns.Add( objBoundColumn )
    ' Add HyperLink Column
    objHyperLinkColumn = New HyperlinkColumn
    objHyperLinkColumn.DataTextField = "ul_briefdesc"
    objHyperLinkColumn.DataNavigateUrlField = "ul_username"
    objHyperLinkColumn.DataNavigateUrlFormatString = _
      aspnetjobsurl & "{0}.aspx"
    dgrdNewResumes.Columns.Add( objHyperLinkColumn )

    ' Bind DataGrid
    dgrdNewResumes.Datasource = GetNewResumes()
    dgrdNewResumes.Databind()

    ' Add Child Controls
    Controls.Add( New LiteralControl( "<b>New Resumes!<b>" ) )
    Controls.Add( dgrdNewResumes )
  End Sub

  Private Function GetNewResumes() As DataSet
    Dim dstResumes As DataSet
    Dim objJobService As JobService

    dstResumes = Context.Cache( "JobService_NewResumes" )
    If dstResumes Is Nothing Then
      objJobService = New JobService
      dstResumes = objJobService.GetNewResumes()
      Context.Cache.Insert( _
        "JobService_NewResumes", _
        dstResumes, _
        Nothing, _
        DateTime.Now.AddMinutes( 15 ), _
        Timespan.Zero )
    End If
    Return dstResumes
  End Function
End Class

End Namespace

The C# version of this code can be found on the CD-ROM.

Both the NewJobs and NewListings controls inherit from the WebControl class. The NewJobs control contains one subroutine and one function. The subroutine overrides the CreateChildControls method of the base control class. This subroutine is used to add a DataGrid control to the custom control. The DataGrid control is bound to the DataSet returned by the GetNewJobs function.

The GetNewJobs function uses the JobService Web Service to retrieve a DataSet. Notice that the NewJobs control caches the DataSet in the Cache object to improve performance. The control retrieves a new list of jobs from the JobService Web Service every 15 minutes.

When the jobs listings are displayed by the NewJobs control, each listing contains a link back to the ASP.NET job site. The NewJobs control contains a property, named PageSize, which determines how many job listings are displayed by the control.

CAUTION

Before you compile the NewListings control, you must modify the aspnetjobsUrl constants that appear at the top of both the NewJobs and NewResumes classes. These constants specify the location of the ASP.NET job site.

The NewResumes control is similar to the NewJobs control. It also overrides the CreateChildControls subroutine to display a DataGrid. The NewResumes control uses the GetNewResumes function to retrieve a list of new resumes from the JobService Web Service every 15 minutes.

Both the NewJobs and NewResumes controls depend on a proxy class for the JobService Web Service. Before you can compile the source code for the controls, you must first create a proxy class for the JobService Web Service.

To create and compile the proper proxy class, you can execute the MakeJobServiceProxy.bat batch file contained in Listing 30.11.

CAUTION

Before you compile the JobService proxy class, modify the MakeJobServiceProxy.bat file to point to the location of the JobService Web Service on your server. You'll need to replace the phrase "localhost" with the proper domain name for your Web site.

Listing 30.11 Site\Services\MakeJobServiceProxy.bat
[View full width]
Wsdl.exe /l:VB /n:myControls http://localhost/aspnetjobs/site/services/jobservice.asmx?wsdl
vbc /t:library /r:System.dll,System.Web.Services.dll,System.data.dll,System.XML.dll
   Jobservice.vb
copy JobService.dll ..\..\bin
pause

The C# version of this code can be found on the CD-ROM.

The batch file in Listing 30.11 creates a compiled class named JobService.dll. After the JobService.dll proxy class is created, it is copied to the /bin directory of your application.

After you create the proxy class, you can compile the NewJobs and NewResumes custom controls contained in Listing 30.10. Execute the batch file in Listing 30.12 to compile these controls.

Listing 30.12 Site\Services\MakeNewListings.bat
[View full width]
vbc /t:library /r:System.dll,System.Web.dll,System.Web.Services.dll,System.Xml.dll,System
  .Data.dll,Jobservice.dll NewListings.vb
copy NewListings.dll ..\..\bin
pause

The C# version of this code can be found on the CD-ROM.

After you compile the NewJobs and NewResumes controls, you can distribute the controls to any site on the Internet. An affiliate Web site simply needs to copy the NewListings.dll file into its application /bin directory. The page in Listing 30.13 illustrates how you can use these controls in an ASP.NET page located at another Web site.

Listing 30.13 Site\Services\Affiliate.aspx
<%@ Register TagPrefix="myControls" Namespace="myControls"
  Assembly="NewListings" %>

<html>
<head><title>Affiliate.aspx</title></head>
<body>

<myControls:NewJobs
  PageSize="10"
  Runat="Server" />

<p>

<myControls:NewResumes
  PageSize="10"
  Runat="Server" />

</body>
</html>

The C# version of this code can be found on the CD-ROM.

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

  • Installing the ASP.NET Unleashed Sample Store

  • Building the Navigation System

  • Caching the Product Data

  • Building the Shopping Cart

  • Dynamically Loading Product Templates

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.


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 links to all the products in a category.

  • Product.aspx— Displays information on a particular product.

  • CheckOut.aspx— A page that confirms whether a user wants to check out of the store and purchase the items in the shopping cart.

  • PlaceOrder.aspx— The final checkout 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 a list of the product categories.

  • ShoppingCarts— Contains all the user shopping carts.

  • 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 user interface portion of the shopping cart.

  • Address.ascx— A 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.

  3. Modify the Web.Config file so that it contains a valid connection string to your database.

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.

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
<%@ Import Namespace="StoreComponents" %>

<Script runat="Server">
  Sub Page_Load
    Dim intTabIndex

    intTabIndex = Context.Items( "Category" )
    dlstTabs.SelectedIndex = intTabIndex
    dlstTabs.DataSource = CachedData.GetCategories()
    dlstTabs.DataBind()
  End Sub
</Script>

<asp:DataList
  id="dlstTabs"
  RepeatColumns="10"
  GridLines="Both"
  BorderColor="Black"
  BorderWidth="1"
  CellPadding="6"
  ItemStyle-CssClass="TabDefault"
  SelectedItemStyle-CssClass="TabSelected"
  Runat="Server">
  <ItemTemplate>
    <%# String.Format( _
       "<a href=""/aspnetstore/Category.aspx?cat={0}"">{1}</a>", _
       Container.ItemIndex, _
       Container.DataItem( "CategoryName" ) ) %>
  </ItemTemplate>
</asp:DataList>

The C# version of this code can be found on the CD-ROM.

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
<%@ Import Namespace="StoreComponents" %>
<%@ Import Namespace="System.Data" %>

<Script runat="Server">

Sub Application_BeginRequest( s As Object, e As EventArgs )
  Dim intCategory As Integer = -1
  Dim intProductID As Integer = -1
  Dim objProductInfo As DataRowView

  ' 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

</Script>

The C# version of this code can be found on the CD-ROM.

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.


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
[View full width]
''''''''''''''''''''''''''''''''''''''''''
' 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

    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

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

The C# version of this code can be found on the CD-ROM.

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 Cache, the method immediately calls the GetCategoriesFromDb function.


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.

Figure 31.2. The shopping cart.

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.

Listing 31.4 ShoppingCart.ascx
<%@ Import Namespace="StoreComponents" %>

<Script runat="Server">

Dim intProductID As Integer
Dim intCategory As Integer
Dim strProductName As String
Dim decUnitPrice As Decimal
Dim 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

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
</Script>

<table>
<tr>
  <td>
  <font face="Arial" size="3">
Your Shopping Cart
  </font>
  </td>
</tr>
<tr>
  <td>
<asp:DataGrid
  id="dgrdShoppingCart"
  OnItemCommand="LinkButton_Click"
  AutoGenerateColumns="False"
  GridLines="Horizontal"
  BackColor="White"
  BorderStyle="Solid"
  BorderColor="Black"
  BorderWidth="2"
  Width="200"
  CellPadding="4"
  Font-Face="Arial"
  Font-Size="10pt"
  HeaderStyle-Font-Face="Arial"
  HeaderStyle-Font-Size="10pt"
  HeaderStyle-BackColor="orange"
  Runat="Server">
<Columns>
  <asp:TemplateColumn>
    <ItemTemplate>
    <asp:LinkButton
      Text="x"
      Style="color:red;font:bold 12pt Arial;text-decoration:none"
      Runat="Server" />
    </ItemTemplate>
  </asp:TemplateColumn>
  <asp:BoundColumn
    DataField="ProductName"
    HeaderText="item" />
  <asp:BoundColumn
    DataField="UnitPrice"
    DataFormatString="{0:c}"
    HeaderText="price" />
  <asp:BoundColumn
    DataField="Quantity"
    HeaderText="qty" />
</Columns>
</asp:DataGrid>
  </td>
</tr>
<tr>
  <td align="right">
  Total:
  <%=String.Format( "{0:c}", objShoppingCart.GetItemTotal() )%>
  </td>
</tr>
<tr>
  <td align="right">
  <asp:LinkButton
    id="btnAdd"
    Text="Add to Cart"
    OnClick="btnAdd_Click"
    Runat="Server" />
  &nbsp;&nbsp;
  <asp:HyperLink
    id="btnCheckOut"
    Text="Check Out"
    Runat="Server" />
  </td>
</tr>
</table>

The C# version of this code can be found on the CD-ROM.

Listing 31.4 is long, but easy to understand. The majority of Listing 31.4 is devoted to formatting a DataGrid control to look like a tiny shopping cart.

Two links appear directly below the shopping cart. One link enables you to add the current product to your shopping cart. The second link enables you to purchase all the items in your shopping cart by going to the CheckOut.aspx page.

When you click the Add to Cart link, the btnAdd_Click subroutine executes. This subroutine adds a new item to the shopping cart by calling the Add method of the ShoppingCart class.

All the database functionality for the shopping cart control is implemented within the StoreComponent assembly in a class named, appropriately enough, ShoppingCart. The full source for this class is contained in Listing 31.5.

Listing 31.5 ShoppingCart.vb
[View full width]
Public Class ShoppingCart

  Private _conCart As SqlConnection
  Private _dadCart As SqlDataAdapter
  Private _dstCart As DataSet
  Private _cbCart As SqlCommandBuilder
  Private _guidUserID As GUID

  Sub New()
    Dim strConString As String
    Dim strSelect As String
    Dim objCookie As HttpCookie

    ' Prepare db connection
    strConString = ConfigurationSettings.AppSettings( "connectionString" )
    _conCart = New SqlConnection( strConString )

    ' Check for StoreUserID cookies
    objCookie = HttpContext.Current.Request.Cookies( "StoreUserID" )
    If objCookie Is Nothing Then
      _guidUserID = Guid.NewGuid
      objCookie = New HttpCookie( "StoreUserID", _guidUserID.ToString )
      objCookie.Expires = DateTime.Now.AddYears( 3 )
      HttpContext.Current.Response.Cookies.Add( objCookie )
    Else
      _guidUserID = New GUID( objCookie.Value )
    End If

    ' Retrieve the shopping cart
    strSelect = "Select itemID, userID, ProductID, ProductName, UnitPrice, Quantity From
   ShoppingCarts Where userID = @userID"
    _dadCart = New SqlDataAdapter( strSelect, _conCart )
    _dadCart.SelectCommand.Parameters.Add( "@userID", _guidUserID )
    _dadCart.MissingSchemaAction = MissingSchemaAction.AddWithKey
    _cbCart = New SqlCommandBuilder( _dadCart )
    _dstCart = New DataSet
    _dadCart.Fill( _dstCart, "Cart" )
  End Sub

  Public ReadOnly Property Items As DataView
    Get
      Return _dstCart.Tables( "Cart" ).DefaultView
    End Get
  End Property

  Public Sub Add( ProductID As Integer, ProductName As String, UnitPrice As Decimal )
    Dim blnMatch As Boolean = False
    Dim drowItem As DataRow

    For each drowItem in _dstCart.Tables( "Cart" ).Rows
      If drowItem( "ProductID" ) = ProductID Then
        drowItem( "Quantity" ) += 1
        blnMatch = True
        Exit For
      End If
    Next
    If Not blnMatch Then
      drowItem = _dstCart.Tables( "Cart" ).NewRow
      drowItem( "UserID" ) = _guidUserID
      drowItem( "ProductID" ) = ProductID
      drowItem( "ProductName" ) = ProductName
      drowItem( "UnitPrice" ) = UnitPrice
      drowItem( "Quantity" ) = 1
      _dstCart.Tables( "Cart" ).Rows.Add( drowItem )
    End If
    UpdateCartDB
  End Sub

  Public Sub Remove( ItemIndex As Integer )
    _dstCart.Tables( "Cart" ).Rows( itemIndex ).Delete
    UpdateCartDB
  End Sub

  Function GetItemTotal() As Decimal
    Dim intCounter As Integer
    Dim drowItem As DataRow
    Dim decRunningTotal As Decimal

    For intCounter = 0 To _dstCart.Tables( "Cart" ).Rows.Count - 1
      drowItem = _dstCart.Tables( "Cart" ).Rows( intCounter )
      decRunningTotal += ( drowItem( "UnitPrice" ) * drowItem( "Quantity" ) )
    Next
    Return decRunningTotal
  End Function

  Private Sub UpdateCartDB
    _dadCart.Update( _dstCart, "Cart" )
  End Sub
End Class

The C# version of this code can be found on the CD-ROM.

The shopping cart contained in Listing 31.5 automatically tracks users through a cookie named StoreUserID. The shopping cart automatically creates a persistent cookie that lasts (in theory at least) three years. When a user returns to the store, all the items that the user previously added to the shopping cart will still be there.

The nice thing about this feature is that it works even if the user never registers at the Web site. If an anonymous user adds items to a shopping cart and waits several months to return to the site, all the items previously selected will remain in the shopping cart.

The cookie is created with a Globally Unique Identifier (GUID). The NewGuid method of the Guid structure can return a GUID in a number of different formats. In this case, you just use the default string version of the GUID.

Behind the scenes, the shopping cart in Listing 31.5 is implemented as a DataSet. A DataAdapter is used to create the DataSet and to update the existing database table when changes are made to the data.

In the constructor subroutine in Listing 31.5, a DataAdapter is used to retrieve the shopping cart from the ShoppingCarts table. If any kind of change is made to a shopping cart item, the UpdateCartDB subroutine is called.

The UpdateCartDB subroutine contains only the following statement:

dadCart.Update( _dstCart, "Cart" )

This statement saves any changes to the shopping cart to the ShoppingCarts table. The DataAdapter Update statement automatically checks each row in DataTable for changes between its original and current value. If a change is discovered, the Update() method executes a SQL command to modify the data in the database to match the new value of the row.


Dynamically Loading Product Templates

One common problem that many Web site developers encounter when building an online store concerns the variety of products that the store sells. For example, a store might want to sell a wide range of products, including ping-pong balls, cameras, and CDs. The problem is how to create a single product page that can be used to sell anything.

A page layout that works well with one product might not work well at all with another product. For example, a common problem is that one product will have pictures and another product won't.

One way around this problem is not to try to create a single page for all the products that your Web site might sell. That's the approach taken in the case of the ASP.NET Unleashed Sample Store.

Every product sold through the ASP.NET Unleashed Sample Store is associated with a product template. Products are associated with individual templates through the Template database column in the Products database table.

When you install the ASP.NET Unleashed Sample Store, by default, the same template is associated with every product. This template is named the default template and can be found in the ProductTemplates subdirectory. However, it's easy to add a new template.

To create a special template for all the products in the Seafood category, for example, you first make a new template file. A file with the proper format is contained in Listing 31.6.

Listing 31.6 Seafood.ascx
<%@ Inherits="StoreComponents.ProductTemplate" %>

<h2>The Custom Seafood Template<h2>
<font color="red"><%=Product( "QuantityPerUnit" )%></font>
<hr>
<font size="8" color="DarkRed"><%= ProductName %></font>
<p>
<font size="5"><%= UnitPrice.ToString( "c" ) %></font>

The C# version of this code can be found on the CD-ROM.

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.

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.

NOTE

For an example of an application that makes even more extensive use of dynamically loaded user controls than your sample store, see the IBuySpy Portal application at www.ibuyspy.com.

The code for the Product.aspx page is contained in Listing 31.7.

Listing 31.7 Product.aspx
[View full width]
<!-- #include virtual="/aspnetstore/includes/header.aspx" -->

<Script runat="Server">
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
</Script>

<html>
<head>
<link href="/aspnetstore/Styles/Store.css" type="text/css" rel="stylesheet" />
<title><%=Context.Items( "ProductName" )%></title>
</head>
<body bgcolor="lightblue">
<a href="Default.aspx"><img src="banner.gif" border="1"></a>
<form runat="Server">

<myControls:Tabs
  Runat="Server" />
<table width="800" height="100%" cellpadding="10"
  cellspacing="0" border="0">
<tr>
  <td valign="top" bgcolor="lightyellow">
  <myControls:ShoppingCart
    Runat="Server" />
  </td>
  <td width="100%" valign="top" bgcolor="white">
  <asp:PlaceHolder
    id="plhProductTemplate"
    Runat="Server" />
  </td>
</tr>
</table>

</form>
<!-- #include virtual="/aspnetstore/includes/footer.aspx" -->

The C# version of this code can be found on the CD-ROM.

Within the Page_Load subroutine in Listing 31.7, you dynamically load a user control. The user control is dynamically loaded with the LoadControl method of the Page class. The LoadControl method takes a single parameter: the path of the user control that you want to load.

Next, the dynamically loaded control is added to the controls collection of a PlaceHolder Web control. The PlaceHolder control is located in the body of the page.

The Code-Behind Product Template

You have to use one more trick to get your dynamically loaded product template to work. You might be wondering how you have access to all the columns from the Products database table in your product templates. The answer is that all the product templates derive from a single code-behind product template.

The code-behind product template is declared in the StoreComponents assembly. The source code for this class is contained in Listing 31.8.

Listing 31.8 ProductTemplate.vb
Public Class ProductTemplate : inherits UserControl
  Public ReadOnly Property ProductID As Integer
    Get
      Return Context.Items( "ProductID" )
    End Get
  End Property

  Public ReadOnly Property ProductName As String
    Get
      Return Context.Items( "ProductName" )
    End Get
  End Property

  Public ReadOnly Property UnitPrice As Decimal
    Get
      Return Context.Items( "UnitPrice" )
    End Get
  End Property

  Public ReadOnly Property Product As DataRowView
    Get
      Return Context.Items( "ProductInfo" )
    End Get
  End Property
End Class

The C# version of this code can be found on the CD-ROM.

The code-behind product template contains four properties that represent the values of four database columns: ProductID, ProductName, UnitPrice. The template contains one additional property named Product. The Product column contains all the columns from the underlying database row that represents the product.

You can use the Product property to display the value of any database column. For example, this property was used in the Seafood product template to display the value of the QuantityPerUnit database column like this:

<font color="red"><%=Product( "QuantityPerUnit" )%></font>


Summary

In this chapter, you built the ASP.NET Unleashed Sample Store. This sample store illustrated several advanced features of ASP.NET.

First, you learned how to use the HttpContext Items collection to pass information between modules (such as the Global.asax file) and ASP.NET pages. You learned how the HttpContext Items collection enables you to easily pass information throughout your ASP.NET applications.

Next, you learned how to build a shopping cart with ADO.NET. You saw an example of how to use the methods of the ADO.NET DataSet and DataAdapter classes to synchronize a shopping cart with a database table.

Finally, you learned how to use user controls to create dynamic product templates. You learned how to use the LoadControl method to dynamically load a user control. You also learned how to provide dynamically loaded controls with shared properties by creating a product template code-behind file.


Part X: Appendixes

IN THIS PART

APPENDIX A Migrating from ASP to ASP.NET

APPENDIX B HTML Control Reference

APPENDIX C Web Control Reference


Appendix A. Migrating from ASP to ASP.NET

IN THIS APPENDIX

  • ASP.NET Pages End with the Extension .aspx

  • Option Explicit Is On by Default

  • No More Variants

  • Use Parentheses When Passing Parameters to Subroutines and Methods

  • Functions and Subroutines Must Be Declared in <Script> Tags

  • Parameters Are Passed by Value

  • Forms Should Post Back to the Same Page

  • Only One Server-Side Form per Page

  • Don't Use Set and Let When Working with Objects

  • Apartment-Threaded Components Are Not Supported

  • No Nonindexed Default Properties

  • ASP.NET Pages Can Contain Only a Single Language

  • Differences in Request, Request.Form, and Request.QueryString

This appendix provides information on migrating from earlier versions of Active Server Pages to ASP.NET. If you are an existing Active Server Pages developer, one of the first questions that you'll likely ask is "How compatible is ASP.NET with previous versions of Active Server Pages?" In other words, you might be wondering whether you can simply upgrade your existing Active Server Pages Web site to ASP.NET without rewriting any code.

The answer to this question is complicated. In one trivial sense, ASP.NET is completely backward compatible with previous versions of Active Server Pages. As long as you name your pages with the extension .asp, they will be executed by exactly the same engine that was used to execute previous versions of Active Server Pages (ASP.dll). However, if you continue to execute pages with the extension .asp, you will gain none of the benefits of ASP.NET. For example, you will not gain any of the speed benefits of dynamic page compilation and page caching, and you will not gain access to the functionality encapsulated in the thousands of .NET classes available within an ASP.NET page.

The real answer is that you will have to do some work to get old ASP pages to work within the new ASP.NET framework. You'll have to do some work to migrate your VBScript pages to Visual Basic .NET. You'll have to do even more work to reconstruct your Web site to take full advantage of the ASP.NET framework. In the following sections, I describe several of the important changes between Active Server Pages Classic and ASP.NET.


ASP.NET Pages End with the Extension .aspx

ASP Classic pages end with the extension .asp, and ASP.NET pages end with the extension .aspx. This fact can be very inconvenient if you have existing links to your Web site. For example, you might have a Web site with hundreds of existing pages with names that end with the extension .asp and that have already been indexed by a search engine, such as AltaVista or Google. You might not want to change the extension of these filenames because you fear that you'll lose ranking in the search engines.

You can, in fact, use any extension that you want for ASP.NET pages. To change the extension used for ASP.NET pages, follow these steps:

  1. Launch the Internet Services Manager by choosing Start, Programs, Administrative Tools, Internet Services Manager.

  2. Open your Web site's property sheet by right-clicking the name of your Web site and selecting Properties.

  3. Select the Home Directory tab on the property sheet.

  4. In the Application Settings section, click the Configuration button.

  5. In the Application Configuration dialog box that appears, select the App Mappings tab.

  6. By default, the aspnet_isapi.dll file is mapped to the .aspx extension. If you click Edit, you can map aspnet_isapi.dll to any extension you choose. For example, if you map the extension to .asp files, all pages that end with the extension .asp will work as ASP.NET pages. (You need to delete the existing mapping for .asp files before you do this.)

After you complete the preceding steps, pages with an .asp extension will be processed by the ASP.NET engine. However, you have to complete one more series of steps before they will function correctly as ASP.NET pages:

  1. Open the machine.config file for your Web server with Notepad. The machine.config file is located under the WINNT\Microsoft.NET\Framework\_[version]\CONFIG directory.

  2. Find the <httpHandlers> section. This section lists handlers for files with different extensions, such as .aspx, .asmx, and .asp.

  3. Change the handler used for .asp files to System.Web.UI.PageHandlerFactory like this:

    [View full width]

<add
  verb="*"
  path="*.asp"
  type="System.Web.UI.PageHandlerFactory, System.Web, Version=1.0.2411.0, Culture=neutral,
   PublicKeyToken=b03f5f7f11d50a3a" />

These last three steps are necessary to associate .asp files with the correct handler. If you neglect to complete these steps, you receive an error message when attempting to open an .asp file.


Option Explicit Is On by Default

In previous versions of Active Server Pages, Option Explicit was Off by default. In ASP.NET, on the other hand, Option Explicit is On.

When Option Explicit is On, you must declare any variable before you use it. For example, although the following script works fine in ASP Classic, it generates an error in ASP.NET:

<%
strMessage = "Hello, how are you?"
Response.Write( strMessage )
%>

If you execute an ASP.NET page that contains this script, you receive the error message The name 'strMessage' is not declared.

You can avoid this error in two ways. If you want to follow good programming practices and get the best performance from your ASP.NET pages, you should declare all variables before you use them like this:

<%
Dim strMessage As String
strMessage = "Hello, how are you?"
Response.Write( strMessage )
%>

Alternatively, if you just don't feel like declaring all your variables, you can turn Option Explicit Off at either the page level or server level.

To set Option Explicit Off at the page level, add the following page directive to the top of a page:

<%@ Explicit="False" %>

You can disable Option Explicit for every ASP.NET page on your Web server within the machine.config file (located under the WINNT\Microsoft.NET\Framework\[version]\CONFIG directory). In the <compilation> section, set the explicit attribute to the value False.


No More Variants

VBScript is not a strongly typed language. In VBScript, you cannot declare the type of a variable, which results in all variables being created as variants. Variants are not supported by the .NET Framework. The .NET Framework equivalent to a variant is the Object type.

Consider the following script. In this script, a variable is declared, a value is assigned to it, and its value is displayed:

<%
Dim strMessage

strMessage = "Hello!"
Response.Write( strMessage )
%>

Because the type of the strMessage variable is not explicitly declared, it is treated as an Object. When the string "Hello!" is assigned to the variable, the variable is automatically converted to a String data type.

Automatically converting variables to the appropriate data type is convenient, but it has a negative impact on performance. For performance reasons, the variable type of strMessage should be explicitly declared like this:

<%
Dim strMessage As String

strMessage = "Hello!"
Response.Write( strMessage )
%>

The process of automatically converting a variable to the right type is called late binding. You can avoid late binding in your code by using the Option Strict statement. You enable Option Strict by adding the following page directive to the top of an ASP.NET page:

<%@ Strict="True" %>

When you enable Option Strict, Option Explicit is also automatically enabled.

Alternatively, you can enable Option Strict for every ASP.NET page in the machine.config file. To do so, add the attribute strict="true" to the <compilation> section.


Use Parentheses When Passing Parameters to Subroutines and Methods

In ASP.NET, unlike ASP Classic, you must always use parentheses when passing parameters to a subroutine or a method call. For example, in ASP Classic, the following script works fine:

<%
Response.Write "Hello World!"
%>

In ASP.NET, however, this script generates an error. You need to use parentheses with the Write method like this:

<%
Response.Write( "Hello World!" )
%>

You also need to always use parentheses when passing parameters to a subroutine. If a subroutine does not require any parameters, the parentheses are optional.


Functions and Subroutines Must Be Declared in <Script> Tags

In ASP Classic, you can declare functions and subroutines within <% and %> code render blocks (except in the case of the Global.asa file). For example, the following script works fine in ASP Classic:

<%
Function AddNums( intVal1, intVal2 )
  AddNums = intVal1 + intVal2
End Function

Response.Write( AddNums( 12, 34 ) )
%>

If you try to execute this script in an ASP.NET page, however, you receive an error. You must declare functions and subroutines in a <Script> code declaration block like this:

<Script runat="Server">

Function AddNums( intVal1, intVal2 )
  AddNums = intVal1 + intVal2
End Function

</Script>

<%
Response.Write( AddNums( 12, 34 ) )
%>

One implication of this new requirement is that you cannot render content directly from within a subroutine or function. For example, in ASP Classic, the following script works fine:

<%
Sub ShowError
  %>
  <font color="red">Error!</font>
  <%
End Sub

ShowError

%>

However, because you cannot define a function or subroutine in a code render block within ASP.NET, you would need to rewrite the code like this:

<Script runat="Server">
Sub ShowError
  Response.Write( "<font color=""red"">Error!</font>" )
End Sub
</Script>

<%

ShowError

%>


Parameters Are Passed by Value

By default, in VBScript and earlier versions of Visual Basic, parameters passed to subroutines and functions are passed by reference. In Visual Basic .NET, however, parameters are passed by value.

When a parameter is passed to a function by value, changes to the parameter within the function are not reflected outside the function. When a parameter is passed by value, on the other hand, any changes made to the parameter are retained outside the function.

You can still use the ByVal and ByRef keywords to specify whether a parameter is passed by value or by reference. For example, the following page illustrates how to explicitly pass a parameter to a subroutine by value and by reference:

<Script runat="Server">

Sub AddByVal( ByVal intParam )
  intParam += 1
End Sub

Sub AddByRef( ByRef intParam )
  intParam +=1
End Sub

</Script>

<%
Dim intNum As Integer = 3

' intNum still equals 3 after AddByVal
AddByVal( intNum )

' intNum now equals 4 after AddByRef
AddByRef( intNum )
%>


Forms Should Post Back to the Same Page

To take full advantage of the ASP.NET framework, you should not create HTML forms that post to a separate page. If a form posts to another page, any view state maintained by the page is lost.

In ASP Classic, it is very common to have a form that posts data to a second page, as illustrated in Figure A.1.

Figure A.1. Posting to a new page.

The preferred method for working with forms in the ASP.NET framework is to post forms back to the same page. Posting a form back to itself is called a postback. Performing a postback is illustrated in Figure A.2.

Figure A.2. Performing a postback.

When you have a form post back to itself, you can take advantage of view state (property values of controls are automatically preserved between posts). After you perform a postback, you can use the Response.Redirect method to send a user to a new page.


Only One Server-Side Form per Page

One significant limitation of the ASP.NET framework is that you can have only one server-side form in a page. For example, many Web sites have a single page that enables new users to register and existing users to log in. You cannot easily create this type of page with the ASP.NET framework.

The ASP.NET framework does not allow you to create a single page with two forms that both take advantage of ASP.NET functionality, such as form validation and view state. You must divide the single page into two pages.

This restriction does not apply to normal HTML forms; it applies only to server-side forms. You can have as many standard client-side forms in a page as you wish.


Don't Use Set and Let When Working with Objects

In previous versions of ASP, you were required to use Set when assigning an object to a variable. For example, when creating an instance of the Connection object, you used the following statement:

<%
Set conPubs = Server.CreateObject( "ADODB.Connection" )
%>

You no longer use the Set statement when creating ASP.NET pages with Visual Basic .NET. Instead, you can use a simple assignment:

<%
conPubs = Server.CreateObject( "ADODB.Connection" )
%>

Furthermore, you can no longer use the Let statement.


Apartment-Threaded Components Are Not Supported

By default, components that use a single-threaded apartment (STA) model are not supported in an ASP.NET page. Furthermore, by default, the ASP Classic intrinsic interfaces, such as OnStartPage and OnEndPage, are not supported.

If you want to use an apartment-threaded component, such as the ADO Connection object or the Scripting.Dictionary object, you need to add the following page directive to the top of the page:

<%@ Page ASPCompat="true" %>

For example, the following page displays all the records from the Titles table in the Pubs database using traditional ADO. If you don't enable ASP compatibility, the page fails.

<%@ Page ASPCompat="true" %>
<%
Dim conPubs
Dim rsTitles

conPubs = Server.CreateObject( "ADODB.Connection" )
conPubs.Open( "Provider=sqloledb;UID=sa;PWD=secret;database=pubs" )

rsTitles = ConPubs.Execute( "Select * from Titles" )

While Not rsTitles.eof
  Response.Write( rsTitles( "Title" ).Value )
  rsTitles.MoveNext
End While
%>


No Nonindexed Default Properties

Visual Basic no longer supports nonindexed default properties. For example, in ASP Classic, you do not need to explicitly call the Value property of the RecordSet object. You can display the value of a RecordSet field like this:

Response.Write( RS( "Title" ) )

In ASP.NET, however, you must always explicitly refer to a property. So, you need to rewrite the statement like this:

Response.Write( RS( "Title" ).Value )


ASP.NET Pages Can Contain Only a Single Language

In ASP Classic, you can use the <Script> tag to add scripts written with different languages to the same page. For example, you can create a VBScript script that calls a function from a JScript script within the same page by containing the scripts in separate <Script> blocks.

ASP.NET does not support more than a single language in the same page. If you want to mix multiple languages, you need to create separate components. For example, you can create a component in C# and access the methods and properties of the component in an ASP.NET page written in Visual Basic .NET.


Differences in Request, Request.Form, and Request.QueryString

In ASP Classic, the Request, Request.Form, and Request.QueryString methods return an array of strings. In ASP.NET, all three methods return a single string.

ASP Classic returns an array of strings because multiple items in the Form or QueryString collection might have the same name. For example, the following URL adds two items with the same name to the QueryString collection:

http://www.Superexpert.com/Default.aspx?favColor=blue&favColor=green

In ASP Classic, if you want to display both values of the favColor query string variable, you need to loop through the string array returned by the QueryString method like this:

<%
Dim favColor

For each favColor in Request.QueryString( "favColor" )
  Response.Write( favColor )
Next
%>

In ASP.NET, the QueryString() method always returns a single string. In this case, the string is blue,green. If you need to return a collection instead of a single string, you need to use the GetValues() method like this:

<%
Dim favColor

For each favColor in Request.QueryString.GetValues( "favColor" )
  Response.Write( favColor )
Next
%>

This same discussion applies to Request.Form. If a form contains a list box that allows multiple items to be selected, the Form collection might contain multiple items with the same name. In ASP Classic, Request.Form() returns a string array, whereas in ASP.NET, Request.Form() always returns a single string.

NOTE

For some odd reason, in ASP Classic, the lower bound of the string array returned by Request.QueryString and Request.Form was 1. In ASP.NET, the lower bound of the array returned by the GetValues method is 0.


Appendix B. HTML Control Reference

This quick reference provides a list of the important properties, methods, and events of each HTML control. See Chapter 1, "Building ASP.NET Pages," for more information on using these controls within an ASP.NET page.

Table . HtmlAnchor Control Properties, Methods, and Events

Properties

Description

Attributes

Returns a collection of all attributes of the control.

Disabled

Specifies a Boolean value indicating whether the control is disabled on the form.

HRef

Indicates the URL that is the target of the link.

InnerHtml

Specifies the HTML content that appears within the tag.

InnerText

Specifies the text content that appears within the tag.

Name

Specifies the unique identifier for the control.

Style

Returns a collection of style attributes for the control.

TagName

Returns the name of the tag used when rendering the control.

Target

Displays the page in this window or frame when you click the banner advertisement. Possible values are _top, _new, _child, _self, _parent, and _blank.

Title

Indicates the title of the document being linked. The title also appears as a ToolTip in certain browsers.

Methods

Description

OnServerClick

Raises the ServerClick event.

Events

Description

ServerClick

Raised when a user clicks the control.

Table . HtmlButton Control Properties, Methods, and Events

Properties

Description

Attributes

Returns a collection of all attributes of the control.

CausesValidation

Does not perform validation when the form containing the button is submitted when this property is set to False.

Disabled

Specifies a Boolean value indicating whether the control is disabled on the form.

InnerHtml

Specifies the HTML content that appears within the tag.

InnerText

Specifies the text content that appears within the tag.

Style

Returns a collection of style attributes for the control.

TagName

Returns the name of the tag used when rendering the control.

Events

Description

OnServerClick

Raises the ServerClick event.

Events

Description

ServerClick

Raised when a user clicks the control.

Table . HtmlForm Control Properties, Methods, and Events

Properties

Description

Attributes

Returns a collection of all attributes of the control.

Disabled

Specifies a Boolean value indicating whether the form is disabled.

Enctype

Identifies a string that specifies the encoding type used to encode the form data. Possible values include application/x-www-form-urlencoded, multipart/_form-data, and text/plain.

InnerHtml

Specifies the HTML content that appears within the tag.

InnerText

Specifies the text content that appears within the tag.

Method

Identifies a string that specifies how form data should be posted. Possible values include Post and Get. If you change this property from its default value of Get, view state might fail.

Name

Returns the name of the HtmlForm.

Style

Returns a collection of style attributes for the control.

TagName

Returns the name of the tag used when rendering the control.

Target

Displays the page in this window or frame when you click the banner advertisement. Possible values are top, new, child, self, parent, and blank.

Methods

Description

None

Events

Description

None

Table . HtmlGenericControl Control Properties, Methods, and Events

Properties

Description

Attributes

Returns a collection of all attributes of the control.

Disabled

Specifies a Boolean value indicating whether the control is disabled on the form.

InnerHtml

Specifies the HTML content that appears within the tag.

InnerText

Specifies the text content that appears within the tag.

Style

Returns a collection of style attributes for the control.

TagName

Returns the name of the tag used when rendering the control.

Methods

Description

None

Events

Description

None

Table . HtmlImage Control Properties, Methods, and Events

Properties

Description

Align

Identifies a string that specifies the alignment of the image within the page. Possible values include left, center, right, top, and bottom.

Alt

Specifies the alternative text that is displayed on browsers that do not support images.

Attributes

Returns a collection of all attributes of the control.

Border

Indicates the width in pixels of the border around the image.

Disabled

Specifies a Boolean value indicating whether the control is disabled on the form.

Height

Specifies the height of the image in pixels.

Src

Specifies the URL of the image file to display.

Style

Returns a collection of style attributes for the control.

TagName

Returns the name of the tag used when rendering the control.

Width

Specifies the width of the image in pixels.

Methods

Description

None

Events

Description

None

Table . HtmlInputButton Control Properties, Methods, and Events

Properties

Description

Attributes

Returns a collection of all attributes of the control.

CausesValidation

Does not perform validation when the form containing the button is submitted when this property is set to False.

Disabled

Specifies a Boolean value indicating whether the control is disabled on the form.

Name

Specifies the unique identifier for the control.

Style

Returns a collection of style attributes for the control.

TagName

Returns the name of the tag used when rendering the control.

Type

Specifies a string representing the type of the input control.

Value

Specifies the text used to label the button.

Methods

Description

OnServerClick

Raises the ServerClick event.

Events

Description

ServerClick

Raised when a user clicks the control.

Table . HtmlInputCheckBox Control Properties, Methods, and Events

Properties

Description

Attributes

Returns a collection of all attributes of the control.

Checked

Specifies a Boolean value indicating whether the check box is checked.

Disabled

Specifies a Boolean value indicating whether the control is disabled on the form.

Name

Specifies the unique identifier for the control.

Style

Returns a collection of style attributes for the control.

TagName

Returns the name of the tag used when rendering the control.

Type

Specifies a string representing the type of the input control.

Value

Specifies a string representing the contents of the control.

Methods

Description

OnServerChange

Raises the ServerChange event.

Events

Description

ServerChange

Raised when a check box is checked or unchecked.

Table . HtmlInputFile Control Properties, Methods, and Events

Properties

Description

Accept

Contains a comma-separated list of MIME types that indicate the type of file that can be uploaded—for example, image/gif,image/jpeg.

Attributes

Returns a collection of all attributes of the control.

Disabled

Specifies a Boolean value indicating whether the control is disabled on the form.

Maxlength

Specifies the maximum length of the file path for the file being uploaded.

Name

Specifies the unique identifier for the control.

PostedFile

Contains an instance of the HttpPostedFile class that represents the file uploaded.

Size

Specifies the width of the text box in which the file path is entered.

Style

Returns a collection of style attributes for the control.

TagName

Returns the name of the tag used when rendering the control.

Type

Specifies a string representing the type of the input control.

Value

Specifies a string representing the contents of the control.

Methods

Description

None

Events

Description

None

Table . HtmlInputHidden Control Properties, Methods, and Events

Properties

Description

Attributes

Returns a collection of all attributes of the control.

Disabled

Specifies a Boolean value indicating whether the control is disabled on the form.

Name

Specifies the unique identifier for the control.

Style

Returns a collection of style attributes for the control.

TagName

Returns the name of the tag used when rendering the control.

Type

Specifies a string representing the type of the input control.

Value

Specifies a string representing the contents of the control.

Methods

Description

OnServerChange

Raises the ServerChange event.

Events

Description

ServerChange

Raised when the contents of the control are changed.

Table . HtmlInputImage Control Properties, Methods, and Events

Properties

Description

Align

Identifies a string that specifies the alignment of the image within the page. Possible values include left, center, right, top, and bottom.

Alt

Specifies the alternative text that is displayed on browsers that do not support images.

Attributes

Returns a collection of all attributes of the control.

Border

Specifies the width in pixels of the border around the image.

CausesValidation

Does not perform validation when the form containing the button is submitted when this property is set to False.

Disabled

Specifies a Boolean value indicating whether the control is disabled on the form.

Name

Specifies the unique identifier for the control.

Src

Specifies the URL of the image file to display.

Style

Returns a collection of style attributes for the control.

TagName

Returns the name of the tag used when rendering the control.

Type

Specifies a string representing the type of the input control.

Value

Specifies a string representing the contents of the control.

Methods

Description

OnServerClick

Raises the ServerClick event.

Events

Description

ServerClick

Raised when the control is clicked.

Table . HtmlInputRadioButton Control Properties, Methods, and Events

Properties

Description

Attributes

Returns a collection of all attributes of the control.

Checked

Specifies a Boolean value indicating whether the radio button is checked.

Disabled

Specifies a Boolean value indicating whether the control is disabled on the form.

Name

Specifies the unique identifier for the control.

Style

Returns a collection of style attributes for the control.

TagName

Returns the name of the tag used when rendering the control.

Type

Specifies a string representing the type of the input control.

Value

Specifies a string representing the contents of the control.

Methods

Description

None

Events

Description

None

Table . HtmlInputText Control Properties, Methods, and Events

Properties

Description

Attributes

Returns a collection of all attributes of the control.

Disabled

Specifies a Boolean value indicating whether the control is disabled on the form.

MaxLength

Specifies the maximum number of characters that can be entered into the text box.

Name

Specifies the unique identifier for the control.

Size

Specifies the size of the text box in characters.

Style

Returns a collection of style attributes for the control.

TagName

Returns the name of the tag used when rendering the control.

Type

Specifies a string representing the type of the input control.

Value

Specifies a string representing the contents of the control.

Methods

Description

OnServerChange

Raises the ServerChange event.

Events

Description

ServerChange

Raised when the contents of the text box are changed.

Table . HtmlSelect Control Properties, Methods, and Events

Properties

Description

Attributes

Returns a collection of all attributes of the control.

DataMember

Identifies the table in the data source to bind to the control.

DataSource

Identifies the source for the data displayed by the control.

DataTextField

Identifies the field in the data source used for the text for each option.

DataValueField

Identifies the field in the data source used for the value for each option.

Disabled

Specifies a Boolean value indicating whether the control is disabled on the form.

InnerHtml

Specifies the HTML content that appears within the tag.

InnerText

Specifies the text content that appears within the tag.

Items

Returns a collection of ListItem objects that represent the options in the select list.

Multiple

Specifies a Boolean value indicating whether multiple options can be selected.

SelectedIndex

Specifies the index of the currently selected option.

Size

Displays a scrolling list box when this property is set to greater than 1.

Style

Returns a collection of style attributes for the control.

TagName

Returns the name of the tag used when rendering the control.

Value

Specifies a string representing the contents of the control.

Methods

Description

OnServerChange

Raises the ServerChange event.

Events

Description

ServerChange

Raised when a new option is selected.

Table . HtmlTable Control Properties, Methods, and Events

Properties

Description

Align

Identifies a string that specifies the alignment of the contents of the table. Possible values include left, center, and right.

Attributes

Returns a collection of all attributes of the control.

BgColor

Identifies a string specifying the background color of the table.

Border

Identifies an integer representing the width of the table border in pixels.

BorderColor

Identifies a string specifying the color of the table border.

CellPadding

Specifies the amount of padding in pixels within each table cell.

CellSpacing

Specifies the amount of padding in pixels between each table cell.

Disabled

Specifies a Boolean value indicating whether the control is disabled on the form.

Height

Specifies the height of the table in pixels.

InnerHtml

Specifies the HTML content that appears within the tag.

InnerText

Specifies the text content that appears within the tag.

Rows

Returns a collection of TableRow objects contained in the table.

Style

Returns a collection of style attributes for the control.

TagName

Returns the name of the tag used when rendering the control.

Width

Specifies the width of the table in pixels.

Methods

Description

None

Events

Description

None

Table . HtmlTableCell Control Properties, Methods, and Events

Properties

Description

Align

Identifies a string that specifies the alignment of the contents of the cell. Possible values include left, center, and right.

Attributes

Returns a collection of all attributes of the control.

BgColor

Identifies a string specifying the background color of the cell.

BorderColor

Identifies a string specifying the color of the cell border.

ColSpan

Specifies the number of columns that the cell spans.

Disabled

Specifies a Boolean value indicating whether the control is disabled on the form.

Height

Specifies the height of the cell in pixels.

InnerHtml

Specifies the HTML content that appears within the tag.

InnerText

Specifies the text content that appears within the tag.

NoWrap

Does not word-wrap contents of the cell when this property is set to True.

RowSpan

Specifies the number of rows that the cell spans.

Style

Returns a collection of style attributes for the control.

TagName

Returns the name of the tag used when rendering the control.

VAlign

Specifies the vertical alignment of the contents of the cell. Possible values include top, middle, bottom, and baseline.

Width

Specifies the width of the cell in pixels.

Methods

Description

None

Events

Description

None

Table . HtmlTableRow Control Properties, Methods, and Events

Properties

Description

Align

Identifies a string that specifies the alignment of the cells contained in the row. Possible values include left, center, and right.

Attributes

Returns a collection of all attributes of the control.

BgColor

Identifies a string specifying the background color of the row.

BorderColor

Identifies a string specifying the color of the row border.

Cells

Returns a collection of TableCell objects contained in the row.

Disabled

Specifies a Boolean value indicating whether the control is disabled on the form.

Height

Specifies the height of the row in pixels.

InnerHtml

Specifies the HTML content that appears within the tag.

InnerText

Specifies the text content that appears within the tag.

Style

Returns a collection of style attributes for the control.

TagName

Returns the name of the tag used when rendering the control.

VAlign

Specifies the vertical alignment of the contents of the cells contained in the row. Possible values include top, middle, bottom, and baseline.

Methods

Description

None

Events

Description

None

Table . HtmlTextArea Control Properties, Methods, and Events

Properties

Description

Attributes

Returns a collection of all attributes of the control.

Cols

Specifies the width of the text area in characters.

Disabled

Specifies a Boolean value indicating whether the control is disabled on the form.

InnerHtml

Specifies the HTML content that appears within the tag.

InnerText

Specifies the text content that appears within the tag.

Name

Specifies the unique identifier for the control.

Rows

Specifies the height of the text area in characters.

Style

Returns a collection of style attributes for the control.

TagName

Returns the name of the tag used when rendering the control.

Value

Specifies a string representing the contents of the control.

Methods

Description

OnServerChange

Raises the ServerChange event.

Events

Description

ServerChange

Raised when the contents of the text area are changed.


Appendix C. Web Control Reference

This quick reference includes a list of the important properties, methods, and events for each Web control. At the end of the reference, the formatting properties of the base Web Control, TableStyle, and TableItemStyle classes are listed.

Table . AdRotator Control Properties, Methods, and Events

Properties

Description

AdvertisementFile

Specifies the path to an XML file that contains a list of banner advertisements to display.

KeywordFilter

Returns only those banner advertisements that match the filter when this property is set.

Target

Displays the page in this window or frame when you click the banner advertisement. Possible values are top, new, child, self, parent, and blank.

Methods

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.

NOTE

See Chapter 4, "Advanced Control Programming," for more information.

Table . Button Properties, Methods, and Events

Properties

Description

CausesValidation

Does not validate the form submitted by the button when False. The default value is True.

CommandArgument

Passes this value to the Command event when the button is clicked.

CommandName

Passes this value to the Command event when the button is clicked.

Text

Gets or sets the text displayed on the button.

Methods

Description

OnClick

Raises the Click event.

OnCommand

Raises the Command event.

Events

Description

Click

Raised when the button is clicked and the form containing the button is submitted to the server.

Command

Also raised when the button is clicked. However, the values of the CommandName and CommandArgument properties are passed with this event.

NOTE

See Chapter 2, "Building Forms with Web Server Controls," for more information.

Table . Calendar Properties, Methods, and Events

Properties

Description

CellPadding

Specifies the number of pixels between the contents of a cell and its border.

CellSpacing

Specifies the number of pixels between cells.

DayHeaderStyle

Specifies the style of the weekdays listed at the top of the calendar.

DayNameFormat

Sets the format of the day names. Possible values are FirstLetter, FirstTwoLetters, Full, and Short; the default is Short.

DayStyle

Specifies the style applied to each day in the calendar.

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.

NextMonthText

Specifies the text for the Next Month hyperlink if ShowNextPrevMonth is True.

NextPrevFormat

Specifies the format of the Next and Previous hyperlinks. Possible values are CustomText, FullMonth, and ShortMonth; the default is CustomText.

NextPrevStyle

Specifies the style applied to the next and previous month links.

OtherMonthDayStyle

Specifies the style applied to days from other months that appear in the current month.

PrevMonthText

Specifies the text for the Previous Month hyperlink if ShowNextPrevMonth is True.

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.

SelectedDayStyle

Specifies the style applied to the currently selected day.

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.

SelectorStyle

Specifies the style applied to the link for selecting the week or month.

SelectWeekText

Contains HTML text displayed for selecting weeks when SelectionMode has the value DayWeek or DayWeekMonth.

ShowDayHeader

Displays the names of the days of the week if True.

ShowGridLines

Renders the calendar with lines around the days if True.

ShowNextPrevMonth

Displays Next Month and Previous Month links if True.

ShowTitle

Displays the calendar's title if True.

TitleFormat

Determines how the month name appears in the title bar. Possible values are Month and MonthYear.

TitleStyle

Specifies the style applied to the calendar's title bar.

TodayDayStyle

Specifies the style applied to the current date.

TodaysDate

Specifies a DateTime value that sets the calendar's current date.

VisibleDate

Specifies a DateTime value that sets the month to display.

WeekendDayStyle

Specifies the style applied to weekend days.

Methods

Description

OnDayRender

Raises the DayRender event.

OnSelectionChanged

Raises the SelectionChanged event.

OnVisibleMonthChanged

Raises the VisibleMonthChanged event.

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.

NOTE

See Chapter 4 for more information.

Table . CheckBox Properties, Methods, and Events

Properties

Description

AutoPostBack

Automatically posts the form containing the check box whenever it is checked or unchecked when True.

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.

Methods

Description

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.

OnCheckedChanged

Raises the CheckedChanged event.

Events

Description

CheckedChanged

Raised when the check box changes from being checked to unchecked or unchecked to checked.

NOTE

See Chapter 2 for more information.

Table . CheckBoxList Properties, Methods, and Events

Properties

Description

AutoPostBack

Automatically posts the form containing the CheckBoxList whenever a new check box is selected or unselected when True.

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.

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.

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

SelectedValue

Gets or sets 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 the SelectedIndexChanged event.

Events

Description

SelectedIndexChanged

Raised whenever a new check box is checked or unchecked in the CheckBoxList.

NOTE

See Chapter 2 for more information.

Table . 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. The default value is True.

Enabled

Enables or disables both server- and client-side validation. The default value is True.

ErrorMessage

Specifies the error message 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, and 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

NOTE

See Chapter 3, "Performing Form Validation with Validation Controls," for more information.

Table . 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. The default value is True.

Enabled

Enables or disables both server- and client-side validation. The default value is True.

ErrorMessage

Specifies the error message 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 subroutine for performing the server-side validation.

NOTE

See Chapter 3 for more information.

Table . DataGrid Properties, Methods, and Events

Properties

Description

AllowCustomPaging

Enables custom paging when True.

AllowPaging

Enables normal paging when True.

AllowSorting

Enables sorting when True.

AlternatingItemStyle

Specifies the style applied to alternating rows displayed in the DataGrid.

AutoGenerateColumns

Automatically generates columns from the data source when True.

BackImageUrl

Specifies the URL of an image to display as the background for the DataGrid.

CellPadding

Specifies the amount of padding in each cell in a DataGrid.

CellSpacing

Specifies the amount of spacing between cells in a DataGrid.

Columns

Represents the collection of columns in the DataGrid (does not include autogenerated columns).

CurrentPageIndex

Sets the current page when paging is enabled.

DataKeyField

Gets or sets the primary key field in the data source.

DataKeys

Returns a collection of primary keys from the data source.

DataMember

Specifies the table in the DataSet to bind to the DataGrid.

DataSource

Specifies the data source for the DataGrid.

EditItemIndex

Sets the current row selected for editing.

EditItemStyle

Specifies the style applied to the row selected for editing.

FooterStyle

Specifies the style applied to the footer of the DataGrid.

GridLines

Determines how lines are displayed between cells. Possible values are Both, Horizontal, None, and Vertical.

HeaderStyle

Specifies the style applied to the header of the DataGrid.

HorizontalAlign

Sets the horizontal alignment of the DataGrid. Possible values are Center, Justify, Left, NotSet, and Right.

Items

Represents a collection of DataGridItem objects in the DataGrid.

ItemStyle

Specifies the style applied to each item displayed in the DataGrid.

PageCount

Specifies a count of the number of pages when paging is enabled.

PagerStyle

Specifies the style applied to the paging interface. The style properties are represented by the DataGridPagerStyleObject.

PageSize

Specifies the number of rows displayed in a page when paging is enabled.

SelectedIndex

Sets the row that is currently selected in the DataGrid.

SelectedItem

Returns a DataGridItem that represents the currently selected row in the DataGrid.

SelectedItemStyle

Specifies the style applied to the selected item in the DataGrid.

ShowFooter

Displays a footer at the bottom of the DataGrid when True.

ShowHeader

Displays a header at the top of the DataGrid when True.

VirtualItemCount

Gets or sets the number of items in the DataGrid when custom paging is enabled.

Methods

Description

DataBind

Binds the DataGrid to its data source.

OnCancelCommand

Raises the CancelCommand event.

OnDeleteCommand

Raises the DeleteCommand event.

OnEditCommand

Raises the EditCommand event.

OnItemCommand

Raises the ItemCommand event.

OnItemCreated

Raises the ItemCreated event.

OnItemDataBound

Raises the ItemDataBound event.

OnPageIndexChanged

Raises the PageIndexChanged event.

OnSelectedIndexChanged

Raises the SelectedIndexChanged event.

OnSortCommand

Raises the SortCommand event.

OnUpdateCommand

Raises the UpdateCommand event.

Events

Description

CancelCommand

Raised when the Cancel button is clicked.

DeleteCommand

Raised when the Delete button is clicked.

EditCommand

Raised when the Edit button is clicked.

ItemCommand

Raised when any button is clicked.

ItemCreated

Raised when each item is created in the DataGrid.

ItemDataBound

Raised when an item is bound in the DataGrid.

PageIndexChanged

Raised when the current page is changed when paging is enabled.

SelectedIndexChanged

Raised when an item in the DataGrid is selected.

SortCommand

Raised when a column is sorted and sorting is enabled.

UpdateCommand

Raised when the Update button is clicked.

NOTE

See Chapter 11, "Using the DataList and DataGrid Controls," for more information.

Table . DataList Properties, Methods, and Events

Properties

Description

AlternatingItemStyle

Specifies the style applied to alternating rows displayed in the DataList.

AlternatingItemTemplate

Specifies the template used for alternating items in the DataList.

CellPadding

Specifies the amount of padding in each cell in a DataList.

CellSpacing

Specifies the amount of spacing between cells in a DataList.

DataKeyField

Gets or sets the primary key field in the data source.

DataKeys

Returns a collection of primary keys from the data source.

DataMember

Specifies the table in the DataSet to bind to the DataGrid.

DataSource

Specifies the data source for the DataList.

EditItemIndex

Sets the current item selected for editing.

EditItemStyle

Specifies the style applied to the item selected for editing.

EditItemTemplate

Specifies the template used for the item selected for editing.

ExtractTemplateRows

Extracts rows from Table controls displayed in templates and displays them as rows in one large Table when True.

FooterStyle

Specifies the style applied to the footer of the DataGrid.

FooterTemplate

Specifies the template used for displaying the footer of the DataList.

GridLines

Determines how lines are displayed between cells. Possible values are Both, Horizontal, None, and Vertical.

HeaderStyle

Specifies the style applied to the header of the DataList.

HeaderTemplate

Specifies the template used for displaying the header of the DataList.

HorizontalAlign

Sets the horizontal alignment of the DataList. Possible values are Center, Justify, Left, NotSet, and Right.

Items

Represents a collection of DataListItem objects in the DataList.

ItemStyle

Specifies the style applied to each item displayed in the DataList.

ItemTemplate

Specifies the template used for displaying each item in the DataList.

RepeatColumns

Specifies the number of columns to display in the DataList.

RepeatDirection

Determines the direction that items are repeated. Possible values are Horizontal and Vertical.

RepeatLayout

Determines whether items in a DataList are displayed with an HTML table. Possible values are Table and Flow.

SelectedIndex

Sets the row that is currently selected in the DataList.

SelectedItem

Returns a DataListItem that represents the currently selected item in the DataList.

SelectedItemStyle

Specifies the style applied to the selected item in the DataList.

SelectedItemTemplate

Specifies the template used for displaying selected items in the DataList.

SeparatorStyle

Specifies the style applied to the separator between each item in a DataList.

SeparatorTemplate

Specifies the template used for displaying separators between each item in a DataList.

ShowFooter

Displays a footer at the bottom of the DataList when True.

ShowHeader

Displays a header at the top of the DataList when True.

Methods

Description

DataBind

Binds the DataGrid to its data source.

OnCancelCommand

Raises the CancelCommand event.

OnDeleteCommand

Raises the DeleteCommand event.

OnEditCommand

Raises the EditCommand event.

OnItemCommand

Raises the ItemCommand event.

OnItemCreated

Raises the ItemCreated event.

OnItemDataBound

Raises the ItemDataBound event.

OnSelectedIndexChanged

Raises the SelectedIndexChanged event.

OnUpdateCommand

Raises the UpdateCommand event.

Events

Description

CancelCommand

Raised when the Cancel button is clicked.

DeleteCommand

Raised when the Delete button is clicked.

EditCommand

Raised when the Edit button is clicked.

ItemCommand

Raised when any button is clicked.

ItemCreated

Raised when each item is created in the DataList.

ItemDataBound

Raised when an item is bound in the DataList.

SelectedIndexChanged

Raised when an item in the DataList is selected.

UpdateCommand

Raised when the Update button is clicked.

NOTE

See Chapter 11 for more information.

Table . DropDownList Properties, Methods, and Events

Properties

Description

AutoPostBack

Automatically posts the form containing the drop-down list whenever a new option is selected when True.

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

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

SelectedIndex

Specifies the index number of the selected option.

SelectedItem

Represents the selected option.

SelectedValue

Gets or sets the selected option.

Methods

Description

DataBind

Binds the DropDownList 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 DropDownList.

NOTE

See Chapter 2 for more information.

Table . HyperLink Properties, Methods, and Events

Properties

Description

ImageUrl

Specifies the URL of an image to display for the link.

NavigateUrl

Specifies the URL of the page to which the user is transferred when the link is clicked.

Target

Specifies the target window or frame for the link. Possible values are _top, _self, _parent, _search, or _blank.

Text

Specifies the text label for the link.

Methods

Description

None

Events

Description

None

NOTE

See Chapter 2 for more information.

Table . Image Properties, Methods, and Events

Properties

Description

AlternateText

Specifies text displayed by browsers that do not support images.

ImageAlign

Determines the alignment of the image. Possible values are AbsBottom, AbsMiddle, BaseLine, Bottom, Left, Middle, NotSet, Right, TextTop, and Top.

ImageUrl

Specifies the URL of an image to display.

Methods

Description

None

Events

Description

None

Table . ImageButton Properties, Methods, and Events

Properties

Description

AlternateText

Gets or sets the text displayed when a browser does not support images.

CausesValidation

Does not validate the form submitted by the button when False. The default value is True.

CommandArgument

Passes this value to the Command event when the button is clicked.

CommandName

Passes this value to the Command event when the button is clicked.

ImageAlign

Specifies one of these possible values: AbsBottom, AbsMiddle, Baseline, Bottom, Left, Middle, NotSet, Right, TextTop, or Top.

ImageURL

Specifies the URL of the image to use for the button.

Methods

Description

OnClick

Raises the Click event.

OnCommand

Raises the Command event.

Events

Description

Click

Raised when the button is clicked and the form containing the button is submitted to the server.

Command

Also raised when the button is clicked. However, the values of the CommandName and CommandArgument properties are passed to this event.

NOTE

See Chapter 2 for more information.

Table . Label Properties, Methods, and Events

Properties

Description

Text

Gets or sets the text displayed by the Label control.

Methods

Description

None

Events

Description

None

NOTE

See Chapter 2 for more information.

Table . LinkButton Properties, Methods, and Events

Properties

Description

CausesValidation

Does not validate the form submitted by the button when False. The default value is True.

CommandArgument

Passes this value to the Command event when the link is clicked.

CommandName

Passes this value to the Command event when the link is clicked.

Text

Gets or sets the text displayed as the label for the link.

Methods

Description

OnClick

Raises the Click event.

OnCommand

Raises the Command event.

Events

Description

Click

Raised when the link is clicked and the form containing the link is submitted to the server.

Command

Also raised when the link is clicked. However, the values of the CommandName and CommandArgument properties are passed to this event.

NOTE

See Chapter 2 for more information.

Table . ListBox Properties, Methods, and Events

Properties

Description

AutoPostBack

Automatically posts the form containing the list box whenever a new option is selected when True.

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.

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.

SelectedValue

Gets or sets the selected list item.

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

Raised whenever a new option is selected in the list box.

NOTE

See Chapter 2 for more information.

Table . Literal Properties, Methods, and Events

Properties

Description

Text

Gets or sets the text displayed by the control.

Methods

Description

None

Events

Description

None

Table . Panel Properties, Methods, and Events

Properties

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

Wraps the contents of the panel when True. The default value is True.

Methods

Description

None

Events

Description

None

NOTE

See Chapter 4 for more information.

Table . PlaceHolder Properties, Methods, and Events

Properties

Description

None

Methods

Description

None

Events

Description

None

NOTE

See Chapter 4 for more information.

Table . RadioButton Properties, Methods, and Events

Properties

Description

AutoPostBack

Automatically posts the form containing the radio button whenever a new radio button is selected from the radio button group when True.

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

Raised when the radio button changes from being checked to unchecked, or unchecked to checked.

NOTE

See Chapter 2 for more information.

Table . RadioButtonList Properties, Methods, and Events

Properties

Description

AutoPostBack

Automatically posts the form containing the RadioButtonList whenever a new radio button is selected when True.

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.

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.

SelectedValue

Gets or sets 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.

OnSelected IndexChanged

Raises the SelectedIndexChanged event.

Events

Description

Selected IndexChanged

Raised when a new radio button is selected in the RadioButtonList.

NOTE

See Chapter 2 for more information.

Table . RangeValidator 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. The default value is True.

Enabled

Enables or disables both server- and client-side validation. The default value is True.

ErrorMessage

Specifies the error message 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

NOTE

See Chapter 3 for more information.

Table . RegularExpressionValidator 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. The default value is True.

Enabled

Enables or disables both server- and client-side validation. The default value is True.

ErrorMessage

Specifies the error message 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

NOTE

See Chapter 3 for more information.

Table . Repeater Properties, Methods, and Events

Properties

Description

Alternating ItemTemplate

Specifies the template used for alternating items in the Repeater.

DataMember

Specifies the table in the DataSet to bind to the Repeater.

DataSource

Specifies the data source for the Repeater.

FooterTemplate

Specifies the template used for displaying the footer of the Repeater.

HeaderTemplate

Specifies the template used for displaying the header of the Repeater.

Items

Represents a collection of RepeaterItem objects in the Repeater.

ItemTemplate

Specifies the template used for displaying each item in the DataList.

SeparatorTemplate

Specifies the template used for displaying separators between each item in the Repeater.

Methods

Description

DataBind

Binds the DataGrid to its data source.

OnItemCommand

Raises the ItemCommand event.

OnItemCreated

Raises the ItemCreated event.

OnItemDataBound

Raises the ItemDataBound event.

Events

Description

ItemCommand

Raised when any button is clicked.

ItemCreated

Raised when each item is created in the Repeater.

ItemDataBound

Raised when an item is bound in the Repeater.

NOTE

See Chapter 10 for more information.

Table . 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. The default value is True.

Enabled

Enables or disables both server- and client-side validation. The default value is True.

ErrorMessage

Specifies the error message 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.

Methods

Description

Validate

Performs validation and updates the IsValid property.

Events

Description

None

NOTE

See Chapter 3 for more information.

Table . Table Properties, Methods, and Events

Properties

Description

BackImageURL

Gets or sets the URL of a background image to be displayed behind the contents of the table.

CellPadding

Specifies the amount of padding in pixels contained in each cell.

CellSpacing

Specifies the amount of padding in pixels between cells.

GridLines

Determines how lines are displayed between cells. Possible values are Both, Horizontal, None, and Vertical.

HorizontalAlign

Sets the horizontal alignment of the contents of the table. Possible values are Center, Justify, Left, NotSet, and Right.

Rows

Returns the collection of rows in the Table.

Methods

Description

None

Events

Description

None

Table . TableCell Properties, Methods, and Events

Properties

Description

ColumnSpan

Specifies the number of columns that the cell occupies.

HorizontalAlign

Sets the horizontal alignment of the contents of the cell. Possible values are Center, Justify, Left, NotSet, and Right.

RowSpan

Specifies the number of rows that the cell occupies.

Text

Specifies the text that appears in the cell.

VerticalAlign

Sets the vertical alignment of the cell. Possible values are NotSet, Top, Middle, and Bottom.

Wrap

Word-wraps the contents of the cell when True.

Methods

Description

None

Events

Description

None

Table . TableRow Properties, Methods, and Events

Properties

Description

Cells

Returns the collection of TableCell objects contained in the TableRow.

HorizontalAlign

Sets the horizontal alignment of the contents of the TableRow. Possible values are Center, Justify, Left, NotSet, and Right.

VerticalAlign

Sets the vertical alignment of the cell. Possible values are NotSet, Top, Middle, and Bottom.

Methods

Description

None

Events

Description

None

Table . TableBox Properties, Methods, and Events

Properties

Description

AutoPostBack

Automatically posts the form containing the text box whenever a change is made to the contents of the text box when True.

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

ReadOnly

Indicates whether the text box is read-only 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, or 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

Raised when the contents of a text box have been changed.

NOTE

See Chapter 2 for more information.

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

EnableClientScript

Enables or disables client-side form validation. The default value is True.

Enabled

Enables or disables both server- and client-side validation. The default value is True.

HeaderText

Sets the text displayed at the top of the summary.

ShowMessageBox

Displays error messages in a pop-up message box when True.

ShowSummary

Enables or disables the summary of error messages.

Methods

Description

None

Events

Description

None

NOTE

See Chapter 3 for more information.

Table . Xml Properties, Methods, and Events

Properties

Description

Document

Specifies the System.Xml.XmlDocument object to display.

DocumentContent

Specifies a string representing the XML document to display.

DocumentSource

Specifies the URL of a document to display.

Transform

Specifies the System.Xml.Xsl.XslTransform object used to format the XML document.

TransformArgumentList

Specifies the XsltArgumentList used to format the XML document.

TransformSource

Specifies the URL of an XSL Transform used to format the XML document.

Methods

Description

None

Events

Description

None

NOTE

See Chapter 13, "Working with XML," for more information.

Table . Base Web Control Formatting Properties and Methods

Properties

Description

AccessKey

Indicates a keyboard shortcut for selecting a control. Use a single letter or number when holding down the Alt key.

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

BorderColor

Indicates the border color of the control.

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.

CssClass

Specifies the Cascading Style Sheet class used for formatting the output of the control.

Enabled

Prevents the control from functioning when False. On Internet Explorer, the control appears ghosted.

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

Style

Returns a collection of style attributes for the outer tag of a control.

Properties

Description

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.

Methods

Description

ApplyStyle

Copies any nonblank elements of a Style object to a control.

MergeStyle

Copies any nonblank elements of a Style object to a control, but does not overwrite existing elements.

Table . TableStyle Formatting Properties and Methods

Properties

Description

BackColor

Indicates the color that appears behind the text of a Table control. This value can be specified by name (red, blue) or by RGB value (#FF0000, #0000FF).

BackImageUrl

Specifies the URL of an image to display behind the Table control.

BorderColor

Indicates the border color of the control.

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.

CellPadding

Specifies the padding within cells in the Table control in pixels.

CellSpacing

Specifies the padding between cells in the Table control in pixels.

CssClass

Specifies the Cascading Style Sheet class used for formatting the output of the control.

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

GridLines

Determines how lines appear between cells. Possible values are Both, Horizontal, None, and Vertical.

Height

Sets the height of the control in pixels.

HorizontalAlign

Sets the horizontal alignment of the Table control within the page. Possible values are Center, Justify, Left, NotSet, and Right.

Width

Sets the width of the control in pixels or percentage.

Methods

Description

CopyFrom

Copies any nonblank elements of a Style object to the Table control.

MergeWith

Copies any nonblank elements of a Style object to the Table control, but does not overwrite existing elements.

Table . TableItemStyle Formatting Properties and Methods

Properties

Description

BackColor

Indicates the color that appears behind the text of a cell. This value can be specified by name (red, blue) or by RGB value (#FF0000, #0000FF).

BorderColor

Indicates the border color of the control.

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.

CssClass

Specifies the Cascading Style Sheet class used for formatting the output of the control.

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

HorizontalAlign

Sets the horizontal alignment of the contents of the cell. Possible values are Center, Justify, Left, NotSet, and Right.

VerticalAlign

Sets the vertical alignment of the contents of the cell. Possible values are Bottom, Middle, NotSet, and Top.

Width

Sets the width of the control in pixels or percentage.

Wrap

Word-wraps the contents of the cell when True.

Methods

Description

CopyFrom

Copies any nonblank elements of a Style object to the control.

MergeWith

Copies any nonblank elements of a Style object to the control, but does not overwrite existing elements.


What's on the CD-ROM

The companion CD-ROM contains all of the source code for the examples developed in the book, the Community Starter Kit, Microsoft's .NET Framework 1.1 Redistributable and Software Development Kit, Microsoft SQL Server 2000 Desktop Engine, and Web Matrix.


Windows Installation Instructions

  1. Insert the disc into your CD-ROM drive.

  2. From the Windows desktop, double-click the My Computer icon.

  3. Double-click the icon representing your CD-ROM drive.

  4. Double-click on start.exe. Follow the on-screen prompts to access the CD-ROM information.

NOTE

If you have the AutoPlay feature enabled, start.exe will be launched automatically whenever you insert the disc into your CD-ROM drive.


License Agreement

By opening this package, you are also agreeing to be bound by the following agreement:

You may not copy or redistribute the entire CD-ROM as a whole. Copying and redistribution of individual software programs on the CD-ROM is governed by terms set by individual copyright holders.

The installer and code from the author(s) are copyrighted by the publisher and the author(s). Individual programs and other items on the CD-ROM are copyrighted or are under an Open Source license by their various authors or other copyright holders.

This software is sold as-is without warranty of any kind, either expressed or implied, including but not limited to the implied warranties of merchantability and fitness for a particular purpose. Neither the publisher nor its dealers or distributors assumes any liability for any alleged or actual damages arising from the use of this program. (Some states do not allow for the exclusion of implied warranties, so the exclusion may not apply to you.)


The Community Starter Kit, Microsoft .NET Framework 1.1 Redistributable and Software Development Kit, Microsoft SQL Server 2000 Desktop Engine, and Web Matrix

This program was reproduced by Sams Publishing under a special arrangement with Microsoft Corporation. For this reason, Sams Publishing is responsible for the product warranty and support. If your disc is defective, please return it to Sams Publishing, which will arrange for its replacement. PLEASE DO NOT RETURN IT TO MICROSOFT CORPORATION. Any product support will be provided, if at all, by Sams Publishing. PLEASE DO NOT CONTACT MICROSOFT CORPORATION FOR PRODUCT SUPPORT. End users of this Microsoft program shall not be considered "registered owners" of a Microsoft product and therefore shall not be eligible for upgrades, promotions or other benefits available to "registered owners" of Microsoft products.

NOTE

This CD-ROM uses long and mixed-case filenames requiring the use of a protected-mode CD-ROM Driver.


Article

    License Agreement

    The Community Starter Kit, Microsoft .NET Framework 1.1 Redistributable and Software Development Kit, Microsoft SQL Server 2000 Desktop Engine, and Web Matrix


[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Z]

[ Team LiB ]


[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Z]

<deny
<Script[chevron] tags
declaring functions/subroutines 2nd 3rd
.aspx extensions 2nd
.NET
applications 2nd 3rd
Application BeginRequest event 2nd 3rd 4th 5th 6th
application-level error handling 2nd 3rd 4th 5th 6th
creating WhosOn 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
customizing Web.Config files 2nd 3rd
errors 2nd 3rd 4th
events 2nd 3rd 4th 5th 6th
Global.asax file 2nd 3rd 4th 5th
HTTP handlers 2nd 3rd 4th 5th 6th
HTTP modules 2nd 3rd 4th
locking Web.Config files 2nd 3rd
modifying Web.Config files 2nd 3rd 4th 5th 6th
monitoring 2nd 3rd 4th
page-level error handling 2nd 3rd 4th 5th 6th 7th 8th
retrieving process information 2nd 3rd 4th 5th
states 2nd 3rd
synchronizing states 2nd 3rd 4th
tracing 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th
viewing errors 2nd 3rd 4th
Web.Config files 2nd 3rd 4th 5th 6th 7th 8th 9th
breakpoints
creating 2nd 3rd 4th 5th 6th
chart applications
creating 2nd 3rd 4th 5th 6th
debugging
attaching 2nd
encryption 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st 22nd 23rd
language support 2nd
migrating 2nd 3rd
Option Explicit
paint applications
creating 2nd 3rd 4th 5th 6th
performance counters 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
SSL (Secure Socket Layer) 2nd 3rd
tracing 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th
Unleashed Sample Store 2nd 3rd
building navigation systems 2nd 3rd
caching product data 2nd 3rd 4th
installing 2nd
loading product templates 2nd 3rd 4th 5th
shopping carts 2nd 3rd 4th 5th 6th 7th 8th 9th
viewing product templates 2nd 3rd
.NET framework
ADO.NET 2nd 3rd 4th 5th
adding records to databases 2nd 3rd 4th 5th
applying output parameters 2nd 3rd 4th
CommandBehavior parameter 2nd 3rd 4th
connection pooling 2nd 3rd 4th 5th
deleting records in databases 2nd 3rd 4th 5th 6th
executing database tasks 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st 22nd 23rd 24th 25th 26th 27th 28th 29th 30th
executing stored procedures 2nd 3rd 4th 5th
GetSchemaTable() method 2nd 3rd
optimizing databases 2nd 3rd 4th 5th 6th 7th 8th
retrieving return values 2nd 3rd
transactions 2nd 3rd 4th
updating records in databases 2nd 3rd 4th 5th
ASP.NET
Class Library 2nd
compatible languages 2nd 3rd
namespaces 2nd
standard namespaces 2nd
/BIN directory
/bin directory
adding

[ Team LiB ]


[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Z]

Abandon method
absolute expiration policies
creating 2nd 3rd
AbsoluteExpiration.aspx
accepting
file uploads 2nd 3rd 4th 5th 6th
access
Context properties 2nd
databases 2nd 3rd 4th 5th
TemperatureService Web Service
Web sites
HTTP 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
WebService
callback functions 2nd 3rd
creating pages 2nd 3rd 4th 5th 6th
limitations 2nd
retrieving database data 2nd 3rd 4th 5th 6th
troubleshooting 2nd 3rd
updating 2nd 3rd
XML Web Services
proxy classes
accessing
secure XML Web Services 2nd 3rd
accessor functions
properties
applying 2nd 3rd
accounts
users
executing 2nd 3rd 4th
impersonating 2nd 3rd 4th 5th
activation
mobile forms 2nd
ActiveX Direct Objects (ADO). [See also ADO.NET]
AdCampaigns.xml
AdCreatedEventArgs control
Add() method
Adder.vb
AdderProperties.vb
AddEventLog.aspx
adding
/bin directory
application logic 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th
attachments 2nd
AutoIncrement columns 2nd 3rd 4th
BoundColumns
DataGrid 2nd 3rd 4th 5th
ButtonColumns
DataGrid 2nd 3rd 4th
child controls 2nd 3rd 4th 5th 6th 7th 8th
comments 2nd
controls 2nd 3rd 4th 5th 6th 7th
custom trace messages 2nd 3rd
DataTables
DataSets 2nd 3rd 4th
EditCommandColumns
DataGrid 2nd 3rd
events
controls 2nd 3rd 4th
file dependecies
caches 2nd 3rd 4th
form validation logic 2nd
configuring 2nd 3rd
disabling 2nd 3rd
enabling 2nd 3rd
forms
dynamically generating 2nd 3rd
Hyperlink control 2nd
HyperLinkColumns
DataGrid 2nd 3rd 4th 5th
items
ArrayList collection 2nd 3rd 4th
caches 2nd 3rd 4th
HashTable collection
session states 2nd
SortedList collection 2nd
synchronizing 2nd 3rd 4th
key dependecies
caches 2nd 3rd
lists
dynamically generating 2nd 3rd 4th
methods
controls 2nd 3rd 4th 5th
properties
accessor functions 2nd 3rd
components 2nd 3rd 4th 5th
controls 2nd 3rd
records
databases 2nd 3rd 4th 5th
strings 2nd
TemplateColumns
DataGrid 2nd 3rd 4th
templates
controls 2nd 3rd 4th 5th 6th 7th 8th 9th
creating multiple instances 2nd 3rd 4th
text
applying Label controls 2nd 3rd
applying TextBox controls 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th
toolbars 2nd 3rd 4th 5th 6th
trigger dependecies
caches 2nd 3rd
user controls 2nd 3rd 4th 5th 6th 7th
values
view state 2nd
AddJob procedure
Address.ascx
addresses
e-mail
validating 2nd 3rd
multiple forms
viewing
Web
validating 2nd 3rd 4th
zip codes
validating 2nd
AddressHashTable control
AddressProperties control
AddUser procedure
AddValues.aspx
ADO (ActiveX Direct Objects). [See also ADO.NET]
ADO.NET 2nd 3rd 4th 5th
binary documents
indexing 2nd 3rd 4th
uploading 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th
databases
adding records 2nd 3rd 4th 5th
applying output parameters 2nd 3rd 4th
CommandBehavior parameter 2nd 3rd 4th
connection pooling 2nd 3rd 4th 5th
deleting records 2nd 3rd 4th 5th 6th
executing stored procedures 2nd 3rd 4th 5th
executing tasks 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st 22nd 23rd 24th 25th 26th 27th 28th 29th 30th
GetSchemaTable() method 2nd 3rd
optimizing 2nd 3rd 4th 5th 6th 7th 8th
retrieving return values 2nd 3rd
transactions 2nd 3rd 4th
updating records 2nd 3rd 4th 5th
queries
executing 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
executing Boolean queries 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
SQL server full-text searches 2nd
configuring 2nd 3rd 4th 5th
AdRotator 2nd
AdRotator control 2nd 3rd 4th 5th 6th 7th
AdRotator.aspx
AdRotatorImpressions.aspx
Advanced Encryption Standard (AES)
advertisements
viewing 2nd 3rd 4th 5th 6th 7th 8th 9th
AES (Advanced Encryption Standard)
aggregate columns
creating 2nd 3rd 4th 5th 6th
AggregateColumn.aspx
aggregation
XML Web Services
algorithms
hash 2nd 3rd 4th 5th
Luhn
symmetric encryption 2nd 3rd 4th 5th 6th 7th
alignment
text 2nd
Alignment.aspx
alpha values
colors
configuring 2nd
Alpha.aspx
alternative sequences of characters
matching 2nd
apartment-threaded components 2nd
AppCache/Global.asax
Appcache/Global.asax file
appending
strings 2nd 3rd
AppendText method
AppendText.aspx
AppFixLinks/BadLinks.xml file
AppFixLinks/Global.asax file
Appinit/Global.asax file
Application BeginRequest event 2nd 3rd 4th 5th 6th
application logic
code-behind
applying 2nd 3rd 4th 5th
compiling 2nd 3rd 4th 5th
inheriting multiple pages 2nd 3rd 4th 5th
components
moving 2nd 3rd 4th 5th
application-level tracing 2nd 3rd
ApplicationCounter.aspx
ApplicationLogic.aspx
applications 2nd 3rd
ASP.NET Paint
Chart
controls 2nd
benefits of 2nd
browser compatibility 2nd
converting HTML 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
HTML 2nd 3rd 4th 5th 6th 7th
object models 2nd 3rd
separating code from content 2nd
view states 2nd 3rd 4th 5th
Web 2nd 3rd 4th 5th 6th
debugging 2nd
email
adding attachements 2nd
applying Microsoft SMTP Service 2nd
configuring Microsoft SMTP Service 2nd
HTML 2nd 3rd 4th 5th 6th 7th 8th
Microsoft SMTP Service
sending 2nd 3rd 4th 5th 6th 7th
errors 2nd 3rd 4th
application-level error handling 2nd 3rd 4th 5th 6th
page-level error handling 2nd 3rd 4th 5th 6th 7th 8th
viewing 2nd 3rd 4th
events 2nd 3rd 4th 5th 6th
Application BeginRequest 2nd 3rd 4th 5th 6th
logging 2nd 3rd 4th
GDI+
building 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th
interactive meeting schedulers
creating 2nd 3rd
logic
adding 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th
Microsoft Message Queuing 2nd 3rd 4th
applying 2nd 3rd 4th 5th 6th 7th
configuring
receiving 2nd 3rd 4th 5th 6th
retrieving 2nd 3rd
sedning 2nd 3rd 4th
sending 2nd 3rd 4th 5th 6th
viewing 2nd 3rd
monitoring 2nd 3rd 4th
processes
retrieving information 2nd 3rd 4th 5th
simulators for mobile devices 2nd 3rd 4th
smart forms
applying Button controls 2nd 3rd 4th 5th
building 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st
CheckBox controls 2nd 3rd 4th 5th 6th
CheckBoxList controls 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th
DropDownList controls 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th
ImageButton controls 2nd 3rd 4th 5th 6th
LinkButton controls 2nd 3rd
ListBox controls 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th
RadioButton controls 2nd 3rd 4th 5th 6th 7th 8th 9th
RadioButtonList controls 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th
Starter Kits 2nd
Commerce 2nd
Community 2nd 3rd 4th 5th 6th 7th
Portal 2nd
Reports Sample 2nd
Time Tracker 2nd
states 2nd 3rd
Global.asax file 2nd 3rd 4th 5th
synchronizing 2nd 3rd 4th
XML Web Services 2nd 3rd
tracing 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th
Web
dividing into multiple layers 2nd 3rd 4th 5th 6th 7th
Web.Config files 2nd 3rd
customizing 2nd 3rd
locking 2nd 3rd
modifying 2nd 3rd 4th 5th 6th
sections 2nd 3rd 4th 5th 6th
XML Web Services
communicating 2nd
ApplicationService.asmx
applying
application-level tracing 2nd 3rd
brushes 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
Button controls 2nd 3rd 4th 5th
CheckBox 2nd 3rd 4th 5th 6th
CheckBoxList 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th
DropDownList 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th
ImageButton 2nd 3rd 4th 5th 6th
LinkButton 2nd 3rd
ListBox 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th
RadioButton 2nd 3rd 4th 5th 6th 7th 8th 9th
RadioButtonList 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th
client-side validation 2nd
code-behind 2nd 3rd 4th 5th
collections
ArrayList 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th
HashTable 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
SortedList 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
colors
drawing curves 2nd 3rd 4th
drawing ellipses 2nd
drawing lines
drawing rectangles 2nd
filling shapes 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st
images 2nd 3rd 4th 5th 6th 7th
CONTAINS predicate 2nd 3rd
CONTAINSTABLE function 2nd 3rd 4th 5th 6th
controls 2nd
object models 2nd 3rd
view states 2nd 3rd 4th 5th
cookies 2nd 3rd 4th
DataSets
ASP.NET pages 2nd 3rd 4th
filtering caches 2nd 3rd 4th
searching caches 2nd 3rd 4th 5th
viewing caches 2nd 3rd 4th
expressions 2nd
backreferences/capture groups 2nd 3rd 4th
classes 2nd 3rd 4th 5th
customizing 2nd 3rd 4th 5th
metacharacters 2nd 3rd 4th 5th 6th 7th 8th 9th
quantifiers 2nd 3rd 4th
replacement patterns 2nd
text
for server certificates 2nd
formatting
Web controls 2nd 3rd 4th 5th 6th
FREETEXT predicates 2nd 3rd 4th
FREETEXTTABLE function 2nd 3rd 4th 5th 6th
graphics
objects 2nd 3rd
HTML pattern matching 2nd
building Six Degrees Web Service 2nd 3rd 4th 5th 6th
creating services 2nd 3rd 4th 5th 6th
creating WSDL pattern matching 2nd 3rd 4th 5th
input parameters 2nd 3rd 4th
specifying regular expresions 2nd
HttpContext
components 2nd
import directives 2nd
Label controls 2nd 3rd
LoadControl method 2nd 3rd
Microsoft Indexing Service
Boolean queries 2nd
configuring 2nd 3rd 4th 5th 6th
free text queries 2nd 3rd
querying properties 2nd 3rd
retrieving properties 2nd 3rd 4th
Microsoft Message Queuing 2nd 3rd 4th 5th 6th 7th
Microsoft SMTP Service 2nd
page data caching 2nd 3rd
adding items 2nd 3rd 4th
callback methods 2nd 3rd 4th 5th 6th
configuring item priorities 2nd
creating absolute expiration policies 2nd 3rd
creating sliding expiration policies 2nd 3rd
file dependecies 2nd 3rd 4th
key dependecies 2nd 3rd
trigger dependecies 2nd 3rd
page directives 2nd 3rd
parameters
with queries 2nd 3rd 4th 5th 6th 7th 8th
parenthesis
passing parameters
performance counters 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
Placeholder control 2nd 3rd
ReaqXML with schemas 2nd 3rd 4th 5th 6th
rich controls
AdRotator control 2nd 3rd 4th 5th 6th
Calendar control 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st
HTMLInputFile control 2nd 3rd 4th 5th 6th
session states 2nd
adding items 2nd
capturing events 2nd 3rd
deleting items
disbling 2nd
ending sessions 2nd
starting sessions
storing 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
strings 2nd
formatting 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th
methods/properties 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th
StringBuilder class 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th
styles
DataGrid control 2nd 3rd 4th 5th
Web controls 2nd 3rd 4th 5th 6th 7th 8th 9th
templates
DataList control 2nd
forms 2nd 3rd
TextBox controls 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th
view state
WebService behavior
callback functions 2nd 3rd
creating pages 2nd 3rd 4th 5th 6th
limitations 2nd
retrieving database data 2nd 3rd 4th 5th 6th
troubleshooting 2nd 3rd
updating 2nd 3rd
XML Web Services
configuring properties 2nd
Discovery tool 2nd 3rd
proxy classes 2nd 3rd
XMLDataDocuments
DataSets 2nd 3rd 4th 5th 6th
AppTrace/Web.Config
AppVanity/Custom/Default.as
AppVanity/Global.asax file
arcs, drawing
ArrayList
serializing 2nd 3rd
ArrayList collection 2nd
items
adding 2nd 3rd 4th
deleting 2nd
iterating 2nd
searching 2nd 3rd 4th
sorting 2nd 3rd
arrays
XML Web Services 2nd 3rd
ASP.NET
applications 2nd 3rd
Application BeginRequest event 2nd 3rd 4th 5th 6th
application-level error handling 2nd 3rd 4th 5th 6th
attaching debuggier 2nd
creating breakpoints 2nd 3rd 4th 5th 6th
creating WhosOn 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
customizing Web.Config files 2nd 3rd
debugging
errors 2nd 3rd 4th
events 2nd 3rd 4th 5th 6th
Global.asax file 2nd 3rd 4th 5th
HTTP handlers 2nd 3rd 4th 5th 6th
HTTP modules 2nd 3rd 4th
locking Web.Config files 2nd 3rd
modifying Web.Config files 2nd 3rd 4th 5th 6th
monitoring 2nd 3rd 4th
page-level error handling 2nd 3rd 4th 5th 6th 7th 8th
performance counters 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
retrieving process information 2nd 3rd 4th 5th
states 2nd 3rd
synchronizing states 2nd 3rd 4th
tracing 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th
viewing errors 2nd 3rd 4th
Web.Config files 2nd 3rd 4th 5th 6th 7th 8th 9th
chart applications
creating 2nd 3rd 4th 5th 6th
encryption 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st 22nd 23rd
executing 2nd
language support 2nd
migrating 2nd 3rd 4th 5th
Option Explicit
paint applications
creating 2nd 3rd 4th 5th 6th
session states 2nd
adding items 2nd
capturing events 2nd 3rd
cookieless 2nd 3rd 4th 5th 6th
deleting items
disabling 2nd
ending sessions 2nd
starting sessions
storing 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
SSL (Secure Socket Layer) 2nd 3rd
tracing 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th
transactions
creating 2nd
Unleashed Sample Store 2nd 3rd
building navigation systems 2nd 3rd
caching product data 2nd 3rd 4th
installing 2nd
loading product templates 2nd 3rd 4th 5th
shopping carts 2nd 3rd 4th 5th 6th 7th 8th 9th
viewing product templates 2nd 3rd
Xml control 2nd 3rd 4th 5th
ASP.NET Paint application
ASP.NET Starter Kits 2nd
Commerce 2nd
Community 2nd 3rd 4th 5th 6th 7th
Portal 2nd
Reports Sample 2nd
Time Tracker 2nd
ASPTransaction.aspx
assemblies
System.Messaging
assigning
strings 2nd
text 2nd 3rd
values
Listboxes 2nd
assignment
values
columns 2nd 3rd
association
session states 2nd
adding items 2nd
capturing events 2nd 3rd
deleting items
disabling 2nd
ending sessions 2nd
starting sessions
storing 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
subroutines 2nd 3rd 4th 5th 6th 7th
TreeView controls 2nd
check boxes 2nd 3rd
images 2nd 3rd
asymmetric encryption 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
AsymmetricRead.aspx
AsymmetricWrite.aspx
attaching
debugger 2nd
attachments
adding 2nd
attributes
CacheByHeader
ClientTarget
ContentType
forms
Location
Method
trace 2nd 3rd 4th 5th 6th 7th 8th
VaryByCustom
VaryByParam
verbs
WebMethod
configuring 2nd
WebService
configuring 2nd
authenitcation
forms-based 2nd
applying Web farms 2nd
configuring 2nd
creating Authentication Tickets 2nd 3rd 4th 5th 6th
creating sign out pages 2nd
database tables 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
enabling 2nd 3rd 4th 5th 6th 7th
implementing roles-based authenitcation 2nd 3rd 4th 5th
retrieving user information 2nd 3rd 4th 5th
storing passwords 2nd 3rd 4th 5th 6th 7th
XML storage 2nd 3rd 4th 5th 6th 7th 8th 9th
Authenticate() function
authentication
MAC (message authentication code)
Passport 2nd
enabling 2nd 3rd 4th
retrieving user information 2nd 3rd 4th 5th
sign in/sign out 2nd 3rd 4th 5th 6th
session keys 2nd
SSL (Secure Socket Layer) 2nd 3rd
users
jobs sites 2nd 3rd 4th 5th 6th 7th
Windows
configuring
configuring IIS 2nd 3rd
creating custom roles 2nd 3rd
enabling security 2nd 3rd 4th
retrieving user information 2nd 3rd
AuthenticationTicket.aspx
AuthenticationTicket.Config
AuthenticationTicketefault.aspx
authorization
forms-based
configuring 2nd 3rd
Windows
configuring 2nd 3rd 4th 5th
AutoIncrement columns
adding 2nd 3rd 4th
AutoIncrement.aspx
AutoPostBack
AutoPostBack property 2nd 3rd 4th 5th 6th 7th 8th

[ Team LiB ]


[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Z]

backreferences
regular expressions 2nd 3rd 4th
Badmail directory
BadPageCounter.aspx
banners
viewing 2nd 3rd 4th 5th 6th
Base Web control
formatting properties 2nd 3rd
base Web control class properties 2nd 3rd 4th 5th
Base Web Control Formatting properties and methods
BaseWebControl.aspx
BaseWebControlDynamic.aspx
behavior
WebService
callback functions 2nd 3rd
creating pages 2nd 3rd 4th 5th 6th
limitations 2nd
retrieving database data 2nd 3rd 4th 5th 6th
troubleshooting 2nd 3rd
updating 2nd 3rd
BehaviorCallback.aspx
BehaviorDataTable.aspx
BehaviorErrors.aspx
BehaviorRefresh.aspx
benefits of controls 2nd
browser compatibility 2nd
code
separating from content 2nd
object models 2nd 3rd
view states 2nd 3rd 4th 5th
BetterNewsService.asmx
Bezier curves, drawing
binary documents
indexing 2nd 3rd 4th
uploading 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th
binary files
creating
reading from 2nd 3rd
writing to 2nd 3rd 4th
XML Web Services 2nd 3rd 4th
binary serialization 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th
BinaryFormatter class 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th
BinaryReader class 2nd 3rd
BinaryReader.aspx
BinarySearchArrayList.aspx
BinaryWriter.aspx
binding
controls
creating DataBound controls 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st 22nd 23rd 24th
DataSets 2nd
data
without expressions 2nd 3rd
data. [See data binding]
lists
mobile controls 2nd 3rd 4th
TreeView controls
to database tables 2nd 3rd 4th
to XML files 2nd 3rd
bitmaps
creating 2nd 3rd 4th 5th 6th
BizObject.vb
blocks
code declaration 2nd 3rd
code render 2nd
server-side comments 2nd
TRY..CATCH 2nd 3rd 4th 5th
BlueTextBox.vb
BlueTextBoxChanged.vb
BlueTextBoxPostBack.vb
body of messages, viewing 2nd 3rd
Boolean queries
executing 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
Microsoft Indexing Service 2nd
BoundColumn
DataGrid
adding 2nd 3rd 4th 5th
breakpoints
creating 2nd 3rd 4th 5th 6th
browsers
compatibility 2nd
cookies
applying 2nd 3rd 4th
configuring properties 2nd 3rd
creating 2nd 3rd 4th 5th 6th 7th
limitations of 2nd 3rd
browsers. [See also interfaces]
brushes
applying 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
bubbling
events 2nd 3rd
building
custom control builders
creating 2nd 3rd
GDI+ applications 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th
images 2nd 3rd 4th 5th 6th
applying colors 2nd 3rd 4th 5th 6th 7th
applying pens 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
creating bitmaps 2nd 3rd 4th 5th 6th
curves 2nd 3rd 4th
drawing
ellipses 2nd
filling shapes 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
optimizing 2nd
rectangles 2nd 3rd
strings of text 2nd 3rd
master/detail forms
DataList controls 2nd 3rd 4th 5th
navigation systems
online stores 2nd 3rd
secure XML Web Services 2nd 3rd 4th
self-building pages
shopping carts 2nd 3rd 4th 5th 6th 7th 8th 9th
Six Degrees Web Service 2nd 3rd 4th 5th 6th
smart forms 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st
applying Button controls 2nd 3rd 4th 5th
CheckBox controls 2nd 3rd 4th 5th 6th
CheckBoxList controls 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th
DropDownList controls 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th
ImageButton controls 2nd 3rd 4th 5th 6th
LinkButton controls 2nd 3rd
ListBox controls 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th
RadioButton controls 2nd 3rd 4th 5th 6th 7th 8th 9th
RadioButtonList controls 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th
user controls
XML Web Services 2nd 3rd 4th 5th 6th 7th
business components
code-behind
applying 2nd 3rd 4th 5th
compiling 2nd 3rd 4th 5th
inheriting mulitple pages 2nd 3rd 4th 5th
creating 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th
web applications
dividing into multiple layers 2nd 3rd 4th 5th 6th 7th
Button
Button control
Repeater control 2nd 3rd 4th
Button controls
applying 2nd 3rd 4th 5th
CheckBox 2nd 3rd 4th 5th 6th
CheckBoxList 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th
DropDownList 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th
ImageButton 2nd 3rd 4th 5th 6th
LinkButton 2nd 3rd
ListBox 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th
RadioButton 2nd 3rd 4th 5th 6th 7th 8th 9th
RadioButtonList 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th
Button.aspx
ButtonColumn
DataGrid
adding 2nd 3rd 4th
buttons
toolbars
formatting 2nd

[ Team LiB ]


[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Z]

CacheByHeader attribute
CacheByHeader.aspx
CacheCallback.aspx
CachedData class
CacheDuration property
CacheEmployees.aspx
CacheProducts.aspx
caches
HttpCachePolicy class
applying 2nd 3rd
locations
confuguring 2nd
page data
aadding items 2nd 3rd 4th
applying 2nd 3rd
callback methods 2nd 3rd 4th 5th 6th
configuring item priorities 2nd
creating absolute expiration policies 2nd 3rd
creating slideing expiration policies 2nd 3rd
file dependencies 2nd 3rd 4th
key dependencies 2nd 3rd
trigger dependencies 2nd 3rd
page fragment
applying 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
product data
online stores 2nd 3rd 4th
products
viewing
session keys 2nd
CacheStore.aspx
caching
DataSets 2nd 3rd
filtering 2nd 3rd 4th
searching 2nd 3rd 4th 5th
viewing 2nd 3rd 4th
page output 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th
configuring 2nd
calculated columns
creating 2nd 3rd 4th 5th 6th
CalculatedColumn.aspx
Calendar 2nd 3rd
Calendar control 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st 22nd
Calendar.aspx
CalendarBaseFormatting.aspx
CalendarHoroscope.aspx
calendars
mobile controls
viewing 2nd 3rd
CalendarSchedule.aspx
CalendarSelected.aspx
CalendarStyle.aspx
Call.aspx
callback functions
WebService behavior 2nd 3rd
callback methods
creating 2nd 3rd 4th 5th 6th
calls
placing 2nd
campaigns. [See also banners]
capabilities
mobile
detecting 2nd 3rd 4th
capture groups
regular expressions 2nd 3rd 4th
capturing
DayRender 2nd 3rd 4th 5th 6th
events
DataList control 2nd 3rd 4th 5th
session events 2nd 3rd
CartItem class
Cascading Style Sheets
Web controls
applying 2nd 3rd 4th 5th 6th 7th 8th 9th
CatchException.aspx
catching
exceptions 2nd 3rd 4th 5th
unhandled exceptions 2nd
categories
trace
creating 2nd 3rd
Categories.aspx
Category.aspx
CausesValidation property
CausesValidation.a
cell phones. [See also mobile devices]
certificate signing request. [See CSR]
certificates
certifcate authority
clients
servers
applying for 2nd
installing 2nd
Web Server Certificate Wizard
characters
entry length
validating 2nd 3rd
matching 2nd 3rd 4th 5th 6th 7th 8th 9th
quantifiers
matching 2nd 3rd 4th
strings
deleting 2nd 3rd
searching 2nd 3rd
words
Chart application
Chart.ascx
ChartImage.aspx
charts
applications
creating 2nd 3rd 4th 5th 6th
pie charts
drawing
check boxes
TreeView controls
associating 2nd 3rd
CheckBox 2nd
CheckBox control 2nd
Checkbox control 2nd
CheckBox control 2nd 3rd 4th
CheckBox.aspx
CheckBoxList 2nd
CheckBoxList control 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th
Repeater control 2nd 3rd
CheckBoxList properties 2nd 3rd
CheckBoxList.aspx 2nd
checkboxlist2.aspx
CheckBoxListAutoPostBack.aspx
CheckBoxListColumns.aspx
CheckBoxListDataValue.aspx
CheckBoxListMultiSelect.aspx
CheckBoxListValue.aspx
CheckBoxListValueSelected.aspx
checking
data types 2nd 3rd 4th
child controls
adding 2nd 3rd 4th 5th 6th 7th 8th
cHTML (compact HTML)
classes
.NET encryption 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st 22nd 23rd
ADO.NET 2nd 3rd 4th 5th
adding records to databases 2nd 3rd 4th 5th
applying output parameters 2nd 3rd 4th
CommandBehavior parameter 2nd 3rd 4th
connection pooling 2nd 3rd 4th 5th
deleting records in databases 2nd 3rd 4th 5th 6th
executing database tasks 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st 22nd 23rd 24th 25th 26th 27th 28th 29th 30th
executing stored procedures 2nd 3rd 4th 5th
GetSchemaTable() method 2nd 3rd
optimizing databases 2nd 3rd 4th 5th 6th 7th 8th
retrieving return values 2nd 3rd
transactions 2nd 3rd 4th
updating records in databases 2nd 3rd 4th 5th
base Web control properties 2nd 3rd 4th 5th
BinaryFormatter 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th
BinaryReader 2nd 3rd
CartItem
Class Library 2nd
controls
inheriting 2nd
DataSets 2nd 3rd 4th
elements 2nd
directories 2nd
retrieving paths 2nd 3rd
viewing 2nd 3rd
Dns
files 2nd
binary files
creating text files
reading from binary files 2nd 3rd
reading from text files 2nd 3rd
writing to binary files 2nd 3rd 4th
writing to text files 2nd 3rd 4th
FileStream 2nd 3rd 4th 5th 6th 7th
FormsAuthenticationTicket
GDI+
drawing objects
Graphics
applying 2nd 3rd
HttpCachePolicy
applying 2nd 3rd
HttpWebRequest 2nd 3rd 4th 5th
HttpWebResponse
MailAttachment
MailAttachments 2nd
MailMessage 2nd 3rd 4th
MessageQueue 2nd 3rd
namespaces 2nd
standard ASP.NET 2nd
Path 2nd 3rd
ProcessInfo
proxy
accessing XML Web Services
applying XML Web Services 2nd 3rd
generating XML Web Services 2nd 3rd
regular expressions 2nd 3rd 4th 5th
serializing 2nd 3rd 4th 5th 6th 7th
ServiceTicket
ShoppingCart 2nd 3rd 4th
SmtpMail 2nd 3rd 4th 5th 6th
SqlCommand 2nd 3rd 4th
state bag
adding values 2nd
StringBuilder 2nd
adding strings 2nd
appending strings 2nd 3rd
converting strings 2nd
deleting strings
iterating strings 2nd 3rd
replacing strings 2nd
Style 2nd 3rd
UserInfo
WebClient 2nd 3rd 4th
WebRequest
WebResponse
WindowsIdentity
XML 2nd 3rd
XML Web Services 2nd 3rd 4th 5th
XmlSerializer 2nd 3rd 4th 5th 6th 7th
XSLTransform
classic ASP files
client-side validation
applying 2nd
configuring 2nd 3rd
disabling 2nd 3rd
enabling 2nd 3rd
clients
certificates
ClientTarget attribute
code
application logic
adding 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th
content
separating 2nd
declaration blocks 2nd 3rd
IsPostBack property
executing 2nd
render blocks 2nd
user controls
adding progammatically 2nd 3rd 4th 5th 6th 7th
validation
writing 2nd
code-behind
applying 2nd
code-behind product templates 2nd
CodeBehindMultiple.vb
CodeRender.aspx
collections
ArrayList 2nd
adding items 2nd 3rd 4th
deleting items 2nd
iterating items 2nd
searching items 2nd 3rd 4th
sorting items 2nd 3rd
DataKeys 2nd
Datakeys
DataList control 2nd
HashTable 2nd 3rd
adding items
deleting items 2nd
iterating items 2nd
searching items 2nd 3rd
ListItemCollection
members
viewing 2nd 3rd 4th 5th 6th 7th
SortedList 2nd
adding items 2nd
deleting items 2nd
iterating items 2nd
searching items 2nd 3rd
colors
converting 2nd
iamge
drawing curves 2nd 3rd 4th
drawing ellipses 2nd
drawing lines
drawing rectangles 2nd
iamges
applying 2nd 3rd 4th 5th 6th 7th
applying pens 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
fillign shapes 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
columns
aggregate
creating 2nd 3rd 4th 5th 6th
AutoIncrement
adding 2nd 3rd 4th
calculated
creating 2nd 3rd 4th 5th 6th
DataGrid control
adding BoundColumns 2nd 3rd 4th 5th
ButtonColumns 2nd 3rd 4th
creating 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st 22nd
EditCommandColumns 2nd 3rd
inserting HyperLinkColumns 2nd 3rd 4th 5th
sorting 2nd 3rd 4th
TemplateColumns 2nd 3rd 4th
DataList control
creating 2nd 3rd
properties
configuring in DataTables 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th
unique
specifying 2nd
values
computing in DataTables 2nd 3rd
Command event
CommandBehavior parameter 2nd 3rd 4th
commands
database
creating
transactions 2nd 3rd 4th
Delete 2nd 3rd 4th 5th 6th
Start menu
Computer Management
Internet Services Manager
Update 2nd 3rd 4th 5th
View menu (Internet Explorer)
Source
View menu (Netscape Navigator)
Page Source
comments
server-side 2nd
Commerce Starter Kit 2nd
communication
applications
XML Web Services 2nd
Community Starter Kit 2nd 3rd 4th 5th 6th 7th
CommunityASPNET.xml
compact HTML (cHTML)
CompareValidator 2nd 3rd
CompareValidator control 2nd 3rd 4th 5th 6th 7th 8th 9th
data type checks 2nd 3rd 4th
CompareValidator.aspx
CompareValidatorDataTypeCheck.aspx
CompareValidatorRange.aspx
CompareValidatorValue.aspx
comparisons
strings 2nd
values 2nd 3rd 4th 5th 6th 7th 8th 9th
data type checks 2nd 3rd 4th
compatibility
ASP.NET mobile controls 2nd 3rd 4th
browsers 2nd
compatible languages, .NET framework 2nd 3rd
Compilation.aspx
CompiledTemperatureService.asmx
CompiledTemperatureService.vb
compilers
JIT (Just in Time)
compiling
code-behind 2nd 3rd 4th 5th
components
pages 2nd 3rd 4th
compliation
errors
components
application logic
moving 2nd 3rd 4th 5th
apratment-threaded 2nd
business
creating 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th
code-behind
applying 2nd 3rd 4th 5th
compiling 2nd 3rd 4th 5th
inheriting multiple pages 2nd 3rd 4th 5th
compiling
HttpContext
applying 2nd
properties
adding 2nd 3rd 4th 5th
Web applications
diving into multiple layers 2nd 3rd 4th 5th 6th 7th
composite controls
creating 2nd 3rd 4th
handling events 2nd 3rd
computation
values
columns 2nd 3rd
computations
hash values
Compute.aspx
ComputeHash method
ComputeHash.aspx
Computer Management command (Start menu)
ConfigCustom/Web.Config
ConfigErrors/Web.Config 2nd
ConfigLocationDir/Web.Config
ConfigLocationFile/Web.Co
ConfigLock/Web.Config
configuration
alpha values
colors 2nd
binary files
reading to 2nd 3rd
writing to 2nd 3rd 4th
breakpoints 2nd 3rd 4th 5th 6th
business components 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th
caches 2nd
applying HttpCachePolicy class 2nd 3rd
page data caching 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st 22nd 23rd 24th 25th 26th 27th 28th 29th 30th 31st
page fragment caching 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
client-side validation 2nd 3rd
composite controls 2nd 3rd 4th
handling events 2nd 3rd
controls 2nd 3rd 4th
cookies 2nd 3rd 4th 5th 6th 7th
limitations 2nd 3rd
properties 2nd 3rd
custom control builders 2nd 3rd
custom errors 2nd 3rd
DataBound controls 2nd 3rd 4th 5th 6th
applying templates 2nd 3rd 4th
binding to DataSets 2nd 3rd 4th 5th 6th 7th 8th 9th
states 2nd 3rd 4th 5th
DataTables
column properties 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th
computing column values 2nd 3rd
defining relationships 2nd 3rd 4th
properties 2nd 3rd 4th
retrieving schemas 2nd 3rd 4th 5th
updating records 2nd 3rd 4th 5th
errors. [See also errors]
Featured Product Web Services 2nd 3rd 4th 5th 6th 7th 8th 9th
forms-based authenitcation 2nd 3rd
applying Web farms 2nd
creating Authentication Tickets 2nd 3rd 4th 5th 6th
creating sign out pages 2nd
database tables 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
enabling 2nd 3rd 4th 5th 6th 7th
implementing roles-based authentication 2nd 3rd 4th 5th
retriveing user information 2nd 3rd 4th 5th
storing passwords 2nd 3rd 4th 5th 6th 7th
XML storage 2nd 3rd 4th 5th 6th 7th 8th 9th
forms-based authorization 2nd 3rd
full-text search services 2nd 3rd 4th 5th
IIS (Internet Information Server) 2nd 3rd
Microsoft Indexing Service 2nd 3rd 4th 5th 6th
Boolean queries 2nd
free text queries 2nd 3rd
querying properties 2nd 3rd
retrieving properties 2nd 3rd 4th
Microsoft Message Queuing
Microsoft SMTP Service 2nd
online stores 2nd 3rd
pages
WebService behavior 2nd 3rd 4th 5th 6th
properties
XML Web Services 2nd
regular expressions 2nd 3rd 4th 5th
security
policies 2nd 3rd
SSL (Secure Socket Layer) 2nd 3rd 4th 5th 6th 7th 8th
.NET encryption classes 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st 22nd 23rd
ASP.NET pages 2nd 3rd
TabStrip control 2nd 3rd
text
files
reading from files 2nd 3rd
writing to files 2nd 3rd 4th
Toolbar controls 2nd 3rd 4th 5th 6th
trace categories 2nd 3rd
TreeView controls 2nd 3rd
virtual directories
watches 2nd
Web.Config files 2nd 3rd
customizing 2nd 3rd
locking 2nd 3rd
modifying 2nd 3rd 4th 5th 6th
sections 2nd 3rd 4th 5th 6th
WebMethod attributes 2nd
WebService attributes 2nd
WhosOn 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
Windows authentication
creating custom roles 2nd 3rd
retrieving user information 2nd 3rd
Windows authorization 2nd 3rd 4th 5th
connections
databases
opening 2nd 3rd 4th 5th
pooling
stored procedures 2nd 3rd 4th 5th
constructors
FormsAuthenticationTicket
containers
placeholder control 2nd 3rd
CONTAINS function
Microsoft Indexing Service 2nd
CONTAINS predicate, applying 2nd 3rd
Contains.aspx
ContainsArrayList.aspx
CONTAINSTABLE function, applying 2nd 3rd 4th 5th 6th
ContainsTable.aspx
content
caches
varying by custom strings 2nd 3rd 4th
varying by headers 2nd 3rd 4th 5th
varying by parameters 2nd 3rd 4th 5th
code
separating 2nd
code-behind
applying 2nd 3rd 4th 5th
compiling 2nd 3rd 4th 5th
inheriting multiple pages 2nd 3rd 4th 5th
hiding 2nd
Enabled property 2nd 3rd 4th 5th
Panel control 2nd 3rd 4th 5th
Visible property 2nd 3rd 4th 5th
parsing 2nd 3rd
user controls
entering 2nd 3rd 4th 5th
viewing 2nd
Enabled property 2nd 3rd 4th 5th
Panel control 2nd 3rd 4th 5th
Visible property 2nd 3rd 4th 5th
content. [See also text]
contents
reversing
ContentType attribute
Context properties
accessing 2nd
ControlCaps.vb
ControlContext.vb
ControlEvents.vb
controlling
serialization 2nd 3rd 4th
controls 2nd
AdCreatedEventArgs
adding 2nd 3rd 4th 5th 6th 7th
AddressHashTable
AddressProperties
application logic
adding 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th
ASP.NET
ASP.NET mobile controls 2nd 3rd 4th
activating mobile forms 2nd
advertisements 2nd 3rd
binding lists 2nd 3rd 4th
calendars 2nd 3rd
creating mobile forms 2nd 3rd
formatting text boxes 2nd 3rd 4th
images 2nd 3rd
interactive lists 2nd 3rd 4th
object lists 2nd 3rd 4th 5th 6th
paging text 2nd
placing phone calls 2nd
validating user input 2nd 3rd 4th
viewing lists 2nd
viewing text 2nd 3rd
benefits of 2nd
browsers 2nd
Button 2nd 3rd 4th
CheckBoxList 2nd 3rd
child controls
adding 2nd 3rd 4th 5th 6th 7th 8th
code
separating from content 2nd
CompareValidator 2nd 3rd 4th 5th 6th 7th 8th 9th
data type checks 2nd 3rd 4th
composite
creating 2nd 3rd 4th
handling events 2nd 3rd
content
entering 2nd 3rd 4th 5th
Context properties
accessing 2nd
creating 2nd 3rd 4th
custom control builders
creating 2nd 3rd
CustomValidator 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
data binding 2nd 3rd 4th 5th 6th 7th 8th
binding Repeater Controls 2nd 3rd 4th
Button controls (Repeater Controls) 2nd 3rd 4th
CheckBoxList controls (Repeater Controls) 2nd 3rd
DropDownList controls (Repeater Controls) 2nd 3rd
formatting Repeater Controls 2nd 3rd 4th 5th
Hyperlink controls (Repeater Controls) 2nd 3rd 4th 5th
Image controls (Repeater Controls) 2nd 3rd
LinkButton controls (Repeater Controls) 2nd 3rd 4th
ListBox controls (Repeater Controls) 2nd 3rd
RadioButtonList controls (Repeater Controls) 2nd 3rd
view states (Repeater Controls) 2nd
DataBound
applying templates 2nd 3rd 4th
binding to DataSets 2nd 3rd 4th 5th 6th 7th 8th 9th
dataBound
creating 2nd 3rd 4th 5th 6th
DataBound
states 2nd 3rd 4th 5th
DataGrid 2nd 3rd 4th
addign BoundColumns 2nd 3rd 4th 5th
applying styles 2nd 3rd 4th 5th
ButtonColumns 2nd 3rd 4th
creating columns 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st 22nd
DataKeys collection 2nd
EditCommandColumns 2nd 3rd
editing items 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
event bubbling 2nd 3rd
inserting HyperLinkColumns 2nd 3rd 4th 5th
paging records 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
selecting rows 2nd 3rd
sorting columns 2nd 3rd 4th
TemplateColumns 2nd 3rd 4th
templates 2nd
DataList
applying templates 2nd
building master/detail forms 2nd 3rd 4th 5th
capturing events 2nd 3rd 4th 5th
creating columns 2nd 3rd
DataKeys collection 2nd 3rd 4th
editing items 2nd 3rd 4th 5th 6th 7th 8th
event bubbling 2nd 3rd
formatting styles 2nd 3rd 4th 5th
selecting items 2nd 3rd 4th
templates 2nd
viewing records 2nd 3rd 4th
DataSets
binding 2nd
debugging 2nd 3rd
DisplayAddressProperties
DisplayControlEvents
DropDownList 2nd 3rd
events
adding 2nd 3rd 4th
associating subroutines 2nd 3rd 4th 5th 6th 7th
vieiwng 2nd
viewing user controls 2nd 3rd
formatting 2nd 3rd 4th 5th 6th
applying styles 2nd 3rd 4th 5th 6th 7th 8th 9th
HTML 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st 22nd
converting 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
Hyperlink 2nd 3rd 4th 5th
adding 2nd
Image 2nd 3rd
inheriting 2nd
inner content
parsing 2nd 3rd
Label
applying 2nd 3rd
LinkButton 2nd 3rd 4th
ListBox 2nd 3rd
loading 2nd 3rd 4th 5th 6th 7th
methods
adding 2nd 3rd 4th 5th
viewing 2nd 3rd 4th
object models 2nd 3rd
Panel 2nd 3rd 4th 5th
PanelMultiPage
Placeholder 2nd 3rd
postbacks 2nd 3rd 4th 5th 6th 7th 8th
handling events 2nd 3rd 4th
properties
accessor functions 2nd 3rd
adding 2nd 3rd
viewing 2nd 3rd 4th
RadioButtonList 2nd 3rd
RangeValidator 2nd 3rd 4th 5th 6th 7th
RegualrExpressionValidatorEmail
RegualrExpressionValidatorPassword
RegualrExpressionValidatorZip
RegularExpressionValidator 2nd 3rd 4th 5th
RegularExpressionValidatorLength
RegularExpressionValidatorPhone
RegularExpressionWeb
RequiredFieldValidator 2nd 3rd 4th 5th 6th
RequiredFieldValidatorInitialValue 2nd 3rd
rich controls
AdRotator control 2nd 3rd 4th 5th 6th
Calendar control 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st
HTMLInputFile control 2nd 3rd 4th 5th 6th
tab
navigating
templates
adding 2nd 3rd 4th 5th 6th 7th 8th 9th
creating multiple instances 2nd 3rd 4th
TextBox
applying 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th
UserControlEvents
validation
disabling 2nd
ValidationSummary 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
view state
adding values 2nd
applying
disabling 2nd 3rd 4th 5th
view states 2nd 3rd 4th 5th
Web 2nd 3rd 4th 5th 6th
building smart forms 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st
Button 2nd 3rd 4th 5th
CheckBox 2nd 3rd 4th 5th 6th
CheckBoxList 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th
DropDownList 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th
ImageButton 2nd 3rd 4th 5th 6th
LinkButton 2nd 3rd
ListBox 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th
RadioButton 2nd 3rd 4th 5th 6th 7th 8th 9th
RadioButtonList 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th
viewing user controls 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
Web Services
applying 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
WebControls
TabStrip control 2nd 3rd 4th 5th 6th 7th 8th 9th
Toolbar control 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
TreeView control 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st 22nd 23rd
vertical TabStrip controls 2nd 3rd
Xml (should remain s/c) 2nd 3rd 4th 5th
Controls property
Controls.aspx
ControlsAdd property
ControlsAdd.aspx
ControlsRemove property
ControlTrace.vb
conventions
naming 2nd
conversion
strings 2nd
conversions
colors 2nd
HTML 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
selecting object models 2nd 3rd
objects 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st 22nd 23rd 24th 25th
ConvertTemperature.aspx
Cookie header
Cookieless/Web.Config
cookies
applying 2nd 3rd 4th
creating 2nd 3rd 4th 5th 6th 7th
files
Forms Authentication
applying 2nd
limitations of 2nd 3rd
properties
configuring 2nd 3rd
session states
applying cookieless sessions 2nd 3rd 4th 5th 6th
CopyField.aspx
copying
text
CreateCheckPassword.sql
CreateCookie.aspx
CreateDataTable.aspx
CreatePrivateQueue.aspx
CreatePublicQueue.aspx
CreateQueue() method 2nd 3rd
CreateSessionKeys.sql
CreateText method 2nd 3rd 4th
CreateUploadsTable.sql
CreateUserList.sql
CreateWebServiceUsers.sql
credit card numbers
validating 2nd 3rd 4th
cross-device-compatible mobile pages, creating 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
Cryptographic Service Providers (CSPs)
CSPs (Cryptographic Service Providers)
CSR (certificate signing request)
generating 2nd 3rd
CssClass property
CssClassDynamic.aspx
CultureInfo.aspx
currency
fromatting 2nd
CurrentMovies.aspx
curves
drawing 2nd 3rd 4th
custom roles
Windows
configuring 2nd 3rd
custom SOAP headers
retrieving 2nd
CustomErrors/Web.Config
CustomErrorsOff/Web.Config
CustomErrorsOff/Web.Config file
customization
cache content
varying by strings 2nd 3rd 4th
Calendar control 2nd 3rd 4th
control builders
creating 2nd
controls
adding templates 2nd 3rd 4th 5th 6th 7th 8th 9th
applying Web Services 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
binding to DataSets 2nd 3rd 4th 5th 6th 7th 8th 9th
creating multiple instances of templates 2nd 3rd 4th
controls.
user controls [See also controls]
errors 2nd 3rd
events
adding to controls 2nd 3rd 4th
paging 2nd 3rd 4th 5th
pens 2nd 3rd 4th 5th 6th 7th 8th
regular expressions 2nd 3rd 4th 5th
trace messages
adding 2nd 3rd
Web.Config files 2nd 3rd
CustomPen.aspx
CustomRoles.asax
CustomRoles.Config
CustomValidator
CustomValidator control 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
CustomValidator.aspx
CustomValidatorLuhn.aspx

[ Team LiB ]


[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Z]

Dartboard.aspx
DashStyle.aspx
data
page caching 2nd 3rd
adding items 2nd 3rd 4th
configuring item priorities 2nd
creating absolute expiration policies 2nd 3rd
creating sliding expiration policies 2nd 3rd
file dependencies 2nd 3rd 4th
key dependencies 2nd 3rd
trigger dependencies 2nd 3rd
data binding 2nd 3rd 4th 5th 6th 7th 8th
master/detail forms
creating 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
Repeater controls
binding to 2nd 3rd 4th
Button controls 2nd 3rd 4th
CheckBoxList controls 2nd 3rd
DropDownList controls 2nd 3rd
formatting templates 2nd 3rd 4th 5th
Hyperlink controls 2nd 3rd 4th 5th
Image controls 2nd 3rd
LinkButton controls 2nd 3rd 4th
ListBox controls 2nd 3rd
RadioButtonList controls 2nd 3rd
view states 2nd
without expressions 2nd 3rd
data integrity
SSL (Secure Socket Layer)
Data Source Name (DSN)
data sources
data binding. [See data binding]
lists
binding to 2nd 3rd 4th
data types
checking 2nd 3rd 4th
DatabaseDependency.aspx
databases
ADO.NET
adding records 2nd 3rd 4th 5th
deleting records 2nd 3rd 4th 5th 6th
executing tasks 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st 22nd 23rd 24th 25th 26th 27th 28th 29th 30th
optimizing records 2nd 3rd 4th 5th 6th 7th 8th
updating records 2nd 3rd 4th 5th
Boolean queries
executing 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
connections
opening 2nd 3rd 4th 5th
DataSets 2nd 3rd 4th
adding DataTables 2nd 3rd 4th
applying with ASP.NEt pages 2nd 3rd 4th
binding controls 2nd
elements 2nd
filtering caches 2nd 3rd 4th
reading XML 2nd 3rd
ReadXML with schemas 2nd 3rd 4th 5th 6th
searching caches 2nd 3rd 4th 5th
viewing 2nd 3rd 4th 5th 6th
viewing caches 2nd 3rd 4th
writing XML 2nd 3rd 4th 5th
XML
XMLDataDocuments 2nd 3rd 4th 5th 6th
DataTables
column properties 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th
computing column values 2nd 3rd
configuring properties 2nd 3rd 4th
creating 2nd 3rd
defining relationships 2nd 3rd 4th
retrieving schemas 2nd 3rd 4th 5th
updating records 2nd 3rd 4th 5th
DataViews
filtering rows 2nd 3rd 4th 5th
searching rows 2nd 3rd
sorting rows 2nd 3rd
free text queries 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
Oracle
passwords
storing 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
queries
applying parameters 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
exectuing from forms 2nd 3rd 4th
records
retrieving 2nd 3rd 4th 5th 6th 7th 8th 9th
Repeater control
binding 2nd 3rd 4th
Button controls 2nd 3rd 4th
CheckBoxList controls 2nd 3rd
DropDownList controls 2nd 3rd
formatting templates 2nd 3rd 4th 5th
Hyperlink controls 2nd 3rd 4th 5th
Image controls 2nd 3rd
LinkButton controls 2nd 3rd 4th
ListBox controls 2nd 3rd
RadioButtonList controls 2nd 3rd
view states 2nd
rows
returning 2nd
session states
storing 2nd 3rd 4th
SQL CREATE TABLE statement
executing 2nd 3rd
tables
creating (XML Web Services) 2nd
viewing Uploads 2nd 3rd
TreeView controls
binding 2nd 3rd 4th
WebService behavior
retrieving 2nd 3rd 4th 5th 6th
DataBind() method
DataBindTest1.aspx
DataBound controls
applying templates 2nd 3rd 4th
binding to DataSets 2nd 3rd 4th 5th 6th 7th 8th 9th
creating 2nd 3rd 4th 5th 6th
states 2nd 3rd 4th 5th
DataBoundDataSet.aspx
DataBoundState.vb
DataDocumentNavigator class
DataGrid 2nd
DataGrid control 2nd 3rd 4th
columns
adding BoundColumns 2nd 3rd 4th 5th
ButtonColumns 2nd 3rd 4th
creating 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st 22nd
EditCommandColumns 2nd 3rd
inserting HypeLinkColumns 2nd 3rd 4th 5th
sorting 2nd 3rd 4th
TemplateColumns 2nd 3rd 4th
DataKeys collection 2nd
event bubbling 2nd 3rd
items
editing 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
records
paging 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
rows
selecting 2nd 3rd
styles
applying 2nd 3rd 4th 5th
templates 2nd
DataGrid.aspx 2nd
DataGridBoundColumns.aspx
DataGridButtonColumn.aspx
DataGridCustomPaging.aspx
DataGridDataFormatString.aspx
DataGridEditCommandColumn.aspx
DataGridEditProducts.aspx
DataGridEditTemplate.aspx
DataGridHyperLink.aspx
DataGridMasterDetail.aspx
DataGridPaging.aspx
DataGridSort.aspx
DataGridSortExpression.aspx
DataGridStyles.aspx
DataGridTemplate.aspx
DataKeys collection 2nd
DataList control 2nd
DataList 2nd 3rd 4th
DataList control
columns
creating 2nd 3rd
DataKeys collection 2nd 3rd 4th
event bubbling 2nd 3rd
events
capturing 2nd 3rd 4th 5th
items
editing 2nd 3rd 4th 5th 6th 7th 8th
selecting 2nd 3rd 4th
master/detail forms
building 2nd 3rd 4th 5th
records
viewing 2nd 3rd 4th
styles
formatting 2nd 3rd 4th 5th
templates 2nd
applying 2nd
DataList.aspx
DataListEdit.aspx
DataListEvents.aspx
DataListMultiColumn.aspx
DataListSelected.aspx
DataListStyles.aspx
DataListTemplate.aspx
DataObject.vb
DataReader
rows
returning 2nd
DataRelation.aspx
DataSet
DataSetDoubleFill.aspx
DataSets 2nd 3rd 4th
ASP.NET pages
applying 2nd 3rd 4th
filtering caches 2nd 3rd 4th
searching caches 2nd 3rd 4th 5th
viewing caches 2nd 3rd 4th
caching 2nd 3rd
filtering 2nd 3rd 4th
searching 2nd 3rd 4th 5th
viewing 2nd 3rd 4th
controls
binding 2nd
DataBound controls
binding to 2nd 3rd 4th 5th 6th 7th 8th 9th
DataTables
adding 2nd 3rd 4th
elements 2nd
viewing 2nd 3rd 4th 5th 6th
XML
reading 2nd 3rd
ReadXML with schemas 2nd 3rd 4th 5th 6th
writing 2nd 3rd 4th 5th
XMLDataDocuments 2nd 3rd 4th 5th 6th
XML Web Services 2nd 3rd
DataSetTransform.aspx
DataSlide.aspx
DataTableProperties.aspx
DataTables
columns
computing values 2nd 3rd
creating 2nd 3rd
DataSets
adding 2nd 3rd 4th
properties
columns 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th
configuring 2nd 3rd 4th
records
updating 2nd 3rd 4th 5th
relationships
defining 2nd 3rd 4th
schemas
retrieving 2nd 3rd 4th 5th
DataViews 2nd
rows
filtering 2nd 3rd 4th 5th
searching 2nd 3rd
sorting 2nd 3rd
dates
fromatting 2nd 3rd 4th
DayRender event 2nd 3rd 4th 5th 6th 7th
DayRenderEventArgs subroutine
days
Calendar control
rendering 2nd 3rd 4th 5th 6th
selecting 2nd 3rd
DBUpload.aspx
DBUploadSearch.aspx
Debug.aspx
debugging
applications 2nd 3rd
attaching 2nd
controls 2nd 3rd
DebugMode/Web.Config 2nd
declaration
code declaration blocks 2nd 3rd
functions 2nd 3rd
Global.asax file 2nd 3rd 4th 5th
public variables
subroutines 2nd 3rd
variables
Option Explicit
VBScript 2nd
Decoration property
decryption
files
decryption. [See also encryption]
Default SMTP Virtual Server
Default.aspx 2nd 3rd
defaults
languages
modifying
nonindexed default properties
Option Explicit
values
columns 2nd 3rd
DefaultValue.aspx
defining
relationships
DataTables 2nd 3rd 4th
Delete command
SQL 2nd 3rd 4th 5th 6th
deleting
characters
strings 2nd 3rd
items
ArrayList collection 2nd
HashTable collection 2nd
session states
SortedList collection 2nd
records 2nd 3rd 4th 5th 6th
strings
dependencies
caches
adding to 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
deriving
user controls 2nd 3rd
DESCryptoServiceProvider class
Deserialize method 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th
DeserializeArrayList.aspx
DeserializeClass.aspx
DeserializeXmlClass.aspx
Details.aspx
detection
mobile capabilities 2nd 3rd 4th
tracing 2nd 3rd
TreeView controls 2nd 3rd 4th
devices
mobile. [See mobile devices]2nd [See mobile devices]3rd [See mobile devices]
selecting 2nd
DeviceSpecific element 2nd
DeviceSpecific.aspx
dialog boxes
Quick Watch
Save As
digital signatures
generating
digital signatures, verifying
DIME (Direct Internet Message Encapsulation)
Direct Internet Message Encapsulation (DIME)
directives
client-side validation
disabling
import 2nd
OutputCache
page 2nd 3rd
server-side include 2nd
directories 2nd
/BIN
/bin
adding
Badmail
Drop 2nd
FormSimple
indexing
inetpub/wwwroot
paths
retrieving 2nd 3rd
Pickup 2nd
viewing 2nd 3rd
virtual
creating
wwwroot
disabling
client-side validation 2nd 3rd
session states 2nd
validation 2nd
view state 2nd 3rd 4th 5th
Discovery tool
XML Web Services
applying 2nd 3rd
Display.aspx
DisplayAddress.aspx
DisplayAddressHashTable.aspx
DisplayAddressProperties control
DisplayBetterNews.aspx
DisplayBlueTextBox.aspx
DisplayBlueTextBoxChanged.aspx
DisplayBlueTextBoxPostBack.aspx
DisplayControlEvents control
DisplayControlTrace.aspx
DisplayDataBoundDataSet.aspx
DisplayDataBoundState.aspx
DisplayDataBoundTemplate.aspx
DisplayEventLog.aspx
DisplayFeatured.aspx
DisplayFeaturedProduct.aspx
DisplayFeaturedProductCodeBehind.aspx
DisplayFeaturedProductType.aspx
DisplayHelloWorld.aspx
DisplayImage.aspx
DisplayLogin.aspx
DisplayLoginEvent.aspx
displayMessage._aspx
DisplayMessage.aspx
DisplayMultipleTemplates.aspx
DisplayNews.aspx
DisplayProducts.aspx
DisplayProducts.aspx page
DisplayRandomEvent.aspx
DisplayReverseMethodControl.aspx
DisplayShowColor.aspx
DisplayShowColor2.aspx
DisplayShowGreen.aspx
DisplaySimpleDataBound.aspx
DisplaySimpleImage.aspx
DisplaySimpleRotator1.aspx
DisplaySimpleRotator2.aspx
DisplaySimpleRotator3.aspx
DisplaySimpleTemplate.aspx
DisplaySixDegrees.aspx
DisplayTemplateNumbers.aspx
DisplayTextBoxColor.aspx
DisplayWebControl.aspx
DisplayWordRepeater.aspx
Divider.ascx
division
code-behind
applying 2nd 3rd 4th 5th
compiling 2nd 3rd 4th 5th
inheriting multiple pages 2nd 3rd 4th 5th
Web applications into multiple layers 2nd 3rd 4th 5th 6th 7th
Dns class
Document Type Definition (DTD)
DocumentNavigator class
documents
binary
indexing 2nd 3rd 4th
uploading 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th
Microsoft Indexing Service
Boolean queries 2nd
configuring 2nd 3rd 4th 5th 6th
free text queries 2nd 3rd
querying properties 2nd 3rd
retrieving properties 2nd 3rd 4th
uploading 2nd 3rd 4th 5th 6th
searching 2nd 3rd
WSDL
creating 2nd 3rd 4th 5th
domains
names
resolving 2nd
doucments
properties
querying 2nd 3rd
downloading
with HttpWebRequest class 2nd 3rd 4th
with WebClient class 2nd 3rd
drawing
images 2nd 3rd
applying colors 2nd 3rd 4th 5th 6th 7th
applying pens 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
creating bitmaps 2nd 3rd 4th 5th 6th
curves 2nd 3rd 4th
ellipses 2nd
filling shapes 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
lines
optimizing 2nd
rectangles 2nd
strings of text 2nd 3rd
DrawString method 2nd 3rd
Drop directory 2nd
DropDownList 2nd
DropDownList control 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th
Repeater control 2nd 3rd
DropDownList.aspx
DropDownListAutoPostBack.aspx
DropDownListDataBind.as
DropDownListDataValue.aspx
DropDownListSelected.aspx
DropDownListValue.aspx
DropDownListValueSelected.aspx
DSACryptoServiceProvider class
DSN (Data Source Name)
DTD (Document Type Definition)
dynamically generating forms 2nd 3rd
dynamically generating list items 2nd 3rd 4th
DynamicForm.aspx

[ Team LiB ]


[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Z]

e-mail
addresses
validating 2nd 3rd
EditCommandColumn
DataGrid
adding 2nd 3rd
editing
Community Starter Kit 2nd
items
DataGrid control 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
DataList controls 2nd 3rd 4th 5th 6th 7th 8th
elements
<deny
DataSets 2nd
DeviceSpecific 2nd
pages 2nd
ASP.NET controls
code declaration blocks 2nd 3rd
code render blocks 2nd
directives 2nd 3rd 4th 5th 6th
HTML tags/text 2nd
server-side comments 2nd
server-side include-directives 2nd
ellipses
drawing 2nd
email
Microsoft Message Queuing 2nd 3rd 4th
applying 2nd 3rd 4th 5th 6th 7th
configuring
receiving 2nd 3rd 4th 5th 6th
retrieving 2nd 3rd
sending 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
viewing 2nd 3rd
sending 2nd 3rd 4th 5th 6th 7th
adding attachments 2nd
applying Microsoft SMTP Service 2nd
configuring Microsoft SMTP Service 2nd
HTML 2nd 3rd 4th 5th 6th 7th 8th
Microsoft SMTP Service
emulators
Microsoft Mobile Explorer
Enabled property 2nd 3rd 4th 5th
EnableSession property
EnableViewState property
modifying
EnableViewState.aspx
EnableWindows.Config
enabling
client-side validation 2nd 3rd
cookieless sessions
debug mode
forms-based authentication 2nd 3rd 4th 5th 6th 7th
page tracing
Passport authentication 2nd 3rd 4th
security 2nd 3rd 4th
tracing
detecting 2nd 3rd
encryption
.NET classes 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st 22nd 23rd
asymmetric 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
files
passwords
Web.Config file 2nd 3rd
SSL (Secure Socket Layer) 2nd
ending
sessions 2nd
entering
content
user controls 2nd 3rd 4th 5th
entries
length
validating 2nd 3rd
EnumerateProducts.aspx
enumeration
CommandBehavior parameter 2nd 3rd 4th
FileMode
ErrorPage/ErrorPage.aspx
errors
application-level error handling 2nd 3rd 4th 5th 6th
page-level error handling 2nd 3rd 4th 5th 6th 7th 8th
pop-up messages
viewing 2nd 3rd 4th
summarizing 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
types of 2nd 3rd 4th
viewing 2nd 3rd 4th
errors. [See also troubleshooting]
events
AdRotator 2nd
tracking 2nd 3rd
AdRotator control
bubbling 2nd 3rd
Button
Button controls
Calendar 2nd 3rd
calendar control
CheckBox 2nd
CheckBox control 2nd
CheckBoxList 2nd
CheckBoxList control 2nd 3rd
Command
CompareValidator 2nd 3rd
CompareValidator control
composite controls
creating 2nd 3rd
controls
adding 2nd 3rd 4th
associating subroutines 2nd 3rd 4th 5th 6th 7th
viewing 2nd
CustomValidator
CustomValidator control 2nd
DataGrid 2nd
DataList 2nd 3rd 4th
DataList control
capturing 2nd 3rd 4th 5th
DropDownList
DropDownList control 2nd 3rd
Global.asax file 2nd 3rd 4th 5th
handling
subroutines
HTML controls 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th
HTMLInputFile control
HttpPostedFile control
Hyperlink
Hyperlink control
Image 2nd
ImageButton 2nd
ImageButton control
Label
Label controls
LabelButton 2nd
labels
LinkButton control
ListBox
ListBox control 2nd 3rd 4th
Literal
Load
executing IsPostBack property 2nd
logging 2nd 3rd 4th
pages
adding application logic 2nd 3rd
Panel
panel control
Placeholder
postback
handling 2nd 3rd 4th
RadioButton 2nd
RadioButton control
RadioButtonList 2nd 3rd
RadioButtonList control
RangeValidator 2nd
RangeValidator control
RegularExpressionValidator
RegularExpressionValidator control
rendering 2nd 3rd 4th 5th 6th
Repeater 2nd
RequiredFieldValidator 2nd
RequiredFieldValidator control
session
capturing 2nd 3rd
Table 2nd
TableCell
TableRow
TextBox
Textbox controls 2nd
TextChanged 2nd 3rd
toolbars
handling 2nd 3rd
user controls
viewing 2nd 3rd
ValidationSummary 2nd
ValidationSummary control 2nd 3rd 4th
exceptions
catching 2nd 3rd 4th 5th
unhandled
catching 2nd
ExecuteScalar() method
execution
applications
monitoring 2nd 3rd 4th
tracing 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th
ASP.NET pages 2nd 3rd 4th
Boolean queries 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
breakpoints
creating 2nd 3rd 4th 5th 6th
code
IsPostBack property 2nd
data type checks 2nd 3rd 4th
database commands
transactions 2nd 3rd 4th
databases 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st 22nd 23rd 24th 25th 26th 27th 28th 29th 30th
adding records 2nd 3rd 4th 5th
applying output parameters 2nd 3rd 4th
connection pooling 2nd 3rd 4th 5th
deleting records 2nd 3rd 4th 5th 6th
grouping stored procedures 2nd 3rd 4th 5th
optimizing records 2nd 3rd 4th 5th 6th 7th 8th
retrieving return values 2nd 3rd
transactions 2nd 3rd 4th
updating records 2nd 3rd 4th 5th
debugging
attaching 2nd
queries 2nd 3rd 4th
applying parameters with 2nd 3rd 4th 5th 6th 7th 8th
SQL CREATE TABLE statement 2nd 3rd
SQL statements 2nd 3rd 4th 5th 6th
applying output parameters 2nd 3rd 4th
connection pooling 2nd 3rd 4th 5th
grouping stored procedures 2nd 3rd 4th 5th
retrieving return values 2nd 3rd
statements
troubleshooting 2nd 3rd 4th 5th
exeucting
user accounts 2nd 3rd 4th
existing controls
inheriting 2nd
expiration
persistent cookies 2nd 3rd
expiration policies
creating 2nd 3rd 4th 5th 6th
exposing
Web services 2nd 3rd
expressions 2nd
backreferences/capture groups 2nd 3rd 4th
classes 2nd 3rd 4th 5th
customizing 2nd 3rd 4th 5th
data binding without 2nd 3rd
inline 2nd
metacharacters 2nd 3rd 4th 5th 6th 7th 8th 9th
quantifiers 2nd 3rd 4th
regular
specifying 2nd
RegularExpressionValidator control 2nd 3rd 4th 5th
replacement patterns 2nd
testing
text
eXtensible Markup Language. [See XML]
eXtensible Stylesheet Language. [See XSL]
extensions
.aspx 2nd
files
HTTP handlers 2nd 3rd 4th 5th 6th
HTTP modules 2nd 3rd 4th
ExternalFile.aspx

[ Team LiB ]


[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Z]

farms
applying 2nd
FavLinks table
Featured Product Web Services, creating 2nd 3rd 4th 5th 6th 7th 8th 9th
Featured1.ascx
Featured2.ascx
FeaturedControl
FeaturedControl.vb
FeaturedProduct.vb
FeaturedProduct1.ascx
FeaturedProduct2.ascx
FeaturedService.asmx 2nd
fields
RequiredFieldValidator control 2nd 3rd 4th 5th 6th
RequiredFieldValidatorInitialValue control 2nd 3rd
file systems
security 2nd
FileContains.aspx
FileDependency.aspx
FileFreeText.aspx
FileMode enumeration
files 2nd
Appcache/Global.asax
AppFixLinks/BadLinks.xml
AppFixLinks/Global.asax file
Appinit/Global.asax
AppVanity/Global.asax
binary
creating
reading from 2nd 3rd
writing to 2nd 3rd 4th
XML Web Services 2nd 3rd 4th
classic ASP
code-behind. [See also code-behind]
cookies
decrypting
dependencies
adding 2nd 3rd 4th
encrypting
extensions
Global.asax 2nd 3rd 4th 5th 6th
handling errors 2nd
HTML. [See also HTML]
HTTP handlers 2nd 3rd 4th 5th 6th
HTTP modules 2nd 3rd 4th
Machine.Config
NotePad
saving
Password.Config
server-side include directives 2nd
text
creating
reading from 2nd 3rd
writing to 2nd 3rd 4th
uploads
accepting 2nd 3rd 4th 5th 6th
validation
validation. [See also validation]
Web.Config 2nd 3rd 4th
configuring forms-based authentication 2nd
configuring forms-based authorization 2nd 3rd
creating sign out pages 2nd
CustomErrorsOff
customizing 2nd 3rd
database tables 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
forms-based authentication 2nd 3rd 4th 5th
job sites 2nd 3rd 4th 5th 6th 7th
locking 2nd 3rd
modifying 2nd 3rd 4th 5th 6th
Passport authentication
retrieving user information 2nd 3rd 4th 5th
sections 2nd 3rd 4th 5th 6th
storing passwords 2nd 3rd 4th 5th 6th 7th
WhosOn applications 2nd
XML storage 2nd 3rd 4th 5th 6th 7th 8th 9th
XML
binding TreeView controls 2nd 3rd
FileStream class 2nd 3rd 4th 5th 6th 7th
filling shapes 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
FillSchema.aspx
filtering
caches
DataSets 2nd 3rd 4th
rows
DataViews 2nd 3rd 4th 5th
Find method 2nd 3rd
FindDataView.aspx
foreign numbers
formatting 2nd 3rd 4th
Form.aspx
FormatDateTime.aspx
FormatInfo.xml
FormatNumber.aspx
formatting
aggregate columns 2nd 3rd 4th 5th 6th
Authentication Tickets 2nd 3rd 4th 5th 6th
Base Web control 2nd 3rd
binary files
bitmaps 2nd 3rd 4th 5th 6th
breakpoints 2nd 3rd 4th 5th 6th
calculated columns 2nd 3rd 4th 5th 6th
Calendar control 2nd 3rd 4th
columns
DataGrid control 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st 22nd 23rd 24th 25th 26th 27th 28th 29th 30th 31st 32nd 33rd 34th 35th 36th 37th 38th 39th 40th 41st 42nd 43rd
DataList control 2nd 3rd
Community Starter Kits 2nd
composite controls 2nd 3rd 4th
handling events 2nd 3rd
controls 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
styles 2nd 3rd 4th 5th 6th 7th 8th 9th
cross-device-compatible mobile pages 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
currency 2nd
custom control builders 2nd 3rd
databases
XML Web Services 2nd
DataBound controls 2nd 3rd 4th 5th 6th
applying templates 2nd 3rd 4th
binding to DataSets 2nd 3rd 4th 5th 6th 7th 8th 9th
states 2nd 3rd 4th 5th
DataTables 2nd 3rd
dates 2nd 3rd 4th
Featured Product Web Services 2nd 3rd 4th 5th 6th 7th 8th 9th
foreign numbers 2nd 3rd 4th
home pages 2nd 3rd 4th
HTML pattern matching services 2nd 3rd 4th 5th 6th
HtmlTextWriter
applying 2nd 3rd
interactive meeting schedulers 2nd 3rd
job sites
XML Web Services 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
master/detail forms 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
multiselect list boxes
numbers 2nd 3rd
pages
WebService behavior 2nd 3rd 4th 5th 6th
queues 2nd 3rd
sign out pages 2nd
strings 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th
methods/properties 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th
styles
DataList control 2nd 3rd 4th 5th
TabStrip control 2nd 3rd 4th 5th
images 2nd 3rd
vertical TabStrip controls 2nd 3rd
templates
Repeater controls 2nd 3rd 4th 5th
view states (Repeater controls) 2nd
text
files
time 2nd 3rd 4th
Toolbar controls 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
trace categories 2nd 3rd
TreeView controls 2nd 3rd
vanity URLs 2nd 3rd
virtual directories
watches 2nd
WhosOn 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
WSDL documents 2nd 3rd 4th 5th
XML
ASP.NET Xml controls 2nd 3rd 4th 5th
FormConfig.aspx
FormConfig.Config
forms
characters
validating 2nd 3rd
composite controls
creating 2nd 3rd 4th
handling events 2nd 3rd
content
Enabled property 2nd 3rd 4th 5th
hiding 2nd
Panel control 2nd 3rd 4th 5th
viewing 2nd
Visible property 2nd 3rd 4th 5th
CustomValidator control 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
e-mail
validating addresses 2nd 3rd
email
sending from
generating dynamically 2nd 3rd
HTML
submitting 2nd 3rd 4th 5th
HTML. [See also HTML]
images
uploading 2nd 3rd 4th 5th 6th
master/detail
building with DataList controls 2nd 3rd 4th 5th
creating 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
mobile
activating 2nd
advertisements 2nd 3rd
binding lists 2nd 3rd 4th
calendars 2nd 3rd
creating 2nd 3rd
fromatting text boxes 2nd 3rd 4th
images 2nd 3rd
interactive lists 2nd 3rd 4th
object lists 2nd 3rd 4th 5th 6th
paging text 2nd
placing phone calls 2nd
validating user input 2nd 3rd 4th
viewing lists 2nd
viewing text 2nd 3rd
multipage
simulating 2nd 3rd 4th 5th
multiple addresses
viewing
objects
converting 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st 22nd 23rd 24th 25th
one server-sid epage per
passwords
validating 2nd 3rd
phone numbers
validating 2nd
Placeholder control 2nd 3rd
postback events
handling 2nd 3rd 4th
postbacks 2nd
queries
applying parameters with 2nd 3rd 4th 5th 6th 7th 8th
executing 2nd 3rd 4th
records
creating 2nd
deleting 2nd 3rd 4th 5th 6th
updating 2nd 3rd 4th 5th
RequiredFieldvalidator control 2nd 3rd 4th 5th 6th
RequiredFieldvalidatorInitialValue control 2nd 3rd
smart
applying Button controls 2nd 3rd 4th 5th
building 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st
CheckBox controls 2nd 3rd 4th 5th 6th
CheckBoxList controls 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th
DropDownList controls 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th
ImageButton controls 2nd 3rd 4th 5th 6th
LinkButton controls 2nd 3rd
ListBox controls 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th
RadioButton controls 2nd 3rd 4th 5th 6th 7th 8th 9th
RadioButtonList controls 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th
templates
applying 2nd 3rd
URLs
validating 2nd 3rd 4th
usernames
validating 2nd 3rd
validation
adding 2nd
configuring 2nd 3rd
disabling 2nd 3rd 4th 5th
enabling 2nd 3rd
ValidationSummary control 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
values
checking data types 2nd 3rd 4th
comparing 2nd 3rd 4th 5th 6th 7th 8th 9th
RangeValidator control 2nd 3rd 4th 5th 6th 7th
Web
applying formatting to controls 2nd 3rd 4th 5th 6th
styles 2nd 3rd 4th 5th 6th 7th 8th 9th
zip codes
validating 2nd
Forms Authentication
applying 2nd
Forms authentication
jobs sites 2nd 3rd 4th 5th 6th 7th
forms-based authentication 2nd
applying Web farms 2nd
configuring 2nd
creating Authentication Tickets 2nd 3rd 4th 5th 6th
creating sign out pages 2nd
database tables 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
enabling 2nd 3rd 4th 5th 6th 7th
implementing roles-based authentication 2nd 3rd 4th 5th
retrieving user information 2nd 3rd 4th 5th
storing passwords 2nd 3rd 4th 5th 6th 7th
XML storage 2nd 3rd 4th 5th 6th 7th 8th 9th
forms-based authorization
configuring 2nd 3rd
FormsAttributes.Config
FormsAuthenticationTicket class
FormsAuthenticationTicket constructor
FormsDB.aspx 2nd
FormsDB.Config
FormsDB.sql 2nd 3rd
FormsIdentityormsIdentity.aspx
FormSimple directory
FormsRoles.asax
FormsRoles.aspx
FormsRoles.xml
FormsSimple.aspx
FormsSimple.Config 2nd
FormsSimple/Web.Co
FormsXml.aspx 2nd
FormsXml.Config
FormsXml.xml
FormTemplates.aspx
fragments
page caching 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
pages
configuring caches 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
framework
ADO.NET 2nd 3rd 4th 5th
adding records to databases 2nd 3rd 4th 5th
applying output parameters 2nd 3rd 4th
CommandBehavior parameter 2nd 3rd 4th
connection pooling 2nd 3rd 4th 5th
deleting records in databases 2nd 3rd 4th 5th 6th
executing database tasks 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st 22nd 23rd 24th 25th 26th 27th 28th 29th 30th
executing stored procedures 2nd 3rd 4th 5th
GetSchemaTable() method 2nd 3rd
optimizing atabases 2nd 3rd 4th 5th 6th 7th 8th
retrieving return values 2nd 3rd
transactions 2nd 3rd 4th
updating records in databases 2nd 3rd 4th 5th
Class Library 2nd
compatible languages 2nd 3rd
debugging
attaching 2nd
namespaces 2nd
standard ASP.NET 2nd
frameworks
session states 2nd
adding items 2nd
capturing events 2nd 3rd
cookieless 2nd 3rd 4th 5th 6th
deleting items
disabling 2nd
ending sessions 2nd
starting sessions
storing 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
free text queries 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
Microsoft Indexing Service 2nd 3rd
free-form queries 2nd 3rd
FREETEXT function
Microsoft Indexing Service 2nd 3rd
FREETEXT predicate
FREETEXT predicates, applying 2nd 3rd 4th
FreeText.aspx
FREETEXTTABLE function
FREETEXTTABLE function, applying 2nd 3rd 4th 5th 6th
FreeTextTable.aspx
full population
full populations 2nd
full-text searching 2nd
configuring 2nd 3rd 4th 5th
functions
<Script[chevron] tags
declaring 2nd 3rd
accessor
applying 2nd 3rd
Authenticate()
callback
WebService behavior 2nd 3rd
CONTAINS
Microsoft Indexing Service 2nd
CONTAINSTABLE
applying 2nd 3rd 4th 5th 6th
FREETEXT
Microsoft Indexing Service 2nd 3rd
FREETEXTTABLE
applying 2nd 3rd 4th 5th 6th
GetVaryByCustomString

[ Team LiB ]


[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Z]

GDI+
applications
building 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th
objects
drawing
generating
digital signatures
generation
CSR (certificate signing request) 2nd 3rd
forms 2nd 3rd
lists
items 2nd 3rd 4th
XML Web Services
proxy classes 2nd 3rd
GeoID
GetFiles.aspx
GetLuckyNumber() Method
GetmessageEnumerator.aspx
GetPublicQueues() method
GetPublicQueues.aspx
GetSchemaTable() method 2nd 3rd
GetVaryByCustomString function
GetXml.aspx
GetXmlSchema.aspx
Global.asax 2nd
Global.asax file 2nd 3rd 4th 5th
errors
handling 2nd
Global.asax files
GlobalError/Global.asax
Globally Unique Identifier (GUID) 2nd
GoodCounterPage.aspx
GoodPageCounter.aspx
graphics
drawing
applying colors 2nd 3rd 4th 5th 6th 7th
applying pens 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
creating bitmaps 2nd 3rd 4th 5th 6th
curves 2nd 3rd 4th
ellipses 2nd
filling shapes 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
lines
optimizing 2nd
rectangles 2nd
strings of text 2nd 3rd
objects
applying 2nd 3rd
viewing 2nd 3rd 4th 5th 6th
Graphics.aspx
grouping
stored procedures 2nd 3rd 4th 5th
groups
capture
regular expressions 2nd 3rd 4th
objects
converting 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st 22nd 23rd 24th 25th
Windows
configuring 2nd 3rd
Guestbook.aspx
GUID (Globally Unique Identifier) 2nd

[ Team LiB ]


[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Z]

handlers
WhosOn applications 2nd 3rd
handling
events
subroutines
toolbars 2nd 3rd
postbacks
events 2nd 3rd 4th
hash algorithms 2nd 3rd 4th 5th
Hash.aspx
HashTable collection 2nd 3rd
items
adding
deleting 2nd
iterating 2nd
searching 2nd 3rd
HasRows porperty 2nd
HatchBrush
applying 2nd
HatchBrush.aspx
headers
cache content
varying 2nd 3rd 4th 5th
Cookie
custom SOAP
retrieving 2nd
Set-Cookie
User-Agent
HeaderTitle.ascx
Hello World!
creating 2nd 3rd 4th
HelloGoodbye.aspx
HelloWorld.vb
hiding
content 2nd
Enabled property 2nd 3rd 4th 5th
Panel control 2nd 3rd 4th 5th
Visible property 2nd 3rd 4th 5th
errors 2nd 3rd
histories
returning 2nd 3rd 4th 5th
hoem pages
job sites
creating vanity URLs 2nd 3rd
home pages
creating 2nd 3rd 4th
home pages.
Web pages [See also job sites]
Home.aspx
HomePage.aspx
HomePageDate.aspx
HomePageTitle.aspx
horoscopes
viewing
HTML
browser compatibility 2nd
controls 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st 22nd
viewing user controls 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
converting 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
selecting object models 2nd 3rd
email
sending 2nd 3rd 4th 5th 6th 7th 8th
forms
submitting 2nd 3rd 4th 5th
pattern matching 2nd
applying input parameters 2nd 3rd 4th
building Six Degress Web Service 2nd 3rd 4th 5th 6th
creating services 2nd 3rd 4th 5th 6th
creating WSDL documents 2nd 3rd 4th 5th
specifying regular expressions 2nd
pattern-matching
tags 2nd
HtmlAnchor control
HtmlButton control
HtmlForm control
HtmlGenericControl
HtmlImage control
HtmlInputButton control
HtmlInputCheckBox control
HtmlInputFile control
HTMLInputFile control 2nd 3rd 4th 5th 6th 7th
HtmlInputFile.aspx
HtmlInputHidden control
HtmlInputImage control
HtmlInputRadioButton control
HtmlInputText control
HtmlSelect control
HtmlTable control
HtmlTableCell
HtmlTableRow control
HtmlTextArea control
HtmlTextWriter
applying 2nd 3rd
HtmlTextWriter.aspx
HTTP 2nd 3rd
handlers 2nd 3rd 4th 5th 6th
modules 2nd 3rd 4th
Web sites
accessing 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
HTTP POST
Web services
testing
HTTP-Get
XML Web Services
invoking 2nd 3rd
HTTP-Post
XML Web Services
invoking 2nd
HttpCachePolicy class 2nd 3rd
HttpCachePolicy.aspx
HttpContext
components
applying 2nd
HttpPostedFile control
HttpWebRequest class 2nd 3rd 4th 5th
HttpWebRequest.aspx
HttpWebResponse class
Hyperlink
Hyperlink control
adding 2nd
Repeater control 2nd 3rd 4th 5th
HyperLink.aspx
HyperLinkColumn
DataGrid
adding 2nd 3rd 4th 5th
HyperLinkList.aspx

[ Team LiB ]


[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Z]

IIS (Internet Information Server)
configuring 2nd 3rd
IL Disassembler (ILDASM)
ILDASM (IL Disassembler)
Image 2nd
Image control
Repeater control 2nd 3rd
Image.aspx
ImageButton 2nd
ImageButton control 2nd 3rd 4th 5th 6th
ImageList.aspx
images
drawing 2nd 3rd
applying colors 2nd 3rd 4th 5th 6th 7th
applying pens 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
creating bitmaps 2nd 3rd 4th 5th 6th
curves 2nd 3rd 4th
ellipses 2nd
filling shapes 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
lines
optimizing 2nd
rectangles 2nd
strings of text 2nd 3rd
HTML email 2nd 3rd 4th
Hyperlink control
adding 2nd
mobile devices
viewing 2nd 3rd
TabStrip control
creating with 2nd 3rd
TreeView controls
associating 2nd 3rd
uploading 2nd 3rd 4th 5th 6th
viewing 2nd 3rd 4th 5th 6th
ImageService.asmx
Impersonate.Config
impersonating user accounts 2nd 3rd 4th 5th
implementation
DataBound controls 2nd 3rd
roles-based authentication 2nd 3rd 4th 5th
import directives 2nd
ImportNamespace.aspx
incremental population
increments
AutoIncrement columns
adding 2nd 3rd 4th
population 2nd
indexes
binary documents 2nd 3rd 4th
Microsoft Indexing Service
Boolean queries 2nd
configuring 2nd 3rd 4th 5th 6th
free text queries 2nd 3rd
querying properties 2nd 3rd
retrieving properties 2nd 3rd 4th
nonindexed default properties
populating 2nd
SQL Server Full-Text Indexing Wizard
IndexOfAny method 2nd
inetpub/wwwroot directory
inheritance
code-behind files
multiple pages 2nd 3rd 4th 5th
controls 2nd
Init event
applications 2nd 3rd 4th 5th 6th
initial values 2nd 3rd
inline code 2nd
inline expressions 2nd
inner content
parsing 2nd 3rd
input
validating 2nd 3rd 4th
input parameters
HTML pattern matching
creating 2nd 3rd 4th
inserting
ButtonColumns
DataGrid 2nd 3rd 4th
EditCommandColumns
DataGrid 2nd 3rd
HyperLinkColumns
DataGrid 2nd 3rd 4th 5th
TemplateColumns
DataGrid 2nd 3rd 4th
InstallAspNETJobsDatabase.sql script 2nd
installation
job sites 2nd
online stores 2nd
server certificates 2nd
instances
templates
creating 2nd 3rd 4th
integration
custom controls/Web Services 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
integrity
SSL (Secure Socket Layer)
interaction. [See also controls]
interactive calendars
viewing 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st
interactive lists
creating 2nd 3rd 4th
interactive meeting schedulers
creating 2nd 3rd
interfaces
controls 2nd
benefits of 2nd
browser compatibility 2nd
converting HTML 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
HTML 2nd 3rd 4th 5th 6th 7th
object models 2nd 3rd
separating code form content 2nd
view states 2nd 3rd 4th 5th
Web 2nd 3rd 4th 5th 6th
cookies 2nd 3rd 4th
configuring properties 2nd 3rd
creating 2nd 3rd 4th 5th 6th 7th
limitations of 2nd 3rd
cross-device-compatible mobile pages
creating 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
smart forms
applying Button controls 2nd 3rd 4th 5th
building 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st
CheckBox controls 2nd 3rd 4th 5th 6th
CheckBoxList controls 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th
DropDownList controls 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th
ImageButton controls 2nd 3rd 4th 5th 6th
LinkButton controls 2nd 3rd
ListBox controls 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th
RadioButton controls 2nd 3rd 4th 5th 6th 7th 8th 9th
RadioButtonList controls 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th
TreeView control 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st 22nd 23rd
Web controls
modifying 2nd 3rd 4th 5th
Winsock32
Internet Explorer
commands
Source (View menu)
WebControls
TabStrip control 2nd 3rd 4th 5th 6th 7th 8th 9th
Toolbar control 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
TreeView control 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st 22nd 23rd
vertical TabStrip controls 2nd 3rd
Internet Information Server (IIS)
Internet Information Server. [See IIS]
Internet Services Manager command (Start menu)
intranets
Windows authentication
configuring
configuring IIS 2nd 3rd
creating custom roles 2nd 3rd
enabling security 2nd 3rd 4th
retrieving user information 2nd 3rd
Windows authorization
configuring 2nd 3rd 4th 5th
intuitive object models 2nd 3rd
invoking
XML Web Services 2nd 3rd 4th 5th
XML Web Services with HTTP-Get 2nd 3rd
XML Web Services with HTTP-Post 2nd
XML Web Services with SOAP
IsAuthenticated property
IsPostBack property 2nd
IsSecureConnection.aspx
ItemDataBound.aspx
items
adding
ArrayList collection 2nd 3rd 4th
HashTable collection
session states 2nd
SortedList collection 2nd
synchronizing 2nd 3rd 4th
caches
adding to 2nd 3rd 4th
configuring priorities 2nd
DataGrid control
editing 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
DataList control
editing 2nd 3rd 4th 5th 6th 7th 8th
selecting 2nd 3rd 4th
deleting
ArrayList collection 2nd
HashTable collection 2nd
session states
SortedList collection 2nd
iterating
ArrayList collection 2nd
HashTable collection 2nd
SortedList collection 2nd
lists
dynamically generating 2nd 3rd 4th
searching
ArrayList collection 2nd 3rd 4th
HashTable collection 2nd 3rd
SortedList collection 2nd 3rd
sorting
ArrayList collection 2nd 3rd
ItemTemplate 2nd 3rd 4th 5th
iteration
items
ArrayList collection 2nd
HashTable collection 2nd
SortedList collection 2nd
strings 2nd 3rd
IUSER_MachineName 2nd

[ Team LiB ]


[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Z]

JavaScript 2nd
JIT (Just in Time)
job sites
home pages
creating 2nd 3rd 4th
installing 2nd
listing 2nd 3rd 4th
updating 2nd 3rd 4th
users
authenticating 2nd 3rd 4th 5th 6th 7th
vanity URLs
creating 2nd 3rd
XML Web Service
creating 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
joining
strings 2nd
Jumble.aspx
Just in Time (JIT)

[ Team LiB ]


[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Z]

key dependencies
adding 2nd 3rd
KeyDependency.aspx
keys
sessions
authentication 2nd
caching 2nd

[ Team LiB ]


[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Z]

Label
Label.aspx 2nd
LabelButton 2nd
LabelDateTime.aspx
labels
controls
applying 2nd 3rd
languages
.NET framework 2nd 3rd
.NET framework Class Library 2nd
defaults
modifying
JavaScript 2nd
support 2nd
VBScript
layers
multitiered Web applications
creating 2nd 3rd 4th 5th 6th 7th
length
characters
validating 2nd 3rd
Let statement
libraries
Class Library 2nd
limitations
cookieless sessions 2nd 3rd
cookies 2nd 3rd
one server-side form per pages
page fragment caching 2nd 3rd
WebService behavior 2nd
LinearGradientBrush
applying 2nd
LinearGradientBrush.aspx
LineJoin.aspx
lines
drawing 2nd 3rd 4th
LinkButton control 2nd 3rd
Repeater control 2nd 3rd 4th
LinkButtonList.aspx
links
HTML email 2nd 3rd 4th
Hyperlink control
adding 2nd
List_ItemCommand subroutine
ListBinding.aspx
ListBox 2nd
ListBox control 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th
Repeater control 2nd 3rd
ListBox.aspx 2nd
ListBoxAutoPostBack.aspx
ListBoxDataBind.aspx
ListBoxDataValue.aspx
ListBoxes
values
assigning 2nd
ListBoxMultiSelect.aspx
ListBoxSelected.aspx
ListBoxSelectedMultiple.aspx
ListBoxValue.aspx
ListBoxValueSelected.aspx
ListItem collection
ListItemCollection collection
ListItemCollection.aspx
ListLinks.aspx
ListNumbered.aspx
ListPagerStyle.aspx
ListPaginate.aspx
lists
controls
viewing 2nd 3rd 4th 5th 6th 7th
items
generating dynamically 2nd 3rd 4th
job sites 2nd 3rd 4th
mobile controls
binding 2nd 3rd 4th
interactive 2nd 3rd 4th
object 2nd 3rd 4th 5th 6th
viewing 2nd
paging 2nd
queues
retrieving 2nd
ListValue.aspx
Literal
Literal.aspx
LiteralControl
Load event
IsPostBack property
executing 2nd
LoadControl method 2nd 3rd 4th
loading
product templates 2nd 3rd 4th 5th
user controls 2nd 3rd 4th 5th 6th 7th
LoadSessionKeys subroutine
Location attribute
locations
caches
configuring 2nd
Lock method 2nd
locking
Web.Config files 2nd 3rd
LogError/Global.asax
logging
events 2nd 3rd 4th
logic
application
adding 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th
applying code-behind 2nd 3rd 4th 5th
compiling code-behind 2nd 3rd 4th 5th
inheriting multiple pages 2nd 3rd 4th 5th
moving components 2nd 3rd 4th 5th
validation
adding 2nd
configuring 2nd 3rd
disabling 2nd 3rd
enabling 2nd 3rd
login
pages
Login() method
XML Web Services 2nd 3rd 4th 5th
Login.vb
LoginEvent.vb
loops
ArrayList collection 2nd
HashTable collection 2nd
SortedList collection 2nd
strings 2nd 3rd
Luhn algorithm

[ Team LiB ]


[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Z]

MAC (message authentication code)
Machine.Config file
magic cookie files
MailAttachment class 2nd 3rd
MailMessage class 2nd 3rd 4th
Managers.Config
margins
Master.aspx
master/detail forms
creating 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
DataList control
building 2nd 3rd 4th 5th
MasterDetail.aspx
matching
characters 2nd 3rd 4th 5th 6th 7th 8th 9th
quantifiers 2nd 3rd 4th
values
RegularExpressionValidator control 2nd 3rd 4th 5th
maximum values
rangeValidator control 2nd 3rd 4th 5th 6th 7th
MD5 class
MD5 hash algorithm
meetings
interactive meeting schedulers
creating 2nd 3rd
members
viewing 2nd 3rd 4th 5th 6th 7th
Menu.ascx
Menu.xml 2nd
MenuSchema.xml
message authentication code (MAC)
MessagePriorities.aspx
MessageQueue class 2nd 3rd
messages
custom trace
adding 2nd 3rd
email. [See email]
errors
ValidationSummary control 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
viewing pop-up error messages 2nd 3rd 4th
HTML. [See HTML]
Micosoft Message Queuing 2nd 3rd 4th
applying 2nd 3rd 4th 5th 6th 7th
configuring
receiving 2nd 3rd 4th 5th 6th
retrieving 2nd 3rd
sending 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
viewing 2nd 3rd
trace
viewing errors in 2nd
metacharacters
regular expressions 2nd 3rd 4th 5th 6th 7th 8th 9th
Method attribute
methods
Abandon
Add()
AdRotator 2nd
AdRotator control
Append 2nd 3rd
AppendText
Base Web Control Formatting
Button
Button controls
caches
callback methods 2nd 3rd 4th 5th 6th
Calendar 2nd 3rd
Calendar control
callback
caches 2nd 3rd 4th 5th 6th
CheckBox 2nd
CheckBox control 2nd
CheckBoxList 2nd
CheckBoxList control 2nd 3rd
CompareValidator 2nd 3rd
CompareValidator control
ComputeHash
controls
adding 2nd 3rd 4th 5th
CreateQueue() 2nd 3rd
CreateText 2nd 3rd 4th
CustomValidator
CustomValidator control 2nd
DataBind()
DataGrid 2nd
DataList 2nd 3rd 4th
Deserialize 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th
DrawString 2nd 3rd
DropDownList
DropDownList control 2nd 3rd
Find 2nd 3rd
GetLuckyNumber()
getPublicQueues()
GetSchemaTable() 2nd 3rd
HTML controls 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th
HTMLInputFile control
HttpPostedFile control
Hyperlink
Hyperlink control
Image 2nd
ImageButton 2nd
ImageButton control
IndexOfAny 2nd
Insert 2nd
Label
Label controls
LabelButton 2nd
labels
LinkButton control
ListBox
ListBox control 2nd 3rd 4th
ListItemCollection collection
Literal
LoadControl
applying 2nd 3rd
Lock 2nd
Login()
XML Web Services 2nd 3rd 4th 5th
OpenText 2nd 3rd
Panel
panel control
parameters
applying parenthesis
Placeholder
RadioButton 2nd
RadioButton control
RadioButtonList 2nd 3rd
RadioButtonList control
RangeValidator 2nd
RangeValidator control
Redirect()
applying 2nd
RedirectFromLoginPage
RegEx Method 2nd
RegularExpressionValidator
RegularExpressionValidator control
Remove 2nd
Repeater 2nd
Replace 2nd
Request 2nd
request.Form 2nd
Request.QueryString 2nd
RequiredFieldValidator 2nd
RequiredFieldValidator control
Send() 2nd 3rd 4th
Serialize 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th
SetCacheability
SetExpires()
SignData
SignHash
Sort
ArrayList collection 2nd 3rd
Synchronized()
Table 2nd
TableCell
TableItemStyle 2nd 3rd
TableRow
TableStyle 2nd
TextBox
TextBox controls 2nd
ToCelsius
navigating
ToString 2nd
trim
Unlock 2nd
user controls
viewing 2nd 3rd 4th
ValidationSummary 2nd
ValidationSummary control 2nd 3rd 4th
Web services
invoking
Write. [See also writing]
Microsoft
Passport authentication 2nd
enabling 2nd 3rd 4th
retrieving user information 2nd 3rd 4th 5th
sign in/sign out 2nd 3rd 4th 5th 6th
Microsoft Indexing Service
Boolean queries 2nd
configuring 2nd 3rd 4th 5th 6th
free text queries 2nd 3rd
properties
querying 2nd 3rd
retrieving 2nd 3rd 4th
Microsoft Intermediate Language (MSIL)
Microsoft Message Queuing 2nd 3rd 4th
applying 2nd 3rd 4th 5th 6th 7th
configuring
receiving 2nd 3rd 4th 5th 6th
retrieving 2nd 3rd
sending 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
viewing 2nd 3rd
Microsoft Mobile Explorer
Microsoft SMTP Service
applying 2nd
configuring 2nd
MicrosoftASPNET.xml
migration
ASP.NET 2nd
page extensions 2nd
minimum values
RangeValidator control 2nd 3rd 4th 5th 6th 7th
MissingSchemaAction.aspx
mobile devices
ASP.NET mobile controls 2nd 3rd 4th
activating mobile forms 2nd
advertisements 2nd 3rd
binding lists 2nd 3rd 4th
calendars 2nd 3rd
creating mobile forms 2nd 3rd
formatting text boxes 2nd 3rd 4th
images 2nd 3rd
interactive lists 2nd 3rd 4th
object lists 2nd 3rd 4th 5th 6th
paging text 2nd
placing phone calls 2nd
validating user input 2nd 3rd 4th
viewing lists 2nd
viewing text 2nd 3rd
cross-device-compatible mobile pages
creating 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
software simulators 2nd 3rd 4th
MobileCapabilities.aspx
models
objects
controls 2nd 3rd
modes
debug
enablinmg
modification
AutoPostBack property 2nd 3rd 4th 5th
Calendar control 2nd 3rd 4th
defaults
languages
EnableViewState property
HTML pages
SmoothingMode property 2nd
TextChanged event 2nd 3rd
Web controls 2nd 3rd 4th 5th
Web.Config files 2nd 3rd 4th 5th 6th
modifying
Community Starter Kits 2nd
strings 2nd
modules
HTTP 2nd 3rd 4th
WhosOn applications 2nd 3rd 4th
monitoring
applications 2nd 3rd 4th 5th
months
Calendar control
rendering days 2nd 3rd 4th 5th 6th
selecting 2nd 3rd
moving
application logic
components 2nd 3rd 4th 5th
MSIL (Microsoft Intermediate Language)
mulitiered Web applications
creating 2nd 3rd 4th 5th 6th 7th
MultiArrayList.aspx
multipage forms
simulating 2nd 3rd 4th 5th
multipage master/detail forms
creating 2nd 3rd 4th 5th
multiple address forms
viewing
multiple columns
creating 2nd 3rd
multiple instances
templates
creating 2nd 3rd 4th
multiple SQL statements
grouping 2nd 3rd 4th 5th
multiple templates
controls
adding 2nd 3rd 4th
multiple user controls
deriving 2nd 3rd
MultipleTemplates.vb
multiselect list boxes
creating
myAds.xml 2nd
myCodeBehind.vb
myPage.vb
myWebControl.vb

[ Team LiB ]


[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Z]

Name property
names
domains
resolving 2nd
usernames
validating 2nd 3rd
namespaces 2nd
ADO.NET 2nd 3rd 4th 5th
adding records to databases 2nd 3rd 4th 5th
applying output parameters 2nd 3rd 4th
CommandBehavior parameter 2nd 3rd 4th
connection pooling 2nd 3rd 4th 5th
deleting records in databases 2nd 3rd 4th 5th 6th
executing database tasks 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st 22nd 23rd 24th 25th 26th 27th 28th 29th 30th
executing stored procedures 2nd 3rd 4th 5th
GetSchemaTable() method 2nd 3rd
optimizing databases 2nd 3rd 4th 5th 6th 7th 8th
retrieving return values 2nd 3rd
transactions 2nd 3rd 4th
updating records in databases 2nd 3rd 4th 5th
Oracle
standard ASP.NET 2nd
System
System.IO
Path class 2nd 3rd
System.Web.Security
naming conventions 2nd
navigating
ToCelsius method
navigation
ASP.NET Unleashed Sample Store
building 2nd 3rd 4th
pages
adding Hyperlink controls 2nd
applying Redirect() method 2nd
submitting HTML forms 2nd 3rd 4th 5th
tab control
TreeView control 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st 22nd 23rd
NestedRowFilter.aspx
netscape Navigator
commands
Page Source (View menu)
networks
custom controls
integrating Web Services 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
NewJobs control
NewListings.vb
NewResumes control
NewsItem.vb
NewsService.asmx
NoAnon.Config
nodes
TreeView controls
associating 2nd
check boxes 2nd 3rd
detecting 2nd 3rd 4th
images 2nd 3rd
NoJane.Config
Nokia. [See also mobile devices]
nonindexed default properties
NotePad
files
saving
NotFound.aspx
numbers
foreign cultures
formatting 2nd 3rd 4th
fromatting 2nd 3rd

[ Team LiB ]


[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Z]

ObjectList.aspx
objects
ADO.NET. [See also ADO.NET]
Calendar control
converting 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st 22nd 23rd 24th 25th
GDI+
drawing
Global.asax file 2nd 3rd 4th 5th
graphics
applying 2nd 3rd
Let/Set statements
lists
creating 2nd 3rd 4th 5th 6th
models
controls 2nd 3rd
SqlCommand
Trace
debugging controls 2nd 3rd
OleDbCommand class
OleDbConnection class
OleDbConnection.aspx
OleDbDataAdapter.aspx
OleDbDataReader.aspx
OleDbDelete.aspx
OleDbInsert.aspx
OleDbParameterSelect.aspx
OleDbReader class
OleDbUpdate.aspx
one server-side form per page
online stores
creating 2nd 3rd
installing 2nd
navigation systems
building 2nd 3rd
product data
caching 2nd 3rd 4th
shopping carts
building 2nd 3rd 4th 5th 6th 7th 8th 9th
templates
loading 2nd 3rd 4th 5th
viewing 2nd 3rd
opening
connections
pooling 2nd 3rd 4th 5th
databases 2nd 3rd 4th 5th
OpenText method 2nd 3rd
OpenText.aspx
OpenWave
optimization
caches 2nd
applying HttpCache Policy class 2nd 3rd
page data caching 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st 22nd 23rd 24th 25th 26th 27th 28th 29th 30th 31st
page fragment caching 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
controls 2nd
browser compatibility 2nd
object models 2nd 3rd
separating code from content 2nd
view states 2nd 3rd 4th 5th
databases 2nd 3rd 4th 5th 6th 7th 8th
applying output parameters 2nd 3rd 4th
connection pooling 2nd 3rd 4th 5th
grouping stored procedures 2nd 3rd 4th 5th
retrieving return values 2nd 3rd
transactions 2nd 3rd 4th
graphics 2nd
page output caching 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th
performance counters 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
records 2nd 3rd 4th 5th 6th 7th 8th
Option Explicit
options
regualr expressions 2nd 3rd 4th 5th
Oracle
databases
OrderForm.aspx
OurCompany.aspx
output
controls
adding 2nd 3rd 4th 5th 6th 7th 8th 9th
creating multiple instances of templates 2nd 3rd 4th
HtmlTextWriter
applying 2nd 3rd
page caching 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th
parameters
retrieving 2nd 3rd 4th 5th 6th 7th
quote components
OutputCache directive

[ Team LiB ]


[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Z]

Page Error subroutines 2nd
Page Load subroutine
page output caching 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th
applying HttpCachePolicy class 2nd 3rd
configuring 2nd
Page Source command (View menu)
Netscape Navigator
page-level error handling 2nd 3rd 4th 5th 6th 7th 8th
PageContents.ascx
PageCounter
PageError.aspx
PageEvents.aspx
PageIsPostBack.aspx
PageLoad.aspx
pages
.aspx extensions 2nd
application logic
adding 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th
ASP.NET mobile controls 2nd 3rd 4th
activating mobile forms 2nd
advertisements 2nd 3rd
binding lists 2nd 3rd 4th
calendars 2nd 3rd
creating mobile forms 2nd 3rd
formatting text boxes 2nd 3rd 4th
images 2nd 3rd
interactive lists 2nd 3rd 4th
object lists 2nd 3rd 4th 5th 6th
paging text 2nd
placing phone calls 2nd
validating user input 2nd 3rd 4th
viewing lists 2nd
viewing text 2nd 3rd
breakpoints
creating 2nd 3rd 4th 5th 6th
business components
creating 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th
code-behind
applying 2nd 3rd 4th 5th
compiling 2nd 3rd 4th 5th
inheriting multiple pages 2nd 3rd 4th 5th
compiling 2nd 3rd 4th
controls
adding 2nd 3rd
cross-device-compatible mobile pages
creating 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
DataSets
applying 2nd 3rd 4th
filtering caches 2nd 3rd 4th
searching caches 2nd 3rd 4th 5th
viewing caches 2nd 3rd 4th
elements 2nd
ASP.NET controls
code declaration blocks 2nd 3rd
code render blocks 2nd
directives 2nd 3rd 4th 5th 6th
HTML tags/text 2nd
server-side comments 2nd
server-side include directives 2nd
encryption 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st 22nd 23rd
events
adding application logic 2nd 3rd
executing 2nd
forms
dynamically generating 2nd 3rd
postbacks 2nd
language support 2nd
lists
dynamically generating 2nd 3rd 4th
login
Microsoft Indexing Service
Boolean queries 2nd
configuring 2nd 3rd 4th 5th 6th
free text queries 2nd 3rd
querying properties 2nd 3rd
retrieving proeprties 2nd 3rd 4th
multipage forms
simulating 2nd 3rd 4th 5th
navigating
adding Hyperlink controls 2nd
apllying Redirect() method 2nd
submitting HTML forms 2nd 3rd 4th 5th
one server-side page per form
Option Explicit
output caching 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th
applying HttpCachePolicy 2nd 3rd
configuring 2nd
self-building
sign in/sign out
Passport authentication 2nd 3rd 4th 5th 6th
sign out
creating 2nd
SSL (Secure Socket Layer) 2nd 3rd
tracing 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th
transactions
creating 2nd
troubleshooting 2nd
Unleashed Sample Store 2nd 3rd
building navigation systems 2nd 3rd
caching product data 2nd 3rd 4th
installing 2nd
loading product templates 2nd 3rd 4th 5th
shopping carts 2nd 3rd 4th 5th 6th 7th 8th 9th
viewing product templates 2nd 3rd
user accounts
executing under 2nd 3rd 4th
vanity
WebService behavior
callback functions 2nd 3rd
creating 2nd 3rd 4th 5th 6th
retrieving database data 2nd 3rd 4th 5th 6th
troubleshooting 2nd 3rd
updating 2nd 3rd
PageTitle variable 2nd
PageViewState.aspx
paging
customizing 2nd 3rd 4th 5th
lists 2nd
records
DataGrid control 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
text
mobile form controls 2nd
paint application
creating 2nd 3rd 4th 5th 6th
Painter.aspx
PaintImage.aspx
Palm Pilots. [See also mobile devices]
Panel
Panel control 2nd 3rd 4th 5th
PanelMultiPage control
PanelMultiPage.aspx
parameters
cache content
varying 2nd 3rd 4th 5th
CommandBehavior
specifying 2nd 3rd 4th
FREETEXT predicates
applying 2nd 3rd 4th
input
HTML pattern matching 2nd 3rd 4th
output
retrieving 2nd 3rd 4th 5th 6th 7th
page fragment caching
varying 2nd 3rd 4th
passing
applying parenthesis
queries
applying with 2nd 3rd 4th 5th 6th 7th 8th
values
passing by
parenthesis
parameters
applying when passing
Parser.aspx
parsing
errors
inner content 2nd 3rd
passing
parameters
applying parentheses
by value
Passport authentication 2nd
enabling 2nd 3rd 4th
retrieving user information 2nd 3rd 4th 5th
sign in/sign out 2nd 3rd 4th 5th 6th
Passport Unique ID (PUID)
Passport.aspx 2nd
Passport.Config 2nd
Password.Config file
passwords
encrypting 2nd 3rd
job sites
Passport authentication 2nd
enabling 2nd 3rd 4th
retrieving user information 2nd 3rd 4th 5th
sign in/sign out 2nd 3rd 4th 5th 6th
storing 2nd 3rd 4th 5th 6th 7th
database tables 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
XML 2nd 3rd 4th 5th 6th 7th 8th 9th
validating 2nd 3rd
Path.aspx
PathGradientBrush
applying 2nd
PathGradientBrush.aspx
paths
directories
retrieving 2nd 3rd
HTTP handlers 2nd 3rd 4th 5th 6th
HTTP modules 2nd 3rd 4th
pattern matching
HTML 2nd
applying input parameters 2nd 3rd 4th
building Six Degrees Web Service 2nd 3rd 4th 5th 6th
creating services 2nd 3rd 4th 5th 6th
creating WSDL documents 2nd 3rd 4th 5th
specifying regular expressions 2nd
pattern-matching
HTML
patterns
replacement
regular expressions 2nd
PDAs (personal digital assistants)
PenCaps.aspx
pens
applying 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
perfromance counters 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
persisitent cookies
persistent cookies
creating 2nd 3rd
personal digital assistants (PDAs)
phone calls
placing 2nd
phone numbers
validating 2nd
Pickup directory 2nd
pie charts
drawing
Placeholder
Placeholder controls 2nd 3rd
Pocket PC browsers. [See also mobile devices]
policies
expiration
creating 2nd 3rd 4th 5th 6th
security
configuring 2nd 3rd
polygons
drawing 2nd
pooling
connections
ADO.NET 2nd 3rd 4th 5th
POP (Post Office Protocol)
pop-up error messages, viewing 2nd 3rd 4th
population
indexes 2nd
Portal Starter Kit 2nd
Post Office Protocol (POP)
Post.aspx
postbacks 2nd 3rd 4th 5th 6th 7th 8th
events
handling 2nd 3rd 4th
forms 2nd
posting
data from forms 2nd 3rd 4th
PostInvoke.html
precompilation
XML Web Services 2nd 3rd
predicates
CONTAINS
applying 2nd 3rd
FREETEXT
applying 2nd 3rd 4th
presentation content
code-behind
applying 2nd 3rd 4th 5th
compiling 2nd 3rd 4th 5th
inheriting multiple pages 2nd 3rd 4th 5th
presentation content. [See also content]
Presentation.aspx
PresentationMultiple1.aspx
PresentationMultiple2.aspx
PresentationMultiple3.aspx
preservation
view states
priorities
cache itesm
configuring 2nd
private queues
creating 2nd 3rd
procedures
SQL
applying output parameters 2nd 3rd 4th
connection pooling 2nd 3rd 4th 5th
executing 2nd 3rd 4th 5th 6th
grouping 2nd 3rd 4th 5th
retrieving return values 2nd 3rd
processes
retrieving information 2nd 3rd 4th 5th
ProcessHistory.aspx
ProcessInfo class
ProcessModel.aspx
product templates, code-behind 2nd
product templates, loading 2nd 3rd 4th
product templates, viewing 2nd 3rd
Product.aspx 2nd
ProductOrder.vb
ProductPage.aspx
ProductPageByParam.aspx
ProductPageByParamCache.aspx
products
viewing
Products.aspx
ProductsHandler
ProductsHandler/ProductsHandler.vb
ProductsService.asmx 2nd
ProductTemplate.vb
programmatically adding user controls 2nd 3rd 4th 5th 6th 7th
programming languages. [See languages]
properties
AdCreatedEventArgs control
AdRotator 2nd
AdRotator control
AutoPostBack 2nd 3rd 4th 5th 6th
Base Web control 2nd 3rd
Base Web Control Formatting
Button
Button controls
CacheDuration
Calendar 2nd 3rd
Calendar control
CausesValidation
CheckBox 2nd
CheckBox control 2nd
CheckBoxList 2nd
CheckBoxList control 2nd 3rd
CompareValidator 2nd 3rd
CompareValidator control
components
adding 2nd 3rd 4th 5th
Context
accessing 2nd
Controls
controls
accessor functions 2nd 3rd
adding 2nd 3rd
ControlsAdd
ControlsRemove
Cookie 2nd 3rd 4th 5th 6th 7th
cookies
configuring 2nd 3rd
CssClass
CustomValidator
CustomValidator control 2nd
data binding 2nd 3rd 4th 5th 6th 7th 8th
DataGrid 2nd
DataList 2nd 3rd 4th
DataTable
columns 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th
configuring 2nd 3rd 4th
DayRenderEventArgs subroutine
Decoration
DropDownList
DropDownList control 2nd 3rd
Enabled 2nd 3rd 4th 5th
EnableSession
EnableViewState
modifying
HasRows 2nd
HTML controls 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th
HTMLInputFile control
HttpPostedFile control
Hyperlink
Hyperlink control
Image 2nd
ImageButton 2nd
ImageButton control
IsAuthenticated
IsPostBack 2nd
Label
Label controls
LabelButton 2nd
labels
LinkButton control
ListBox
ListBox control 2nd 3rd 4th
ListItemCollection collection
Literal
Microsoft Indexing Service
retrieving 2nd 3rd 4th
Name
nonindexed default properties
Panel
Panel control
Placeholder
querying 2nd 3rd
RadioButton 2nd
RadioButton control
RadioButtonList 2nd 3rd
RadioButtonList control
RangeValidator 2nd
RangeValidator control
RegularExpressionValidator
RegularExpressionValidator control
Repeater 2nd
RequiredFieldValidator 2nd
RequiredFieldValidator control
SmoothingMode 2nd 3rd
strings
applying 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th
Table 2nd
TableCell
TableItemStyle 2nd 3rd
TableRow
TableStyle 2nd
TextBox
TextBox controls 2nd
textMode control
TextRenderingHint
Tickets
Trace.IsEnabled 2nd 3rd
Unique 2nd
user controls
viewing 2nd 3rd 4th
ValdationSummary control 2nd 3rd 4th
ValidationSummary 2nd
Visible 2nd 3rd 4th 5th
WebMethod attribute
Wrapping
XML Web Services
configuring 2nd
protocols
DIME
HTTP 2nd 3rd
POP (Post Office Protocol)
SMTP
applying Microsoft SMTP Service 2nd
configuring Microsoft SMTP Service 2nd
Microsoft SMTP Service
SOAP (Simple Object Access Protocol)
TDS (Tabular Data Stream)
proxy classes
XML Web Services
accessing
applying 2nd 3rd
generating 2nd 3rd
public queues
creating 2nd 3rd
public variables
declaring
PUID (Passport Unique ID)

[ Team LiB ]


[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Z]

quantifiers
regular expressions 2nd 3rd 4th
queries
documents properties 2nd 3rd
forms
executing from 2nd 3rd 4th
free text 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
free text queries
Microsoft Indexing Service 2nd 3rd
free-form 2nd 3rd
Microsoft Indexing Service. [See also Microsoft Indexing Service]
parameters
applying 2nd 3rd 4th 5th 6th 7th 8th
rows
returning 2nd
Query Analyzer 2nd
queuing
messages 2nd 3rd 4th
applying 2nd 3rd 4th 5th 6th 7th
configuring
receiving 2nd 3rd 4th 5th 6th
retrieving 2nd 3rd
sending 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
viewing 2nd 3rd
Quick Watch dialog box
quote component
output
Quote.vb

[ Team LiB ]


[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Z]

RadioButton 2nd
RadioButton control 2nd 3rd 4th 5th 6th 7th 8th 9th
RadioButtonList 2nd 3rd
RadioButtonList control 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th
Repeater control 2nd 3rd
RadioButtonList.aspx
RadioButtonListAutoPostBack.aspx
random banners
creating 2nd 3rd 4th 5th 6th
RandomEvent.vb
RangeValidator 2nd
RangeValidator control 2nd 3rd 4th 5th 6th 7th
RC2CryptoServiceProvider class
ReadCounters.aspx
ReadDirectory.aspx
reading
binary files 2nd 3rd
persistent cookies 2nd 3rd
session cookies 2nd 3rd 4th
text
files 2nd 3rd
XML
DataSets 2nd 3rd
ReadXML with schemas 2nd 3rd 4th 5th 6th
ReadMenu.aspx
ReadRecipes.aspx
ReadSchema.aspx
ReadSchemaData.aspx
receiving
messages 2nd 3rd 4th 5th 6th
Recipes.xml
recording. [See also logging]
records
databases
adding 2nd 3rd 4th 5th
deleting 2nd 3rd 4th 5th 6th
optimizing 2nd 3rd 4th 5th 6th 7th 8th
updating 2nd 3rd 4th 5th
DataGrid control
paging 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
DataList control
viewing 2nd 3rd 4th
DataSets 2nd 3rd 4th
elements 2nd
DataTable
updating 2nd 3rd 4th 5th
retrieving 2nd 3rd 4th 5th 6th 7th 8th 9th
rows
returning 2nd
rectangles
drawing 2nd 3rd 4th 5th
Redirect() method
applying 2nd
Redirect.aspx
RedirectFromPageLogin method
RegEx Replace method 2nd
Register.vb
Registry
regsvr32.exe
regitering
user controls
RegualrExpressionValidatorEmail control
regular expressions 2nd
backreferences/capture groups 2nd 3rd 4th
classes 2nd 3rd 4th 5th
customizing 2nd 3rd 4th 5th
metacharacters 2nd 3rd 4th 5th 6th 7th 8th 9th
quantifiers 2nd 3rd 4th
replacement patterns 2nd
specifying 2nd
testing
text
RegularExpressionIgnoreCase.aspx
RegularExpressionValidator
RegularExpressionValidator control 2nd 3rd 4th 5th
RegularExpressionValidator.aspx
RegularExpressionValidatorEmail.aspx
RegularExpressionValidatorLength control
RegularExpressionValidatorPassword control
RegularExpressionValidatorPassword.aspx
RegularExpressionValidatorPhone control
RegularExpressionValidatorPhone.aspx
RegularExpressionValidatorWeb.aspx
RegularExpressionValidatorZip control
RegularExpressionWeb control
relationships
DataTable
defining 2nd 3rd 4th
Remove method
RemoveCurrent.aspx
rendering
days
Calendar control 2nd 3rd 4th 5th 6th
rendering text
Repeater 2nd
Repeater controls
binding 2nd 3rd 4th
Button controls 2nd 3rd 4th
CheckBoxList controls 2nd 3rd
DropDownList controls 2nd 3rd
Hyperlink controls 2nd 3rd 4th 5th
Image controls 2nd 3rd
LinkButton controls 2nd 3rd 4th
ListBox controls 2nd 3rd
RadioButtonList controls 2nd 3rd
templates
formatting 2nd 3rd 4th 5th
view states 2nd
Repeater.aspx
RepeaterTable.aspx
replacement patterns, regular expressions 2nd
replacing
strings 2nd
reporting
security status
Reports Sample Starter Kit 2nd
Request class
Cookie property 2nd 3rd 4th 5th 6th 7th
Request method 2nd
Request Validation 2nd
Request.Form method 2nd
Request.QueryString method 2nd
requests
Application BeginRequest event 2nd 3rd 4th 5th 6th
CSR (certificate signing request) 2nd 3rd
HTTP handlers 2nd 3rd 4th 5th 6th
HTTP modules 2nd 3rd 4th
session states 2nd
adding items 2nd
capturing events 2nd 3rd
deleting items
disabling 2nd
ending sessions 2nd
starting sessions
storing 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
RequiredFieldValidator 2nd 3rd
RequiredFieldValidator control 2nd 3rd 4th 5th 6th
RequiredFieldValidator.aspx
RequiredFieldValidatorDisplay.aspx
RequiredFieldValidatorInitialValue control 2nd 3rd
RequiredFieldValidatorInitialValue.aspx
requirements
fields
RequiredFieldValidator control 2nd 3rd 4th 5th 6th
RequiredFieldValidatorInitialValue control 2nd 3rd
resolution
domain names 2nd
Response object
Cookie property 2nd 3rd 4th 5th 6th 7th
Results.aspx
retreiving
messages
from queues 2nd 3rd
retrieving
custom SOAP headers 2nd
database data
WebService behavior 2nd 3rd 4th 5th 6th
database records 2nd 3rd 4th 5th 6th 7th 8th 9th
applying parameters with queries 2nd 3rd 4th 5th 6th 7th 8th
executing queries 2nd 3rd 4th
output parameters 2nd 3rd 4th 5th 6th 7th
paths
directories 2nd 3rd
performance counters 2nd 3rd 4th 5th
process information 2nd 3rd 4th 5th
properties
Microsoft Indexing Sevrice 2nd 3rd 4th
queues 2nd
relationships
schemas 2nd 3rd 4th 5th
retrun values 2nd 3rd
table schema information 2nd 3rd
user information 2nd 3rd 4th 5th
Passport authentication 2nd 3rd 4th 5th
Windows 2nd 3rd
return values
retrieving 2nd 3rd
returning
histories 2nd 3rd 4th 5th
rows 2nd
reversals
page contents
ReverseArrayList.aspx
ReverseMethodControl.vb
revisions
items
DataGrid control 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
DataList controls 2nd 3rd 4th 5th 6th 7th 8th
rich controls
AdRotator control 2nd 3rd 4th 5th 6th
Calendar control 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st
HTMLInputFile control 2nd 3rd 4th 5th 6th
RijndaelManaged cla
roles
Windows
configuring 2nd 3rd
roles-based authentication
implementing 2nd 3rd 4th 5th
rows
cached DataSets
searching 2nd 3rd 4th 5th
DataGrid control
selecting 2nd 3rd
DataViews
filtering 2nd 3rd 4th 5th
searching 2nd 3rd
sorting 2nd 3rd
Repeater controls
binding 2nd 3rd 4th
returning 2nd
RSACryptoServiceProvider class
runtime
errors
Runtime.aspx

[ Team LiB ]


[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Z]

Sales.Config
Save As dialog box
saving
files
NotePad
passwords 2nd 3rd 4th 5th 6th 7th
database tables 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
XML 2nd 3rd 4th 5th 6th 7th 8th 9th
session states 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
schedules
interactive meeting schedulers
creating 2nd 3rd
schema
tables
retrieving 2nd 3rd
SchemaData.xml
schemas
DataTable
retrieving 2nd 3rd 4th 5th
ReadXML
applying 2nd 3rd 4th 5th 6th
SDK (Software Development Kit)
Seafood.ascx
searching
Boolean queries
executing 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
caches
DataSets 2nd 3rd 4th 5th
characters
strings 2nd 3rd
free text queries 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
items
ArrayList collection 2nd 3rd 4th
HashTable collection 2nd 3rd
SortedList collection 2nd 3rd
rows
DataViews 2nd 3rd
SQL server full-text searches 2nd
configuring 2nd 3rd 4th 5th
uploaded documents 2nd 3rd
sections
authentication
Web.Config files 2nd 3rd 4th 5th 6th
SecureService.asmx
security
file systems 2nd
forms-based authentication 2nd
applying Web farms 2nd
configuring 2nd
creating Authentication Tickets 2nd 3rd 4th 5th 6th
creating sign out pages 2nd
database tables 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
enabling 2nd 3rd 4th 5th 6th 7th
implementing roles-based authentication 2nd 3rd 4th 5th
retrieving user information 2nd 3rd 4th 5th
storing passwords 2nd 3rd 4th 5th 6th 7th
XML storage 2nd 3rd 4th 5th 6th 7th 8th 9th
forms-based authorization
configuring 2nd 3rd
job sites
Passport authentication 2nd
enabling 2nd 3rd 4th
retrieving user information 2nd 3rd 4th 5th
sign in/sign out 2nd 3rd 4th 5th 6th
passwords
validating 2nd 3rd
policies
configuring 2nd 3rd
SSL (Secure Socket Layer) 2nd 3rd
.NET encryption classes 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st 22nd 23rd
ASP.NET pages 2nd 3rd
authentication 2nd 3rd
configuring 2nd 3rd 4th 5th 6th 7th 8th
data integrity
encryption 2nd
status
reporting
Web Services
XML 2nd 3rd 4th 5th 6th 7th 8th 9th
Windows authenitcation
configuring
configuring IIS 2nd 3rd
creating custom roles 2nd 3rd
enabling 2nd 3rd 4th
retrieving user information 2nd 3rd
Windows authorization
configuring 2nd 3rd 4th 5th
XML Web Services
accessing 2nd 3rd
building 2nd 3rd 4th
Select statment
databases
retrieving records 2nd 3rd 4th 5th 6th 7th 8th 9th
selecting
devices 2nd
rowss
DataGrid control 2nd 3rd
selection
days, weeks, months (Calendar control) 2nd 3rd
items
DataList control 2nd 3rd 4th
SelectMovie.aspx
self-building pages
Send() method 2nd 3rd 4th
SendAttachment.aspx
SendHtml.aspx
SendImage.aspx
sending
email 2nd 3rd 4th 5th 6th 7th
adding attachments 2nd
applying Microsoft SMTP Service 2nd
configuring Microsoft SMTP Service 2nd
HTML 2nd 3rd 4th 5th 6th 7th 8th
Microsoft SMTP Service
messages 2nd 3rd 4th 5th 6th
to queues 2nd 3rd 4th
SendMail.aspx
SendProductOrder.aspx
SendQueue.aspx
separation
code from content 2nd
sequences
characters
matching 2nd
serialization 2nd
ArrayList 2nd 3rd
binary 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th
classes 2nd 3rd 4th 5th 6th 7th
controlling 2nd 3rd 4th
XML 2nd 3rd 4th 5th 6th 7th
serialization. [See also conversions]
SerializeArrayList.aspx
SerializeXmlClass.aspx
server-side comments 2nd
server-side include diretcives 2nd
ServerComments.aspx
servers
certificates
applying for 2nd
installing 2nd
HttpWebRequest class 2nd 3rd 4th
IIS. [See IIS]
one server-side forms per page
SQL. [See also SQL]
SSL (Secure Socket Layer)
.NET encryption classes 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st 22nd 23rd
ASP.NET pages 2nd 3rd
configuring 2nd 3rd 4th 5th 6th 7th 8th
WebClient class 2nd 3rd
ServerVariables.aspx
services
full-text searches
configuring 2nd 3rd 4th 5th
HTML pattern matching
creating 2nd 3rd 4th 5th 6th
Microsoft Indexing Service
Boolean queries 2nd
configiuring 2nd 3rd 4th 5th 6th
free text queries 2nd 3rd
querying properties 2nd 3rd
retrieving properties 2nd 3rd 4th
Microsoft SMTP Service
applying 2nd
configuring 2nd
Web Services
accessing via proxy classes
application states 2nd 3rd
applying custom controls 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
applying via proxy classes 2nd 3rd
arrays 2nd 3rd
binary files 2nd 3rd 4th
building Six Degrees 2nd 3rd 4th 5th 6th
classes 2nd 3rd 4th 5th
configuring properties 2nd
creating XML job sites 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
DataSets 2nd 3rd
Discovery tool 2nd 3rd
generating via proxy classes 2nd 3rd
security
session states 2nd 3rd 4th 5th 6th
TemperatureService
XML 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st 22nd 23rd 24th
XML security 2nd 3rd 4th 5th 6th 7th 8th 9th
Windows
storing session states 2nd 3rd
ServiceTicket Class
SessionCounterBad.aspx
SessionCounterGood.aspx
SessionRedirect/Global.asax
sessions
cookies
creating 2nd 3rd 4th
keys
authentication 2nd
caching 2nd
states 2nd
adding items 2nd
capturing events 2nd 3rd
cookieless 2nd 3rd 4th 5th 6th
deleting items
disabling 2nd
ending sessions 2nd
starting sessions
storing 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
XML Web Services 2nd 3rd 4th 5th 6th
SessionService.asmx
SessionService/Web.Config
SessionStart/Global.asax
SessionStart/SessionCount.aspx
Set Cacheability method
Set statement
Set-Cookie header
SetExpires() method
SHA1 class
SHA256 class
SHA384 class
SHA512 class
shapes
filling 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
shopping carts
online stores
building 2nd 3rd 4th 5th 6th 7th 8th 9th
ShoppingCart class 2nd 3rd 4th
ShoppingCart.ascx
ShoppingCart.vb
ShoppingCart.xml
ShowAccount.aspx
ShowColor.vb
ShowColorAccessor.vb
ShowDBUpload.aspx
ShowGreen.vb
ShowMyPage.aspx
ShowQuote.aspx 2nd
sign in/sign out pages
Passport authentication 2nd 3rd 4th 5th 6th
sign out pages
creating 2nd
signatures
verifying
SignData method
SignHash method
SignOut.aspx
simple controls
creating 2nd 3rd 4th
Simple Object Access Protocol. [See SOAP]
SimpleBind.aspx
SimpleDataBound.vb
SimpleHeader.ascx
SimpleImage.aspx
SimplePageDataBind.as
SimpleRotator1.vb
SimpleRotator2.vb
SimpleRotator3.vb
SimpleRowFilter.aspx
SimpleTabStrip.aspx
SimpleTemplate control
SimpleTemplate.vb
SimpleToolbar.aspx
SimpleTreeView.aspx
simulation
multipage forms 2nd 3rd 4th 5th
simulators
mobile devices 2nd 3rd 4th
single characters
matching 2nd 3rd
single-page master/detail forms
creating 2nd 3rd
single-threaded apartment (STA) 2nd
Site.asmx
Site.aspx 2nd
Site.bat 2nd
sites
Web. [See Web sites]
sites. job. [See job sites]
Six Degrees Web Service
building 2nd 3rd 4th 5th 6th
SixDegrees.Wsdl
sliding expiration policies
creating 2nd 3rd
smart forms
building 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st
Button controls
applying 2nd 3rd 4th 5th
CheckBox controls
applying 2nd 3rd 4th 5th 6th
CheckBoxList controls
applying 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th
DropDownList controls
applying 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th
ImageButton controls
applying 2nd 3rd 4th 5th 6th
LinkButton controls
applying 2nd 3rd
ListBox controls
applying 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th
RadioButton controls
applying 2nd 3rd 4th 5th 6th 7th 8th 9th
RadioButtonList controls
applying 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th
SmoothingMode property 2nd 3rd
SMTP
Microsoft SMTP Service
applying 2nd
configuring 2nd
SmtpMail class 2nd 3rd 4th 5th 6th
SOAP
custom headers
retrieving 2nd
Web services
exposing 2nd 3rd
SOAP (Simple Object Access Protocol)
XML Web Services
invoking
software
simulators for mobile devices 2nd 3rd 4th
Software Development Kit (SDK)
SolidBrush
applying
SolidBrush.aspx
SortArrayList.aspx
SortDataView.aspx
SortedList collection 2nd
items
adding 2nd
deleting 2nd
iterating 2nd
searching 2nd 3rd
sorting
columns
DataGrid control 2nd 3rd 4th
items
ArrayList collection 2nd 3rd
rows
DataViews 2nd 3rd
Source command (View menu)
Internet Explorer
spanning multiple ASP.NET pages 2nd 3rd 4th 5th
special characters
matching 2nd 3rd 4th
specification
CommandBehavior parameter 2nd 3rd 4th
regular expressions 2nd
user controls
types of 2nd
specifications
unique columns 2nd
splitting
strings 2nd
SQL
binary documents
indexing 2nd 3rd 4th
uploading 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th
Boolean queries
executing 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
Delete command 2nd 3rd 4th 5th 6th
free text queries 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
full-text searches 2nd
configuring 2nd 3rd 4th 5th
Microsoft Indexing Service
configuring 2nd
queries
applying parameters with 2nd 3rd 4th
Query tool
Server Enterprise Manager
Server Full-Text Indexing Wizard
Server Service Manager
statements
applying output parameters 2nd 3rd 4th
connection pooling 2nd 3rd 4th 5th
executing 2nd 3rd 4th 5th 6th
grouping stored procedures 2nd 3rd 4th 5th
retrieving return values 2nd 3rd
Update command 2nd 3rd 4th 5th
SQL CREATE TABLE statement 2nd 3rd 4th 5th
SqlAddProduct.aspx
SqlAddUniqueProduct.aspx
SqlCloseConnection.aspx
SqlCommand class 2nd 3rd 4th 5th
SqlCommand object
SqlConnection class
SqlConnection.aspx
SqlDataAdapter.aspx
SqlDataReader class 2nd
SqlDataReader.aspx
SqlDelete.aspx
SqlDeleteRecordsAffected.aspx
SqlException.aspx
SqlExecuteScalar.aspx
SqlFormDelete.aspx
SqlFormInsert.aspx
SqlGetAuthorCount.aspx
SqlGetAuthorLastname.aspx
SqlGetSchemaTable.aspx
SqlInsert.aspx 2nd
SqlParameterSelect.aspx
SqlQueryTool.aspx
SQLState/Web.Config
SqlStoredProcedure.aspx
SqlStoredProcedureQuery.aspx
SqlTransaction.aspx
SqlUpdate.aspx
SqlUpdateRecordsAffected.aspx
Src attribute
SSL (Secure Socket Layer) 2nd 3rd
ASP.NET pages 2nd 3rd
encrpytion classes 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st 22nd 23rd
authentication 2nd 3rd
configuring 2nd 3rd 4th 5th 6th 7th 8th
data integrity
encryption 2nd
STA (single-threaded apratment) 2nd
standard ASP.NET namespaces 2nd
standard brushes
applying
standard pens, applying
StandardBrush.aspx
StandardPens.aspx
Start event
applications 2nd 3rd 4th 5th 6th
Start menu commands
Computer Management
Start menu commmads
Internet Services Manager
Starter Kits 2nd
Commerce 2nd
Community 2nd 3rd 4th 5th 6th 7th
Portal 2nd
Reports Sample 2nd
Time Tracker 2nd
starting
sessions
state bag class
values
adding 2nd
StateBag.aspx
statements
Let
Select
retrieving database records 2nd 3rd 4th 5th 6th 7th 8th 9th
Set
SQL
applying output parameters 2nd 3rd 4th
connection pooling 2nd 3rd 4th 5th
executing 2nd 3rd 4th 5th 6th
grouping stored procedures 2nd 3rd 4th 5th
retrieving retrun values 2nd 3rd
SQL CREATE TABLE 2nd 3rd 4th 5th
troubleshooting 2nd 3rd 4th 5th
states
applications 2nd 3rd
Global.asax file 2nd 3rd 4th 5th
synchronizing 2nd 3rd 4th
XML Web Services 2nd 3rd
DataBound controls
applying 2nd 3rd 4th 5th
sessiions 2nd
adding items 2nd
capturing events 2nd 3rd
cookieless 2nd 3rd 4th 5th 6th
deleting items
disabling 2nd
ending sessions 2nd
starting sessions
storing 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
sessions
XML Web Services 2nd 3rd 4th 5th 6th
view 2nd 3rd 4th 5th
adding values 2nd
applying
disabling 2nd 3rd 4th 5th
view. [See view states]
statistics
AdRotator control 2nd 3rd
status
security
reporting
stepping through pages 2nd
StoreComponents
StoreComponents assembly
stored procedures
SQL
applying output parameters 2nd 3rd 4th
connection pooling 2nd 3rd 4th 5th
executing 2nd 3rd 4th 5th 6th
grouping 2nd 3rd 4th 5th
retrieving return values 2nd 3rd
stores
creating 2nd 3rd
installing 2nd
navigation systems
building 2nd 3rd
product data
caching 2nd 3rd 4th
shopping carts
building 2nd 3rd 4th 5th 6th 7th 8th 9th
templates
loading 2nd 3rd 4th 5th
viewing 2nd 3rd
storing
passwords 2nd 3rd 4th 5th 6th 7th
database tables 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
XML 2nd 3rd 4th 5th 6th 7th 8th 9th
session states 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
StringBuilder class 2nd
adding strings 2nd
appending strings 2nd 3rd
converting strings 2nd
deleting strings
iterating strings 2nd 3rd
replacing strings 2nd
strings 2nd
assigning 2nd
cache content
varying 2nd 3rd 4th
characters
deleting 2nd 3rd
searching 2nd 3rd
comparing 2nd
formatting 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th
joining 2nd
methods 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th
modifying 2nd
of text
drawing 2nd 3rd
splitting 2nd
StringBuilder class 2nd
adding 2nd
appending 2nd 3rd
converting 2nd
deleting
iterating 2nd 3rd
replacing 2nd
StripImage.aspx
structure
pages 2nd
ASP.NET controls
code declaration blocks 2nd 3rd
code render blocks 2nd
directives 2nd 3rd 4th 5th 6th
HTML tags/text 2nd
server-side comments 2nd
server-side include-directives 2nd
structures
colors 2nd 3rd 4th 5th 6th 7th
Style class 2nd 3rd
Style.aspx
StyleClass.aspx
StyleDynamic.aspx
styles
DataGrid control
applying 2nd 3rd 4th 5th
DataList control
formatting 2nd 3rd 4th 5th
objects
Calendar control
pens
selecting 2nd 3rd 4th 5th 6th 7th
Web controls
applying 2nd 3rd 4th 5th 6th 7th 8th 9th
stylesheets
XSL. [See XSL]
submission
HTML forms 2nd 3rd 4th 5th
subroutines
associating 2nd 3rd 4th 5th 6th 7th
DayRenderEventArgs
event handling
List_ItemCommand
LoadSessionKeys
Page Error 2nd
Page Load
parameters
applying parenthesis
summarizing
errors 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
sunroutines
<Script[chevron] tags
declaring 2nd 3rd
support
language 2nd
symmetric encryption algorithms 2nd 3rd 4th 5th 6th 7th
SymmetricRead.aspx
SymmetricWrite.aspx
synchronization
states 2nd 3rd 4th
Synchronized() method
System
System namespace
System.Collections
System.Collections.Specialized
System.Configuration
System.Data.SqlClient namespace
System.IO namespace
Path class 2nd 3rd
System.Messaging assembly
System.Security.Cryptography namespace
System.Text
System.Text.RegularExpressions
System.Web
System.Web.Caching
System.Web.Security
System.Web.Security namespace
System.Web.SessionState
System.Web.UI
System.Web.UI.HTMLControls
System.Web.UI.WebControls

[ Team LiB ]


[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Z]

tab controls
navigating
Table 2nd
TableCell
TableItemStyle 2nd 3rd
TableRow
tables
databases
applying parameters with queries 2nd 3rd 4th 5th 6th 7th 8th
binding Repeater controls 2nd 3rd 4th
binding TreeView controls 2nd 3rd 4th
Button controls (Repeater controls) 2nd 3rd 4th
CheckBoxList controls (Repeater controls) 2nd 3rd
creating (XML Web Services) 2nd
DropDownList controls (Repeater controls) 2nd 3rd
executing queries 2nd 3rd 4th
formatting Repeater controls 2nd 3rd 4th 5th
Hyperlink controls (Repeater controls) 2nd 3rd 4th 5th
Image controls (Repeater controls) 2nd 3rd
LinkButton controls (Repeater controls) 2nd 3rd 4th
ListBox controls (Repeater controls) 2nd 3rd
RadioButtonList controls (Repeater controls) 2nd 3rd
retrieving records 2nd 3rd 4th 5th 6th 7th 8th 9th
saving passwords 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
storing session states 2nd 3rd 4th
view states (Repeater controls) 2nd
viewing Uploads 2nd 3rd
DataSets
adding DataTables 2nd 3rd 4th
applying with ASP.NET pages 2nd 3rd 4th
filtering caches 2nd 3rd 4th
reading XML 2nd 3rd
ReadXML with schemas 2nd 3rd 4th 5th 6th
searching caches 2nd 3rd 4th 5th
viewing 2nd 3rd 4th 5th 6th
viewing caches 2nd 3rd 4th
writing XML 2nd 3rd 4th 5th
XML
XMLDataDocuments 2nd 3rd 4th 5th 6th
DataTables
column properties 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th
computing column values 2nd 3rd
configuring properties 2nd 3rd 4th
creating 2nd 3rd
defining relationships 2nd 3rd 4th
retrieving schemas 2nd 3rd 4th 5th
updating records 2nd 3rd 4th 5th
DataViews
filtering rows 2nd 3rd 4th 5th
searching rows 2nd 3rd
sorting rows 2nd 3rd
FavLinks
schema
retrieving 2nd 3rd
SQL CREATE TABLE statement
executing 2nd 3rd
TableStyle 2nd
Tabs.ascx
TabStrip control
creating 2nd 3rd
formatting 2nd
images
formatting 2nd 3rd
vertical TabStrip controls 2nd 3rd
TabStripImage.aspx
TabStripVertical.aspx
Tabular Data Stream (TDS)
TagName attribute
TagPrefix attribute
tags
<Script[chevron]
decalring functions/subroutines 2nd 3rd
HTML 2nd
TargetTypes.aspx
TDS (Tabular Data Stream)
TemperatureService Web Service
TemperatureService.asmx 2nd
Template.Wsdl
TemplateColumn
DataGrid
adding 2nd 3rd 4th
TemplateNumbers.vb
templates
controls
adding 2nd 3rd 4th 5th 6th 7th 8th 9th
creating multiple instances 2nd 3rd 4th
DataBound controls
applying 2nd 3rd 4th
DataGrid control 2nd
editing items 2nd 3rd 4th 5th
DataList control 2nd
applying 2nd
forms
applying 2nd 3rd
online stores
code-behind 2nd
loading 2nd 3rd 4th 5th
viewing 2nd 3rd
Repeater control
formatting 2nd 3rd 4th 5th
view states 2nd
termporary cookies
TestChart.aspx
testing
regular expressions
Web services
XML Web Services 2nd 3rd
TestRegEx.aspx
TestSecureService.aspx
TestTitlesService.aspx
TestTitlesServiceInput.aspx
text
aligning 2nd
assigning 2nd 3rd
copying
entry length
validating 2nd 3rd
files
creating
reading from 2nd 3rd
writing to 2nd 3rd 4th
free text queries 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
Microsoft Indexing Service 2nd 3rd
HTML 2nd
HtmlTextWriter
applying 2nd 3rd
Hyperlink control
adding 2nd
Label controls
applying 2nd 3rd
ListBoxes
assigning 2nd
mobile forms
binding lists 2nd 3rd 4th
interactive lists 2nd 3rd 4th
object lists 2nd 3rd 4th 5th 6th
paging 2nd
viewing 2nd 3rd
viewing lists 2nd
postbacks
handling events 2nd 3rd 4th
viewing 2nd 3rd 4th 5th 6th 7th 8th
regular expressions
rendering
SQL server full-text searches 2nd
configuring 2nd 3rd 4th 5th
strings of
drawing 2nd 3rd
TextBox controls
applying 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th
uploading 2nd 3rd 4th 5th 6th
wrapping 2nd
text boxes
controls
applying 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th
mobile forms
creating 2nd 3rd 4th
TextBox 2nd 3rd
textbox.aspx
TextBox.aspx
TextBoxAutoPostBack.aspx
TextBoxChanged.aspx
TextBoxColor.vb
TextBoxText.aspx
TextBoxTypes.aspx
TextChanged event 2nd 3rd
TextMode
properties
TextRenderingHint property
TextureBrush
applying 2nd
TextureBrush.aspx
themes
Community Starter Kit 2nd
Ticket property
tickets
Authentication
creating 2nd 3rd 4th 5th 6th
time
formatting 2nd 3rd 4th
Time Tracker Starter Kit 2nd
Titles.Wsdl
TitlesDS.xsd
TitlesInput.Wsdl
ToCelsius method
navigating
Toolbar control
creating 2nd 3rd 4th 5th 6th
formatting 2nd 3rd 4th
ToolbarButtonStyle.aspx
ToolbarEvents.aspx
ToolbarStyle.aspx
tools
Discovery
applying XML Web Services 2nd 3rd
ILDASM
regsvr32.exe
SQL Query
Wsdl.exe
ToString method 2nd
Trace object
controls
debugging 2nd 3rd
Trace.aspx 2nd
TraceCategories.aspx
TraceException.aspx
TraceIsEnabled.aspx
tracing
applications 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th
pages
tracking
AdRotator impressions 2nd 3rd
session states 2nd
adding items 2nd
capturing events 2nd 3rd
deleting items
disabling 2nd
ending sessions 2nd
starting sessions
storing 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
transfers
users 2nd
transformation
XSLTransform class
transformations
XML with XSL
TransformProducts.xsl
TransformRecipes.xsl
TreeNodes.xml
TreeView control 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st 22nd 23rd
TreeViewCheckBox.aspx
TreeViewDataSet.aspx
TreeViewImageUrl.aspx
TreeViewNavigateUrl.aspx
TreeViewNodeSources.aspx
TreeViewNodeSrc.aspx
TreeViewNodeType.aspx
TreeViewSelectedIndexChanged.aspx
triggers
dependencies
adding 2nd 3rd
Trim method
TripleDESCryptoServiceProvider class
troubleshooting
controls 2nd 3rd
errors 2nd 3rd 4th
application-level error handling 2nd 3rd 4th 5th 6th
page-level error handling 2nd 3rd 4th 5th 6th 7th 8th
viewing 2nd 3rd 4th
missing validation files
WebService behavior 2nd 3rd
TRY..Catch block 2nd 3rd 4th 5th
TryCatch.aspx
TypedDataSet.aspx
types
data
checking 2nd 3rd 4th
of errors 2nd 3rd 4th
of text boxes
viewing 2nd
of user controls
specifying 2nd

[ Team LiB ]


[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Z]

unhandled exceptions
catching 2nd
unique columns
specifying 2nd
UniqueConstraint.aspx
Unlock method 2nd
Update command
SQL 2nd 3rd 4th 5th
UpdateCache.sql
UpdateDataSet.aspx
updating
job sites 2nd 3rd 4th
records 2nd 3rd 4th 5th
relationships
records 2nd 3rd 4th 5th
WebService behavior 2nd 3rd
uploading
binary documents 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th
documents
searching 2nd 3rd
files
accepting 2nd 3rd 4th 5th 6th
URLs
job sites
creating 2nd 3rd
validating 2nd 3rd 4th
user accounts
executing 2nd 3rd 4th
user controls
content
entering 2nd 3rd 4th 5th
events
viewing 2nd 3rd
loading 2nd 3rd 4th 5th 6th 7th
methods/properties
viewing 2nd 3rd 4th
Web controls
viewing 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
user controls. [See also controls]
User-Agent header
UserControlEvents control
UserControlsDivider.aspx
UserInfo class
UserInfo.vb
usernames
Passport authentication 2nd
enabling 2nd 3rd 4th
retrieving user information 2nd 3rd 4th 5th
sign in/sign out 2nd 3rd 4th 5th 6th
storing 2nd 3rd 4th 5th 6th 7th
database tables 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
XML 2nd 3rd 4th 5th 6th 7th 8th 9th
validating 2nd 3rd
usernames.
names [See also authentication]
UserRegistration.aspx
users
accounts
impersonating 2nd 3rd 4th 5th
information
retrieving 2nd 3rd 4th 5th
input
validating 2nd 3rd 4th
job sites
authenticating 2nd 3rd 4th 5th 6th 7th
Passport authentication 2nd 3rd 4th 5th
redirecting 2nd
sessions
ending 2nd
starting
Windows
retrieving information 2nd 3rd
utilities
ILDASM
Path class 2nd 3rd

[ Team LiB ]


[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Z]

validation
client-side 2nd
configuring 2nd 3rd
disabling 2nd 3rd
enabling 2nd 3rd
CompareValidator control 2nd 3rd 4th 5th 6th 7th 8th 9th
data type checks 2nd 3rd 4th
credit card numbers 2nd 3rd 4th
CustomValidator control 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
disabling 2nd
e-mail addresses 2nd 3rd
entry length 2nd 3rd
errors
summarizing 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
passwords 2nd 3rd
phone numbers 2nd
ranges 2nd 3rd 4th 5th 6th 7th
RegularExpressionValidator control 2nd 3rd 4th 5th
Request Validation 2nd
RequiredFieldValidator control 2nd 3rd 4th 5th 6th
RequiredFieldValidatorInitialValue control 2nd 3rd
user input 2nd 3rd 4th
usernames 2nd 3rd
Web addresses 2nd 3rd 4th
WebUIValidation.js
zip codes 2nd
validation. [See also security]
Validation.aspx
ValidationSummary 2nd
ValidationSummary control 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th
ValidationSummaryPopUp.aspx
Value property
values
alpha
configuring colors 2nd
columns
assigning 2nd 3rd
comparing 2nd 3rd 4th 5th 6th 7th 8th 9th
checking data types 2nd 3rd 4th
data binding 2nd 3rd 4th 5th 6th 7th 8th
DataTable
computing 2nd 3rd
hash
computating
initial 2nd 3rd
ListBoxes
assigning 2nd
parameters
passing by
RangeValidator control 2nd 3rd 4th 5th 6th 7th
RegularExpressionValidator control 2nd 3rd 4th 5th
view state
adding 2nd
vanity pages
vanity URLs
jobs sites
creating 2nd 3rd
variables
Option Explicit
declaring
public
declaring
VBScript
declaring 2nd
variants 2nd
VaryByCustom attribute
VaryByCustom/Global.asax
VaryByParam
VaryByParam attribute
varying cache contents by custom strings 2nd 3rd 4th
varying cache contents by headers 2nd 3rd 4th 5th
varying cache contents by parameters 2nd 3rd 4th 5th
varying page fragment caching by parameter 2nd 3rd 4th
vbc.exe (Visual Basic compiler)
VBScript
language support 2nd
variables
declaring 2nd
verbs attribute
verification
digital signatures
VerifyPassword procedure
Verisign server certificates, applying for
vertical TabStrip controls
creating 2nd 3rd
View menu commands
Internet Explorer
Source
Netscape Navigator
Page Source
view state
adding values 2nd
applying
disabling 2nd 3rd 4th 5th
view states 2nd 3rd 4th 5th
Repeater controls 2nd
viewing
advertisements 2nd 3rd
banners 2nd 3rd 4th 5th 6th
cached products
caches
DataSets 2nd 3rd 4th
calendars
mobile controls 2nd 3rd
certificates
content 2nd
Enabled property 2nd 3rd 4th 5th
Panel control 2nd 3rd 4th 5th
Visible property 2nd 3rd 4th 5th
cookies
DataSets 2nd 3rd 4th 5th 6th
directories 2nd 3rd
errors 2nd 3rd 4th
trace messages 2nd
events
controls 2nd
user controls 2nd 3rd
Featured Products Web Services 2nd
horoscopes
images 2nd 3rd 4th 5th 6th
mobile devices 2nd 3rd
interactive calendars 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st
lists
mobile controls 2nd
members 2nd 3rd 4th 5th 6th 7th
messages 2nd 3rd
methods
user controls 2nd 3rd 4th
multiple address forms
performance counters
pop-up error messages 2nd 3rd 4th
postbacks 2nd 3rd 4th 5th 6th 7th 8th
product templates 2nd 3rd
properties
user controls 2nd 3rd 4th
records
DataList control 2nd 3rd 4th
rows
binding Repeater controls 2nd 3rd 4th
text
mobile form controls 2nd 3rd
text boxes 2nd
Uploads database table 2nd 3rd
user controls
Web controls 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
user information
views
DataViews 2nd
filtering rows 2nd 3rd 4th 5th
searching rows 2nd 3rd
sorting rows 2nd 3rd
ViewState.aspx
virtual directories
creating
Visible property 2nd 3rd 4th 5th
Visual Basic
business components
creating 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th
Visual Basic compiler (vbc.exe)

[ Team LiB ]


[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Z]

W3C (World Wide Web Consortium)
watches
creating 2nd
Web
applications
creating interactive meeting schedulers 2nd 3rd
dividing into multiple layers 2nd 3rd 4th 5th 6th 7th
controls 2nd 3rd 4th 5th 6th 7th 8th
applying styles 2nd 3rd 4th 5th 6th 7th 8th 9th
benefits of 2nd
browser compatibility 2nd
building smart forms 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st
Button 2nd 3rd 4th 5th
CheckBox 2nd 3rd 4th 5th 6th
CheckBoxList 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th
converting HTML 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
DropDownList 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th
fomratting 2nd 3rd 4th 5th 6th
HTML 2nd 3rd 4th 5th 6th 7th
ImageButton 2nd 3rd 4th 5th 6th
LinkButton 2nd 3rd
ListBox 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th
object models 2nd 3rd
RadioButton 2nd 3rd 4th 5th 6th 7th 8th 9th
RadioButtonList 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th
separating code form content 2nd
view states 2nd 3rd 4th 5th
viewing user controls 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
forms
entry length 2nd 3rd
passwords 2nd 3rd
phone numbers 2nd
RequiredFieldValidator control 2nd 3rd 4th 5th 6th
RequiredFieldValidatorInitialValue control 2nd 3rd
URLs 2nd 3rd 4th
usernames 2nd 3rd
validating e-mail addresses 2nd 3rd
zip codes 2nd
Web pages
banners
viewing 2nd 3rd 4th 5th 6th
Web Server Certificate Wizard
Web Services
controls
applying 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
Description Language tool (Wsdl.exe)
Featured Product
creating 2nd 3rd 4th 5th 6th 7th 8th 9th
security
XML 2nd 3rd 4th 5th 6th 7th 8th 9th
Six Degrees
building 2nd 3rd 4th 5th 6th
Web services
SOAP
exposing 2nd 3rd
Web Services
TemperatureService
Web services
testing
Web Services
XML 2nd 3rd
accessing 2nd 3rd
accessing via proxy classes
aggregation
application communication 2nd
application states 2nd 3rd
applying via proxy classes 2nd 3rd
arrays 2nd 3rd
authenticating session keys 2nd
binary files 2nd 3rd 4th
building 2nd 3rd 4th
caching session keys 2nd
classes 2nd 3rd 4th 5th
configuring properties 2nd
creating 2nd 3rd 4th 5th 6th 7th
creating job sites 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
DataSets 2nd 3rd
Discovery tool 2nd 3rd
generating via proxy classes 2nd 3rd
invoking 2nd 3rd 4th 5th
precompiling 2nd 3rd
retrieving custom SOAP headers 2nd
session states 2nd 3rd 4th 5th 6th
testing 2nd 3rd
Web sites
Application BeginRequest events 2nd 3rd 4th 5th 6th
caches
applying HttpCachePolicy class 2nd 3rd
configuring locations 2nd
page data caching 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st 22nd 23rd 24th 25th 26th 27th 28th 29th 30th 31st
page fragment caching 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
Community Starter Kit 2nd 3rd 4th 5th 6th 7th
cookies. [See cookies]
entry length
validating 2nd 3rd
events 2nd 3rd 4th 5th 6th
forms-based authentication
applying Web farms 2nd
configuring 2nd
creating Authenitcation Tickets 2nd 3rd 4th 5th 6th
creating sign out pages 2nd
database tables 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
enabling 2nd 3rd 4th 5th 6th 7th
implementing roles-based authentication 2nd 3rd 4th 5th
retrieving user information 2nd 3rd 4th 5th
storing passwords 2nd 3rd 4th 5th 6th 7th
XML storage 2nd 3rd 4th 5th 6th 7th 8th 9th
forms-based authorization
configuring 2nd 3rd
free text queries 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
HtmlTextWriter
applying 2nd 3rd
HTTP
accessing 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
images
applying colors 2nd 3rd 4th 5th 6th 7th
applying pens 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
building 2nd 3rd 4th 5th 6th
creating bitmaps 2nd 3rd 4th 5th 6th
curves 2nd 3rd 4th
drawing
ellipses 2nd
filling shapes 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
lines
optimizing 2nd
rectangles 2nd
strings of text 2nd 3rd
Microsoft
Nokia
OpenWave
page output caching 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th
Passport authentication 2nd
enabling 2nd 3rd 4th
retrieving user information 2nd 3rd 4th 5th
sign in/sign out 2nd 3rd 4th 5th 6th
passwords
validating 2nd 3rd
postbacks
handling events 2nd 3rd 4th
viewing 2nd 3rd 4th 5th 6th 7th 8th
session states 2nd
adding items 2nd
capturing events 2nd 3rd
deleting items
disabling 2nd
ending sessions 2nd
starting sessions
storing 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
URLs
validating 2nd 3rd 4th
usernames
validating 2nd 3rd
Web.Config 2nd 3rd
Web.Config file
CustomErrorsOff
forms-based authentication 2nd 3rd 4th 5th
configuring 2nd
creating sign out pages 2nd
database tables 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
retrieving user information 2nd 3rd 4th 5th
storing passwords 2nd 3rd 4th 5th 6th 7th
XML storage 2nd 3rd 4th 5th 6th 7th 8th 9th
forms-based authorization
configuring 2nd 3rd
job sites 2nd 3rd 4th 5th 6th 7th
Passport authentication
Web.Config files 2nd 3rd
customizing 2nd 3rd
locking 2nd 3rd
modifying 2nd 3rd 4th 5th 6th
sections 2nd 3rd 4th 5th 6th
WhosOn applications 2nd
WebClient class 2nd 3rd 4th
WebControl class
properties 2nd 3rd 4th 5th
WebControls
TabStrip control
creating 2nd 3rd
formatting 2nd
images 2nd 3rd
vertical TabStrip controls 2nd 3rd
Toolbar control
creating 2nd 3rd 4th 5th 6th
formatting 2nd 3rd 4th
TreeView control 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th 17th 18th 19th 20th 21st 22nd 23rd
WebMethod attribute
configuring 2nd
WebRequest class
WebResponse classes
WebService
callback functions 2nd 3rd
limitations 2nd
pages
creating 2nd 3rd 4th 5th 6th
retrieving database data 2nd 3rd 4th 5th 6th
troubleshooting 2nd 3rd
updating 2nd 3rd
WebService attribute
configuring 2nd
WebUIValidation.js
weeks
Calendar control
rendering daysg 2nd 3rd 4th 5th 6th
selecting 2nd 3rd
whitespace
WhosOn applications
creating 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
WhosOnHandler.vb
Windows
authentication
configuring
configuring IIS 2nd 3rd
creating custom roles 2nd 3rd
enabling security 2nd 3rd 4th
retrieving user information 2nd 3rd
authorization
configuring 2nd 3rd 4th 5th
Windows service
session states
storing 2nd 3rd
Windows Socket Interface (Winsock32)
WindowsIdentity class 2nd 3rd 4th
WindowsPrincipal class 2nd 3rd
Winsock32 (Windows Socket Interface)
wizards
SQL Server Full-Text Indexing
Web Server Certificate
word characters
WordRepeater.ascx
World Wide Web Consortium (W3C)
wrapping
text 2nd
Wrapping.aspx
WriteText.aspx
WriteXml.aspx
WriteXMLResponse.aspx
writing
binary files 2nd 3rd 4th
text
files 2nd 3rd 4th
validation logic 2nd
XML
DataSets 2nd 3rd 4th 5th
WSDL
documents
creating 2nd 3rd 4th 5th
Wsdl.exe (Web Services Description Language tool)
wwwroot directory

[ Team LiB ]


[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Z]

XML
AppFixLinks/BadLinks.xml file
ASP.NET Xml control 2nd 3rd 4th 5th
classes 2nd 3rd
DataSets
reading 2nd 3rd
ReadXML with schemas 2nd 3rd 4th 5th 6th
writing 2nd 3rd 4th 5th
XMLDataDcouments 2nd 3rd 4th 5th 6th
passwords
storing 2nd 3rd 4th 5th 6th 7th 8th 9th
serialization 2nd 3rd 4th 5th 6th 7th
TreeView controls
binding 2nd 3rd
Web Service
accessing 2nd 3rd
authenticating session keys 2nd
building 2nd 3rd 4th
caching session keys 2nd
retrieving custom SOAP headers 2nd
Web Services 2nd 3rd 4th 5th
accessing via proxy classes
aggregation
application communication 2nd
application states 2nd 3rd
applying via proxy classes 2nd 3rd
arrays 2nd 3rd
binary files 2nd 3rd 4th
classes 2nd 3rd 4th 5th
configuring properties 2nd
creating 2nd 3rd 4th 5th 6th 7th
creating database tables 2nd
creating job sites 2nd 3rd 4th 5th 6th 7th 8th 9th 10th
DataSets 2nd 3rd
Discovery tool 2nd 3rd
formatting Login() method 2nd 3rd 4th 5th
generating via proxy classes 2nd 3rd
invoking 2nd 3rd 4th 5th
precompiling 2nd 3rd
session states 2nd 3rd 4th 5th 6th
testing 2nd 3rd
XSL
transforming with
XSLTransform class
XML (eXtensible Markup Language)
XmlControl.aspx
XmlDataDocument class
XmlDataDocument.aspx
XmlDocument class
XmlNodeReader class
XmlTextReader class
XmlTextWriter class
XSL
XML
transforming
XSL (eXtensible Stylesheet Language)
Xsl.aspx
XslTransform class
XSLTransform class

[ Team LiB ]


[ Team LiB ]

[SYMBOL] [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Z]

zip codes
validating 2nd

[ Team LiB ]


Team LiB

Brought to You by

Like the book? Buy it!